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:
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user