From 0a51b98c8de4917ab108bbe3b09746f650001328 Mon Sep 17 00:00:00 2001 From: keowu Date: Mon, 27 Oct 2025 21:06:08 -0300 Subject: [PATCH] feat: Begin implementing MBA Obfuscation Pass and bug fixies on RunMiniVMObfuscation - Started working on the feature to add a custom standalone pass that adds support for MBA obfuscation in arithmetic operators with guaranteed equivalence and semantics. - Now Ryujin requires Z3 for MBA Obfuscation equivalence.. - Fixed context and flag handling issues in RunMiniVMObfuscation. --- RyujinConsole/RyujinConsole/RyujinConsole.cc | 489 ++++++++++++++++++ .../RyujinCore/RyujinObfuscationCore.cc | 4 +- 2 files changed, 491 insertions(+), 2 deletions(-) diff --git a/RyujinConsole/RyujinConsole/RyujinConsole.cc b/RyujinConsole/RyujinConsole/RyujinConsole.cc index b464d03..cb01483 100644 --- a/RyujinConsole/RyujinConsole/RyujinConsole.cc +++ b/RyujinConsole/RyujinConsole/RyujinConsole.cc @@ -7,6 +7,15 @@ #include #include "RyujinCore.hh" +// MBA Obfuscation Pass +#include +#include +#include +#include +#include +#include +#include + void RyujinCustomPassDemo(RyujinProcedure* proc) { /* @@ -46,6 +55,484 @@ void RyujinCustomPassDemo(RyujinProcedure* proc) { } +void RyujinMBAObfuscationPass(RyujinProcedure* proc) { + + // Traduzindo os registradores do Zydis para Registradores do ASMJIT + auto get_asm_reg_64 = [&](ZydisRegister z_reg) -> asmjit::x86::Gp { + + switch (z_reg) { + + case ZYDIS_REGISTER_RAX: return asmjit::x86::rax; + case ZYDIS_REGISTER_RBX: return asmjit::x86::rbx; + case ZYDIS_REGISTER_RCX: return asmjit::x86::rcx; + case ZYDIS_REGISTER_RDX: return asmjit::x86::rdx; + case ZYDIS_REGISTER_RSI: return asmjit::x86::rsi; + case ZYDIS_REGISTER_RDI: return asmjit::x86::rdi; + case ZYDIS_REGISTER_R8: return asmjit::x86::r8; + case ZYDIS_REGISTER_R9: return asmjit::x86::r9; + case ZYDIS_REGISTER_R10: return asmjit::x86::r10; + case ZYDIS_REGISTER_R11: return asmjit::x86::r11; + case ZYDIS_REGISTER_R12: return asmjit::x86::r12; + case ZYDIS_REGISTER_R13: return asmjit::x86::r13; + case ZYDIS_REGISTER_R14: return asmjit::x86::r14; + case ZYDIS_REGISTER_R15: return asmjit::x86::r15; + + case ZYDIS_REGISTER_EAX: return asmjit::x86::rax; + case ZYDIS_REGISTER_EBX: return asmjit::x86::rbx; + case ZYDIS_REGISTER_ECX: return asmjit::x86::rcx; + case ZYDIS_REGISTER_EDX: return asmjit::x86::rdx; + case ZYDIS_REGISTER_ESI: return asmjit::x86::rsi; + case ZYDIS_REGISTER_EDI: return asmjit::x86::rdi; + case ZYDIS_REGISTER_R8D: return asmjit::x86::r8; + case ZYDIS_REGISTER_R9D: return asmjit::x86::r9; + case ZYDIS_REGISTER_R10D: return asmjit::x86::r10; + case ZYDIS_REGISTER_R11D: return asmjit::x86::r11; + case ZYDIS_REGISTER_R12D: return asmjit::x86::r12; + case ZYDIS_REGISTER_R13D: return asmjit::x86::r13; + case ZYDIS_REGISTER_R14D: return asmjit::x86::r14; + case ZYDIS_REGISTER_R15D: return asmjit::x86::r15; + + } + + // Fallback + return asmjit::x86::rax; + }; + + std::printf("[RyujinMBAObfuscationPass] Processando equivalencia MBA em %s\n", proc->name.c_str()); + + // Iniciando decoder + ZydisDecoder decoder; + ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64); + + // Begin z3 context e unique block id + z3::context ctx; + uint64_t unique_id = 0; + + for (auto& block : proc->basic_blocks) { + + std::vector> new_instructions; + new_instructions.reserve(block.opcodes.size()); + + for (auto& opcode : block.opcodes) { + + // Decodificando instruções com base nos opcodes armazenados em nossos basic blocks(sempre com o contexto atualizado) + ZydisDecodedInstruction instruction{}; + std::vector operands(ZYDIS_MAX_OPERAND_COUNT); + std::memset(operands.data(), 0, operands.size() * sizeof(ZydisDecodedOperand)); + + ZyanStatus status = ZydisDecoderDecodeFull(&decoder, opcode.data(), opcode.size(), &instruction, operands.data()); + if (!ZYAN_SUCCESS(status)) { + + new_instructions.push_back(opcode); + + continue; + } + + + // Checando se a instrução atual é um candidato para ter uma nova expression MBA(apenas com operações aritimeticas básicas) + bool isMbaRewritten = false; + + if ((instruction.mnemonic == ZYDIS_MNEMONIC_ADD || instruction.mnemonic == ZYDIS_MNEMONIC_SUB || instruction.mnemonic == ZYDIS_MNEMONIC_XOR || + instruction.mnemonic == ZYDIS_MNEMONIC_AND || instruction.mnemonic == ZYDIS_MNEMONIC_OR) + && instruction.operand_count_visible == 2 + && operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER) { + + auto is_src_reg = (operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER); + auto is_src_imm = (operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE); + auto is_src_mem = (operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY); + + if (is_src_mem) { + + new_instructions.push_back(opcode); + + continue; + } + + // Validação da semantica MBA... + + // Checando equivalencia(semantica) da expressão com o Z3 para garantir que a expression inserida não quebre posteriormente. + std::string xs_name = "x_" + std::to_string(unique_id); + std::string ys_name = "y_" + std::to_string(unique_id); + unique_id++; + + z3::symbol sx = ctx.str_symbol(xs_name.c_str()); + z3::symbol sy = ctx.str_symbol(ys_name.c_str()); + + z3::sort bv64 = ctx.bv_sort(64); + z3::expr x = ctx.constant(sx, bv64); + z3::expr y = ctx.constant(sy, bv64); + + z3::expr target = ctx.bv_val(0ULL, 64); + + switch (instruction.mnemonic) { + + case ZYDIS_MNEMONIC_ADD: target = x + y; break; + case ZYDIS_MNEMONIC_SUB: target = x - y; break; + case ZYDIS_MNEMONIC_XOR: target = x ^ y; break; + case ZYDIS_MNEMONIC_AND: target = x & y; break; + case ZYDIS_MNEMONIC_OR: target = x | y; break; + default: target = x; + + } + + // MBA's expressions para ofuscação + std::vector obf_variants; + switch (instruction.mnemonic) { + + case ZYDIS_MNEMONIC_ADD: + + obf_variants.push_back((x ^ y) + z3::shl(x & y, ctx.bv_val(1ULL, 64))); + obf_variants.push_back((x | y) + (x & y)); + obf_variants.push_back(~(~x + ~y) + ctx.bv_val(1ULL, 64)); + + break; + case ZYDIS_MNEMONIC_SUB: + + obf_variants.push_back(x + (~y + ctx.bv_val(1ULL, 64))); + obf_variants.push_back((x ^ y) - z3::shl(~x & y, ctx.bv_val(1ULL, 64))); + obf_variants.push_back(~(y - ctx.bv_val(1ULL, 64)) - ~x); + + break; + case ZYDIS_MNEMONIC_XOR: + + obf_variants.push_back((x + y) - z3::shl(x & y, ctx.bv_val(1ULL, 64))); + obf_variants.push_back((x | y) - (x & y)); + obf_variants.push_back(~(x & y) - ~(x | y) + ctx.bv_val(2ULL, 64)); + + break; + case ZYDIS_MNEMONIC_AND: + + obf_variants.push_back((x + y) - (x | y)); + obf_variants.push_back(~(~x | ~y)); + obf_variants.push_back((x ^ y) + (x | y) - (x + y)); + + break; + case ZYDIS_MNEMONIC_OR: + + obf_variants.push_back(x + y - (x & y)); + obf_variants.push_back(~(~x & ~y)); + obf_variants.push_back((x & y) + (x ^ y)); + + break; + + } + + // Sanity Check + if (obf_variants.empty()) { + + new_instructions.push_back(opcode); + + continue; + } + + // RNG Deterministico por unique_id para teste de expressions + std::mt19937 gen(static_cast(unique_id)); + std::uniform_int_distribution dist(0, obf_variants.size() - 1); + size_t variant_idx = dist(gen); + z3::expr obf = obf_variants[variant_idx]; + + z3::solver solver(ctx); + + // Verificando se a expression MBA é válida + solver.add(obf != target); + if (solver.check() != z3::unsat) { + + std::cout << "[RyujinMBAObfuscationPass] Validação de expressões de mesma semantica com o Z3 retornou unsat para o procedimento. não tem uma equivalencia matematica para reescrever como mba de forma segura...\n"; + new_instructions.push_back(opcode); + + continue; + } + + try { + + // Insertion das expressions MBA... + + // Preparando expressions MBA validadas anteriormente e gerando novas instruções para reproduzirem o mesmo resultado com a teoria de MBA + asmjit::JitRuntime rt; + asmjit::CodeHolder code; + code.init(rt.environment(), rt.cpuFeatures()); + asmjit::x86::Assembler a(&code); + + asmjit::x86::Gp dest64 = get_asm_reg_64(operands[0].reg.value); + asmjit::x86::Gp tmp_x = asmjit::x86::rcx; + asmjit::x86::Gp tmp_y = asmjit::x86::rdx; + asmjit::x86::Gp tmp_tmp = asmjit::x86::r8; + asmjit::x86::Gp tmp_extra = asmjit::x86::r9; + + // Salvando contexto + a.push(asmjit::x86::rax); + a.push(asmjit::x86::rcx); + a.push(asmjit::x86::rdx); + a.push(asmjit::x86::r8); + a.push(asmjit::x86::r9); + + a.mov(tmp_x, dest64); + + if (is_src_reg) { + + asmjit::x86::Gp src64 = get_asm_reg_64(operands[1].reg.value); + a.mov(tmp_y, src64); + + } + else if (is_src_imm) { + + uint64_t imm = operands[1].imm.value.u; + a.mov(tmp_y, imm); + + } + + // Sanity Check: Alinhando instruções para evitar desalinhamentos + a.align(asmjit::AlignMode::kCode, 16); + + // Inserindo expressions MBA para cada mnemonic suportado + if (instruction.mnemonic == ZYDIS_MNEMONIC_ADD) { + + if (variant_idx == 0) { + + // (x ^ y) + shl(x & y,1) + a.mov(tmp_tmp, tmp_x); // tmp_tmp = x + a.and_(tmp_tmp, tmp_y); // tmp_tmp = x & y + a.shl(tmp_tmp, 1); // tmp_tmp = (x & y) << 1 + a.xor_(tmp_extra, tmp_x); // tmp_extra = x + a.xor_(tmp_extra, tmp_y); // tmp_extra = x ^ y + a.add(tmp_extra, tmp_tmp); // tmp_extra = (x ^ y) + shl(x & y,1) + a.mov(tmp_x, tmp_extra); + + } + else if (variant_idx == 1) { + + // (x | y) + (x & y) + a.mov(tmp_tmp, tmp_x); + a.or_(tmp_tmp, tmp_y); // tmp_tmp = x | y + a.mov(tmp_extra, tmp_x); + a.and_(tmp_extra, tmp_y); // tmp_extra = x & y + a.add(tmp_tmp, tmp_extra); // tmp_tmp = (x | y) + (x & y) + a.mov(tmp_x, tmp_tmp); + + } + else { + + // ~(~x + ~y) + 1 -> mesmo que x + y + a.mov(tmp_tmp, tmp_x); + a.not_(tmp_tmp); // tmp_tmp = ~x + a.mov(tmp_extra, tmp_y); + a.not_(tmp_extra); // tmp_extra = ~y + a.add(tmp_tmp, tmp_extra); // tmp_tmp = ~x + ~y + a.not_(tmp_tmp); // tmp_tmp = ~(~x + ~y) + a.add(tmp_tmp, 1); // +1 + a.mov(tmp_x, tmp_tmp); + + } + + a.mov(dest64, tmp_x); + } + else if (instruction.mnemonic == ZYDIS_MNEMONIC_SUB) { + + if (variant_idx == 0) { + + // x + (~y + 1) == x - y + a.mov(tmp_tmp, tmp_y); // tmp_tmp = y + a.not_(tmp_tmp); // tmp_tmp = ~y + a.add(tmp_tmp, 1); // tmp_tmp = ~y + 1 + a.add(tmp_x, tmp_tmp); // x = x + (~y + 1) + + } + else if (variant_idx == 1) { + + // (x ^ y) - shl((~x & y),1) + a.mov(tmp_tmp, tmp_x); // tmp_tmp = x + a.xor_(tmp_extra, tmp_x); // tmp_extra = x + a.xor_(tmp_extra, tmp_y); // tmp_extra = x ^ y + a.mov(tmp_tmp, tmp_x); + a.not_(tmp_tmp); // tmp_tmp = ~x + a.and_(tmp_tmp, tmp_y); // tmp_tmp = (~x & y) + a.shl(tmp_tmp, 1); // tmp_tmp = shl(~x & y,1) + a.sub(tmp_extra, tmp_tmp); // tmp_extra = (x ^ y) - shl(...) + a.mov(tmp_x, tmp_extra); + + } + else { + + // ~(y - 1) - ~x + a.mov(tmp_tmp, tmp_y); // tmp_tmp = y + a.sub(tmp_tmp, 1); // tmp_tmp = y - 1 + a.not_(tmp_tmp); // tmp_tmp = ~(y - 1) + a.mov(tmp_extra, tmp_x); // tmp_extra = x + a.not_(tmp_extra); // tmp_extra = ~x + a.sub(tmp_tmp, tmp_extra); // tmp_tmp = ~(y-1) - ~x + a.mov(tmp_x, tmp_tmp); + + } + + a.mov(dest64, tmp_x); + } + else if (instruction.mnemonic == ZYDIS_MNEMONIC_XOR) { + + if (variant_idx == 0) { + + // (x + y) - shl(x & y,1) + a.mov(tmp_extra, tmp_x); // tmp_extra = x + a.add(tmp_extra, tmp_y); // tmp_extra = x + y + a.mov(tmp_tmp, tmp_x); + a.and_(tmp_tmp, tmp_y); // tmp_tmp = x & y + a.shl(tmp_tmp, 1); // tmp_tmp = shl(x & y,1) + a.sub(tmp_extra, tmp_tmp); // tmp_extra = (x + y) - shl(...) + a.mov(tmp_x, tmp_extra); + + } + else if (variant_idx == 1) { + + // (x | y) - (x & y) + a.mov(tmp_tmp, tmp_x); + a.or_(tmp_tmp, tmp_y); // tmp_tmp = x | y + a.mov(tmp_extra, tmp_x); + a.and_(tmp_extra, tmp_y); // tmp_extra = x & y + a.sub(tmp_tmp, tmp_extra); // tmp_tmp = (x | y) - (x & y) + a.mov(tmp_x, tmp_tmp); + + } + else { + + // ~(x & y) - ~(x | y) + 2 + a.mov(tmp_tmp, tmp_x); + a.and_(tmp_tmp, tmp_y); // tmp_tmp = x & y + a.not_(tmp_tmp); // tmp_tmp = ~(x & y) + a.mov(tmp_extra, tmp_x); + a.or_(tmp_extra, tmp_y); // tmp_extra = x | y + a.not_(tmp_extra); // tmp_extra = ~(x | y) + a.sub(tmp_tmp, tmp_extra); // tmp_tmp = ~(x&y) - ~(x|y) + a.add(tmp_tmp, 2); // +2 + a.mov(tmp_x, tmp_tmp); + + } + + a.mov(dest64, tmp_x); + } + else if (instruction.mnemonic == ZYDIS_MNEMONIC_AND) { + + if (variant_idx == 0) { + + // (x + y) - (x | y) + a.mov(tmp_extra, tmp_x); + a.add(tmp_extra, tmp_y); // tmp_extra = x + y + a.mov(tmp_tmp, tmp_x); + a.or_(tmp_tmp, tmp_y); // tmp_tmp = x | y + a.sub(tmp_extra, tmp_tmp); // tmp_extra = (x + y) - (x | y) + a.mov(tmp_x, tmp_extra); + + } + else if (variant_idx == 1) { + + // ~(~x | ~y) => Igual x & y + a.mov(tmp_tmp, tmp_x); + a.not_(tmp_tmp); // tmp_tmp = ~x + a.mov(tmp_extra, tmp_y); + a.not_(tmp_extra); // tmp_extra = ~y + a.or_(tmp_tmp, tmp_extra); // tmp_tmp = ~x | ~y + a.not_(tmp_tmp); // tmp_tmp = ~(~x | ~y) + a.mov(tmp_x, tmp_tmp); + + } + else { + + // (x ^ y) + (x | y) - (x + y) + a.mov(tmp_tmp, tmp_x); + a.xor_(tmp_tmp, tmp_y); // tmp_tmp = x ^ y + a.mov(tmp_extra, tmp_x); + a.or_(tmp_extra, tmp_y); // tmp_extra = x | y + a.add(tmp_tmp, tmp_extra); // tmp_tmp = (x ^ y) + (x | y) + a.mov(tmp_extra, tmp_x); + a.add(tmp_extra, tmp_y); // tmp_extra = x + y + a.sub(tmp_tmp, tmp_extra); // tmp_tmp = ... - (x + y) + a.mov(tmp_x, tmp_tmp); + + } + + a.mov(dest64, tmp_x); + } + else if (instruction.mnemonic == ZYDIS_MNEMONIC_OR) { + + if (variant_idx == 0) { + + // x + y - (x & y) + a.mov(tmp_extra, tmp_x); + a.add(tmp_extra, tmp_y); // tmp_extra = x + y + a.mov(tmp_tmp, tmp_x); + a.and_(tmp_tmp, tmp_y); // tmp_tmp = x & y + a.sub(tmp_extra, tmp_tmp); // tmp_extra = x + y - (x & y) + a.mov(tmp_x, tmp_extra); + + } + else if (variant_idx == 1) { + + // ~(~x & ~y) => x | y + a.mov(tmp_tmp, tmp_x); + a.not_(tmp_tmp); // tmp_tmp = ~x + a.mov(tmp_extra, tmp_y); + a.not_(tmp_extra); // tmp_extra = ~y + a.and_(tmp_tmp, tmp_extra); // tmp_tmp = ~x & ~y + a.not_(tmp_tmp); // tmp_tmp = ~(~x & ~y) + a.mov(tmp_x, tmp_tmp); + + } + else { + + // (x & y) + (x ^ y) + a.mov(tmp_tmp, tmp_x); + a.and_(tmp_tmp, tmp_y); // tmp_tmp = x & y + a.mov(tmp_extra, tmp_x); + a.xor_(tmp_extra, tmp_y); // tmp_extra = x ^ y + a.add(tmp_tmp, tmp_extra); // tmp_tmp = (x & y) + (x ^ y) + a.mov(tmp_x, tmp_tmp); + + } + + a.mov(dest64, tmp_x); + } + + // Recuperando contexto... + a.pop(asmjit::x86::r9); + a.pop(asmjit::x86::r8); + a.pop(asmjit::x86::rdx); + a.pop(asmjit::x86::rcx); + a.pop(asmjit::x86::rax); + + // Gerando novos opcodes processados pelo algoritmo de MBA + asmjit::Section* section = code.sectionById(0); + if (!section || section->buffer().empty()) { + + new_instructions.push_back(opcode); + + continue; + } + + const auto& buf = section->buffer(); + std::vector generated(buf.begin(), buf.end()); + new_instructions.push_back(std::move(generated)); + isMbaRewritten = true; + } + catch (const std::exception& ex) { + + // Apenas se a semantica foi inválidada ou alguma instrução não ser semanticamente esperada... + std::cerr << "[RyujinMBAObfuscationPass] assembly exception: " << ex.what() << ".\n"; + new_instructions.push_back(opcode); + + continue; + } + } + + if (!isMbaRewritten) new_instructions.push_back(opcode); + + } + + // Overwriting opcodes with the new obfuscated ones + block.opcodes.clear(); + block.opcodes.assign(new_instructions.begin(), new_instructions.end()); + } + +} + + auto print_help() -> void { std::cout << R"(Ryujin Obfuscator CLI @@ -137,6 +624,8 @@ auto main(int argc, char* argv[]) -> int { // Registering a new custom pass for invocation via callback config.RegisterCallback(RyujinCustomPassDemo); + config.RegisterCallback(RyujinMBAObfuscationPass); + if (has_flag(args, "--procs")) { auto rawList = args["--procs"]; size_t start = 0; diff --git a/RyujinCore/Ryujin/RyujinCore/RyujinObfuscationCore.cc b/RyujinCore/Ryujin/RyujinCore/RyujinObfuscationCore.cc index 4010669..944777e 100644 --- a/RyujinCore/Ryujin/RyujinCore/RyujinObfuscationCore.cc +++ b/RyujinCore/Ryujin/RyujinCore/RyujinObfuscationCore.cc @@ -2702,15 +2702,15 @@ std::vector RyujinObfuscationCore::RunMiniVmObfuscation() { ZydisRegister selected_zydis_reg = safe_unused_registers[reg_dis(gen)]; asmjit::x86::Gp selected_reg = zydisToAsmJitGp(selected_zydis_reg); + a.pushf(); a.push(selected_reg); a.mov(selected_reg, selected_reg); - a.and_(selected_reg, asmjit::imm(0xFFFFFFFF)); - a.add(selected_reg, asmjit::imm(0)); a.pop(selected_reg); + a.popf(); code.flatten();