feat: Introducing Ryujin Custom Passes header, bug fixies and much more

- Now Ryujin have a independent header file to organize all Custom Passes.
- Some bug fixies when detecting "mba_" prefix procedures was made.
This commit is contained in:
keowu
2025-10-29 22:47:06 -03:00
parent 09075db2f3
commit 22813fcd07
4 changed files with 536 additions and 530 deletions

View File

@@ -6,534 +6,7 @@
#include <sstream>
#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) {
/*
This is a sample callback that demonstrates how Ryujin users can register callbacks during its operation/integration to be used with the RyujinCore engine.
This callback provides direct access to the RyujinProcedure class, which encapsulates all the logic of basic blocks and information about each obfuscated procedure,
allowing modifications and extensions limited only by the implementer's creativity.
In this example, the registered callback is used solely to display relevant information about each procedure to be obfuscated, as a demonstration.
A sample output:
----------------------------------------------
RyujinCustomPassDemo get called for subadd
subadd has 31 bytes, resides on 0x7ff6c7fd1100, with 1 basic blocks.
Instructions:
mov [rsp+0x08], ecx
mov eax, [rsp+0x08]
add eax, 0x0A
mov [rsp+0x08], eax
mov eax, [rsp+0x08]
sub eax, 0x02
mov [rsp+0x08], eax
mov eax, [rsp+0x08]
ret
----------------------------------------------
*/
std::printf("----------------------------------------------\n");
std::printf("RyujinCustomPassDemo get called for %s\n", proc->name.c_str());
std::printf("%s has %lld bytes, resides on 0x%llx, with %llx basic blocks.\n", proc->name.c_str(), proc->size, proc->address, proc->basic_blocks.size());
std::printf("Instructions:\n");
for (auto& block : proc->basic_blocks)
for (auto& inst : block.instructions)
std::printf("%s\n", inst.instruction.text);
std::printf("----------------------------------------------\n");
}
void RyujinMBAObfuscationPass(RyujinProcedure* proc) {
// Translating Zydis registers to ASMJIT registers
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;
};
if (proc->name.find("mba_") != std::string::npos) return;
std::printf("[RyujinMBAObfuscationPass] Processando equivalencia MBA em %s\n", proc->name.c_str());
// Starting decoder
ZydisDecoder decoder;
ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
// Begin z3 context and 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) {
// Decoding instructions based on the opcodes stored in our basic blocks (always with the context updated)
ZydisDecodedInstruction instruction{};
std::vector<ZydisDecodedOperand> operands(ZYDIS_MAX_OPERAND_COUNT);
std::memset(operands.data(), 0, operands.size() * sizeof(ZydisDecodedOperand));
auto status = ZydisDecoderDecodeFull(&decoder, opcode.data(), opcode.size(), &instruction, operands.data());
if (!ZYAN_SUCCESS(status)) {
new_instructions.push_back(opcode);
continue;
}
// Checking if the current instruction is a candidate to have a new MBA expression (only with basic arithmetic operations)
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;
}
// Validating MBA semantics...
// Checking semantic equivalence of the expression with Z3 to ensure the inserted expression won't break later.
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 expressions for obfuscation
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;
}
// Deterministic RNG by unique_id for expression testing
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);
// Verifying if the MBA expression is valid
solver.add(obf != target);
if (solver.check() != z3::unsat) {
std::cout << "[RyujinMBAObfuscationPass] Validation of semantically equivalent expressions with Z3 returned unsat for the procedure. There is no mathematical equivalence to safely rewrite it as an MBA...\n";
new_instructions.push_back(opcode);
continue;
}
try {
// Insertion of the MBA expressions...
// Preparing previously validated MBA expressions and generating new instructions to reproduce the same result using MBA theory
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;
// Save context
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: Aligning instructions to avoid misalignments
a.align(asmjit::AlignMode::kCode, 16);
// Inserting MBA expressions for each supported mnemonic
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 -> same as 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) => same as 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);
}
// Retrieving context...
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);
// Generating new opcodes processed by the MBA algorithm
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) {
// Only if the semantics were invalidated or some instruction is not semantically expected...
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());
}
}
#include "RyujinCustomPasses.hh"
auto print_help() -> void {
@@ -624,9 +97,9 @@ auto main(int argc, char* argv[]) -> int {
config.m_isHVPass = has_flag(args, "--HVPass");
// Registering a new custom pass for invocation via callback
config.RegisterCallback(RyujinCustomPassDemo);
config.RegisterCallback(RyujinCustomPasses::RyujinCustomPassDemo);
config.RegisterCallback(RyujinMBAObfuscationPass);
config.RegisterCallback(RyujinCustomPasses::RyujinMBAObfuscationPass);
if (has_flag(args, "--procs")) {
auto rawList = args["--procs"];

View File

@@ -147,6 +147,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="RyujinCore.hh" />
<ClInclude Include="RyujinCustomPasses.hh" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

View File

@@ -23,5 +23,8 @@
<ClInclude Include="RyujinCore.hh">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="RyujinCustomPasses.hh">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,529 @@
#include <z3++.h>
#include <Zydis/Zydis.h>
#include <asmjit/asmjit.h>
#include <cstdio>
#include <stdexcept>
#include <cstdint>
#include <random>
namespace RyujinCustomPasses {
void RyujinCustomPassDemo(RyujinProcedure* proc) {
/*
This is a sample callback that demonstrates how Ryujin users can register callbacks during its operation/integration to be used with the RyujinCore engine.
This callback provides direct access to the RyujinProcedure class, which encapsulates all the logic of basic blocks and information about each obfuscated procedure,
allowing modifications and extensions limited only by the implementer's creativity.
In this example, the registered callback is used solely to display relevant information about each procedure to be obfuscated, as a demonstration.
A sample output:
----------------------------------------------
RyujinCustomPassDemo get called for subadd
subadd has 31 bytes, resides on 0x7ff6c7fd1100, with 1 basic blocks.
Instructions:
mov [rsp+0x08], ecx
mov eax, [rsp+0x08]
add eax, 0x0A
mov [rsp+0x08], eax
mov eax, [rsp+0x08]
sub eax, 0x02
mov [rsp+0x08], eax
mov eax, [rsp+0x08]
ret
----------------------------------------------
*/
std::printf("----------------------------------------------\n");
std::printf("RyujinCustomPassDemo get called for %s\n", proc->name.c_str());
std::printf("%s has %lld bytes, resides on 0x%llx, with %llx basic blocks.\n", proc->name.c_str(), proc->size, proc->address, proc->basic_blocks.size());
std::printf("Instructions:\n");
for (auto& block : proc->basic_blocks)
for (auto& inst : block.instructions)
std::printf("%s\n", inst.instruction.text);
std::printf("----------------------------------------------\n");
}
void RyujinMBAObfuscationPass(RyujinProcedure* proc) {
// Translating Zydis registers to ASMJIT registers
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;
};
if (proc->name.find("mba_") == std::string::npos) return;
std::printf("[RyujinMBAObfuscationPass] Processando equivalencia MBA em %s\n", proc->name.c_str());
// Starting decoder
ZydisDecoder decoder;
ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64);
// Begin z3 context and 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) {
// Decoding instructions based on the opcodes stored in our basic blocks (always with the context updated)
ZydisDecodedInstruction instruction{};
std::vector<ZydisDecodedOperand> operands(ZYDIS_MAX_OPERAND_COUNT);
std::memset(operands.data(), 0, operands.size() * sizeof(ZydisDecodedOperand));
auto status = ZydisDecoderDecodeFull(&decoder, opcode.data(), opcode.size(), &instruction, operands.data());
if (!ZYAN_SUCCESS(status)) {
new_instructions.push_back(opcode);
continue;
}
// Checking if the current instruction is a candidate to have a new MBA expression (only with basic arithmetic operations)
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;
}
// Validating MBA semantics...
// Checking semantic equivalence of the expression with Z3 to ensure the inserted expression won't break later.
std::string xs_name = "x_" + std::to_string(unique_id);
std::string ys_name = "y_" + std::to_string(unique_id);
unique_id++;
auto sx = ctx.str_symbol(xs_name.c_str());
auto sy = ctx.str_symbol(ys_name.c_str());
auto bv64 = ctx.bv_sort(64);
auto x = ctx.constant(sx, bv64);
auto y = ctx.constant(sy, bv64);
auto 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 expressions for obfuscation
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;
}
// Deterministic RNG by unique_id for expression testing
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);
auto obf = obf_variants[variant_idx];
z3::solver solver(ctx);
// Verifying if the MBA expression is valid
solver.add(obf != target);
if (solver.check() != z3::unsat) {
std::cout << "[RyujinMBAObfuscationPass] Validation of semantically equivalent expressions with Z3 returned unsat for the procedure. There is no mathematical equivalence to safely rewrite it as an MBA...\n";
new_instructions.push_back(opcode);
continue;
}
try {
// Insertion of the MBA expressions...
// Preparing previously validated MBA expressions and generating new instructions to reproduce the same result using MBA theory
asmjit::JitRuntime rt;
asmjit::CodeHolder code;
code.init(rt.environment(), rt.cpuFeatures());
asmjit::x86::Assembler a(&code);
auto dest64 = get_asm_reg_64(operands[0].reg.value);
auto tmp_x = asmjit::x86::rcx;
auto tmp_y = asmjit::x86::rdx;
auto tmp_tmp = asmjit::x86::r8;
auto tmp_extra = asmjit::x86::r9;
// Save context
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) {
auto src64 = get_asm_reg_64(operands[1].reg.value);
a.mov(tmp_y, src64);
}
else if (is_src_imm) {
auto imm = operands[1].imm.value.u;
a.mov(tmp_y, imm);
}
// Sanity Check: Aligning instructions to avoid misalignments
a.align(asmjit::AlignMode::kCode, 16);
// Inserting MBA expressions for each supported mnemonic
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 -> same as 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) => same as 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);
}
// Retrieving context...
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);
// Generating new opcodes processed by the MBA algorithm
auto 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) {
// Only if the semantics were invalidated or some instruction is not semantically expected...
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());
}
}
};