{ enessakircolak }

Self_Modifying-Code

Mar 14, 2024
5 minutes

Anti-Analysis_103

Everybody can change, but what about code ?
I would like to say they are loyal… but sorryy they are not :'(
Don’t worry, you can detect it before it upsets you.

Overview

Before all the problems just imagine, your code is changing according to your conditions. It looks like it’s executing a function, but then that code never ever working.
Don’t you think it would be extremely difficult for analysis process ? All the static analysis is wrong (if you can’t detect self modifying) and confusing at dynamic analysis.
To summarize briefly, opcodes at “.TEXT”(.code) section will change at runtime so static analysis will be harder than dynamic analysis. It will be fixed at runtime.

Primitive

Yeah it is easy to say to change the code. Wait… do you know default permission of executable files?
We need to +W permission, but executable files have just RX perm, so first of all we need to write permission.

int main()
{

    get_write_perm();

    self_func((int*)init_func,(int *)second_func);
    // init_func return 6;
    // second_func return 0;

    std::cout << second_func()<<" Func value " << std::endl; 

    return 0;
}

Look at that code it seems magical. We are not interested in how to get +WRITE permission in this article, we just get it. If you want to look deeply you can reach that code at link.

Wrong Output

int second_func() {

    std::string b = "ifyoucan";
    MessageBoxA(0, b.c_str(), "catchme", NULL);
    char buffer[MAX_PATH];
    DWORD length = GetTempPathA(MAX_PATH, buffer);
    MessageBoxA(0, buffer, "path", NULL);

    // you need to close optimization settings to use this junk codes
    // Junk code reason ->  need to more size than which is written to this function

    int junk1 = { 5 };
    junk1 = junk1 + 26;
    junk1 = junk1 + (int)second_func;
    junk1 = junk1 ^ 0x55;
    junk1 = junk1 * 3;
    // junk1 = junk1 - (int)init_func();

    spiynxx asd; //Junk class defined at ".h" file

    int a = 0;
    return a;
};
int init_func(int size) {

    MessageBoxA(0, "iGotYou", "catchme", NULL);
    int a = 6;
    return a;
};

If we call second_func, return value will be “0” and two messagebox will be appear.
Execute->

msgbox.png

func_value.png



Yeah I didn’t mentioned “a function” at main, as expected all the work is happening in the self_func. Lets inspect it.

int self_func(int* source_addr, int* dest_addr) {

	BYTE* init_func_address = (BYTE*)(void*)source_addr;
	BYTE* second_func_address = (BYTE*)(void*)dest_addr;

    for (int i = 0; i < 0x100; i++) {


        second_func_address[i] = init_func_address[i]; 
        if ((int)init_func_address[i] == 0xc3 || (int)init_func_address[i] == 0xc2 || (int)init_func_address[i] == 0xca || (int)init_func_address[i] == 0xcb) 
            goto label2;
   
    }
label2:
    return 0;
}

Our first parameter is source address, second parameter is destination address. For loop counter is not important if you don’t replace a huge function. If you try to replace huge function you need to rearrange it. Maybe getting size of function with labels can help you.
In our situation, that source function can not be more than 256byte, so we break the loop with “ret” instruction’s opcode(most probably -> c3 || c2).
Now the fatal part ->

second_func_address[i] = init_func_address[i]; 

At this line, we copy opcodes at our source address(init_func) to destination address(second_func).
Of course we will look it at debugger.

Replace Function


dbg1.png


These are functions’s opcodes and both are different.

dbg1.png


This marked line is getting byte(opcode) at source function and assign it to dl register. Next instruction is copying source function’s byte to destination function.

dbg1.png


Here it is. Now they are same function. But we didn’t touch more than init_func size. Who cares? function will execute until ret (C3) instruction and we give it, so after “ret” isn’t important.

Protect Size

I Mentioned for loop size but what if we try to put bigger function into smaller one.
We would be corrupt memory!! So we need to provide destination to have more size than source.
Solution is simple->
Close-> Compiler optimization
Add-> Junk code
We need to close optimization because “junk code” is unnecessary, so compiler usually automatically fixes it(probably remove).

    bool JunkCode8200 = true;
    if (JunkCode8200 == true)
        JunkCode8200 = false;
    try {
        JunkCode8200 = true;
    }
    catch (...) {}
    try {
        JunkCode8200 = false;
    }
    catch (...) {}
    try {
        JunkCode8200 = false;
    }
    catch (...) {}
    bool While5512 = true;
    while (While5512 == true) {
        JunkCode8200 = false;
        While5512 = false;
    }
    if (JunkCode8200 == true)
        JunkCode8200 = true;
    else
        JunkCode8200 = true;
    bool While4363 = true;
    while (While4363 == true) {
        JunkCode8200 = true;
        While4363 = false;
    }
    bool While9845 = true;
    while (While9845 == true) {
        JunkCode8200 = false;
        While9845 = false;
    }
    try {
        JunkCode8200 = false;
    }
    catch (...) {}
    if (JunkCode8200 == true)
        JunkCode8200 = false;

This one is a simple junk code insertion. You can increase function size like this.

Optimization closing->
Project->Properties->C/C+±>Optimization -> in the “Optimization” field, select disabled


Bonus

.code

MySegment segment read write execute

SomeFunction proc
	mov eax, ecx  ;

	mov dword ptr MyFunc, 9005e883h
	mov dword ptr MyFunc[+4h],9090c0ffh	
	;mov dword ptr MyFunc[+8h],90c39090h
	mov dword ptr func , 9005e883h
	mov dword ptr func[+4h],90c39090h

MyFunc:
	add eax, 5	  	  ; sub eax,5
	nop			  ;nop
	nop 			  ;inc eax
	add eax,5		  ;nop nop
	add eax,5		  ;add eax,5
	mov dword ptr func2,9090c031h 
	jmp oyuk
func2:
	add eax,10   ;xor eax - nop - nop ; sub eax,5 -nop
	nop
func:
    mov eax, 10  	;sub eax 5  ; inc eax
	nop			
    add eax, 5  	; ret
    ret
	ret
oyuk:
	dec eax 
	dec eax
	mov dword ptr func , 9090c0ffh  	; inc eax   - nop - nop
	mov dword ptr func2[+4h] ,9005e883h     ; sub eax,5 - nop
	;mov dword ptr func2[+950h] ,9005e883h  
	add eax,5
	sub eax,5
	jmp func2
SomeFunction endp

MySegment ends

end

This one is my first crush. My whole reverse engineering story started with this code.
So what is it doing ?
If you look at the top there is “read write execute” permission. It means no need to struggle with virtualprotect.
For this example, you need to struggle with opcodes. Also I’m not going to explain it :))
You can resolve opcodes to assembly and get opcodes of assembly instructions with this link

Detection

Actually this part is easy. If your “.TEXT” section has +W permission you can think it may change opcodes, otherwise why does it take the +W permission?

last


Also if you saw some bytes which is meaningless, you can check if these opcodes or not.
Sometimes it could be shellcode or whatever, but we know it is worth to inspect.

SourceCodes

~It is a rough road that leads to the heights of greatness