feat: New HVPass (extension for code obfuscation) + MiniVM code mutation (for HVPass and standard MiniVM); Bug fixes for extracted unused registers (with future XMM support); Bug fixes for junk/mutation overwriting the RAX register unexpectedly; Improvements and added support for fixing relocation offsets in memory mov instructions; articles/projects diagrams and more.

- New HVPass feature – This feature allows the code VM to run through Microsoft’s Hypervisor API, adding an extra layer of analysis difficulty.
- MiniVM (normal) or MiniVM + HVPass – Now support junk/mutation in the stub, making the logic and instructions randomized at each interaction, further protecting the stub’s code.
- Bug fix – Fixed an issue in the extraction of unused registers from candidate procedures, where some registers were not being handled correctly.
- Bug fix – Fixed an issue in the extraction of XMM registers to enable junk/mutation support for multimedia registers.
- Bug fix – Fixed a problem in the junk/mutation logic for the instructions cdqe and cbw, which were incorrectly overwriting the RAX register, breaking results even when the registers were in use.
- Bug fix – Some instructions were not having relocations properly fixed by the RIP-relative relocation algorithm; this has now been corrected.
- Articles + Project Diagrams as well.

Some of these issues, as well as feature suggestions like HVPass, were discovered or suggested by the reviewers of Ryujin’s article.
This commit is contained in:
keowu
2025-08-28 21:20:58 -03:00
parent d8c37b2d4c
commit 2f5f9e2bd5
10 changed files with 8439 additions and 334 deletions

View File

