feat: Finalized IAT Obfuscation Feature Implementation

- Completed the implementation of the IAT obfuscation logic.
- Integrated AsmJit-based code emission for obfuscation.
- Obfuscated the PEB and its offset entries to evade automated scanning.
 - Added a randomized algorithm to generate a unique key for each iteration.
- Implemented runtime polymorphic IAT resolution to disrupt disassemblers and decompilers.
This commit is contained in:
keowu
2025-06-08 11:45:59 -03:00
parent 351756a1b4
commit 21cd08a327
2 changed files with 82 additions and 28 deletions

View File

@@ -53,10 +53,10 @@ BOOL RyujinObfuscationCore::extractUnusedRegisters() {
if (op.type == ZYDIS_OPERAND_TYPE_REGISTER) usedRegs.insert(op.reg.value); if (op.type == ZYDIS_OPERAND_TYPE_REGISTER) usedRegs.insert(op.reg.value);
else if (op.type == ZYDIS_OPERAND_TYPE_POINTER) { else if (op.type == ZYDIS_OPERAND_TYPE_POINTER) {
if (op.mem.base != ZYDIS_REGISTER_NONE) usedRegs.insert(op.mem.base); if (op.mem.base != ZYDIS_REGISTER_NONE) usedRegs.insert(op.mem.base);
if (op.mem.index != ZYDIS_REGISTER_NONE) usedRegs.insert(op.mem.index); if (op.mem.index != ZYDIS_REGISTER_NONE) usedRegs.insert(op.mem.index);
} }
} }
@@ -152,14 +152,14 @@ void RyujinObfuscationCore::obfuscateIat() {
} }
opcode_id++; opcode_id++;
} }
block_id++; block_id++;
} }
return std::make_pair(-1, -1); return std::make_pair(-1, -1);
}; };
for (auto& block : m_obfuscated_bb) { for (auto& block : m_obfuscated_bb) {
@@ -186,23 +186,76 @@ void RyujinObfuscationCore::obfuscateIat() {
const uintptr_t next_instruction_address = orInstr.addressofinstruction + orInstr.instruction.info.length; 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 // 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; uint32_t iat_target_rva = (next_instruction_address + orInstr.instruction.operands->mem.disp.value) - m_ProcImageBase;
/*
Let's obfuscate our RVA
*/
// Generating two random bytes for the key
std::mt19937 rng(std::random_device{}());
// A single random value of 2 bytes (uint16_t)
std::uniform_int_distribution<uint16_t> dist(0, 0xFFFF);
uint16_t xorKey = dist(rng);
// Obfsucate the RVA with a XOR
iat_target_rva ^= xorKey;
// Obfuscate PEB offset from automatic scan
unsigned char PebGsOffset = 0x60 ^ (xorKey & 0xFF);
unsigned char ImageBasePeb = 0x10 ^ (xorKey & 0xFF);
// A new vector to store our corrected IAT // A new vector to store our corrected IAT
std::vector<ZyanU8> new_iat_call; std::vector<ZyanU8> new_iat_call;
unsigned char pebRecoverModuleBase[24]{ //Begin ASMJIT configuration
0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, // mov rax, gs:60h asmjit::JitRuntime runtime;
0x48, 0x8B, 0x40, 0x10, // mov rax, qword ptr ds:[rax+0x10] asmjit::CodeHolder code;
0x48, 0x05, 0x00, 0x00, 0x00, 0x00, // add rax, 22228h code.init(runtime.environment());
0x48, 0x8B, 0x00, //mov rax, qword ptr ds:[rax] -> Recover the IAT jump value stored in the data segment asmjit::x86::Assembler a(&code);
0xFF, 0xD0//call rax
};
std::memcpy(&*(pebRecoverModuleBase + 15), &iat_target_rva, sizeof(uint32_t)); // Using `rdgsbase rax` to store the base address of the GS segment in RAX -> rdgsbase rax
a.emit(asmjit::x86::Inst::kIdRdgsbase, asmjit::x86::rax);
new_iat_call.insert(new_iat_call.end(), std::begin(pebRecoverModuleBase), std::end(pebRecoverModuleBase)); // Adding the obfuscated offset of the PEB in the GS segment -> add rax, PebGsOffset
a.add(asmjit::x86::rax, PebGsOffset);
// Undoing the XOR operation with the obfuscated RAX value and the XOR key -> xor rax, lastByteXorKey
a.xor_(asmjit::x86::rax, asmjit::imm(xorKey & 0xFF));
// Accessing the resulting address to retrieve the PEB instance -> mov rax, [rax]
a.mov(asmjit::x86::rax, asmjit::x86::ptr(asmjit::x86::rax));
// Adding the obfuscated offset of the ImageBase field in the PEB -> add rax, ImageBasePeb
a.add(asmjit::x86::rax, ImageBasePeb);
// Undoing the XOR operation with the obfuscated value and the XOR key -> xor rax, lastByteXorKey
a.xor_(asmjit::x86::rax, asmjit::imm(xorKey & 0xFF));
// Accessing the resulting address to retrieve the PEB+ImageBase instance -> mov rax, [rax]
a.mov(asmjit::x86::rax, asmjit::x86::ptr(asmjit::x86::rax));
// Adding the RVA that points to the entry in the IAT -> add rax, imm32 -> Adding the offset of the IAT entry
a.add(asmjit::x86::rax, asmjit::imm(iat_target_rva));
// Undoing the XOR operation with the obfuscated value and the XOR key -> xor rax, xorKey
a.xor_(asmjit::x86::rax, asmjit::imm(xorKey));
// mov rax, [rax] -> retrieving the resolved address for the IAT entry by the OS loader
a.mov(asmjit::x86::rax, asmjit::x86::ptr(asmjit::x86::rax));
// call rax -> Calling the IAT
a.call(asmjit::x86::rax);
// Obtaining the new section buffer
auto& opcodeBuffer = code.sectionById(0)->buffer();
// Obtaining the pointer to the buffer of raw opcode data generated
const auto pOpcodeBuffer = opcodeBuffer.data();
// Reserving space in the IAT vector
new_iat_call.reserve(opcodeBuffer.size());
// Storing each opcode individually in the vector for our new IAT call
for (auto i = 0; i < opcodeBuffer.size(); ++i) new_iat_call.push_back(static_cast<ZyanU8>(pOpcodeBuffer[i]));
// Replacing opcodes of the call in question with the new ones // Replacing opcodes of the call in question with the new ones
data.assign(new_iat_call.begin(), new_iat_call.end()); data.assign(new_iat_call.begin(), new_iat_call.end());
@@ -245,16 +298,15 @@ BOOL RyujinObfuscationCore::Run() {
} }
/* /*
if (config.m_isVirtualized) todoAction();
if (config.m_isJunkCode) todoAction(); if (config.m_isJunkCode) todoAction();
if (config.m_isVirtualized) todoAction();
*/ */
return TRUE; return TRUE;
} }
uint32_t RyujinObfuscationCore::findOpcodeOffset(const uint8_t* data, size_t dataSize, const void* opcode, size_t opcodeSize) { uint32_t RyujinObfuscationCore::findOpcodeOffset(const uint8_t* data, size_t dataSize, const void* opcode, size_t opcodeSize) {
if (opcodeSize == 0 || dataSize < opcodeSize) return 0; if (opcodeSize == 0 || dataSize < opcodeSize) return 0;
for (size_t i = 0; i <= dataSize - opcodeSize; ++i) if (std::memcmp(data + i, opcode, opcodeSize) == 0) return static_cast<uint32_t>(i); for (size_t i = 0; i <= dataSize - opcodeSize; ++i) if (std::memcmp(data + i, opcode, opcodeSize) == 0) return static_cast<uint32_t>(i);
@@ -263,14 +315,14 @@ uint32_t RyujinObfuscationCore::findOpcodeOffset(const uint8_t* data, size_t dat
} }
std::vector<uint8_t> RyujinObfuscationCore::fix_branch_near_far_short(uint8_t original_opcode, uint64_t jmp_address, uint64_t target_address) { std::vector<uint8_t> RyujinObfuscationCore::fix_branch_near_far_short(uint8_t original_opcode, uint64_t jmp_address, uint64_t target_address) {
static const std::unordered_map<uint8_t, uint8_t> SHORT_TO_NEAR = { static const std::unordered_map<uint8_t, uint8_t> SHORT_TO_NEAR = {
{ 0x70, 0x80 }, { 0x71, 0x81 }, { 0x72, 0x82 }, { 0x73, 0x83 }, { 0x70, 0x80 }, { 0x71, 0x81 }, { 0x72, 0x82 }, { 0x73, 0x83 },
{ 0x74, 0x84 }, { 0x75, 0x85 }, { 0x76, 0x86 }, { 0x77, 0x87 }, { 0x74, 0x84 }, { 0x75, 0x85 }, { 0x76, 0x86 }, { 0x77, 0x87 },
{ 0x78, 0x88 }, { 0x79, 0x89 }, { 0x7A, 0x8A }, { 0x7B, 0x8B }, { 0x78, 0x88 }, { 0x79, 0x89 }, { 0x7A, 0x8A }, { 0x7B, 0x8B },
{ 0x7C, 0x8C }, { 0x7D, 0x8D }, { 0x7E, 0x8E }, { 0x7F, 0x8F } { 0x7C, 0x8C }, { 0x7D, 0x8D }, { 0x7E, 0x8E }, { 0x7F, 0x8F }
}; };
std::vector<uint8_t> result; std::vector<uint8_t> result;
@@ -280,10 +332,10 @@ std::vector<uint8_t> RyujinObfuscationCore::fix_branch_near_far_short(uint8_t or
const int64_t short_disp = static_cast<int64_t>(target_address) - (jmp_address + short_length); const int64_t short_disp = static_cast<int64_t>(target_address) - (jmp_address + short_length);
if (short_disp >= -128 && short_disp <= 127) { if (short_disp >= -128 && short_disp <= 127) {
result.push_back(original_opcode); result.push_back(original_opcode);
result.push_back(static_cast<uint8_t>(short_disp)); result.push_back(static_cast<uint8_t>(short_disp));
return result; return result;
} }
@@ -292,7 +344,7 @@ std::vector<uint8_t> RyujinObfuscationCore::fix_branch_near_far_short(uint8_t or
if (it == SHORT_TO_NEAR.end()) { if (it == SHORT_TO_NEAR.end()) {
result.push_back(original_opcode); result.push_back(original_opcode);
return result; // Does not apply conversion return result; // Does not apply conversion
} }
@@ -373,7 +425,7 @@ void RyujinObfuscationCore::applyRelocationFixupsToInstructions(uintptr_t imageB
std::printf("[OK] Fixing CALL IMM -> %s from 0x%X to 0x%X\n", instruction.instruction.text, immediateValue, new_immediate_reloc); std::printf("[OK] Fixing CALL IMM -> %s from 0x%X to 0x%X\n", instruction.instruction.text, immediateValue, new_immediate_reloc);
} }
//Fixing all Call to a memory(IAT) values from our obfuscated opcodes -> CALL [MEMORY] //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 && !m_config.m_isIatObfuscation) { else if (instruction.instruction.info.meta.category == ZYDIS_CATEGORY_CALL && instruction.instruction.operands->type == ZYDIS_OPERAND_TYPE_MEMORY && !m_config.m_isIatObfuscation) {
@@ -462,7 +514,7 @@ void RyujinObfuscationCore::applyRelocationFixupsToInstructions(uintptr_t imageB
*/ */
// Calculating the address of the instruction following the original instruction // Calculating the address of the instruction following the original instruction
const uintptr_t original_rip = original_address + instruction.instruction.info.length; const uintptr_t original_rip = original_address + instruction.instruction.info.length;
// Calculating the original target address of the original instruction // Calculating the original target address of the original instruction
const uintptr_t target_original = original_rip + memmory_immediate_offset; const uintptr_t target_original = original_rip + memmory_immediate_offset;
@@ -481,7 +533,7 @@ void RyujinObfuscationCore::applyRelocationFixupsToInstructions(uintptr_t imageB
} }
else if (instruction.instruction.info.meta.category == ZYDIS_CATEGORY_COND_BR || instruction.instruction.info.meta.category == ZYDIS_CATEGORY_UNCOND_BR) { else if (instruction.instruction.info.meta.category == ZYDIS_CATEGORY_COND_BR || instruction.instruction.info.meta.category == ZYDIS_CATEGORY_UNCOND_BR) {
// References for data and vector size with obfuscated opcodes // References for data and vector size with obfuscated opcodes
auto size = new_opcodes.size(); auto size = new_opcodes.size();
auto data = new_opcodes.data(); auto data = new_opcodes.data();
@@ -537,12 +589,12 @@ void RyujinObfuscationCore::applyRelocationFixupsToInstructions(uintptr_t imageB
// Clearing the branch so we can insert the new branch with the corrected opcode and its offset // Clearing the branch so we can insert the new branch with the corrected opcode and its offset
std::memset(&*(data + offset), 0x90, 9); // Equivalent to -> branch + offset and possibly some add reg, value -> we have space because "addPaddingSpaces" into this section. std::memset(&*(data + offset), 0x90, 9); // Equivalent to -> branch + offset and possibly some add reg, value -> we have space because "addPaddingSpaces" into this section.
// Patching the cleared region with the new branch, now fully fixed and with the newly calculated jump displacement // Patching the cleared region with the new branch, now fully fixed and with the newly calculated jump displacement
std::memcpy(&*(data + offset), corrected.data(), corrected.size()); std::memcpy(&*(data + offset), corrected.data(), corrected.size());
std::printf("[OK] Fixing %s -> %X -> id: %X\n", instruction.instruction.text, instruction.instruction.operands[0].imm.value.u, block_id); std::printf("[OK] Fixing %s -> %X -> id: %X\n", instruction.instruction.text, instruction.instruction.operands[0].imm.value.u, block_id);
} }
} }
@@ -576,7 +628,7 @@ void RyujinObfuscationCore::removeOldOpcodeRedirect(uintptr_t newMappedPE, std::
0xE9, 0, 0, 0, 0, //JMP imm 0xE9, 0, 0, 0, 0, //JMP imm
}; };
/* /*
Calculating the new displacement between the original code region and the target obfuscated opcode, Calculating the new displacement between the original code region and the target obfuscated opcode,
calculating the relative immediate offset. calculating the relative immediate offset.
*/ */

View File

@@ -3,6 +3,8 @@
#include <vector> #include <vector>
#include <set> #include <set>
#include <cstdint> #include <cstdint>
#include <random>
#include <functional>
#include <unordered_map> #include <unordered_map>
#include <asmjit/asmjit.h> #include <asmjit/asmjit.h>
#include <Zydis/Zydis.h> #include <Zydis/Zydis.h>