6

I am learning reversing an I am studying a code, which modifies itself (actually it is a crackme).

It contains statements like: movw $0xc031, 0x(%edx), where edx contains the address of a statement. With breakpoints I can stop and inspect the modified code. I save it into a binary and disassemble with objdump to see what is the new statement.

Doing this line by line is a bit slow. Is there better way to reverse this code? Maybe there are other tools, or functionalities in gdb unknown to me.

2

3 Answers 3

9

Just for kicks I downloaded the crackme and fed it to radare in a windows box dynamic analysis is not needed to solve this crackme

radare2 facets

lets string this binary

[0x080495e0]> iz
vaddr=0x080483d0 paddr=0x000003d0 ordinal=000 sz=41 len=40 section=.rodata type=
a string=run as ./prog a_number (e.g. ./prog 90)\n
vaddr=0x080483f9 paddr=0x000003f9 ordinal=001 sz=7 len=6 section=.rodata type=a
string=good.\n
vaddr=0x08048400 paddr=0x00000400 ordinal=002 sz=6 len=5 section=.rodata type=a
string=bad.\n

So it appears that the file takes a single argument as input and prints good or bad based on some criteria from the strings output

Lets move forward and see what's at entry point

[0x080495e0]> pd 14
           ;-- entry0:
           ;-- section..text:
           0x080495e0    31ed           xor ebp, ebp                   ; [19]
           0x080495e2    5e             pop esi
           0x080495e3    89e1           mov ecx, esp
           0x080495e5    83e4f0         and esp, 0xfffffff0
           0x080495e8    50             push eax
           0x080495e9    54             push esp
           0x080495ea    52             push edx
           0x080495eb    68a0980408     push 0x80498a0
           0x080495f0    6840980408     push 0x8049840
           0x080495f5    51             push ecx
           0x080495f6    56             push esi
           0x080495f7    6850970408     push 0x8049750
           0x080495fc    e86fedffff     call sym.imp.__libc_start_main
             ^- 0x08048370(unk, unk, unk, unk, unk, unk, unk, unk) ; sym.imp._
           0x08049601    f4             hlt
[0x080495e0]> pd 1 @0x8048370
           ;-- sym.imp.__libc_start_main:
           0x08048370    ff25c8950408   jmp dword [reloc.__libc_start_main_200]
[0x080495e0]>

Searching for __libc_start_main on the web will yield this prototype for the function

(__libc_start_main) (int (*main) (int, char **, char **),
        int argc,
        char *__unbounded *__unbounded ubp_av,
        void (*init) (void),
        void (*fini) (void),
        void (*rtld_fini) (void),
        void *__unbounded stack_end)

So 8049750 must be the address of main, lets disassemble it:

[0x080495e0]> pd @ 0x8049750
           0x08049750    55             push ebp
           0x08049751    89e5           mov ebp, esp
           0x08049753    53             push ebx
           0x08049754    83ec44         sub esp, 0x44
           0x08049757    e800000000     call 0x804975c
             ^- 0x0804975c() ; entry0
           0x0804975c    58             pop eax  
       0x0804975d    81c060feffff   add eax, 0xfffffe60
       ,=< 0x0804977d    0f8422000000   je 0x80497a5 <--other_branch 
       --------------------
           0x0804977a    8945e4         mov dword [ebp - 0x1c], eax

It removes the return address from the stack to eax and adds -1a0, eax = 804975c + (-1a0 ) = 80495bc and stores it in a local var with further reading of disassembly we can deduce it checks if argc is equal to 2 (ie just one argument is provided to the program) and if it is not, it uses this for printing some stuff

0x08049783    8b45e4         mov eax, dword [ebp - 0x1c]
0x08049786    8d8814eeffff   lea ecx, [eax - 0x11ec]
0x0804978c    890c24         mov dword [esp], ecx
0x0804978f    89c3           mov ebx, eax
0x08049791    e8faebffff     call sym.imp.printf
0x08049796    c745f8000000.  mov dword [ebp - 8], 0
0x0804979d    8945e0         mov dword [ebp - 0x20], eax
0x080497a0    e983000000     jmp 0x8049828

[0x080495e0]> pd @ 0x8049828
           0x08049828    8b45f8         mov eax, dword [ebp - 8]
           0x0804982b    83c444         add esp, 0x44
           0x0804982e    5b             pop ebx
           0x0804982f    5d             pop ebp
           0x08049830    c3             ret

lets see what is printed to console

eax- 0x11ec = 80483d0 

So we saw this address in strings output didn't we? Don't remember and lazy to scroll up?

[0x080495e0]> psz @0x80483d0
run as ./prog a_number (e.g. ./prog 90)

so we can call this branch usage()

if(argc != 2){ usage();return 0;}

lets follow the other_branch

It appears it takes the argument passed from the command line and converts it to a number using strtol and calls a function and if the return is not 0, it prints good else, bad .