@@ -67,12 +67,13 @@ Options:
--Troll Crashes the entire OS if a debugger is detected (requires --AntiDebug). --Troll Crashes the entire OS if a debugger is detected (requires --AntiDebug).
--AntiDump Inserts anti-dump mechanisms that break the binary in memory, making dumps harder to analyze. --AntiDump Inserts anti-dump mechanisms that break the binary in memory, making dumps harder to analyze.
--MemoryProtection Protects obfuscated code against in-memory or on-disk patching. --MemoryProtection Protects obfuscated code against in-memory or on-disk patching.
--HVPass Protect some parts of Ryujin using Microsoft Hypervisor APIs
--procs <comma,separated,names> Procedures to obfuscate (default: main, invoke_main, ...) --procs <comma,separated,names> Procedures to obfuscate (default: main, invoke_main, ...)
--help Show this help message --help Show this help message
In Action Usage Example: In Action Usage Example:
RyujinConsole.exe --input C:\\Users\\Keowu\\Documents\\GitHub\\Ryujin\\compiled\\release\\DemoObfuscation.exe --pdb C:\\Users\\Keowu\\Documents\\GitHub\\Ryujin\\compiled\\release\\RyujinConsole.pdb --output C:\\Users\\Keowu\\Documents\\GitHub\\Ryujin\\compiled\\release\\DemoObfuscation.ryujin.exe --virtualize --junk --encrypt --AntiDebug --troll --AntiDump --procs main,sub,subadd,sum,invoke_main,__scrt_common_main,j___security_init_cookie RyujinConsole.exe --input C:\\Users\\Keowu\\Documents\\GitHub\\Ryujin\\compiled\\release\\DemoObfuscation.exe --pdb C:\\Users\\Keowu\\Documents\\GitHub\\Ryujin\\compiled\\release\\RyujinConsole.pdb --output C:\\Users\\Keowu\\Documents\\GitHub\\Ryujin\\compiled\\release\\DemoObfuscation.ryujin.exe --virtualize --junk --encrypt --AntiDebug --troll --AntiDump --iat --HVPass --procs main,sub,subadd,sum,invoke_main,__scrt_common_main,j___security_init_cookie
)"; )";
@@ -131,6 +132,7 @@ auto main(int argc, char* argv[]) -> int {
config.m_isAntiDebug = has_flag(args, "--AntiDebug"); config.m_isAntiDebug = has_flag(args, "--AntiDebug");
config.m_isAntiDump = has_flag(args, "--AntiDump"); config.m_isAntiDump = has_flag(args, "--AntiDump");
config.m_isMemoryProtection = has_flag(args, "--MemoryProtection"); config.m_isMemoryProtection = has_flag(args, "--MemoryProtection");
config.m_isHVPass = has_flag(args, "--HVPass");
// Registering a new custom pass for invocation via callback // Registering a new custom pass for invocation via callback
config.RegisterCallback(RyujinCustomPassDemo); config.RegisterCallback(RyujinCustomPassDemo);

View File

@@ -35,6 +35,7 @@ public:
bool m_isTrollRerversers; // The user wants to trick and use a special feature to troll reversers when their debugs be detected making they loose all the progress bool m_isTrollRerversers; // The user wants to trick and use a special feature to troll reversers when their debugs be detected making they loose all the progress
bool m_isAntiDump; // Enable Anti Dump technic for Ryujin protected binary bool m_isAntiDump; // Enable Anti Dump technic for Ryujin protected binary
bool m_isMemoryProtection; // Memory CRC32 protection bool m_isMemoryProtection; // Memory CRC32 protection
bool m_isHVPass; // Run some features of ryujin using Microsoft Hypervisor Framework API
RyujinObfuscatorProcs m_strProceduresToObfuscate; // Names of the procedures to obfuscate RyujinObfuscatorProcs m_strProceduresToObfuscate; // Names of the procedures to obfuscate
RyujinCallbacks m_callbacks; // Ryujin Custom Pass Callbacks RyujinCallbacks m_callbacks; // Ryujin Custom Pass Callbacks

View File

@@ -37,6 +37,7 @@ public:
bool m_isTrollRerversers; // The user wants to trick and use a special feature to troll reversers when their debugs be detected making they loose all the progress bool m_isTrollRerversers; // The user wants to trick and use a special feature to troll reversers when their debugs be detected making they loose all the progress
bool m_isAntiDump; // Enable Anti Dump technic for Ryujin protected binary bool m_isAntiDump; // Enable Anti Dump technic for Ryujin protected binary
bool m_isMemoryProtection; // Memory CRC32 protection bool m_isMemoryProtection; // Memory CRC32 protection
bool m_isHVPass; // Run some features of ryujin using Microsoft Hypervisor Framework API
RyujinObfuscatorProcs m_strProceduresToObfuscate; // Names of the procedures to obfuscate RyujinObfuscatorProcs m_strProceduresToObfuscate; // Names of the procedures to obfuscate
RyujinCallbacks m_callbacks; // Ryujin Custom Pass Callbacks RyujinCallbacks m_callbacks; // Ryujin Custom Pass Callbacks

File diff suppressed because it is too large Load Diff

View File

@@ -18,54 +18,139 @@ RyujinProcedure RyujinObfuscationCore::getProcessedProc() {
BOOL RyujinObfuscationCore::extractUnusedRegisters() { BOOL RyujinObfuscationCore::extractUnusedRegisters() {
std::vector<ZydisRegister> candidateRegs = { // List of all general-purpose registers considered as candidates for junk/mutation during comparison.
const std::vector<ZydisRegister> candidateGprRegs = {
ZYDIS_REGISTER_RAX,
ZYDIS_REGISTER_RCX,
ZYDIS_REGISTER_RDX,
ZYDIS_REGISTER_RBX,
ZYDIS_REGISTER_RSI,
ZYDIS_REGISTER_RDI,
ZYDIS_REGISTER_R8,
ZYDIS_REGISTER_R9,
ZYDIS_REGISTER_R10,
ZYDIS_REGISTER_R11,
ZYDIS_REGISTER_R12,
ZYDIS_REGISTER_R13,
ZYDIS_REGISTER_R14,
ZYDIS_REGISTER_R15,
ZYDIS_REGISTER_RAX, ZYDIS_REGISTER_RCX, ZYDIS_REGISTER_RDX,
ZYDIS_REGISTER_RBX, ZYDIS_REGISTER_RSI, ZYDIS_REGISTER_RDI,
ZYDIS_REGISTER_R8, ZYDIS_REGISTER_R9, ZYDIS_REGISTER_R10,
ZYDIS_REGISTER_R11, ZYDIS_REGISTER_R12, ZYDIS_REGISTER_R13,
ZYDIS_REGISTER_R14, ZYDIS_REGISTER_R15
}; };
// List of XMM registers considered as candidates for junk/mutation during comparison.
const std::vector<ZydisRegister> candidateXmmRegs = {
ZYDIS_REGISTER_XMM0, ZYDIS_REGISTER_XMM1, ZYDIS_REGISTER_XMM2, ZYDIS_REGISTER_XMM3,
ZYDIS_REGISTER_XMM4, ZYDIS_REGISTER_XMM5, ZYDIS_REGISTER_XMM6, ZYDIS_REGISTER_XMM7,
ZYDIS_REGISTER_XMM8, ZYDIS_REGISTER_XMM9, ZYDIS_REGISTER_XMM10, ZYDIS_REGISTER_XMM11,
ZYDIS_REGISTER_XMM12, ZYDIS_REGISTER_XMM13, ZYDIS_REGISTER_XMM14, ZYDIS_REGISTER_XMM15
};
m_unusedRegisters.clear();
std::set<ZydisRegister> usedRegs; std::set<ZydisRegister> usedRegs;
for (auto blocks : m_proc.basic_blocks) { // Regardless of everything, the stack manipulation registers RSP and RBP will always be considered as used.
usedRegs.insert(ZYDIS_REGISTER_RSP);
usedRegs.insert(ZYDIS_REGISTER_RBP);
for (auto instr : blocks.instructions) { for (const auto& block : m_proc.basic_blocks) {
for (auto i = 0; i < instr.instruction.info.operand_count; ++i) { for (const auto& instr : block.instructions) {
const auto& dinfo = instr.instruction.info;
const uint8_t opcount = dinfo.operand_count;
for (uint8_t i = 0; i < opcount; ++i) {
const ZydisDecodedOperand& op = instr.instruction.operands[i]; const ZydisDecodedOperand& op = instr.instruction.operands[i];
if (op.type == ZYDIS_OPERAND_TYPE_REGISTER) usedRegs.insert(op.reg.value); // Registers with explicit operands
else if (op.type == ZYDIS_OPERAND_TYPE_POINTER) { if (op.type == ZYDIS_OPERAND_TYPE_REGISTER) {
if (op.mem.base != ZYDIS_REGISTER_NONE) usedRegs.insert(op.mem.base); ZydisRegister reg = op.reg.value;
if (op.mem.index != ZYDIS_REGISTER_NONE) usedRegs.insert(op.mem.index); ZydisRegisterClass cls = ZydisRegisterGetClass(reg);
// Normalizing GPRs to GPR64 registers
if (cls == ZYDIS_REGCLASS_GPR8 || cls == ZYDIS_REGCLASS_GPR16 || cls == ZYDIS_REGCLASS_GPR32 || cls == ZYDIS_REGCLASS_GPR64) {
int16_t id = ZydisRegisterGetId(reg);
// Considering the lower-nibble registers
if (cls == ZYDIS_REGCLASS_GPR8 && id >= 4 && id <= 7)
id -= 4;
ZydisRegister reg64 = ZydisRegisterEncode(ZYDIS_REGCLASS_GPR64, id);
if (reg64 != ZYDIS_REGISTER_NONE) usedRegs.insert(reg64);
}
// Fetching XMM registers
else if (cls == ZYDIS_REGCLASS_XMM)
usedRegs.insert(reg);
// Checking for segment registers
else if (cls == ZYDIS_REGCLASS_SEGMENT)
usedRegs.insert(reg);
}
// Registers in use with memory operands
else if (op.type == ZYDIS_OPERAND_TYPE_MEMORY) {
if (op.mem.base != ZYDIS_REGISTER_NONE) {
ZydisRegister base = op.mem.base;
ZydisRegisterClass cls = ZydisRegisterGetClass(base);
if (cls == ZYDIS_REGCLASS_GPR8 || cls == ZYDIS_REGCLASS_GPR16 || cls == ZYDIS_REGCLASS_GPR32 || cls == ZYDIS_REGCLASS_GPR64) {
int16_t id = ZydisRegisterGetId(base);
ZydisRegister base64 = ZydisRegisterEncode(ZYDIS_REGCLASS_GPR64, id);
if (base64 != ZYDIS_REGISTER_NONE) usedRegs.insert(base64);
}
else
usedRegs.insert(base);
}
// Collecting index registers
if (op.mem.index != ZYDIS_REGISTER_NONE) {
ZydisRegister idx = op.mem.index;
ZydisRegisterClass cls = ZydisRegisterGetClass(idx);
if (cls == ZYDIS_REGCLASS_GPR8 || cls == ZYDIS_REGCLASS_GPR16 || cls == ZYDIS_REGCLASS_GPR32 || cls == ZYDIS_REGCLASS_GPR64) {
int16_t id = ZydisRegisterGetId(idx);
ZydisRegister idx64 = ZydisRegisterEncode(ZYDIS_REGCLASS_GPR64, id);
if (idx64 != ZYDIS_REGISTER_NONE) usedRegs.insert(idx64);
}
else
usedRegs.insert(idx);
}
// Hackfix for segment registers like: cs:[rax]
if (op.mem.segment != ZYDIS_REGISTER_NONE)
usedRegs.insert(op.mem.segment);
} }
} }
} }
} }
ZydisRegister freeReg = ZYDIS_REGISTER_NONE; /*
for (auto reg : candidateRegs) Based on the collected registers, compare each register with the list of used registers
if (usedRegs.count(reg) == 0) m_unusedRegisters.push_back(reg); so we can build the unique set of used registers.
*/
for (ZydisRegister r : candidateGprRegs)
if (usedRegs.count(r) == 0)
m_unusedRegisters.push_back(r);
return m_unusedRegisters.size() >= 2; //Theres unused regs for be used by us ? /*
* TEMPORARILY DISABLED until Ryujin implements support for multimedia registers.
*
* Based on the collected XMM registers, compare each register with the list of used XMM registers
* to build the unique set of used XMM registers.
*/
//for (ZydisRegister r : candidateXmmRegs)
// if (usedRegs.count(r) == 0)
// m_unusedRegisters.push_back(r);
// We need at least 2 registers in order for obfuscation to work.
return (m_unusedRegisters.size() >= 2); // Seriously, Keowu? Yes. We need room to run some passes.
} }
void RyujinObfuscationCore::addPaddingSpaces() { void RyujinObfuscationCore::addPaddingSpaces() {
@@ -295,7 +380,7 @@ void RyujinObfuscationCore::insertJunkCode() {
// Ignore stack unused registers, if the feature for extracting unused register fail // Ignore stack unused registers, if the feature for extracting unused register fail
if (idx == 4 /*RSP*/ || idx == 5 /*RBP*/) continue; if (idx == 4 /*RSP*/ || idx == 5 /*RBP*/) continue;
// Converting GB Register Index to a GB Register // Converting GB Register Index to a GB Register
auto regx = a.gpz(uint32_t(idx)); auto regx = a.gpz(uint32_t(idx));
@@ -370,13 +455,33 @@ void RyujinObfuscationCore::insertJunkCode() {
case 31: a.stc(); break; case 31: a.stc(); break;
case 32: a.clc(); break; case 32: a.clc(); break;
case 33: a.cmc(); break; case 33: a.cmc(); break;
case 34: a.cdqe(); break;
case 35: a.cbw(); break; //////////////////////////
// Different logic for these registers because they overwrite RAX
//--------------------------------------------------
case 34: {
a.push(asmjit::x86::rax);
a.pushf();
a.cdqe();
a.popf();
a.pop(asmjit::x86::rax);
break;
}
case 35: {
a.push(asmjit::x86::rax);
a.pushf();
a.cbw();
a.popf();
a.pop(asmjit::x86::rax);
break;
}
///////////////////////////////
case 36: a.sbb(regx, value); break; case 36: a.sbb(regx, value); break;
case 37: a.bsf(regx, regx); break; case 37: a.bsf(regx, regx); break;
default: break; default: break;
} }
} }
// Junk Code Out // Junk Code Out
@@ -788,11 +893,13 @@ void RyujinObfuscationCore::insertVirtualization() {
// Breaking Decompilers // Breaking Decompilers
insertBreakDecompilers(a); insertBreakDecompilers(a);
// Saving the current value of RCX // Saving the current value of RCX
a.push(asmjit::x86::rcx); a.push(asmjit::x86::rcx);
// Saving the current value of RDX // Saving the current value of RDX
a.push(asmjit::x86::rdx); a.push(asmjit::x86::rdx);
// Setup stack for MS HV Code MiniVMm stub
if (m_config.m_isHVPass) a.sub(asmjit::x86::rsp, 0x28);
// Storing in the first argument RCX the value of the register from the first operand of the mathematical operation // 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)); 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 // Storing in the second argument RDX the value of the bytecode sequence to be interpreted by the Ryujin MiniVM
@@ -819,6 +926,8 @@ void RyujinObfuscationCore::insertVirtualization() {
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 // 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);
// Setup stack for MS HV Code MiniVMm stub
if (m_config.m_isHVPass) a.add(asmjit::x86::rsp, 0x28);
// Restoring the original value of RDX // Restoring the original value of RDX
a.pop(asmjit::x86::rdx); a.pop(asmjit::x86::rdx);
// Restoring the original value of RCX // Restoring the original value of RCX
@@ -2653,21 +2762,45 @@ void RyujinObfuscationCore::applyRelocationFixupsToInstructions(uintptr_t imageB
auto size = new_opcodes.size(); auto size = new_opcodes.size();
auto data = new_opcodes.data(); auto data = new_opcodes.data();
//Avoid memory op for stack
if (instruction.instruction.operands->mem.base == ZYDIS_REGISTER_RSP) {
std::printf("Invalid relocation fix candidate for -> %s\n", instruction.instruction.text);
continue;
}
// Getting the memory immediate offset value to build the signature // Getting the memory immediate offset value to build the signature
const uint32_t memmory_immediate_offset = mem->disp.value; const uint32_t memmory_immediate_offset = mem->disp.value;
// Creating a signature to search for the offset in the obfuscated opcodes ZyanI64 offset = 0;
unsigned char ucOpcodeSignature[7]{ 0 }; unsigned char ucOpcodeSignature[7]{ 0 };
std::memcpy(&ucOpcodeSignature, reinterpret_cast<void*>(instruction.addressofinstruction), 3); // 3 BYTES do opcode relativo ao LEA ou MOV
std::memcpy(&*(ucOpcodeSignature + 3), &memmory_immediate_offset, sizeof(memmory_immediate_offset));
// Finding the offset of the "LEA" or "MOV" that uses memory-relative addressing // If original instruction is not a mov reg, cs[](beacuse it has 6 not 7 bytes)
const ZyanI64 offset = findOpcodeOffset(data, size, &ucOpcodeSignature, 7); if (*reinterpret_cast<unsigned char*>(instruction.addressofinstruction) != 0x8B) {
// Creating a signature to search for the offset in the obfuscated opcodes
std::memcpy(&ucOpcodeSignature, reinterpret_cast<void*>(instruction.addressofinstruction), 3); // 3 BYTES do opcode relativo ao LEA ou MOV
std::memcpy(&*(ucOpcodeSignature + 3), &memmory_immediate_offset, sizeof(memmory_immediate_offset));
// Finding the offset of the "LEA" or "MOV" that uses memory-relative addressing
offset = findOpcodeOffset(data, size, &ucOpcodeSignature, 7);
}
else {
// Creating a signature to search for the offset in the obfuscated opcodes
std::memcpy(&ucOpcodeSignature, reinterpret_cast<void*>(instruction.addressofinstruction), 6); // 6 bytes do opcode relativo MOV CODE SEGMENT: mov reg, cs:addr
// Finding the offset of the "MOV reg, cs:addr" that uses memory-relative addressing
offset = findOpcodeOffset(data, size, &ucOpcodeSignature, 6);
}
// If we don't find any offset, there may be an issue or bug. // If we don't find any offset, there may be an issue or bug.
if (offset == 0) { if (offset == 0) {
std::printf("[X] Invalid lea reference or uknown lea detected.....\n"); std::printf("[X] Invalid lea/mov reference or uknown lea/mov detected.....\n");
continue; continue;
} }
@@ -2694,12 +2827,181 @@ void RyujinObfuscationCore::applyRelocationFixupsToInstructions(uintptr_t imageB
const uintptr_t new_memory_immediate = target_original - new_obfuscated_rip; const uintptr_t new_memory_immediate = target_original - new_obfuscated_rip;
// Fixing the immediate value for the "LEA" or "MOV" instruction with the corrected relative immediate value // Fixing the immediate value for the "LEA" or "MOV" instruction with the corrected relative immediate value
std::memcpy(&*(data + offset + 3), &new_memory_immediate, sizeof(uint32_t)); // 3 bytes for the size of the LEA or MOV opcode if (*reinterpret_cast<unsigned char*>(instruction.addressofinstruction) != 0x8B)
std::memcpy(&*(data + offset + 3), &new_memory_immediate, sizeof(uint32_t)); // 3 bytes for the size of the LEA or MOV opcode
else
std::memcpy(&*(data + offset + 2), &new_memory_immediate, sizeof(uint32_t)); // 3 bytes for the size of the LEA or MOV opcode
std::printf("[OK] Fixing -> %s - from %X to %X\n", instruction.instruction.text, mem->disp.value, new_memory_immediate); std::printf("[OK] Fixing -> %s - from %X to %X\n", instruction.instruction.text, mem->disp.value, new_memory_immediate);
} }
}
else if ((instruction.instruction.info.mnemonic == ZYDIS_MNEMONIC_MOV || instruction.instruction.info.mnemonic == ZYDIS_MNEMONIC_LEA) && instruction.instruction.operands->type == ZYDIS_OPERAND_TYPE_MEMORY && instruction.instruction.operands->mem.type == ZYDIS_MEMOP_TYPE_MEM) {
if (instruction.instruction.info.length > 5) {
// References for data and vector size with obfuscated opcodes
auto size = new_opcodes.size();
auto data = new_opcodes.data();
const ZydisDecodedOperandMem* mem = &instruction.instruction.operands[0].mem;
//Avoid memory op for stack
if (instruction.instruction.operands->mem.base == ZYDIS_REGISTER_RSP) {
//std::printf("Invalid relocation fix candidate for -> %s\n", instruction.instruction.text);
continue;
}
// Getting the memory immediate offset value to build the signature
const uint32_t memmory_immediate_offset = mem->disp.value;
ZyanI64 offset = 0, fix_byte = 0;
unsigned char ucOpcodeSignature[11]{ 0 };
if (instruction.instruction.info.length == 6) {
std::memcpy(&ucOpcodeSignature, reinterpret_cast<void*>(instruction.addressofinstruction), 6);
offset = findOpcodeOffset(data, size, &ucOpcodeSignature, 6);
fix_byte = 2;
}
else if (instruction.instruction.info.length == 7) {
std::memcpy(&ucOpcodeSignature, reinterpret_cast<void*>(instruction.addressofinstruction), 7);
offset = findOpcodeOffset(data, size, &ucOpcodeSignature, 7);
fix_byte = 3;
}
else if (instruction.instruction.info.length == 10) {
std::memcpy(&ucOpcodeSignature, reinterpret_cast<void*>(instruction.addressofinstruction), 10);
offset = findOpcodeOffset(data, size, &ucOpcodeSignature, 10);
fix_byte = 2;
}
else {
std::printf("ERROR Unexpected Instruction mov cs[], reg/imm -> %s - %d\n", instruction.instruction.text, instruction.instruction.info.length);
continue;
}
// Retrieving the instruction address in the original section
const uintptr_t original_address = instruction.addressofinstruction;
// Calculating new address in the obfuscated section
const uintptr_t obfuscated_va_address = ((imageBase + virtualAddress + offset));
/*
Calculating new displacement for the immediate value
*/
// Calculating the address of the instruction following the original instruction
const uintptr_t original_rip = original_address + instruction.instruction.info.length;
// Calculating the original target address of the original instruction
const uintptr_t target_original = original_rip + memmory_immediate_offset;
// Calculating the address of the instruction following the obfuscated instruction
const uintptr_t new_obfuscated_rip = obfuscated_va_address + instruction.instruction.info.length;
// New memory immediate value for the instruction
const uintptr_t new_memory_immediate = target_original - new_obfuscated_rip;
// Fixing the immediate value for the "LEA" or "MOV" instruction with the corrected relative immediate value
std::memcpy(&*(data + offset + fix_byte), &new_memory_immediate, sizeof(uint32_t));
std::printf("[OK] Fixing -> %s - from %X to %X\n", instruction.instruction.text, mem->disp.value, new_memory_immediate);
}
}
else if ((instruction.instruction.info.mnemonic == ZYDIS_MNEMONIC_ADD || instruction.instruction.info.mnemonic == ZYDIS_MNEMONIC_SUB || instruction.instruction.info.mnemonic == ZYDIS_MNEMONIC_XOR) && (instruction.instruction.operands[0].type == ZYDIS_OPERAND_TYPE_MEMORY || instruction.instruction.operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY)) {
// References for data and vector size with obfuscated opcodes
auto size = new_opcodes.size();
auto data = new_opcodes.data();
//Avoid memory op for stack
if (instruction.instruction.operands->mem.base == ZYDIS_REGISTER_RSP) {
std::printf("Invalid relocation fix candidate for -> %s\n", instruction.instruction.text);
continue;
}
ZydisDecodedOperandMem* mem;
if (instruction.instruction.operands[0].type == ZYDIS_OPERAND_TYPE_MEMORY)
mem = &instruction.instruction.operands[0].mem;
else
mem = &instruction.instruction.operands[1].mem;
// Getting the memory immediate offset value to build the signature
const uint32_t memmory_immediate_offset = mem->disp.value;
ZyanI64 offset = 0, fix_byte = 0;
unsigned char ucOpcodeSignature[11]{ 0 };
if (instruction.instruction.info.length == 6) {
std::memcpy(&ucOpcodeSignature, reinterpret_cast<void*>(instruction.addressofinstruction), 6);
offset = findOpcodeOffset(data, size, &ucOpcodeSignature, 6);
fix_byte = 2;
}
else if (instruction.instruction.info.length == 7) {
std::memcpy(&ucOpcodeSignature, reinterpret_cast<void*>(instruction.addressofinstruction), 7);
offset = findOpcodeOffset(data, size, &ucOpcodeSignature, 7);
fix_byte = 3;
}
else {
std::printf("ERROR Unexpected Instruction bitwise/math [], reg or bitwise/math reg, [] -> %s - %d\n", instruction.instruction.text, instruction.instruction.info.length);
continue;
}
// Retrieving the instruction address in the original section
const uintptr_t original_address = instruction.addressofinstruction;
// Calculating new address in the obfuscated section
const uintptr_t obfuscated_va_address = ((imageBase + virtualAddress + offset));
/*
Calculating new displacement for the immediate value
*/
// Calculating the address of the instruction following the original instruction
const uintptr_t original_rip = original_address + instruction.instruction.info.length;
// Calculating the original target address of the original instruction
const uintptr_t target_original = original_rip + memmory_immediate_offset;
// Calculating the address of the instruction following the obfuscated instruction
const uintptr_t new_obfuscated_rip = obfuscated_va_address + instruction.instruction.info.length;
// New memory immediate value for the instruction
const uintptr_t new_memory_immediate = target_original - new_obfuscated_rip;
// Fixing the immediate value for the "LEA" or "MOV" instruction with the corrected relative immediate value
std::memcpy(&*(data + offset + fix_byte), &new_memory_immediate, sizeof(uint32_t));
std::printf("[OK] Fixing Math/Bitwise Operators -> %s | %d\n", instruction.instruction.text, instruction.instruction.info.length);
} }
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) {
@@ -2743,6 +3045,12 @@ void RyujinObfuscationCore::applyRelocationFixupsToInstructions(uintptr_t imageB
*/ */
auto obfuscated_target_address = basic_block_obfuscated.instructions.at(0).addressofinstruction; auto obfuscated_target_address = basic_block_obfuscated.instructions.at(0).addressofinstruction;
/*
Preventing problems.
When IAT is obfuscated. theres no JUMP Address(it's always "0")! because it is solved during runtime. so the reloc is fixed, let's advance to the next blog!
*/
if (obfuscated_jmp_address == 0 && m_config.m_isIatObfuscation) continue;
/* /*
Let's fix our new branch. Previously it was a "near" jump, but now it will be "far" depending on the jump length. Let's fix our new branch. Previously it was a "near" jump, but now it will be "far" depending on the jump length.
This procedure will perform the calculation and generate a far or near branch depending on the need This procedure will perform the calculation and generate a far or near branch depending on the need
@@ -2815,9 +3123,18 @@ void RyujinObfuscationCore::removeOldOpcodeRedirect(uintptr_t newMappedPE, std::
We will use findOpcodeOffset to find the exact offset of the procedure's start We will use findOpcodeOffset to find the exact offset of the procedure's start
in the unmapped region with the SEC_IMAGE flag. in the unmapped region with the SEC_IMAGE flag.
*/ */
unsigned char ucSigature[10]{ 0 }; unsigned char* ucSigature = new unsigned char[m_proc.size] { 0 };
std::memcpy(ucSigature, reinterpret_cast<void*>(m_proc.address), 10); std::memcpy(ucSigature, reinterpret_cast<void*>(m_proc.address), m_proc.size);
auto offsetz = findOpcodeOffset(reinterpret_cast<unsigned char*>(newMappedPE), szMapped, &ucSigature, 10); auto offsetz = findOpcodeOffset(reinterpret_cast<unsigned char*>(newMappedPE), szMapped, ucSigature, m_proc.size);
delete[] ucSigature;
/*
* Future assert
if (!offsetz) {
std::printf("[X] Fatal Error on removeOldOpcodeRedirect -> %s\n", m_proc.name);
exit(-1);
}*/
// Based on the obfuscation configuration, some users can decide to not remove the original code from the original procedure after obfuscation. // Based on the obfuscation configuration, some users can decide to not remove the original code from the original procedure after obfuscation.
if (!isIgnoreOriginalCodeRemove) std::memset(reinterpret_cast<void*>(newMappedPE + offsetz), 0x90, m_proc.size); // Removing all the opcodes from the original procedure and replacing them with NOP instructions. if (!isIgnoreOriginalCodeRemove) std::memset(reinterpret_cast<void*>(newMappedPE + offsetz), 0x90, m_proc.size); // Removing all the opcodes from the original procedure and replacing them with NOP instructions.

View File

@@ -17,7 +17,7 @@ class RyujinObfuscationCore {
private: private:
const int MAX_PADDING_SPACE_INSTR = 14; const int MAX_PADDING_SPACE_INSTR = 14;
const int MAX_JUNK_GENERATION_ITERATION = 5; const int MAX_JUNK_GENERATION_ITERATION = 8;
std::vector<ZydisRegister> m_unusedRegisters; std::vector<ZydisRegister> m_unusedRegisters;
std::vector<RyujinBasicBlock> m_obfuscated_bb; std::vector<RyujinBasicBlock> m_obfuscated_bb;
uintptr_t m_ProcImageBase; uintptr_t m_ProcImageBase;

View File

@@ -35,6 +35,7 @@ public:
bool m_isTrollRerversers; // The user wants to trick and use a special feature to troll reversers when their debugs be detected making they loose all the progress bool m_isTrollRerversers; // The user wants to trick and use a special feature to troll reversers when their debugs be detected making they loose all the progress
bool m_isAntiDump; // Enable Anti Dump technic for Ryujin protected binary bool m_isAntiDump; // Enable Anti Dump technic for Ryujin protected binary
bool m_isMemoryProtection; // Memory CRC32 protection bool m_isMemoryProtection; // Memory CRC32 protection
bool m_isHVPass; // Run some features of ryujin using Microsoft Hypervisor Framework API
RyujinObfuscatorProcs m_strProceduresToObfuscate; // Names of the procedures to obfuscate RyujinObfuscatorProcs m_strProceduresToObfuscate; // Names of the procedures to obfuscate
RyujinCallbacks m_callbacks; // Ryujin Custom Pass Callbacks RyujinCallbacks m_callbacks; // Ryujin Custom Pass Callbacks

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,709 @@
{
"type": "excalidraw",
"version": 2,
"source": "https://excalidraw.com",
"elements": [
{
"id": "M8c9eQZB7X5MZItasDS9w",
"type": "rectangle",
"x": 391.20001220703125,
"y": 192.60000610351562,
"width": 344.79998779296875,
"height": 307.20001220703125,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a0",
"roundness": {
"type": 3
},
"seed": 1615675767,
"version": 129,
"versionNonce": 2118539607,
"isDeleted": false,
"boundElements": [
{
"id": "c1iXEu4S5qWnIgYbjx1Zc",
"type": "arrow"
}
],
"updated": 1749686313933,
"link": null,
"locked": false
},
{
"id": "46FHFpNht2wlAuCrvcVyc",
"type": "text",
"x": 516,
"y": 154.1999969482422,
"width": 58.23994445800781,
"height": 25,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a1",
"roundness": null,
"seed": 554549911,
"version": 55,
"versionNonce": 171168151,
"isDeleted": false,
"boundElements": [],
"updated": 1749685574176,
"link": null,
"locked": false,
"text": "Ryujin",
"fontSize": 20,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Ryujin",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "xdJ2irJYkaNC9r3QEIouH",
"type": "rectangle",
"x": 833.6000366210938,
"y": 205.40000915527344,
"width": 144.79998779296875,
"height": 121.59999084472656,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a2",
"roundness": {
"type": 3
},
"seed": 679450583,
"version": 45,
"versionNonce": 916855385,
"isDeleted": false,
"boundElements": [
{
"id": "GSUC-T1BCvyWszlmgyl-H",
"type": "arrow"
},
{
"id": "KVVsXm6d78Iek_N-4mTZ5",
"type": "arrow"
}
],
"updated": 1749686124093,
"link": null,
"locked": false
},
{
"id": "u0hyTPfKwpUasROfCYwNp",
"type": "text",
"x": 856,
"y": 235,
"width": 114.89994812011719,
"height": 75,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a3",
"roundness": null,
"seed": 994638937,
"version": 37,
"versionNonce": 1352983513,
"isDeleted": false,
"boundElements": [],
"updated": 1749685595466,
"link": null,
"locked": false,
"text": "mov rax, 10\nadd rbx, 20\nsub rcx, 30",
"fontSize": 20,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "mov rax, 10\nadd rbx, 20\nsub rcx, 30",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "GSUC-T1BCvyWszlmgyl-H",
"type": "arrow",
"x": 831.2000122070312,
"y": 271,
"width": 212.79998779296875,
"height": 3.20001220703125,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a4",
"roundness": {
"type": 2
},
"seed": 568230297,
"version": 79,
"versionNonce": 1291788375,
"isDeleted": false,
"boundElements": [],
"updated": 1749685606290,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
-212.79998779296875,
3.20001220703125
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "xdJ2irJYkaNC9r3QEIouH",
"focus": -0.05938365959726409,
"gap": 2.4000244140625
},
"endBinding": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": false
},
{
"id": "kJtdz2klrATJ98T_MfkEO",
"type": "rectangle",
"x": 480.8000183105469,
"y": 244.60000610351562,
"width": 118.39999389648438,
"height": 68,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a5",
"roundness": {
"type": 3
},
"seed": 832654263,
"version": 38,
"versionNonce": 842147095,
"isDeleted": false,
"boundElements": [],
"updated": 1749685611598,
"link": null,
"locked": false
},
{
"id": "L_pSiLFD-D1qnLQGHAGoK",
"type": "text",
"x": 498.3999938964844,
"y": 256.6000061035156,
"width": 88.41993713378906,
"height": 50,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a6",
"roundness": null,
"seed": 1291545143,
"version": 17,
"versionNonce": 54828087,
"isDeleted": false,
"boundElements": [],
"updated": 1749685618526,
"link": null,
"locked": false,
"text": "custom\nbytecode",
"fontSize": 20,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "custom\nbytecode",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "KVVsXm6d78Iek_N-4mTZ5",
"type": "arrow",
"x": 578.4000244140625,
"y": 359,
"width": 198.4000244140625,
"height": 10.399993896484375,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a7",
"roundness": {
"type": 2
},
"seed": 479607415,
"version": 100,
"versionNonce": 1093613177,
"isDeleted": false,
"boundElements": [],
"updated": 1749686207162,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
198.4000244140625,
10.399993896484375
]
],
"lastCommittedPoint": null,
"startBinding": null,
"endBinding": {
"elementId": "NFjixAoh2CztYuP1DNIN-",
"focus": -0.4045620317282552,
"gap": 10.39996337890625
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": false
},
{
"id": "NFjixAoh2CztYuP1DNIN-",
"type": "text",
"x": 787.2000122070312,
"y": 363.8000183105469,
"width": 731.2396240234375,
"height": 25,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a8",
"roundness": null,
"seed": 1290016793,
"version": 81,
"versionNonce": 318460471,
"isDeleted": false,
"boundElements": [
{
"id": "iFf2168HoefeSA26Qvyxp",
"type": "arrow"
},
{
"id": "KVVsXm6d78Iek_N-4mTZ5",
"type": "arrow"
}
],
"updated": 1749686206810,
"link": null,
"locked": false,
"text": "Insert a jump into the original code to vmentry(for execute the bytecode)",
"fontSize": 20,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "Insert a jump into the original code to vmentry(for execute the bytecode)",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "j5zq_Cm9IYKmHffzVgFTn",
"type": "rectangle",
"x": 1548.4000244140625,
"y": 304.6000061035156,
"width": 208,
"height": 180.00006103515625,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "a9",
"roundness": {
"type": 3
},
"seed": 1910262775,
"version": 76,
"versionNonce": 1150487223,
"isDeleted": false,
"boundElements": [],
"updated": 1749686159293,
"link": null,
"locked": false
},
{
"id": "Bgo_NssAKTn1SdIHyYsMn",
"type": "text",
"x": 1576.4000244140625,
"y": 319,
"width": 205.4998779296875,
"height": 150,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "aA",
"roundness": null,
"seed": 792391961,
"version": 88,
"versionNonce": 1848161657,
"isDeleted": false,
"boundElements": [
{
"id": "iFf2168HoefeSA26Qvyxp",
"type": "arrow"
}
],
"updated": 1749686197118,
"link": null,
"locked": false,
"text": "nop\nnop\npush rcx\nmov rcx, ptrbytecode\ncall vmentry\nmov rax...",
"fontSize": 20,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "nop\nnop\npush rcx\nmov rcx, ptrbytecode\ncall vmentry\nmov rax...",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "iFf2168HoefeSA26Qvyxp",
"type": "arrow",
"x": 1469.5999755859375,
"y": 383.8000183105469,
"width": 78.4000244140625,
"height": 21.5999755859375,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "aB",
"roundness": {
"type": 2
},
"seed": 1319005721,
"version": 21,
"versionNonce": 1768435865,
"isDeleted": false,
"boundElements": [],
"updated": 1749686197118,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
78.4000244140625,
21.5999755859375
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "NFjixAoh2CztYuP1DNIN-",
"focus": -0.7045371306368834,
"gap": 5
},
"endBinding": {
"elementId": "Bgo_NssAKTn1SdIHyYsMn",
"focus": -0.4601077518323214,
"gap": 28.4000244140625
},
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": false
},
{
"id": "Az2n4_JQ_Rep62w0qbnpI",
"type": "arrow",
"x": 641.2000122070312,
"y": 364.20001220703125,
"width": 63.20001220703125,
"height": 49.600006103515625,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "aC",
"roundness": {
"type": 2
},
"seed": 313306169,
"version": 38,
"versionNonce": 499366105,
"isDeleted": false,
"boundElements": [],
"updated": 1749686269123,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
-63.20001220703125,
49.600006103515625
]
],
"lastCommittedPoint": null,
"startBinding": null,
"endBinding": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": false
},
{
"id": "O5yT6LnxD7xhW31GADKYB",
"type": "text",
"x": 468.3999938964844,
"y": 432.20001220703125,
"width": 820.1596069335938,
"height": 50,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "aD",
"roundness": null,
"seed": 137944663,
"version": 136,
"versionNonce": 1340294711,
"isDeleted": false,
"boundElements": [],
"updated": 1749686305048,
"link": null,
"locked": false,
"text": "ryujin will insert bytecode in some section\nthe vm will interpret it and return back with the full context to not broke the code",
"fontSize": 20,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "ryujin will insert bytecode in some section\nthe vm will interpret it and return back with the full context to not broke the code",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "c1iXEu4S5qWnIgYbjx1Zc",
"type": "arrow",
"x": 434.3999938964844,
"y": 503.20001220703125,
"width": 170.39999389648438,
"height": 156.79998779296875,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "aE",
"roundness": {
"type": 2
},
"seed": 1561486455,
"version": 28,
"versionNonce": 1291089975,
"isDeleted": false,
"boundElements": [],
"updated": 1749686313933,
"link": null,
"locked": false,
"points": [
[
0,
0
],
[
170.39999389648438,
156.79998779296875
]
],
"lastCommittedPoint": null,
"startBinding": {
"elementId": "M8c9eQZB7X5MZItasDS9w",
"focus": 0.8835765524851282,
"gap": 3.399993896484375
},
"endBinding": null,
"startArrowhead": null,
"endArrowhead": "arrow",
"elbowed": false
},
{
"id": "WLU2ZgAEJXhP7nLOkpjLn",
"type": "text",
"x": 663.2000122070312,
"y": 647.2000122070312,
"width": 690.9995727539062,
"height": 25,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "aF",
"roundness": null,
"seed": 870526871,
"version": 98,
"versionNonce": 999258681,
"isDeleted": false,
"boundElements": [],
"updated": 1749686340544,
"link": null,
"locked": false,
"text": "The vm will only allow some simple menemonic for multiplication(for now)",
"fontSize": 20,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "The vm will only allow some simple menemonic for multiplication(for now)",
"autoResize": true,
"lineHeight": 1.25
},
{
"id": "tLeEn07Up0otFbkxmXxyS",
"type": "text",
"x": 359.60003662109375,
"y": 86.39999389648438,
"width": 1619.8392333984375,
"height": 25,
"angle": 0,
"strokeColor": "#1e1e1e",
"backgroundColor": "transparent",
"fillStyle": "solid",
"strokeWidth": 2,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"groupIds": [],
"frameId": null,
"index": "aG",
"roundness": null,
"seed": 1720073049,
"version": 213,
"versionNonce": 1953987225,
"isDeleted": false,
"boundElements": [],
"updated": 1749687328143,
"link": null,
"locked": false,
"text": "to not use too much space we already have padding with nop -> compile the instructions to vm bytecode with a maximum of 8 bytes and interpret eah one individually",
"fontSize": 20,
"fontFamily": 5,
"textAlign": "left",
"verticalAlign": "top",
"containerId": null,
"originalText": "to not use too much space we already have padding with nop -> compile the instructions to vm bytecode with a maximum of 8 bytes and interpret eah one individually",
"autoResize": true,
"lineHeight": 1.25
}
],
"appState": {
"gridSize": 20,
"gridStep": 5,
"gridModeEnabled": false,
"viewBackgroundColor": "#ffffff",
"lockedMultiSelections": {}
},
"files": {}
}

File diff suppressed because one or more lines are too long