feat: Handle multiple procedures at once, remove and redirect original code after obfuscation, and fix bugs

- Now Ryujin removes all the original procedure code after obfuscation.
- Now Ryujin redirects the original procedure to the correct obfuscated location for execution.
- Now Ryujin can handle multiple procedures at once and their relocation, organizing each obfuscated procedure sequentially in the new section.
- Fixed the bug in "RyujinObfuscationCore::fix_branch_near_far_short" that could break in some unexpected branching cases.
This commit is contained in:
keowu
2025-06-03 20:58:00 -03:00
parent caf7a199db
commit a02c72a5e5
6 changed files with 87 additions and 23 deletions

View File

@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35931.197 d17.13
VisualStudioVersion = 17.13.35931.197
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RyujinConsole", "RyujinConsole\RyujinConsole.vcxproj", "{1DC1BB2C-6B3E-4084-8F26-76852C709BB4}"
EndProject

View File

@@ -159,22 +159,31 @@ bool Ryujin::run(const RyujinObfuscatorConfig& config) {
RyujinPESections peSections;
peSections.AddNewSection(m_strInputFilePath, chSectionName);
//Get New Opcodes - todo: improve, this only works for the first procedure
std::vector<unsigned char> tempValued;
uintptr_t offsetVA = 0;
std::vector<unsigned char> opcodesWithRelocsFixed;
for (auto& obc : processed_procs) {
tempValued = obc.getProcessedProc().getUpdateOpcodes();
auto tempValued = obc.getProcessedProc().getUpdateOpcodes();
//Fix relocations
obc.applyRelocationFixupsToInstructions(reinterpret_cast<uintptr_t>(imgDos), peSections.getRyujinSectionVA(), tempValued);
obc.applyRelocationFixupsToInstructions(reinterpret_cast<uintptr_t>(imgDos), peSections.getRyujinSectionVA() + offsetVA, tempValued);
//Removendo e adicionando um salto no procedimento original e removendo opcodes originais para um salto ao novo c<>digo ofuscado
obc.removeOldOpcodeRedirect(peSections.mappedPeDiskBaseAddress(), peSections.getRyujinMappedPeSize(), reinterpret_cast<uintptr_t>(imgDos) + peSections.getRyujinSectionVA() + offsetVA);
//Destructing class
obc.~RyujinObfuscationCore();
//Inserindo procedures na lista de opcodes corrigidos
opcodesWithRelocsFixed.insert(opcodesWithRelocsFixed.end(), tempValued.begin(), tempValued.end());
// Incrementando o offset com o tamanho dos opcodes em quest<73>o
offsetVA += tempValued.size();
}
//Process new opcodes
peSections.ProcessOpcodesNewSection(tempValued);
peSections.ProcessOpcodesNewSection(opcodesWithRelocsFixed);
//Save output file
peSections.FinishNewSection(m_strOutputFilePath);

View File

@@ -97,8 +97,7 @@ void RyujinObfuscationCore::addPaddingSpaces() {
code.init(runtime.environment());
asmjit::x86::Assembler a(&code);
for (auto i = 0; i < MAX_PADDING_SPACE_INSTR; i++)
a.nop();
for (auto i = 0; i < MAX_PADDING_SPACE_INSTR; i++) a.nop();
code.flatten();
@@ -148,43 +147,45 @@ 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) {
// Mapping short opcodes to near
static const std::unordered_map<uint8_t, uint8_t> SHORT_TO_NEAR = {
{ 0x70, 0x80 }, { 0x71, 0x81 }, { 0x72, 0x82 }, { 0x73, 0x83 },
{ 0x74, 0x84 }, { 0x75, 0x85 }, { 0x76, 0x86 }, { 0x77, 0x87 },
{ 0x78, 0x88 }, { 0x79, 0x89 }, { 0x7A, 0x8A }, { 0x7B, 0x8B },
{ 0x7C, 0x8C }, { 0x7D, 0x8D }, { 0x7E, 0x8E }, { 0x7F, 0x8F }
};
std::vector<uint8_t> result;
// First tries as a short jump (2 bytes)
// Tries to handle as a short jump (2 bytes)
const int short_length = 2;
const int64_t short_disp = static_cast<int64_t>(target_address) - (jmp_address + short_length);
if (short_disp >= -128 && short_disp <= 127) {
// Keeps it as a short jump
result.push_back(original_opcode);
result.push_back(static_cast<uint8_t>(short_disp));
return result;
}
// Converts to a near jump (6 bytes)
// If it is not a conditional jump, returns the original
auto it = SHORT_TO_NEAR.find(original_opcode);
if (it == SHORT_TO_NEAR.end()) throw new std::exception("[X] RyujinObfuscationCore::fix_branch_offset_cpp: Branch opcode is not suported to regenerate a branch");
if (it == SHORT_TO_NEAR.end()) {
result.push_back(original_opcode);
return result; // Does not apply conversion
}
// Handles as a near jump (6 bytes)
const uint8_t near_opcode = it->second;
const int near_length = 6;
const int64_t near_disp = static_cast<int64_t>(target_address) - (jmp_address + near_length);
// Checks for 32-bit overflow
if (near_disp < INT32_MIN || near_disp > INT32_MAX) throw std::exception("[X] Offset exceeds the limit of a 32-bit signed integer.");
// Packs the displacement (little-endian)
result.push_back(0x0F);
result.push_back(near_opcode);
@@ -436,6 +437,44 @@ void RyujinObfuscationCore::applyRelocationFixupsToInstructions(uintptr_t imageB
}
void RyujinObfuscationCore::removeOldOpcodeRedirect(uintptr_t newMappedPE, std::size_t szMapped, uintptr_t newObfuscatedAddress) {
/*
Creating signatures to search for the opcode in the PE mapped from disk.
We will use findOpcodeOffset to find the exact offset of the procedure's start
in the unmapped region with the SEC_IMAGE flag.
*/
unsigned char ucSigature[10]{ 0 };
std::memcpy(ucSigature, reinterpret_cast<void*>(m_proc.address), 10);
auto offsetz = findOpcodeOffset(reinterpret_cast<unsigned char*>(newMappedPE), szMapped, &ucSigature, 10);
/*
Removing all the opcodes from the original procedure and replacing them with NOP instructions.
*/
std::memset(reinterpret_cast<void*>(newMappedPE + offsetz), 0x90, m_proc.size);
/*
Creating a new JMP opcode in such a way that it can be added to the old region that was completely replaced by NOP,
thus redirecting execution to the new obfuscated code.
*/
unsigned char ucOpcodeJmp[5]{
0xE9, 0, 0, 0, 0, //JMP imm
};
/*
Calculating the new displacement between the original code region and the target obfuscated opcode,
calculating the relative immediate offset.
*/
const uint32_t offset = newObfuscatedAddress - (m_proc.address + 5);
//Replacing the jump opcode with the new relative immediate displacement value.
std::memcpy(&*(ucOpcodeJmp + 1), &offset, sizeof(uint32_t));
//Inserting the new jump opcode into the original cleaned function to redirect execution to the fully obfuscated code.
std::memcpy(reinterpret_cast<void*>(newMappedPE + offsetz), ucOpcodeJmp, 5);
}
RyujinObfuscationCore::~RyujinObfuscationCore() {
}

View File

@@ -14,18 +14,19 @@
class RyujinObfuscationCore {
private:
const int MAX_PADDING_SPACE_INSTR = 50;
const int MAX_PADDING_SPACE_INSTR = 15;
std::vector<ZydisRegister> m_unusedRegisters;
std::vector<RyujinBasicBlock> m_obfuscated_bb;
RyujinProcedure m_proc;
BOOL extractUnusedRegisters();
void addPaddingSpaces();
std::vector<uint8_t> 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);
uint32_t findOpcodeOffset(const uint8_t* data, size_t dataSize, const void* opcode, size_t opcodeSize);
void applyRelocationFixupsToInstructions(uintptr_t imageBase, DWORD virtualAddress, std::vector<unsigned char>& new_opcodes);
void removeOldOpcodeRedirect(uintptr_t newMappedPE, std::size_t szMapped, uintptr_t newObfuscatedAddress);
BOOL Run();
RyujinProcedure getProcessedProc();
~RyujinObfuscationCore();

View File

@@ -31,11 +31,21 @@ public:
return m_ucResizedPE;
}
uintptr_t mappedPeDiskBaseAddress() {
return reinterpret_cast<uintptr_t>(m_dosHeader);
}
uintptr_t getRyujinSectionSize() {
return m_szNewSec;
}
uintptr_t getRyujinMappedPeSize() {
return m_szFile;
}
BOOL AddNewSection(const std::string& strInputFilePath, char chSectionName[8]);
BOOL ProcessOpcodesNewSection(std::vector<unsigned char>& opcodeData);

View File

@@ -5,7 +5,7 @@ auto main() -> int {
std::cout << "Hello World!\n";
std::unique_ptr<Ryujin> ryujin = std::make_unique<Ryujin>("C:\\Users\\Keowu\\Documents\\GitHub\\MoFei\\x64\\Debug\\DemoObfuscation.exe", "C:\\Users\\Keowu\\Documents\\GitHub\\MoFei\\x64\\Debug\\DemoObfuscation.pdb", "C:\\Users\\Keowu\\Documents\\GitHub\\MoFei\\x64\\Debug\\DemoObfuscation2.exe");
std::unique_ptr<Ryujin> ryujin = std::make_unique<Ryujin>("C:\\Users\\Keowu\\Documents\\GitHub\\Ryujin\\RyujinConsole\\x64\\Debug\\DemoObfuscation.exe", "C:\\Users\\Keowu\\Documents\\GitHub\\Ryujin\\RyujinConsole\\x64\\Debug\\DemoObfuscation.pdb", "C:\\Users\\Keowu\\Documents\\GitHub\\Ryujin\\RyujinConsole\\x64\\Debug\\DemoObfuscation.obfuscated.exe");
ryujin.get()->listRyujinProcedures();
@@ -15,7 +15,12 @@ auto main() -> int {
config.m_isRandomSection = FALSE;
config.m_isVirtualized = FALSE;
std::vector<std::string> procsToObfuscate{
"main"
"main",
"mainCRTStartup",
"invoke_main",
"sum",
"__scrt_common_main",
"j___security_init_cookie"
};
config.m_strProceduresToObfuscate.assign(procsToObfuscate.begin(), procsToObfuscate.end());