[0x080495e0]> pd @ 0x80497a5
           0x080497a5    31c0           xor eax, eax
           0x080497a7    b90a000000     mov ecx, 0xa
           0x080497ac    8b55f0         mov edx, dword [ebp - 0x10]  <- char* argv[]
           0x080497af    8b5204         mov edx, dword [edx + 4]        ; [0x4
           0x080497b2    891424         mov dword [esp], edx 
           0x080497b5    c74424040000.  mov dword [esp + 4], 0          ; [0x4
           0x080497bd    c74424080a00.  mov dword [esp + 8], 0xa        ; [0x8
           0x080497c5    8b5de4         mov ebx, dword [ebp - 0x1c] this is  80495bc
           0x080497c8    8945dc         mov dword [ebp - 0x24], eax
           0x080497cb    894dd8         mov dword [ebp - 0x28], ecx
           0x080497ce    e8cdebffff     call sym.imp.strtol
             ^- 0x080483a0() ; sym.imp.strtol
           0x080497d3    8945ec         mov dword [ebp - 0x14], eax
           0x080497d6    8b45ec         mov eax, dword [ebp - 0x14]
           0x080497d9    890424         mov dword [esp], eax
           0x080497dc    8b5de4         mov ebx, dword [ebp - 0x1c]
           0x080497df    e8fcfeffff     call 0x80496e0
           0x080497e4    8945e8         mov dword [ebp - 0x18], eax
           0x080497e7    837de800       cmp dword [ebp - 0x18], 0
       ,=< 0x080497eb    0f841b000000   je 0x804980c
       |   0x080497f1    8b45e4         mov eax, dword [ebp - 0x1c]
       |   0x080497f4    8d883deeffff   lea ecx, [eax - 0x11c3]
       |   0x080497fa    890c24         mov dword [esp], ecx
       |   0x080497fd    89c3           mov ebx, eax
       |   0x080497ff    e88cebffff     call sym.imp.printf

the string at `11c3 is likely the good boy one:

[0x080495e0]> psz @0x80495bc - 0x11c3
good.
[0x080495e0]>

Lets see the rest of the function:

[0x080495e0]> pd 13 @ 0x804980c
           0x0804980c    8b45e4         mov eax, dword [ebp - 0x1c]
           0x0804980f    8d8844eeffff   lea ecx, [eax - 0x11bc]
           0x08049815    890c24         mov dword [esp], ecx
           0x08049818    89c3           mov ebx, eax
           0x0804981a    e871ebffff     call sym.imp.printf
             ^- 0x08048390() ; sym.imp.printf
           0x0804981f    8945d0         mov dword [ebp - 0x30], eax
           0x08049822    8b45e8         mov eax, dword [ebp - 0x18]
           0x08049825    8945f8         mov dword [ebp - 8], eax
           0x08049828    8b45f8         mov eax, dword [ebp - 8]
           0x0804982b    83c444         add esp, 0x44
           0x0804982e    5b             pop ebx
           0x0804982f    5d             pop ebp
           0x08049830    c3             ret
[0x080495e0]>

string at 11bc

[0x080495e0]> psz @0x80495bc - 0x11bc
bad.
[0x080495e0]>

so we need to analyse the function at 0x80496e0

[0x08049644]> pdi @0x80496e0
0x080496e0 c7054b9704088b1424c3  mov dword [0x804974b], 0xc324148b
0x080496ea       e85c000000  call 0x804974b
0x080496ef     66c7420631c0  mov word [edx + 6], 0xc031
0x080496f5             ebe9  jmp 0x80496e0
[0x08049644]>

So, it writes 0xc324148b to 804974b and calls it (self modifying code] what is executed will be the instruction written there (notice endianness).

Lets uses rasm2 on the reversed stream to get the disassembly that will be executed

[0x08049644]> !rasm2 -d 0x8b1424c3
mov edx, dword [esp]
ret 

So it moves the return address to edx (edx = 0x080496ef ) edx+6 = 0x80496f65, so jmp 0x80496e0 will become xor eax eax

[0x08049644]> !rasm2 -d 31c0
xor eax, eax
[0x08049644]>

I hope that you can follow further from here, execute rasm2 from radare on the stream to get the modified instructions and construct a new modified function and analyse it.

You need to know how to return 1 from that function for you to get good that means you need to know what to pass to the function in command line most probably it will be a xor whose pseudo code would be

function selfie() { if ((argv[0] xor someconstant) == 0) {return 1} else { return 0; }

Thanks jvoisin for the edits

found a few minutes to analyze the function statically using radare

enter image description here

===============================================================================
0x8049750 is main so disassembling 0x70 bytes (750-6e0)    
pDi shows the bytes
instead of executing rasm –d we can use pad too 
[0x080495e0]> pDi 0x70 @0x80496e0
===============================================================================
0x080496e0 c7054b9704088b1424c3   mov dword [0x804974b], 0xc324148b 
    [0x080495e0]> pad 8b1424c3      
    mov edx, dword [esp]
    ret
    so until further modification the following two instructions 
  will be executed on call 0x804974b
  0x804974b   8b1424    mov edx , dword [esp]
  0x804974e   c3        ret
===============================================================================
0x080496ea       e85c000000  call 0x804974b 
    will execute edx = [esp] and retn back
    so edx will hold the return address of this call 
    which is 0x80496ef
===============================================================================
0x080496ef     66c7420631c0  mov word [edx + 6], 0xc031 
    0x80496ef+6 =   0x080496f5 overwrites next instruction with 
    [0x080495e0]> pad 31c0
    xor eax, eax
===============================================================================
0x080496f5             ebe9  jmp 0x80496e0  over written
0x080496f5             31c0  xor eax,eax     new instruction
===============================================================================
0x080496f7   c7420f66b9b001  mov dword [edx + 0xf], 0x1b0b966
    0x80496ef+f =   0x080496fe  overwrites next instruction with 
    [0x080495e0]> pad 66b9b001
  mov cx, 0x1b0

...

===============================================================================
0x080496fe         335c241a  xor ebx, dword [esp + 0x1a]
0x080496fe         66b9b001  mov cx, 0x1b0  first mod
0x080496fe         8b442408  mov eax, dword [esp + 8]  second mod junk 
===============================================================================
0x08049702         66bbb000  mov bx, 0xb0  executed as it is first time
0x08049702         3b442412  cmp eax, dword [esp + 0x12]  second mod junk
===============================================================================
0x08049706       e840000000  call 0x804974b
  Same logic as earlier edx will hold 0x804970b return address
  and will be returning to Next instruction
===============================================================================
0x0804970b   c742f38b442408  mov dword [edx - 0xd], 0x824448b
  0x804970b-0xd = 0x080496fe 
  [0x080495e0]> pad 8b442408
  mov eax, dword [esp + 8]
===============================================================================
0x08049712   c7421466837c24  mov dword [edx + 0x14], 0x247c8366
  0x804970b+0x14  = 0x0804971f 
  0x08049719     66c742180407  mov word [edx + 0x18], 0x704
  0x804970b+0x18  = 0x08049723 
  [0x080495e0]> pad 66837c240407
  cmp word [esp + 4], 7

.

===============================================================================
0x0804971f     66837c24100d  cmp word [esp + 0x10], 0xd  over written
0x0804971f     66837c240407  cmp word [esp + 4], 7   new instruction
===============================================================================
0x08049725         660f44d9  cmove bx, cx                exec as it is 1st time
0x08049725         66894c240f  mov word [esp + 0xf], cx  junk
===============================================================================
0x08049729   c742f73b442412  mov dword [edx - 9], 0x1224443b
0x804970b-0x9  = 0x08049702 see above it is junk
===============================================================================
0x08049730       e816000000  call 0x804974b
Same logic as earlier edx will hold 0x8049735 return address
and will be returning to Next instruction
===============================================================================
0x08049735   c742f1894c240f  mov dword [edx - 0xf], 0xf244c89
0x0804973c   c742f78944240f  mov dword [edx - 9], 0xf244489
    0x8049735-0x9  = 0x0804972c  both writes to middle of sequnce junk mods
===============================================================================
0x08049743         66895a16  mov word [edx + 0x16], bx  <------ 
0x08049747         c64218c3  mov byte [edx + 0x18], 0xc3 <--------
0x8049735+0x16/18 = 804974b,804974d 
bx will hold 0x00b0 or 0x01b0 based on cmove based on cmp word [esp+4],7 Zf flag
and will be executed 

[0x08049720]> pad b000
mov al, 0
[0x08049720]> pad b001
mov al, 1
[0x08049720]>
and the function will return back with either 0 or 1 :)
3

For now, the best way to deal with self-modifying code is to use dynamic analysis. Most of the static analysis techniques will fail to detect and to find out what are the modified parts of the code.

So, you need a tools that will allow you to extract a full trace of the execution of the software (I mean a trace instruction by instruction).

You can consider at least two cases, either the software does not have any anti-debug protection and then you can use the gdb Python API to save and display a full trace of each instruction. Or, the software has some anti-debug protection and you may use tools such as the Intel Pin framework which will let you record the instruction trace.

I got some time to dig the gdb Python API way today. Here is a small script that step through the program, collect all the assembly instruction and send it to the stdout of gdb (I did it quick and extremely dirty... there are many ways to improve it),

import gdb

gdb.execute('break main')
gdb.execute('run')

while (True):
    gdb.write (gdb.execute('x /i $pc', to_string=True).rstrip('\n'), gdb.STDOUT)
    gdb.execute('stepi', to_string=False)
    gdb.flush ()

Then, just execute:

$> gdb -x ./script.py ./main 1> log.txt

I really would have liked to improve the way to end the while loop by detecting that gdb has reached the end of the program. And, also, to have a better control on the output to be cleanly written in a file (and not thrown away in stdout)... But, I have no extra time to finish it.

For more about all that see:

0
2

I usually recommend using Intel's PIN framework however, for a single PE file its a bit over kill.

Why not use Radare2 debugging feature? You can see how it can be used to solve a crackme here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.