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
===============================================================================
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 :)