feat: Improve Ryujin MiniVM to support dual arguments for better compatibility and more

- Ryujin MiniVM now supports dynamic register values, allowing it to store immediate values and registers using dual arguments: the first as the register operand (argument one) and the VM bytecode as the second (argument two).
- Code improvements and bug fixes.
This commit is contained in:
keowu
2025-06-17 21:12:15 -03:00
parent da1c91d07a
commit 0b5c9b3808
3 changed files with 94 additions and 60 deletions

View File

@@ -162,39 +162,45 @@ bool Ryujin::run(const RyujinObfuscatorConfig& config) {
//Insert minivm enter routine
if (config.m_isVirtualized) {
std::vector<unsigned char> miniVmEnter{
0x48, 0x89, 0x4C, 0x24, 0x08, 0x48, 0x83, 0xEC, 0x28, 0x48, 0x8B, 0x44,
0x24, 0x30, 0x48, 0xC1, 0xE8, 0x10, 0x48, 0x25, 0xFF, 0x00, 0x00, 0x00,
0x88, 0x44, 0x24, 0x01, 0x48, 0x8B, 0x44, 0x24, 0x30, 0x48, 0xC1, 0xE8,
0x08, 0x48, 0x25, 0xFF, 0x00, 0x00, 0x00, 0x88, 0x04, 0x24, 0x48, 0x8B,
0x44, 0x24, 0x30, 0x48, 0x25, 0xFF, 0x00, 0x00, 0x00, 0x48, 0x89, 0x44,
0x24, 0x10, 0x48, 0xC7, 0x44, 0x24, 0x08, 0x00, 0x00, 0x00, 0x00, 0x0F,
0xB6, 0x04, 0x24, 0x88, 0x44, 0x24, 0x04, 0x80, 0x7C, 0x24, 0x04, 0x01,
0x74, 0x17, 0x80, 0x7C, 0x24, 0x04, 0x02, 0x74, 0x27, 0x80, 0x7C, 0x24,
0x04, 0x03, 0x74, 0x37, 0x80, 0x7C, 0x24, 0x04, 0x04, 0x74, 0x42, 0xEB,
0x53, 0x48, 0x8B, 0x44, 0x24, 0x10, 0x48, 0x8B, 0x4C, 0x24, 0x08, 0x48,
0x03, 0xC8, 0x48, 0x8B, 0xC1, 0x48, 0x89, 0x44, 0x24, 0x08, 0xEB, 0x45,
0x48, 0x8B, 0x44, 0x24, 0x10, 0x48, 0x8B, 0x4C, 0x24, 0x08, 0x48, 0x2B,
0xC8, 0x48, 0x8B, 0xC1, 0x48, 0x89, 0x44, 0x24, 0x08, 0xEB, 0x2E, 0x48,
0x8B, 0x44, 0x24, 0x08, 0x48, 0x0F, 0xAF, 0x44, 0x24, 0x10, 0x48, 0x89,
0x44, 0x24, 0x08, 0xEB, 0x1C, 0x33, 0xD2, 0x48, 0x8B, 0x44, 0x24, 0x08,
0x48, 0xF7, 0x74, 0x24, 0x10, 0x48, 0x89, 0x44, 0x24, 0x08, 0xEB, 0x09,
0x48, 0xC7, 0x44, 0x24, 0x08, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8B, 0x44,
0x24, 0x08, 0x48, 0x83, 0xC4, 0x28, 0xC3
// Ryujin MiniVM Routine -> TODO: MAKE THIS DYNAMIC
std::vector<unsigned char> miniVmEnter {
0x48, 0x89, 0x54, 0x24, 0x10, 0x48, 0x89, 0x4C, 0x24, 0x08, 0x48, 0x83,
0xEC, 0x28, 0x48, 0x8B, 0x44, 0x24, 0x38, 0x48, 0xC1, 0xE8, 0x10, 0x48,
0x25, 0xFF, 0x00, 0x00, 0x00, 0x88, 0x44, 0x24, 0x01, 0x48, 0x8B, 0x44,
0x24, 0x38, 0x48, 0xC1, 0xE8, 0x08, 0x48, 0x25, 0xFF, 0x00, 0x00, 0x00,
0x88, 0x04, 0x24, 0x48, 0x8B, 0x44, 0x24, 0x38, 0x48, 0x25, 0xFF, 0x00,
0x00, 0x00, 0x48, 0x89, 0x44, 0x24, 0x10, 0x48, 0x8B, 0x44, 0x24, 0x30,
0x48, 0x89, 0x44, 0x24, 0x08, 0x0F, 0xB6, 0x04, 0x24, 0x88, 0x44, 0x24,
0x04, 0x80, 0x7C, 0x24, 0x04, 0x01, 0x74, 0x17, 0x80, 0x7C, 0x24, 0x04,
0x02, 0x74, 0x27, 0x80, 0x7C, 0x24, 0x04, 0x03, 0x74, 0x37, 0x80, 0x7C,
0x24, 0x04, 0x04, 0x74, 0x42, 0xEB, 0x53, 0x48, 0x8B, 0x44, 0x24, 0x10,
0x48, 0x8B, 0x4C, 0x24, 0x08, 0x48, 0x03, 0xC8, 0x48, 0x8B, 0xC1, 0x48,
0x89, 0x44, 0x24, 0x08, 0xEB, 0x45, 0x48, 0x8B, 0x44, 0x24, 0x10, 0x48,
0x8B, 0x4C, 0x24, 0x08, 0x48, 0x2B, 0xC8, 0x48, 0x8B, 0xC1, 0x48, 0x89,
0x44, 0x24, 0x08, 0xEB, 0x2E, 0x48, 0x8B, 0x44, 0x24, 0x08, 0x48, 0x0F,
0xAF, 0x44, 0x24, 0x10, 0x48, 0x89, 0x44, 0x24, 0x08, 0xEB, 0x1C, 0x33,
0xD2, 0x48, 0x8B, 0x44, 0x24, 0x08, 0x48, 0xF7, 0x74, 0x24, 0x10, 0x48,
0x89, 0x44, 0x24, 0x08, 0xEB, 0x09, 0x48, 0xC7, 0x44, 0x24, 0x08, 0x00,
0x00, 0x00, 0x00, 0x48, 0x8B, 0x44, 0x24, 0x08, 0x48, 0x83, 0xC4, 0x28,
0xC3
};
// Inserting the Ryujin MiniVm stub at the beginning of Ryujin section
opcodesWithRelocsFixed.insert(opcodesWithRelocsFixed.end(), miniVmEnter.begin(), miniVmEnter.end());
// Storing the MiniVm Stub Offset
miniVmEnterAddress = peSections.getRyujinSectionVA();
// Calculating the size of the MiniVM Stub
offsetVA += miniVmEnter.size();
}
for (auto& obc : processed_procs) {
// Getting new obfuscated opcodes
auto tempValued = obc.getProcessedProc().getUpdateOpcodes();
// Fix relocations

View File

@@ -408,44 +408,44 @@ void RyujinObfuscationCore::insertJunkCode() {
void RyujinObfuscationCore::insertVirtualization() {
/*
1 - Converter instru<72><75>es do procedimento e seus basic blocks para o bytecode da VM(cada instru<72><75>o gera 8 bytes de bytecode).
2 - Substiuir a instru<72><75>o por uma call para a rotina de interpreta<74><61>o da VM e passar os bytecodes via RCX. (levar em considera<72><61>o o salvamento dos contextos de registradore e stack)
3 - Ser capaz de continuar a execu<EFBFBD><EFBFBD>o sem problemas integrando a rotina da VM com o c<>digo original a ser executado e n<>o ofuscado
4 - Essa rotina deve inserir a stub e bytecode da vm apenas. ap<61>s isso teremos um processamento antes de salvar e corrigir reloca<63><61>es
para sermos capazes de encontrar o padr<64>o da rotina de virtualiza<7A><61>o e colocar o endere<72>o real do interpretador da VM para que a mesma funcione.
1 - Convert the procedure's instructions and their basic blocks into the VM's bytecode (each instruction generates 8 bytes of bytecode).
2 - Replace the instruction with a call to the VM's interpretation routine and pass the bytecodes via RCX. (Take into account saving the register and stack contexts.)
3 - Be able to continue execution without issues, integrating the VM routine with the original code that is to be executed and not obfuscated.
4 - This routine should insert only the VM stub and bytecode. After that, there will be a processing step before saving and fixing relocations, so we can identify the virtualization routine pattern and insert the real address of the VM interpreter to make it work.
Basically, this is a single-VM that:
Analyzes the instruction in question, extracts its opcode and maps it to the VM's opcode, extracts its immediates and stores everything in a single set.
Example:
0x48 -> mov -> bytecode
rbx -> bytecode
10 -> value
Basicamente essa <20> uma single-vm que:
Analisa a instru<72><75>o em quest<73>o. extrair seu opcode e mapear para o da vm, extrair seus immediatos e armazenala em um unico conjunto
exemplo:
0x48 -> mov -> bytecode
rbx -> bytecode
10 -> valor
Example output:
0x112210
Exemplo de sa<73>da:
0x112210
Which will be assigned to the value of RCX:
Que sera atribuido ao valor de rcx:
push rcx
mov rcx, 112210h
call vmentry (but a symbolic value, since the immediate offset wouldn't be inserted here)
-> rax result goes to the register in question that would continue the execution flow or receive the result, in this example: rbx
pop rcx
push rcx
mov rcx, 112210h
call vmentry(mas um valor simbolico visto que n<>o seria inserido o offset immediato aqui)
-> rax resultado vai no registrado em quest<73>o que continuaria o fluxo de execu<63><75>o ou receberia o resultado, nesse exemplo: rbx
pop rcx
Dessa forma o c<>digo continuaria
In this way, the code would continue.
*/
// <20> uma instru<72><75>o candidata a ser virtualizada pela minivm ??
/*
Ryujin MiniVM Logic Begin
*/
// Is it a candidate instruction to be virtualized by the minivm?
auto isValidToSRyujinMiniVm = [&](RyujinInstruction instr) {
return instr.instruction.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && instr.instruction.operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
//Ignorando registradores e opera<72><61>es de stack
// Ignoring registers and stack operations
(instr.instruction.operands[0].reg.value != ZYDIS_REGISTER_RSP && instr.instruction.operands[0].reg.value != ZYDIS_REGISTER_RBP);
};
// Vamos mapear o registrador do Zydis para o ASMJIT
// Let's map the Zydis register to ASMJIT
auto mapZydisToAsmjitGp = [&](ZydisRegister zydisReg) -> asmjit::x86::Gp {
switch (zydisReg) {
@@ -555,7 +555,7 @@ void RyujinObfuscationCore::insertVirtualization() {
};
// Vamos traduzir uma instru<72><75>o para o bytecode da MiniVm do Ryujin
// Let's translate an instruction to the MiniVm bytecode from Ryujin
auto translateToMiniVmBytecode = [&](ZydisRegister reg, ZyanU8 op, ZyanU64 value) {
ZyanU64 miniVmByteCode = 0;
@@ -727,69 +727,89 @@ void RyujinObfuscationCore::insertVirtualization() {
default: break;
}
return miniVmByteCode;
};
// Inicializando o runtime do asmjit
// Initializing the asmjit runtime
asmjit::JitRuntime runtime;
for (auto& block : m_proc.basic_blocks) {
for (auto& instr : block.instructions) {
// Vector para armazenarmos os opcodes da MiniVm do Ryujin
// Vector to store the MiniVm opcodes from Ryujin
std::vector<ZyanU8> minivm_enter;
// Operand type
ZyanU8 opType = 0;
// Encontrando o block info para o opcode atual
// Finding the block info for the current opcode
auto block_info = findBlockId(instr.instruction.info.opcode, instr.instruction.operands[1].imm.value.u, 2, sizeof(unsigned char));
// Caso n<>o encontremos
// If not found
if (block_info.first == -1 || block_info.second == -1) continue;
// Recuperando os opcodes originais desta instru<72><75>o ao qual trabalhamos
// Retrieving the original opcodes of the instruction we're working on
auto& data = m_proc.basic_blocks[block_info.first].opcodes[block_info.second];
// Verificando por operands candidatos a serem virtualizados pela minivm
// Checking for operands that are candidates to be virtualized by the minivm
if (instr.instruction.info.mnemonic == ZYDIS_MNEMONIC_ADD && isValidToSRyujinMiniVm(instr)) opType = 1;
else if (instr.instruction.info.mnemonic == ZYDIS_MNEMONIC_SUB && isValidToSRyujinMiniVm(instr)) opType = 2;
else if (instr.instruction.info.mnemonic == ZYDIS_MNEMONIC_IMUL && isValidToSRyujinMiniVm(instr)) opType = 3;
else if (instr.instruction.info.mnemonic == ZYDIS_MNEMONIC_DIV && isValidToSRyujinMiniVm(instr)) opType = 4;
//Existe um VM Operator novo ?
// Is there a new VM Operator?
if (opType != 0) {
// Inicializando para o asmjit gerar as instru<EFBFBD><EFBFBD>es de nossa minivm
//TODO: Implementar algoritmo para ofuscar as constantes da PEB e do Bytecode da VM para um layer extra de seguran<61>a
//TODO: Tentar fazer a MiniVm Stub din<69>micamente
// Initializing asmjit to generate our minivm instructions
asmjit::CodeHolder code;
code.init(runtime.environment());
asmjit::x86::Assembler a(&code);
// Saving the current value of RCX
a.push(asmjit::x86::rcx);
a.mov(asmjit::x86::rcx, translateToMiniVmBytecode(instr.instruction.operands[0].reg.value, opType, instr.instruction.operands[1].imm.value.u)); //TODO: e se o reg ex: rax j<> tiver uma valor para um add, devemos armazenar rax tamb<6D>m ?
// Saving the current value of RDX
a.push(asmjit::x86::rdx);
// Storing in the first argument RCX the value of the register from the first operand of the mathematical operation
a.mov(asmjit::x86::rcx, mapZydisToAsmjitGp(instr.instruction.operands[0].reg.value));
// Storing in the second argument RDX the value of the bytecode sequence to be interpreted by the Ryujin MiniVM
a.mov(asmjit::x86::rdx, translateToMiniVmBytecode(instr.instruction.operands[0].reg.value, opType, instr.instruction.operands[1].imm.value.u));
// Using `rdgsbase rax` to store the base address of the GS segment in RAX
a.emit(asmjit::x86::Inst::kIdRdgsbase, asmjit::x86::rax);
// Adding to RAX the offset value for the PEB
a.add(asmjit::x86::rax, 0x60);
// Accessing and retrieving the PEB address to store it in RAX
a.mov(asmjit::x86::rax, asmjit::x86::ptr(asmjit::x86::rax));
// Adding to RAX the "ImageBase" field of the PEB
a.add(asmjit::x86::rax, 0x10);
// Accessing the "ImageBase" address in the PEB to obtain the actual value
a.mov(asmjit::x86::rax, asmjit::x86::ptr(asmjit::x86::rax));
// Adding to the "ImageBase" value a "default" offset that will later be overwritten by the actual offset of the MiniVM enter
a.add(asmjit::x86::rax, asmjit::imm(0x88));
// Calling the MiniVMEnter procedure to execute
a.call(asmjit::x86::rax);
// Storing the result of the MiniVM execution stored in RAX into the correct register to continue the normal execution flow
a.mov(mapZydisToAsmjitGp(instr.instruction.operands[0].reg.value), asmjit::x86::rax);
// Restoring the original value of RDX
a.pop(asmjit::x86::rdx);
// Restoring the original value of RCX
a.pop(asmjit::x86::rcx);
// Retrieving from ASMJIT<49>s JIT the resulting opcodes generated by our algorithm
auto& opcodeBuffer = code.sectionById(0)->buffer();
const auto pOpcodeBuffer = opcodeBuffer.data();
minivm_enter.reserve(opcodeBuffer.size());
// Armazenando cada opcocde individual no nosso vector da minivm
// Storing each individual opcode in our minivm vector
for (auto i = 0; i < opcodeBuffer.size(); ++i) minivm_enter.push_back(static_cast<ZyanU8>(pOpcodeBuffer[i]));
// Sobrescrevendo opcodes antigos pelos novos
// Overwriting old opcodes with the new ones
data.assign(minivm_enter.begin(), minivm_enter.end());
std::printf("[!] Inserting a new MiniVm on %s\n", instr.instruction.text);
std::printf("[!] Inserting a new MiniVm ByteCode on %s\n", instr.instruction.text);
}
@@ -1154,17 +1174,23 @@ void RyujinObfuscationCore::InsertMiniVmEnterProcedureAddress(uintptr_t imageBas
//Inserting Ryujin MiniVm Address on each vm entry reference
if (m_config.m_isVirtualized) {
auto size = new_opcodes.size();
auto data = new_opcodes.data();
unsigned char ucSignature[]{ 0x48, 0x05, 0x88, 0x00, 0x00, 0x00 };
for (auto i = 0; i < size; i++)
if (std::memcmp(&*(data + i), ucSignature, 6) == 0) {
std::printf("FIND!!\n");
std::printf("[OK] Inserting MiniVmEnter at %llx\n", imageBase + virtualAddress + i);
std::memset(&*(data + i + 2), 0, 4);
std::memcpy(&*(data + i + 2), &virtualAddress, sizeof(uint32_t));
}
}
}

View File

@@ -18,6 +18,8 @@ auto main() -> int {
config.m_isEncryptObfuscatedCode = FALSE;
std::vector<std::string> procsToObfuscate{
"sum",
"sub",
"subadd",
"main",
"invoke_main"
"__scrt_common_main",