Implementation of Assembly Instruction Obfuscator

Previously, I encountered many obfuscation techniques such as junk instructions, instruction bloat, and virtual machines while unpacking. I wanted to try creating a similar obfuscator. Thus, the idea of writing an instruction obfuscator came to mind. Initially, I intended to write an obfuscator that directly obfuscates and expands opcodes, which is quite challenging. This includes the fact that the offsets of operands for call and jmp instructions, which use offsets as operands, would be corrupted after expansion.

Although it can be done, it is very cumbersome and requires reorganizing the entire code segment, so I decided not to pursue that route. Instead, I wrote an assembly instruction-level obfuscator. This type of obfuscator is easier to write because assembly instructions can use identifiers to represent offsets, and these identifiers will be calculated automatically by the assembler when it begins compiling the assembly code.

Therefore, it is entirely feasible to insert instructions between assembly instructions. If you want to split an instruction, you just need to obtain the opcode of the instruction, then split and reorganize it, directly defining the opcode as data in the source file for the compiler without any issues.

Below is the implementation idea of the assembly instruction obfuscator.

Implementation Idea

The db pseudo-instruction can define data not only in the data segment but also in the .code segment. If the eip points to this data, it will be executed as code. If the eip never points to it, it will remain in the code segment but will not be executed. Thus, we can do this:

.code

push ebp

mov ebp , esp

jmp _START

db ‘this is data, this is code too’

_START:

mov eax , 1

mov esp , ebp

pop ebp

ret

After executing this small program, it will jump to the _START label after executing the first two lines. The data we defined in the code will never be used. However, sometimes this trick can be easily detected, so let’s observe another situation:

.rdata

g_str db ‘hello’ , 0

.code

push offset g_str

call ctr_cprintf ; printf(“hello”)

add esp , 4

The above code looks normal, calling the printf function to print the hello string. But let’s look at the following code:

.code

call _PUSHSTR

db ‘hello’

_PUSHSTR:

call ctr_cprintf

add esp , 4

This code still prints the string hello, but its execution flow is not as clear as the previous one. For example, it first calls the _PUSHSTR label. Since this is a call instruction, the return address will be pushed onto the stack, and coincidentally, the address of the string hello in the code segment will be pushed onto the stack.

When calling the printf function, the address of a string is already saved in the stack. Therefore, the final output will be the string hello, and since the string is stored in the code segment, when this program is decompiled by OD, it will be parsed as opcode, which will not look good during analysis.

Some instructions do not produce any effect after execution, for example:

or eax,080000000h

js J_H_Z_L_XXXXX

db “hello i am a string”

J_H_Z_L_XXXXX:

and eax,0xFFFFFFFF

JCC J_H_Z_L_XXXXX

This instruction seems to have branch execution, but in reality, it does not. Because after executing or eax,080000000h, the SF flag will definitely be set to 1. The js instruction will also be executed, and the subsequent code will behave similarly.

Obfuscator Design

The workflow of the entire obfuscator is as follows:

  1. Read assembly instructions from the .asm file.

  2. Input the assembly instruction to an obfuscator object, which outputs an array of obfuscated instructions containing this instruction (one instruction becomes multiple).

  3. Write the obfuscated instruction array to another file.

  4. Done.

Therefore, it looks something like this:

char line[200]; vector<Instruction> vecIns; // Get each line of assembly source code while( iFile.getline(line,200) ) { // Pass this line of assembly instruction to the obfuscator object, which will output the obfuscated instructions into the vector. if( mixer.mix( line , &vecIns ) ) { // If obfuscation is successful, output the obfuscated content to the file for( auto &i : vecIns ) { oFile << i << endl; } } else { // If obfuscation fails, output the original instruction to the file. oFile << line << endl; } }

As for the obfuscator object, it does the following:

virtual bool mix( const Instruction& pInsObj , vector<Instruction>* vecMixer ) = 0;

Yes, this is a pure virtual function because I have categorized the instructions into different types: data transfer, arithmetic operations, bitwise operations, etc. Different instructions have different obfuscation methods. I do not want to implement all the code in this function, so I derived several obfuscator classes for different types of instructions, which include:

  1. MixEngine_tran : Data transfer instructions

  2. MixEngine_bit : Bitwise operation instructions

  3. MixEngine_jcc : Conditional jump instructions

  4. MixEngine_call : Call instructions

<spanfor as="" data="" example,="" follows:

// Simple obfuscation for data transfer instructionsbool MixEngine_tran::mix(const Instruction& pInsObj, vector<Instruction>* vecMixer){ static int ___ = srand(time(nullptr)); if (pInsObj.type() != e_tran) { return false; } if (vecMixer == nullptr) return false; vecMixer->clear(); /** * Obfuscation method: * Add predefined instructions around the original instruction. */ char* ins[10] = { “add esp,2;add esp,2;sub esp,4”, “ADD EAX,ECX; NEG ECX; ADD ECX,EAX;SUB EAX,ECX;XCHG EAX,ECX”, }; int count = m_hardness > _countof(ins) ? _countof(ins) : m_hardness; int pos = rand() % count; for (int i = 0; i<count; ++i) { InstructionGroup insGrp(ins[i]); for (auto&item : insGrp) { if( i == pos) vecMixer->push_back(item); } } vecMixer->push_back(pInsObj); return true;}

This involves a class called Instruction, which doesn’t have much logic but mainly provides functionality to manipulate an instruction.

const char* mnemonic( )const; // Get the mnemonic of the instruction const char* operator1()const; // Get operand 1 of the instruction const char* operator2( )const;// Get operand 2 of the instruction const char* operator3( )const;// Get operand 3 of the instruction void setMnemonic( const char* mnemonic ); // Set the mnemonic of the instruction void setOperator1(const char* pOperator ); // Set operand 1 of the instruction void setOperator2( const char* pOperator );// Set operand 2 of the instruction void setOperator3( const char* pOperator );// Set operand 3 of the instruction

This class generally receives a string such as: mov eax, 1, and through its member functions, it can obtain the operands of an assembly instruction or replace the operands of the instruction.

There is also an InstructionGroup class that is responsible for storing a group of instructions, which is essentially a vector<Instruction>.

This was written in just one afternoon, so it can only be regarded as a high-level knowledge in assembly, and honestly, it doesn’t have much practical use.

Code: https://github.com/enoorez/win32-sources-mixer

Implementation of Assembly Instruction Obfuscator

This article is originally from the Kx Community by enoorez

Please indicate the source from the Kx Community when reprinting.

Popular Reads

  • Traveling Frog Unity Game Reverse Modification Android & iOS

  • Image Steganography Using PHP to Hide Text in Images

  • LLVM Code Obfuscation Analysis and Logic Restoration

  • Hardening C/C++ Programs

Click to read the original article/read,

More valuable content awaits you~

Implementation of Assembly Instruction Obfuscator

Leave a Comment