From 351756a1b4c755571aa556eb54a7275d9e1679d2 Mon Sep 17 00:00:00 2001 From: keowu Date: Sat, 7 Jun 2025 21:55:58 -0300 Subject: [PATCH] feat: Initial implementation of IAT obfuscation feature - Initial IAT obfuscation feature, Ryujin can now obfuscate the IAT for the configured procedures. - New basic block context generation. - Bug fixes. - Improved obfuscation logic for better organization. --- .../Ryujin/Models/RyujinInstruction.hh | 2 +- RyujinConsole/RyujinConsole/Ryujin/Ryujin.cc | 2 +- .../RyujinCore/RyujinObfuscationCore.cc | 123 +++++++++++++++++- .../RyujinCore/RyujinObfuscationCore.hh | 6 +- RyujinConsole/RyujinConsole/RyujinConsole.cc | 1 - 5 files changed, 127 insertions(+), 7 deletions(-) diff --git a/RyujinConsole/RyujinConsole/Ryujin/Models/RyujinInstruction.hh b/RyujinConsole/RyujinConsole/Ryujin/Models/RyujinInstruction.hh index 83fb714..fdd4fab 100644 --- a/RyujinConsole/RyujinConsole/Ryujin/Models/RyujinInstruction.hh +++ b/RyujinConsole/RyujinConsole/Ryujin/Models/RyujinInstruction.hh @@ -1,4 +1,4 @@ -#pragma once; +#pragma once struct RyujinInstruction { diff --git a/RyujinConsole/RyujinConsole/Ryujin/Ryujin.cc b/RyujinConsole/RyujinConsole/Ryujin/Ryujin.cc index 64f3735..062306a 100644 --- a/RyujinConsole/RyujinConsole/Ryujin/Ryujin.cc +++ b/RyujinConsole/RyujinConsole/Ryujin/Ryujin.cc @@ -136,7 +136,7 @@ bool Ryujin::run(const RyujinObfuscatorConfig& config) { proc.basic_blocks = rybb.createBasicBlocks(ucOpcodes, proc.size, proc.address); //Is time to obfuscate ? - RyujinObfuscationCore obc(config, proc); + RyujinObfuscationCore obc(config, proc, reinterpret_cast(m_mappedPE.get())); obc.Run(); //TODO: Custom passes support diff --git a/RyujinConsole/RyujinConsole/Ryujin/RyujinCore/RyujinObfuscationCore.cc b/RyujinConsole/RyujinConsole/Ryujin/RyujinCore/RyujinObfuscationCore.cc index b14be3c..641ade5 100644 --- a/RyujinConsole/RyujinConsole/Ryujin/RyujinCore/RyujinObfuscationCore.cc +++ b/RyujinConsole/RyujinConsole/Ryujin/RyujinCore/RyujinObfuscationCore.cc @@ -1,8 +1,10 @@ #include "RyujinObfuscationCore.hh" -RyujinObfuscationCore::RyujinObfuscationCore(const RyujinObfuscatorConfig& config, const RyujinProcedure& proc) { +RyujinObfuscationCore::RyujinObfuscationCore(const RyujinObfuscatorConfig& config, const RyujinProcedure& proc, uintptr_t ProcImageBase) { m_proc = proc; + m_config = config; + m_ProcImageBase = ProcImageBase; if (!extractUnusedRegisters()) throw std::exception("No registers avaliable for obfuscation..."); @@ -121,13 +123,128 @@ void RyujinObfuscationCore::addPaddingSpaces() { } +void RyujinObfuscationCore::obfuscateIat() { + + /* + Unexpected Ryujin requires at least one unused register in the current procedure to deobfuscate the IAT during runtime + */ + if (m_unusedRegisters.size() == 0) return; + + auto findBlockId = [&](ZyanU8 uopcode, ZyanI64 value) -> std::pair { + + int block_id = 0; + int opcode_id = 0; + + for (auto& block : m_proc.basic_blocks) { + + opcode_id = 0; + + for (auto& opcode : block.opcodes) { + + auto data = opcode.data(); + auto size = opcode.size(); + + if (data[0] == uopcode) { //0xFF ? + + if (std::memcmp(&*(data + 2), &value, sizeof(uint32_t)) == 0) // Is it the same memory immediate? + + return std::make_pair(block_id, opcode_id); + } + + opcode_id++; + + } + + block_id++; + } + + return std::make_pair(-1, -1); + }; + + for (auto& block : m_obfuscated_bb) { + + for (auto& instr : block.instructions) { + + if (instr.instruction.info.meta.category == ZYDIS_CATEGORY_CALL && instr.instruction.operands->type == ZYDIS_OPERAND_TYPE_MEMORY) { + + // Finding the block info related to the obfuscated opcode + auto block_info = findBlockId(instr.instruction.info.opcode, instr.instruction.operands->mem.disp.value); + + // Call to an invalid IAT in the list of basic blocks + if (block_info.first == -1 || block_info.second == -1) continue; + + // Retrieving the original opcodes where the opcodes have already been updated and obfuscated + auto& data = m_proc.basic_blocks[block_info.first].opcodes[block_info.second]; + + // Retrieving the "INSTRUCTION" from the basic block for our IAT call related to this context we're working with + auto orInstr = m_proc.basic_blocks[block_info.first].instructions.back(); // A call [IAT] will always be the last entry + + /* + Let's calculate the IAT address that stores the resolved address for the given CALL + */ + // Calculating the VA of the next instruction after the "CALL" to the IAT in the original section + const uintptr_t next_instruction_address = orInstr.addressofinstruction + orInstr.instruction.info.length; + + // Calculating the target address of the IAT using the memory immediate from the original instruction + const uint32_t iat_target_rva = (next_instruction_address + orInstr.instruction.operands->mem.disp.value) - m_ProcImageBase; + + // A new vector to store our corrected IAT + std::vector new_iat_call; + + unsigned char pebRecoverModuleBase[24]{ + 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, // mov rax, gs:60h + 0x48, 0x8B, 0x40, 0x10, // mov rax, qword ptr ds:[rax+0x10] + 0x48, 0x05, 0x00, 0x00, 0x00, 0x00, // add rax, 22228h + 0x48, 0x8B, 0x00, //mov rax, qword ptr ds:[rax] -> Recover the IAT jump value stored in the data segment + 0xFF, 0xD0//call rax + }; + + std::memcpy(&*(pebRecoverModuleBase + 15), &iat_target_rva, sizeof(uint32_t)); + + new_iat_call.insert(new_iat_call.end(), std::begin(pebRecoverModuleBase), std::end(pebRecoverModuleBase)); + + // Replacing opcodes of the call in question with the new ones + data.assign(new_iat_call.begin(), new_iat_call.end()); + + std::printf("[OK] Obfuscating IAT CALL: %s\n", instr.instruction.text); + + } + + } + + } + + return; +} + +void RyujinObfuscationCore::updateBasicBlocksContext() { + + auto new_obfuscated_opcodes = getProcessedProc().getUpdateOpcodes(); + auto bb = new RyujinBasicBlockerBuilder(ZYDIS_MACHINE_MODE_LONG_64, ZydisStackWidth_::ZYDIS_STACK_WIDTH_64); + m_obfuscated_bb = bb->createBasicBlocks(new_obfuscated_opcodes.data(), static_cast(new_obfuscated_opcodes.size()), m_proc.address); + +} + BOOL RyujinObfuscationCore::Run() { //Add padding spaces addPaddingSpaces(); + //Update basic blocks view based on the new obfuscated + this->updateBasicBlocksContext(); + + //Obfuscate IAT for the configured procedures + if (m_config.m_isIatObfuscation) { + + //First obfuscate IAT + obfuscateIat(); + + //Update our basic blocks context to rely 1-1 for the new obfuscated opcodes. + this->updateBasicBlocksContext(); + + } + /* - if (config.m_isIatObfuscation) todoAction(); if (config.m_isVirtualized) todoAction(); if (config.m_isJunkCode) todoAction(); @@ -258,7 +375,7 @@ void RyujinObfuscationCore::applyRelocationFixupsToInstructions(uintptr_t imageB } //Fixing all Call to a memory(IAT) values from our obfuscated opcodes -> CALL [MEMORY] - else if (instruction.instruction.info.meta.category == ZYDIS_CATEGORY_CALL && instruction.instruction.operands->type == ZYDIS_OPERAND_TYPE_MEMORY) { + else if (instruction.instruction.info.meta.category == ZYDIS_CATEGORY_CALL && instruction.instruction.operands->type == ZYDIS_OPERAND_TYPE_MEMORY && !m_config.m_isIatObfuscation) { // References for the vector's data and size with the obfuscated opcodes auto size = new_opcodes.size(); diff --git a/RyujinConsole/RyujinConsole/Ryujin/RyujinCore/RyujinObfuscationCore.hh b/RyujinConsole/RyujinConsole/Ryujin/RyujinCore/RyujinObfuscationCore.hh index 43fae19..10fc41c 100644 --- a/RyujinConsole/RyujinConsole/Ryujin/RyujinCore/RyujinObfuscationCore.hh +++ b/RyujinConsole/RyujinConsole/Ryujin/RyujinCore/RyujinObfuscationCore.hh @@ -17,14 +17,18 @@ private: const int MAX_PADDING_SPACE_INSTR = 15; std::vector m_unusedRegisters; std::vector m_obfuscated_bb; + uintptr_t m_ProcImageBase; RyujinProcedure m_proc; + RyujinObfuscatorConfig m_config; BOOL extractUnusedRegisters(); + void updateBasicBlocksContext(); void addPaddingSpaces(); + void obfuscateIat(); std::vector fix_branch_near_far_short(uint8_t original_opcode, uint64_t jmp_address, uint64_t target_address); uint32_t findOpcodeOffset(const uint8_t* data, size_t dataSize, const void* opcode, size_t opcodeSize); public: - RyujinObfuscationCore(const RyujinObfuscatorConfig& config, const RyujinProcedure& proc); + RyujinObfuscationCore(const RyujinObfuscatorConfig& config, const RyujinProcedure& proc, uintptr_t ProcImageBase); void applyRelocationFixupsToInstructions(uintptr_t imageBase, DWORD virtualAddress, std::vector& new_opcodes); void removeOldOpcodeRedirect(uintptr_t newMappedPE, std::size_t szMapped, uintptr_t newObfuscatedAddress, bool isIgnoreOriginalCodeRemove = false); BOOL Run(); diff --git a/RyujinConsole/RyujinConsole/RyujinConsole.cc b/RyujinConsole/RyujinConsole/RyujinConsole.cc index 01ca143..74e00df 100644 --- a/RyujinConsole/RyujinConsole/RyujinConsole.cc +++ b/RyujinConsole/RyujinConsole/RyujinConsole.cc @@ -17,7 +17,6 @@ auto main() -> int { config.m_isIatObfuscation = TRUE; std::vector procsToObfuscate{ "main", - "mainCRTStartup", "invoke_main", "sum", "__scrt_common_main",