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

View File

@@ -408,44 +408,44 @@ void RyujinObfuscationCore::insertJunkCode() {
void RyujinObfuscationCore::insertVirtualization() { 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). 1 - Convert the procedure's instructions and their basic blocks into the VM's bytecode (each instruction generates 8 bytes of 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) 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 - 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 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.
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.
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: Example output:
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 0x112210
exemplo:
0x48 -> mov -> bytecode
rbx -> bytecode
10 -> valor
Exemplo de sa<73>da: Which will be assigned to the value of RCX:
0x112210
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 In this way, the code would continue.
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
*/ */
// <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) { auto isValidToSRyujinMiniVm = [&](RyujinInstruction instr) {
return instr.instruction.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && instr.instruction.operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE && 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); (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 { auto mapZydisToAsmjitGp = [&](ZydisRegister zydisReg) -> asmjit::x86::Gp {
switch (zydisReg) { 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) { auto translateToMiniVmBytecode = [&](ZydisRegister reg, ZyanU8 op, ZyanU64 value) {
ZyanU64 miniVmByteCode = 0; ZyanU64 miniVmByteCode = 0;
@@ -727,69 +727,89 @@ void RyujinObfuscationCore::insertVirtualization() {
default: break; default: break;
} }
return miniVmByteCode; return miniVmByteCode;
}; };
// Inicializando o runtime do asmjit // Initializing the asmjit runtime
asmjit::JitRuntime runtime; asmjit::JitRuntime runtime;
for (auto& block : m_proc.basic_blocks) { for (auto& block : m_proc.basic_blocks) {
for (auto& instr : block.instructions) { 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; std::vector<ZyanU8> minivm_enter;
// Operand type // Operand type
ZyanU8 opType = 0; 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)); 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; 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]; 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; 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_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_IMUL && isValidToSRyujinMiniVm(instr)) opType = 3;
else if (instr.instruction.info.mnemonic == ZYDIS_MNEMONIC_DIV && isValidToSRyujinMiniVm(instr)) opType = 4; 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) { 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; asmjit::CodeHolder code;
code.init(runtime.environment()); code.init(runtime.environment());
asmjit::x86::Assembler a(&code); asmjit::x86::Assembler a(&code);
// Saving the current value of RCX
a.push(asmjit::x86::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); a.emit(asmjit::x86::Inst::kIdRdgsbase, asmjit::x86::rax);
// Adding to RAX the offset value for the PEB
a.add(asmjit::x86::rax, 0x60); 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)); 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); 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)); 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)); a.add(asmjit::x86::rax, asmjit::imm(0x88));
// Calling the MiniVMEnter procedure to execute
a.call(asmjit::x86::rax); 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); 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); a.pop(asmjit::x86::rcx);
// Retrieving from ASMJIT<49>s JIT the resulting opcodes generated by our algorithm
auto& opcodeBuffer = code.sectionById(0)->buffer(); auto& opcodeBuffer = code.sectionById(0)->buffer();
const auto pOpcodeBuffer = opcodeBuffer.data(); const auto pOpcodeBuffer = opcodeBuffer.data();
minivm_enter.reserve(opcodeBuffer.size()); 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])); 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()); 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 //Inserting Ryujin MiniVm Address on each vm entry reference
if (m_config.m_isVirtualized) { if (m_config.m_isVirtualized) {
auto size = new_opcodes.size(); auto size = new_opcodes.size();
auto data = new_opcodes.data(); auto data = new_opcodes.data();
unsigned char ucSignature[]{ 0x48, 0x05, 0x88, 0x00, 0x00, 0x00 }; unsigned char ucSignature[]{ 0x48, 0x05, 0x88, 0x00, 0x00, 0x00 };
for (auto i = 0; i < size; i++) for (auto i = 0; i < size; i++)
if (std::memcmp(&*(data + i), ucSignature, 6) == 0) { 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::memset(&*(data + i + 2), 0, 4);
std::memcpy(&*(data + i + 2), &virtualAddress, sizeof(uint32_t)); std::memcpy(&*(data + i + 2), &virtualAddress, sizeof(uint32_t));
} }
} }
} }

View File

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