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.
This commit is contained in:
keowu
2025-10-27 21:06:08 -03:00
parent eef3473ab5
commit 0a51b98c8d
2 changed files with 491 additions and 2 deletions

View File

@@ -7,6 +7,15 @@
#include <iomanip>
#include "RyujinCore.hh"
// MBA Obfuscation Pass
#include <z3++.h>
#include <Zydis/Zydis.h>
#include <asmjit/asmjit.h>
#include <cstdio>
#include <stdexcept>
#include <cstdint>
#include <random>
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<std::vector<ZyanU8>> new_instructions;
new_instructions.reserve(block.opcodes.size());
for (auto& opcode : block.opcodes) {
// Decodificando instru<72><75>es com base nos opcodes armazenados em nossos basic blocks(sempre com o contexto atualizado)
ZydisDecodedInstruction instruction{};
std::vector<ZydisDecodedOperand> 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<72><75>o atual <20> um candidato para ter uma nova expression MBA(apenas com opera<72><61>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<64><61>o da semantica MBA...
// Checando equivalencia(semantica) da express<73>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<63><61>o
std::vector<z3::expr> 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<uint32_t>(unique_id));
std::uniform_int_distribution<size_t> 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 <20> v<>lida
solver.add(obf != target);
if (solver.check() != z3::unsat) {
std::cout << "[RyujinMBAObfuscationPass] Valida<64><61>o de express<73>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<72><75>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<72><75>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<ZyanU8> 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<6E>lidada ou alguma instru<72><75>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;

View File

@@ -2702,15 +2702,15 @@ std::vector<ZyanU8> 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();