It has been a long time since my last radare2-related writeup, so I tried to fix this during the r2con (great event by the way, you should have come.), but apparently, this time, crp- wrote something really convoluted, so it took me a bit more time to get a working keygen.

Anyway, without further ado, collide, by crp- (local mirror), with radare2 (make sure to use the latest git version, or you won't have fancy things like graphs.)!

$ ./collide [ collide.crp- ] stage0: FAIL

$ r2 - A . / collide [ x ] Analyze al l flags starting with sym. and entry0 ( aa ) [ x ] Analyze len byte s of instructions for references ( aar ) [ x ] Analyze function calls ( aac ) [ x ] Constructing a function name for fcn. * and sym.func. * functions ( aan )) -- Mind that the 'g' in radare is si lent [0 x080485ba ] > pdf ╒ ( fcn ) entry0 369 │ ; var int local_4h @ esp+0x4 │ ; var int local_8h @ esp+0x8 │ ; var int local_ch @ esp+0xc │ ; var int local_10h @ esp+0x10 │ ; var int local_14h @ esp+0x14 │ ; var int local_18h @ esp+0x18 │ ; var int local_1ch @ esp+0x1c │ 0 x080485ba 83 ec2c sub esp , 0x2c │ 0 x080485bd c744240c0000. mov dword [ esp + local_ch ], 0 │ 0 x080485c5 c74424080000. mov dword [ esp + local_8h ], 0 │ 0 x080485cd 83 ec0c sub esp , 0xc │ 0 x080485d0 68739 f0408 push str.__collide.crp____n ; str.__collide.crp____n ; "[ collide.crp- ]." @ 0x8049f73 │ 0 x080485d5 e816feffff call sub.write_3f0 │ 0 x080485da 83 c410 add esp , 0x10 │ 0 x080485dd 83 ec0c sub esp , 0xc │ 0 x080485e0 8 d44241c lea eax , [ esp + local_1ch ] │ 0 x080485e4 50 push eax │ 0 x080485e5 e866010000 call sub.getuid_750 │ 0 x080485ea 83 c410 add esp , 0x10 │ 0 x080485ed 83 ec0c sub esp , 0xc │ 0 x080485f0 68859 f0408 push str.stage0 : ; str.stage0: ; "stage0: " @ 0x8049f85 │ 0 x080485f5 e8f6fdffff call sub.write_3f0 │ 0 x080485fa 83 c410 add esp , 0x10 │ 0 x080485fd 83 ec0c sub esp , 0xc │ 0 x08048600 688 e9f0408 push str..key ; str..key ; ".key" @ 0x8049f8e │ 0 x08048605 e831020000 call sub.getuid_83b │ 0 x0804860a 83 c410 add esp , 0x10 │ 0 x0804860d 89442408 mov dword [ esp + local_8h ], eax │ 0 x08048611 837 c240800 cmp dword [ esp + local_8h ], 0 │ ┌─< 0 x08048616 7505 jne 0x804861d │ ┌──< 0 x08048618 e9de000000 jmp 0x80486fb │ ││ ; JMP XREF from 0x08048616 (entry0) │ │└─> 0 x0804861d 83 ec0c sub esp , 0xc │ │ 0 x08048620 68939 f0408 push str.OK_nstage1 : ; str.OK_nstage1: ; "OK.stage1: " @ 0x8049f93 │ │ 0 x08048625 e8c6fdffff call sub.write_3f0 │ │ 0 x0804862a 83 c410 add esp , 0x10 │ │ 0 x0804862d 83 ec04 sub esp , 4 │ │ 0 x08048630 8 d44240c lea eax , [ esp + local_ch ] │ │ 0 x08048634 50 push eax │ │ 0 x08048635 8 d442414 lea eax , [ esp + local_14h ] │ │ 0 x08048639 50 push eax │ │ 0 x0804863a 688 e9f0408 push str..key ; str..key ; ".key" @ 0x8049f8e │ │ 0 x0804863f e8aefeffff call sub.malloc_4f2 │ │ 0 x08048644 83 c410 add esp , 0x10 │ │ 0 x08048647 837 c240800 cmp dword [ esp + local_8h ], 0 │ │┌─< 0 x0804864c 7 f05 jg 0x8048653 │ ┌───< 0 x0804864e e9a8000000 jmp 0x80486fb │ │││ ; JMP XREF from 0x0804864c (entry0) │ ││└─> 0 x08048653 83 ec08 sub esp , 8 │ ││ 0 x08048656 ff742410 push dword [ esp + local_10h ] │ ││ 0 x0804865a ff742418 push dword [ esp + local_18h ] │ ││ 0 x0804865e e80dfeffff call fcn.08048470 │ ││ 0 x08048663 83 c410 add esp , 0x10 │ ││ 0 x08048666 85 c0 test eax , eax │ ││┌─< 0 x08048668 7431 je 0x804869b │ │││ 0 x0804866a 83 ec0c sub esp , 0xc │ │││ 0 x0804866d 689 f9f0408 push str.___re.....___ ; str.___re.....___ ; "-- re..... -- " @ 0x8049f9f │ │││ 0 x08048672 e879fdffff call sub.write_3f0 │ │││ 0 x08048677 83 c410 add esp , 0x10 │ │││ 0 x0804867a c74424040000. mov dword [ esp + local_4h ], 0 │ │││ ; JMP XREF from 0x08048699 (entry0) │ ┌────> 0 x08048682 817 c2404d307. cmp dword [ esp + local_4h ], 0x7d3 │ ┌─────< 0 x0804868a 7 e02 jle 0x804868e │ ┌──────< 0 x0804868c eb6d jmp 0x80486fb │ ││││││ ; JMP XREF from 0x0804868a (entry0) │ │└─────> 0 x0804868e e885fdffff call fcn.08048418 │ │ ││││ 0 x08048693 8 d442404 lea eax , [ esp + local_4h ] │ │ ││││ 0 x08048697 ff00 inc dword [ eax ] │ │ └────< 0 x08048699 ebe7 jmp 0x8048682 │ │ │││ ; JMP XREF from 0x08048668 (entry0) │ │ ││└─> 0 x0804869b 837 c240800 cmp dword [ esp + local_8h ], 0 │ │ ││┌─< 0 x080486a0 7 e59 jle 0x80486fb │ │ │││ 0 x080486a2 83 ec04 sub esp , 4 │ │ │││ 0 x080486a5 8 d442414 lea eax , [ esp + local_14h ] │ │ │││ 0 x080486a9 50 push eax │ │ │││ 0 x080486aa ff742410 push dword [ esp + local_10h ] │ │ │││ 0 x080486ae ff742418 push dword [ esp + local_18h ] │ │ │││ 0 x080486b2 e814020000 call sub.memcpy_8cb │ │ │││ 0 x080486b7 83 c410 add esp , 0x10 │ │ │││ 0 x080486ba 85 c0 test eax , eax │ │ ┌────< 0 x080486bc 7502 jne 0x80486c0 │ │┌─────< 0 x080486be eb3b jmp 0x80486fb │ ││││││ ; JMP XREF from 0x080486bc (entry0) │ ││└────> 0 x080486c0 83 ec0c sub esp , 0xc │ ││ │││ 0 x080486c3 68 ae9f0408 push str.OK_nstage2 : ; str.OK_nstage2: ; "OK.stage2: " @ 0x8049fae │ ││ │││ 0 x080486c8 e823fdffff call sub.write_3f0 │ ││ │││ 0 x080486cd 83 c410 add esp , 0x10 │ ││ │││ 0 x080486d0 83 ec08 sub esp , 8 │ ││ │││ 0 x080486d3 ff742410 push dword [ esp + local_10h ] │ ││ │││ 0 x080486d7 ff742418 push dword [ esp + local_18h ] │ ││ │││ 0 x080486db e8d6030000 call fcn.08048ab6 │ ││ │││ 0 x080486e0 83 c410 add esp , 0x10 │ ││ │││ 0 x080486e3 85 c0 test eax , eax │ ││┌────< 0 x080486e5 7502 jne 0x80486e9 │ ┌───────< 0 x080486e7 eb12 jmp 0x80486fb │ │││││││ ; JMP XREF from 0x080486e5 (entry0) │ │││└────> 0 x080486e9 83 ec0c sub esp , 0xc │ │││ │││ 0 x080486ec 68 ba9f0408 push str.OK_n__key_accepted___n ; str.OK_n__key_accepted___n ; "OK.[ key accepted ]." @ 0x8049fba │ │││ │││ 0 x080486f1 e8fafcffff call sub.write_3f0 │ │││ │││ 0 x080486f6 83 c410 add esp , 0x10 │ │││┌────< 0 x080486f9 eb10 jmp 0x804870b │ │││││││ ; XREFS: JMP 0x080486e7 JMP 0x080486be JMP 0x0804868c JMP 0x0804864e JMP 0x08048618 JMP 0x080486a0 │ └└└─└└└─> 0 x080486fb 83 ec0c sub esp , 0xc │ │ 0 x080486fe 68 cf9f0408 push str.FAILED_n ; str.FAILED_n ; "FAILED." @ 0x8049fcf │ │ 0 x08048703 e8e8fcffff call sub.write_3f0 │ │ 0 x08048708 83 c410 add esp , 0x10 │ │ ; JMP XREF from 0x080486f9 (entry0) │ └────> 0 x0804870b 837 c240c00 cmp dword [ esp + local_ch ], 0 │ ┌─< 0 x08048710 740 f je 0x8048721 │ │ 0 x08048712 83 ec0c sub esp , 0xc │ │ 0 x08048715 ff742418 push dword [ esp + local_18h ] │ │ 0 x08048719 e892fcffff call sym.imp.free │ │ 0 x0804871e 83 c410 add esp , 0x10 │ │ ; JMP XREF from 0x08048710 (entry0) │ └─> 0 x08048721 83 ec0c sub esp , 0xc │ 0 x08048724 6 a00 push 0 ╘ 0 x08048726 e8a5fcffff call sym.imp._exit [0 x080485ba ] >

Ok, this is a bit long. It seems that the sub.write_3f0 function is always called after strings, so it's likely some kind of puts() :

[0 x080485ba ] > pdf @ sub.write_3f0 ;-- section_end..plt: ;-- section..text: ╒ ( fcn ) sub.write_3f0 40 │ ; XREFS: CALL 0x080485d5 CALL 0x080485f5 CALL 0x08048625 CALL 0x08048703 CALL 0x080486c8 CALL 0x080486f1 CALL 0x08048672 │ 0 x080483f0 83 ec0c sub esp , 0xc ; [9] va=0x080483f0 pa=0x000003f0 sz=7017 vsz=7017 rwx=--r-x .text │ 0 x080483f3 83 ec04 sub esp , 4 │ 0 x080483f6 83 ec08 sub esp , 8 │ 0 x080483f9 ff74241c push dword [ esp + 0x1c ] │ 0 x080483fd e89e1a0000 call fcn.08049ea0 │ 0 x08048402 83 c40c add esp , 0xc │ 0 x08048405 50 push eax │ 0 x08048406 ff742418 push dword [ esp + 0x18 ] │ 0 x0804840a 6 a01 push 1 │ 0 x0804840c e81fffffff call sym.imp.write │ 0 x08048411 83 c410 add esp , 0x10 │ 0 x08048414 83 c40c add esp , 0xc ╘ 0 x08048417 c3 ret [0 x080485ba ] > pdf @ fcn.08049ea0 ╒ ( fcn ) fcn.08049ea0 38 │ ; CALL XREF from 0x080483fd (sub.write_3f0) │ ; CALL XREF from 0x08048491 (fcn.08048470) │ ; CALL XREF from 0x080484a7 (fcn.08048470) │ 0 x08049ea0 83 ec04 sub esp , 4 │ 0 x08049ea3 c70424000000. mov dword [ esp ], 0 │ ; JMP XREF from 0x08049ebd (fcn.08049ea0) │ ┌─> 0 x08049eaa 8 b442408 mov eax , dword [ esp + 8 ] ; [0x8:4]=0 │ │ 0 x08049eae 803800 cmp byte [ eax ], 0 │ ┌──< 0 x08049eb1 7502 jne 0x8049eb5 │ ┌───< 0 x08049eb3 eb0a jmp 0x8049ebf │ │││ ; JMP XREF from 0x08049eb1 (fcn.08049ea0) │ │└──> 0 x08049eb5 89 e0 mov eax , esp │ │ │ 0 x08049eb7 ff00 inc dword [ eax ] │ │ │ 0 x08049eb9 ff442408 inc dword [ esp + 8 ] │ │ └─< 0 x08049ebd ebeb jmp 0x8049eaa │ │ ; JMP XREF from 0x08049eb3 (fcn.08049ea0) │ └───> 0 x08049ebf 8 b0424 mov eax , dword [ esp ] │ 0 x08049ec2 83 c404 add esp , 4 ╘ 0 x08049ec5 c3 ret [0 x080485ba ] >

The subfunction fcn.08049ea0 looks like a strlen . When you take a look at the graph view (by typing VV ), it becomes obvious:

[0 x08049ea0 ] > VV @ fcn.08049ea0 =-----------------------= | 0 x8049ea0 | | ( fcn ) fcn.08049ea0 38 | | sub esp , 4 | | mov dword [ esp ], 0 | =-----------------------= v ' | . -------------------------- . =--------------------------= =----------------------= | 0 x8049eaa | | 0x8049eb5 | | mov eax , dword [ esp + 8 ] | | mov eax , esp | | cmp byte [ eax ], 0 | | inc dword [ eax ] | | jne 0x8049eb5 ;[a] | | inc dword [esp + 8] | =--------------------------= | jmp 0x8049eaa ;[c] | f t = ---------------------- = ' | ^ | `-------------------' =-----------------------= | [0 x8049eb3 ] | | jmp 0x8049ebf ;[b] | =-----------------------= v ' | =----------------------= | 0 x8049ebf | | mov eax , dword [ esp ] | | add esp , 4 | | ret | =----------------------=

It's using a local variable as a counter (in [esp] ), and returns its value when a 0 (aka NULL ) is met.

So radare2 managed to correctly hint us with the name of the function, sub.write_3f0 , it's effectively a wrapper to write .

Then, there is a call to sub.getuid_750 at 0x080485e5 :

[0 x080485ba ] > pdf @ sub.getuid_750 ╒ ( fcn ) sub.getuid_750 235 │ 0 x08048750 83 ec1c sub esp , 0x1c │ 0 x08048753 83 ec0c sub esp , 0xc │ 0 x08048756 83 ec04 sub esp , 4 │ 0 x08048759 e812fcffff call sym.imp.getuid │ 0 x0804875e 83 c404 add esp , 4 │ 0 x08048761 50 push eax │ 0 x08048762 e8d9fbffff call sym.imp.getpwuid │ 0 x08048767 83 c410 add esp , 0x10 │ 0 x0804876a 89442414 mov dword [ esp + 0x14 ], eax │ 0 x0804876e 837 c241400 cmp dword [ esp + 0x14 ], 0 │ ┌─< 0 x08048773 750 d jne 0x8048782 │ │ 0 x08048775 c744240c0000. mov dword [ esp + 0xc ], 0 │ ┌──< 0 x0804877d e9b1000000 jmp 0x8048833 │ │└─> 0 x08048782 c74424186300. mov dword [ esp + 0x18 ], 0x63 │ │ 0 x0804878a 8 b442414 mov eax , dword [ esp + 0x14 ] │ │ 0 x0804878e 8 b00 mov eax , dword [ eax ] │ │ 0 x08048790 89442410 mov dword [ esp + 0x10 ], eax │ │┌─> 0 x08048794 8 b442410 mov eax , dword [ esp + 0x10 ] │ ││ 0 x08048798 803800 cmp byte [ eax ], 0 │ ┌───< 0 x0804879b 7502 jne 0x804879f │ ┌────< 0 x0804879d eb43 jmp 0x80487e2 │ │└───> 0 x0804879f 8 b442410 mov eax , dword [ esp + 0x10 ] │ │ ││ 0 x080487a3 0 fbe10 movsx edx , byte [ eax ] │ │ ││ 0 x080487a6 8 b442418 mov eax , dword [ esp + 0x18 ] │ │ ││ 0 x080487aa 0 fafc2 imul eax , edx │ │ ││ 0 x080487ad 89442418 mov dword [ esp + 0x18 ], eax │ │ ││ 0 x080487b1 8 d442418 lea eax , [ esp + 0x18 ] │ │ ││ 0 x080487b5 c13803 sar dword [ eax ], 3 │ │ ││ 0 x080487b8 8 b442410 mov eax , dword [ esp + 0x10 ] │ │ ││ 0 x080487bc 0 fbe10 movsx edx , byte [ eax ] │ │ ││ 0 x080487bf 8 d442418 lea eax , [ esp + 0x18 ] │ │ ││ 0 x080487c3 3110 xor dword [ eax ], edx │ │┌───> 0 x080487c5 8 b442418 mov eax , dword [ esp + 0x18 ] │ ││││ 0 x080487c9 83 e003 and eax , 3 │ ││││ 0 x080487cc 85 c0 test eax , eax │ ┌─────< 0 x080487ce 7502 jne 0x80487d2 │ ┌──────< 0 x080487d0 eb08 jmp 0x80487da │ │└─────> 0 x080487d2 8 d442418 lea eax , [ esp + 0x18 ] │ │ ││││ 0 x080487d6 d120 shl dword [ eax ], 1 │ │ │└───< 0 x080487d8 ebeb jmp 0x80487c5 │ └──────> 0 x080487da 8 d442410 lea eax , [ esp + 0x10 ] │ │ ││ 0 x080487de ff00 inc dword [ eax ] │ │ │└─< 0 x080487e0 ebb2 jmp 0x8048794 │ └────> 0 x080487e2 8 b442414 mov eax , dword [ esp + 0x14 ] │ │ 0 x080487e6 8 b4008 mov eax , dword [ eax + 8 ] │ │ 0 x080487e9 83 e001 and eax , 1 │ │ 0 x080487ec 8 d1400 lea edx , [ eax + eax ] │ │ 0 x080487ef 8 d442418 lea eax , [ esp + 0x18 ] │ │ 0 x080487f3 0110 add dword [ eax ], edx │ │ 0 x080487f5 8 b442414 mov eax , dword [ esp + 0x14 ] │ │ 0 x080487f9 8 b500c mov edx , dword [ eax + 0xc ] │ │ 0 x080487fc 83 e201 and edx , 1 │ │ 0 x080487ff 8 d442418 lea eax , [ esp + 0x18 ] │ │ 0 x08048803 0110 add dword [ eax ], edx │ │ 0 x08048805 8 b542420 mov edx , dword [ esp + 0x20 ] │ │ 0 x08048809 8 b442414 mov eax , dword [ esp + 0x14 ] │ │ 0 x0804880d 8 b4008 mov eax , dword [ eax + 8 ] │ │ 0 x08048810 8902 mov dword [ edx ], eax │ │ 0 x08048812 8 b542420 mov edx , dword [ esp + 0x20 ] │ │ 0 x08048816 8 b442414 mov eax , dword [ esp + 0x14 ] │ │ 0 x0804881a 8 b400c mov eax , dword [ eax + 0xc ] │ │ 0 x0804881d 894204 mov dword [ edx + 4 ], eax │ │ 0 x08048820 8 b542420 mov edx , dword [ esp + 0x20 ] │ │ 0 x08048824 8 b442418 mov eax , dword [ esp + 0x18 ] │ │ 0 x08048828 894208 mov dword [ edx + 8 ], eax │ │ 0 x0804882b c744240c0100. mov dword [ esp + 0xc ], 1 │ └──> 0 x08048833 8 b44240c mov eax , dword [ esp + 0xc ] │ 0 x08048837 83 c41c add esp , 0x1c ╘ 0 x0804883a c3 ret [0 x080485ba ] >

Err, something long, with computations: graph view to the rescue!

=------------------------------= | [0 x8048750 ] | | ( fcn ) sub.getuid_750 235 | | sub esp , 0x1c | | sub esp , 0xc | | sub esp , 4 | | call sym.imp.getuid ;[a] | | add esp , 4 | | push eax | | call sym.imp.getpwuid ;[b] | | add esp , 0x10 | | mov dword [ esp + 0x14 ], eax | | cmp dword [ esp + 0x14 ], 0 | | jne 0x8048782 ;[c] | =------------------------------= t f . ------------------------------- ' ' ------------------- . | | =------------------------------= =--------------------------= | 0 x8048782 | | 0x8048775 | | mov dword [ esp + 0x18 ], 0x63 | | mov dword [ esp + 0xc ], 0 | | mov eax , dword [ esp + 0x14 ] | | jmp 0x8048833 ;[j] | | mov eax , dword [ eax ] | = -------------------------- = | mov dword [ esp + 0x10 ], eax | v =------------------------------= | v | . ------------------------ . | | | =-----------------------------= | | | 0 x8048794 | | | | mov eax , dword [ esp + 0x10 ] | | | | cmp byte [ eax ], 0 | | | | jne 0x804879f ;[h] | | | =-----------------------------= | | t f | | . ------------ ' ' ------------------------------ . | | | | | | =------------------------------= =--------------------= | | | 0 x804879f | | 0x804879d | | | | mov eax , dword [ esp + 0x10 ] | | jmp 0x80487e2 ;[i] | | | | movsx edx , byte [ eax ] | = -------------------- = | | | mov eax , dword [ esp + 0x18 ] | v | | | imul eax , edx | | | | | mov dword [ esp + 0x18 ], eax | = ------------------------------ = | | | lea eax , [ esp + 0x18 ] | | 0x80487e2 | | | | sar dword [ eax ], 3 | | mov eax , dword [ esp + 0x14 ] | | | | mov eax , dword [ esp + 0x10 ] | | mov eax , dword [ eax + 8 ] | | | | movsx edx , byte [ eax ] | | and eax , 1 | | | | lea eax , [ esp + 0x18 ] | | lea edx , [ eax + eax ] | | | | xor dword [ eax ], edx | | lea eax , [ esp + 0x18 ] | | | =------------------------------= | add dword [ eax ], edx | | | v | mov eax , dword [ esp + 0x14 ] | | | ' | mov edx , dword [ eax + 0xc ] | | | | | and edx , 1 | | | . ------------ . | lea eax , [ esp + 0x18 ] | | | | =-----------------------------= | add dword [ eax ], edx | | | | | 0 x80487c5 | | mov edx , dword [ esp + 0x20 ] | | | | | mov eax , dword [ esp + 0x18 ] | | mov eax , dword [ esp + 0x14 ] | | | | | and eax , 3 | | mov eax , dword [ eax + 8 ] | | | | | test eax , eax | | mov dword [ edx ], eax | | | | | jne 0x80487d2 ;[e] | | mov edx, dword [esp + 0x20] | | | | =-----------------------------= | mov eax , dword [ esp + 0x14 ] | | | | t f | mov eax , dword [ eax + 0xc ] | | | | | | | mov dword [ edx + 4 ], eax | | | | | | | mov edx , dword [ esp + 0x20 ] | | | | | '----------------- . | mov eax , dword [ esp + 0x18 ] | | | | | | | mov dword [ edx + 8 ], eax | | | | | | | mov dword [ esp + 0xc ], 1 | | | | | | =------------------------------= | | | | | | | | | =-----------------------= =--------------------= | | | | | 0 x80487d2 | | 0x80487d0 | | | | | | lea eax , [ esp + 0x18 ] | | jmp 0x80487da ;[f] | '-------.-----------' | | | shl dword [ eax ], 1 | = -------------------- = | | | | jmp 0x80487c5 ;[d] | v | | | =-----------------------= | | | `-------' =-----------------------= =----------------------------= | | 0 x80487da | | 0x8048833 | | | lea eax , [ esp + 0x10 ] | | mov eax , dword [ esp + 0xc ] | | | inc dword [ eax ] | | add esp , 0x1c | | | jmp 0x8048794 ;[g] | | ret | | =-----------------------= =----------------------------= `-------------------------------------'

It's a bit better. The first node is calling getpwuid , and if there is an error, it'll put 0 into [esp + 0xc] , and go to the end of the function, that put [esp + 0xc] into eax , the return value, so this is likely the error handling path.

The branch on the left, 0x8048782 , will put the constant 0x63 at [esp + 0x18] , and the first member ( char* pw_name , aka the current username) of the structure returned by getpwuid is put at [esp + 0x10] .

Then, we have two nested loops:

The first one will iterate over every letter of the current username, and on each cycle it will do something like value = (([esp + 0x10] *) current_pw_name_letter) >> 3) ^ current_pw_name_letter , in the 0x804879f block. The second loop will set value to value << 1 as long as value & 3 is different from zero.

This could be translated, in C, as:

for ( uint32_t value = 63 ; * pw_name ; pw_name ++ ) { value = ((( value * * pw_name ) >> 3 ) ^ * pw_name ); while ( value & 3 ) { value = value << 1 ; } }

The last block before the function is returning (at 0x80487e2 ) is getting the aforementioned return value of getpwuid , stored at [esp + 0x14] , accessing the third field ( pw_uid , aka user id), and'ing it with 1 , multiplying it by two, adding it to our value variable, and finally and'ing the third field ( pw_gid , aka current gid) with 1 , and adding it too. This can be summarised as value = value + (pw_uid & 1) * 2 + (pw_gid & 1) . And finally, it sets eax to one, and returns. Our value variable isn't returned nor used? What is going on?

If you take a close look at 0x080485e0 , you'll see this:

[0 x080485ba ] > pd 3 @ 0x080485e0 │ 0 x080485e0 8 d44241c lea eax , [ esp + local_1ch ] ; 0x1c │ 0 x080485e4 50 push eax │ 0 x080485e5 e866010000 call sub.getuid_750 [0 x080485ba ] >

The program is passing a variable by reference to the function, so maybe it's worth looking again at what's happening in the last bloc of 0x80487e2 , where it's dealing with the stack, moving values around and pointing to others…

It seems indeed that the last part of the bloc is setting the fields of the structure passed to sub.getuid_750 to {pw_uid, pw_gid, value} ; So this structure will likely be used somewhere else.

The next called function is 0x80483f0 , with the parameter .key .

[0 x080485ba ] > pdf @ sub.write_3f0 ╒ ( fcn ) sub.write_3f0 40 │ 0 x080483f0 83 ec0c sub esp , 0xc │ 0 x080483f3 83 ec04 sub esp , 4 │ 0 x080483f6 83 ec08 sub esp , 8 │ 0 x080483f9 ff74241c push dword [ esp + 0x1c ] │ 0 x080483fd e89e1a0000 call fcn.08049ea0 │ 0 x08048402 83 c40c add esp , 0xc │ 0 x08048405 50 push eax │ 0 x08048406 ff742418 push dword [ esp + 0x18 ] │ 0 x0804840a 6 a01 push 1 │ 0 x0804840c e81fffffff call sym.imp.write │ 0 x08048411 83 c410 add esp , 0x10 │ 0 x08048414 83 c40c add esp , 0xc ╘ 0 x08048417 c3 ret [0 x080485ba ] >

It's calling fcn.08049ea0 , then writes something on stdout .

=---------------------------------= | 0 x804883b | | ( fcn ) sub.getuid_83b 144 | | ; var int local_ch @ esp+0xc | | ; var int local_18h @ esp+0x18 | | ; var int local_20h @ esp+0x20 | | ; var int local_28h @ esp+0x28 | | ; var int local_2ch @ esp+0x2c | | ; var int local_3ch @ esp+0x3c | | ; var int local_8ch @ esp+0x8c | | sub esp , 0x7c | | sub esp , 8 | | lea eax , [ esp + local_18h ] | | push eax | | push dword [ esp + local_8ch ] | | call sub.__lxstat_f20 ;[a] | | add esp , 0x10 | | test eax , eax | | jne 0x80488b1 ;[b] | =---------------------------------= f t . --------------- ' ' ------------------- . | | =---------------------------------------= | | 0 x8048859 | | | cmp dword [ esp + local_3ch ], 0x800000 | | | jg 0x80488b1 ;[b] | | =---------------------------------------= | f t | . - ' ' ----------------------------- . | | | | =-----------------------------------= | | | 0 x8048863 | | | | call sym.imp.getuid ;[c] | | | | cmp dword [ esp + local_28h ], eax | | | | jne 0x80488b1 ;[b] | | | =-----------------------------------= | | f t | | . ----- ' ' ------------------------- . | | | | | | =-----------------------------------= | | | | 0 x804886e | | | | | call sym.imp.getgid ;[d] | | | | | cmp dword [ esp + local_2ch ], eax | | | | | jne 0x80488b1 ;[b] | | | | =-----------------------------------= | | | f t | | | . ----- ' ' ------------------------- . | | | | | | | | =----------------------------------= | | | | | 0 x8048879 | | | | | | mov eax , dword [ esp + local_20h ] | | | | | | and eax , 0xf000 | | | | | | cmp eax , 0x8000 | | | | | | jne 0x80488b1 ;[b] | | | | | =----------------------------------= | | | | f t | | | | . ----- ' ' ------------------------- . | | | | | | | | | | =----------------------------------= | | | | | | 0 x8048889 | | | | | | | mov eax , dword [ esp + local_20h ] | | | | | | | and eax , 0x38 | | | | | | | test eax , eax | | | | | | | jne 0x80488b1 ;[b] | | | | | | =----------------------------------= | | | | | f t | | | | | . ----- ' ' ------------------------- . | | | | | | | | | | | | =----------------------------------= | | | | | | | 0 x8048894 | | | | | | | | mov eax , dword [ esp + local_20h ] | | | | | | | | and eax , 7 | | | | | | | | test eax , eax | | | | | | | | jne 0x80488b1 ;[b] | | | | | | | =----------------------------------= | | | | | | f t | | | | | | . ----- ' ' ------------------------- . | | | | | | | | | | | | | | =----------------------------------= | | | | | | | | 0 x804889f | | | | | | | | | mov eax , dword [ esp + local_20h ] | | | | | | | | | and eax , 0x1c0 | | | | | | | | | cmp eax , 0x100 | | | | | | | | | jne 0x80488b1 ;[b] | | | | | | | | =----------------------------------= | | | | | | | f t | | | | | | | ' '-------------- . '--.--' ----- '-----' ----- '-----' ----- ' | | | =--------------------= =-------------------------------= | 0 x80488af | | 0x80488b1 | | jmp 0x80488bb ;[e] | | mov dword [esp + local_ch], 0 | =--------------------= | jmp 0x80488c3 ;[f] | v = ------------------------------- = | v =----------------------------------= ' | 0 x80488bb | | | mov eax , dword [ esp + local_3ch ] | | | mov dword [ esp + local_ch ], eax | | =----------------------------------= | v | |-----------------------------------' | =---------------------------------= | [0 x80488c3 ] | | mov eax , dword [ esp + local_ch ] | | add esp , 0x7c | | ret | =---------------------------------=

Looks like a series of tests.

[0 x080485ba ] > pdf @ sub.__lxstat_f20 ╒ ( fcn ) sub.__lxstat_f20 53 │ ; var int local_4h @ ebp-0x4 │ ; arg int arg_8h @ ebp+0x8 │ ; arg int arg_ch @ ebp+0xc │ ; CALL XREF from 0x0804884d (sub.getuid_83b) │ 0 x08049f20 55 push ebp │ 0 x08049f21 89 e5 mov ebp , esp │ 0 x08049f23 83 ec10 sub esp , 0x10 │ 0 x08049f26 895 dfc mov dword [ ebp - local_4h ], ebx │ 0 x08049f29 8 b550c mov edx , dword [ ebp + arg_ch ] ; [0xc:4]=0 │ 0 x08049f2c e824000000 call fcn.08049f55 │ 0 x08049f31 81 c36f010000 add ebx , 0x16f │ 0 x08049f37 c70424030000. mov dword [ esp ], 3 │ 0 x08049f3e 89542408 mov dword [ esp + 8 ], edx │ 0 x08049f42 8 b5508 mov edx , dword [ ebp + arg_8h ] ; [0x8:4]=0 │ 0 x08049f45 89542404 mov dword [ esp + 4 ], edx │ 0 x08049f49 e812e4ffff call sym.imp.__lxstat │ 0 x08049f4e 8 b5dfc mov ebx , dword [ ebp - local_4h ] │ 0 x08049f51 89 ec mov esp , ebp │ 0 x08049f53 5 d pop ebp ╘ 0 x08049f54 c3 ret [0 x080485ba ] > pdf @ fcn.08049f55 ╒ ( fcn ) fcn.08049f55 4 │ ; CALL XREF from 0x08049f2c (sub.__lxstat_f20) │ 0 x08049f55 8 b1c24 mov ebx , dword [ esp ] ╘ 0 x08049f58 c3 ret [0 x080485ba ] >

It seems that sub.__lxstat_f20 is a simple wrapper around lxstat . You may want to read its manpage to know what are the fields of the structure that it returns.

Our cascade of tests function that will:

Call lxstat and check that there is no error. Check that the file is owned by the current user. Check that the file is owned by the current group. Check that the file is a regular file (Check the implementation of S_ISREG for details, or simply the manpage of lstat .). Check that the file is has rights & 0x38 != 0 and rights & 0x7 != 0 rights & 0x1c0 != 0x100 , meaning that the file should be 0400 , aka only readable by the current user.

If all those conditions are met, the function will return the size of the file, else, it will return zero.

Here it's easy to know what fields of the stat structure are accessed, because everything is done relatively to esp , you only have to know the size of the different fields.

You can of course show those field by doing dynamic analysis:

$e r2 - A - d . / collide Process with PID 11188 started... attach 11188 11188 bin.baddr 0x08048000 Assuming filepath . / collide asm.bits 32 [ x ] Analyze al l flags starting with sym. and entry0 ( aa ) [ x ] Analyze len byte s of instructions for references ( aar ) [ x ] Analyze function calls ( aac ) [ x ] Constructing a function name for fcn. * and sym.func. * functions ( aan )) -- Have you seen the latest radare2 TV sp ot? [0 xf776dac0 ] > db 0x08048852 [0 xf776dac0 ] > dc [ collide.crp - ] stage0: hit breakpoint at : 8048852 attach 11188 1 [0 x08048852 ] > pf w :: ioiiii :: w st_dev st_ino st_mode st_nlink st_uid st_gid st_rdev st_size @ ( esp + 34 ) st_dev : 0xffbc9616 = 0x0000 st_ino : 0xffbc9620 = 5641011 st_mode : 0xffbc9624 = ( octal ) 000100664 st_nlink : 0xffbc9628 = 1 st_uid : 0xffbc962c = 1000 st_gid : 0xffbc9630 = 1000 st_dev : 0xffbc9634 = 0 st_off : 0xffbc9640 = 0x002b [0 x08048852 ] >

The last command may look at bit cryptic, and you may be asking yourself "What about using the t command instead, to deal with types?".

The issue with the t command is that internally, radare2 is using tcc , that doesn't respect very well alignement directives. Maybe it's r2's fault, maybe it's tcc one, but when you're using the to - command and paste the C structure definition (don't forget to add a __attribute__ (( aligned (4))) attribute to it) and print it with pf syntax, this is what you'll get:

[0 x08048852 ] > to . / struct.h # I put the structure into a file for easier edition [0 x08048852 ] > cat stat # define dev_t uint64_t # define ino_t uint32_t # define mode_t uint32_t # define nlink_t uint32_t # define uid_t uint32_t # define gid_t uint32_t # define dev_t uint64_t # define off_t uint64_t # define bl ksize_t uint64_t typedef struct stat { dev_t st_dev ; /* ID of device containing file */ ino_t st_ino ; /* inode number */ mode_t st_mode ; /* protection */ nlink_t st_nlink ; /* number of hard links */ uid_t st_uid ; /* user ID of owner */ gid_t st_gid ; /* group ID of owner */ dev_t st_rdev ; /* device ID (if special file) */ off_t st_size ; /* total size, in bytes */ } stat_s __attribute__ (( al igned ( 8 ))) ; [0 x08048852 ] > t stat pf qiiiiiqq st_dev st_ino st_mode st_nlink st_uid st_gid st_rdev st_size [0 x08048852 ] >

It's not the same that the previous pf one, because currently radare2 and tcc aren't playing nice with structures aligment. But what is pf anyway? And what's the deal with its über-weird syntax?

pf stands for p rint f ormated data. As usual, you can get help by appending ? to it. Its syntax is pretty easy: pf [types] [field names] .

So my previous cryptic line, pf w::ioiiii::w st_dev st_ino st_mode st_nlink st_uid st_gid st_rdev st_size @ (esp + 34) describes a structure that looks like this:

struct stat { word st_dev ; int pad1 [ 2 ]; // padding, won't be displayed int st_ino ; int st_mode ; // `o` as in _octal_ int st_nlink ; int st_uid ; int st_gid ; int st_rdev ; int pad2 [ 2 ]; // padding, won't be displayed quadword st_size ; };

Pretty cool isn't it?

Ok, back to the main function. After the call to sub.getuid_83b , there is a condition check, and a jump to the end of the function, likely a BADBOY branch. The other path leads to a call to the wrapper to write , with the string "OK

stage1". Time to check if our analysis is right:

$ echo test > .key $ chmod 400 .key $ ./collide [ collide.crp- ] stage0: OK stage1: FAILED

Yay!

We successfully passed the comparison at 0x08048611 ; the next function call is sub.malloc_4f2 , at 0x0804863f , with .key as argument:

[0 x080485ba ] > pdsf @ sub.malloc_4f2 0 x080484fe sym.imp.malloc 0 x0804852c sym.imp.open 0 x0804855f sym.imp.read 0 x080485a4 sym.imp.free [0 x080485ba ] >

It seems that it's simply reading the file .key into the heap.

Maybe it's time to get a big overview of what is happening in the main function: You can use the VV command to get the graph view, but like in the visual mode, you can switch between modes with the p / P keys. I really like the summary one:

=-----------------------------------= | 0 x80485ba | | 0 x080485d0 str.__collide.crp____n | | 0 x080485f0 str.stage0 : | | 0 x08048600 str..key | =-----------------------------------= t f . ---------------- ' ' --------------- . | | =----------------------------= =--------------------= | 0 x804861d | | 0x8048618 | | 0 x08048620 str.OK_nstage1 : | = -------------------- = | 0 x0804863a str..key | v =----------------------------= | t f | . -------------------- ' ' ------- . | | | | =-------------------------= =--------------------= | | 0 x8048653 | | 0x804864e | | | 0 x0804865e fcn.08048470 | = -------------------- = | =-------------------------= v | t f | | . ----------- ' ' --------------------- . ' -------------------------| | | | =--------------------= =------------------------------= | | 0 x804869b | | 0x804866a | | =--------------------= | 0 x0804866d str.___re.....___ | | f t = ------------------------------ = | | | v | . ---------------- ' ' ------------ . ' | | | . ----------------- . | | =--------------------= | | =--------------------= | | 0 x80486a2 | | | | 0x8048682 | | =--------------------= | | =--------------------= | t f | | t f | . ------------------- ' ' --------------- . | | . ----------- ' ' ----------- . | | | | | | | | =----------------------------= =--------------------= | | =-------------------------= =--------------------= | | 0 x80486c0 | | 0x80486be | | | | 0x804868e | | 0x804868c | | | 0 x080486c3 str.OK_nstage2 : | = -------------------- = | | | 0x0804868e fcn.08048418 | = -------------------- = | | 0 x080486db fcn.08048ab6 | v | | = ------------------------- = v | =----------------------------= | | `-------' | | t f ' --------- . | | | . --------------- ' ' -------------------------- . | | | | | | | | | | =---------------------------------------= =--------------------= | | | | | 0 x80486e9 | | 0x80486e7 | | | | | | 0 x080486ec str.OK_n__key_accepted___n | = -------------------- = | | | | =---------------------------------------= v | | | | v | | | | | | '-------------- . ---- '----' ------------------------------------ '------------------' | | | =-------------------------= | | 0 x80486fb | | | 0 x080486fe str.FAILED_n | | =-------------------------= | v '--------------------------------------- . ----------------- ' | =--------------------= | 0 x804870b | =--------------------= f t . ---------- ' ' ------- . | | =-------------------------= | | 0 x8048712 | | | 0 x08048719 sym.imp.free | | =-------------------------= | v | '---- . . --------- ' | | =--------------------------= | [0 x8048721 ] | | 0 x08048726 sym.imp._exit | =--------------------------=

Right after fcn.08048470 there is a test, and if it's false, it'll jump to a loop, do something with the string -- re..... -- , and then jumps to FAILED

. So we can ignore this part and focus on making the function return true.

[0 x080485ba ] > pdsf @ fcn.08048470 0 x08048479 "hmpf, i need a hint..." 0 x08048491 fcn.08049ea0 0 x080484a7 fcn.08049ea0 0 x080484ca fcn.08049ec6 [0 x080485ba ] > pdsf @ fcn.08049ea0 [0 x080485ba ] > pdsf @ fcn.08049ec6 [0 x080485ba ] >

Educated guess: fcn.0x09049ea0 is a custom strlen , and fcn.08049ec6 is memcmp , so if we put hmpf, i need a hint.. into our .key file, we should get the message -- re..... -- :

$ echo 'hmpf, i need a hint..' > | .key $ chmod 400 .key $ ./collide [ collide.crp- ] stage0: OK stage1: -- re..... -- FAILED $

So the next interesting function is sub.memcpy_8cb , and it's taking 3 parameters:

The content of the file .key The size of the file The structure {uid, gid, value} .

<@@@@@@> f t . ----- ' | | | [ _88e9_ ] | f t | . ---- ' | | | | | [ _88f0_ ] | | f t | | . ---- '.' | | | | | | [ _88fb_ ] | | | t f | | | . --------- ' ' - . . - '-----' ----- ' | | | [ _8911_ ] [ _8904_ ] v v | '------------ . . ------------------- . | | | | | [ _8937_ ] | | f t | | | '--------------------- . | | [ _899c_ ] | | | f t | | | | '--------------- . | | | [ _89af_ ] | | | | f t | | | | ' '--------- . . - '-----' | | | | | | | [ _89c2_ ] [ _89c4_ ] | | v v | | | '--------- . | | [ _89d1_ ] | | | t f | | | . ------ ' ' -------------- . | | | | | | | | [ _89eb_ ] [ _89de_ ] | | | t f v | | | . ----- ' ' ------------ . ' | | | | | | | | | [ _8a55_ ] [ _8a4b_ ] | | | | f t v | | | | | '----------- . | | | | | | | '--------'-------'- . - ' | [ _8a64_ ] | | | v | | | '-------------'-- . | | | | | [ _8a69_ ] [ _8aae_ ] | v | | | . --------- . | | [ _8a7c_ ] | | t f | | . --- ' ' --------- . | | | | | | [ _8a8b_ ] [ _8a86_ ] | `-----' | | | '-----------------------------------'

Looks really convoluted :/

But we can break it un sub-parts: everything that eventually and unconditionally leads to _8aae_ is potentially a BADBOY location. Potentially, and not certainly, because one of them is the good way to exit the loop.

You can use the minimap-with-disasm view (smash p or P until you land on it) to quickly find the it.

If you check the last node of the graph (at 0x8048aae ), you can see this:

0 x8048aae: mov eax , dword [ esp + 0x10 ] add esp , 0x5c ret

With a bit of luck, the function will return a different value (in eax ) when everything was right, so there should be an location that put an unique value into [esp + 0x10] :

$ rasm2 'mov dword [esp + 0x10], 0' c744241000000000 $ rasm2 'mov dword [esp + 0x10], 1' c744241001000000

So looking for c74424100 should be enough:

[0 x080485ba ] > pdf @ sub.memcpy_8cb~c74424100 │ │└└└─> 0 x08048904 c74424100000. mov dword [ esp + 0x10 ], 0 │ │└└└───> 0 x080489c4 c74424100000. mov dword [ esp + 0x10 ], 0 │ ││││ 0 x080489de c74424100100. mov dword [ esp + 0x10 ], 1 │ │││││ 0 x08048a4b c74424100000. mov dword [ esp + 0x10 ], 0 [0 x080485ba ] >

Yay, there is only one location that puts 1 into [esp + 0x10] , and it's at the basic block 0x80489de . The condition right before it is

=----------------------------= | 0 x80489d1 | | lea eax , [ esp + 0x48 ] | | inc dword [ eax ] | | cmp dword [ esp + 0x48 ], 2 | | jne 0x80489eb ;[f] | =----------------------------=

If you highlight 0x48 (with the / key, like in vim), you'll see that the only other location where it's used is in the first block, where it's initialised to 0 .

So the big loop will be executed once, and the part before our right-exit block, twice.

Lets focus on the first part of the function:

[0 x080485ba ] > pdb @ sub.memcpy_8cb ╒ ( fcn ) sub.memcpy_8cb 491 │ 0 x080488cb 83 ec5c sub esp , 0x5c │ 0 x080488ce c744244c0000. mov dword [ esp + 0x4c ], 0 │ 0 x080488d6 c74424480000. mov dword [ esp + 0x48 ], 0 │ 0 x080488de 8 b442464 mov eax , dword [ esp + 0x64 ] │ 0 x080488e2 83 e001 and eax , 1 │ 0 x080488e5 85 c0 test eax , eax │ ┌─< 0 x080488e7 751 b jne 0x8048904 [0 x080485ba ] >

If you're too lazy to count the offset clever, you can break on 0x080488de , and see that it's the size of the .key file:

jvoisin@kaa 21 : 13 ~/ dev / r2 / exploit / collide r2 - d . / collide Process with PID 17892 started... attach 17892 17892 bin.baddr 0x08048000 Assuming filepath . / collide asm.bits 32 -- Now featuring NoSQL ! [0 xf76eeac0 ] > db 0x080488de [0 xf76eeac0 ] > dc [ collide.crp - ] stage0: OK stage1: hit breakpoint at : 80488 de attach 17892 1 [0 x080488de ] > pf i @ esp + 0x64 0 xff9ff738 = 42 [0 x080488de ] > ls - l .key -- r ---------- 1 1000 : 1000 42 .key [0 x080488de ] >

[0 x080485ba ] > pd 20 @ sub.memcpy_8cb ╒ ( fcn ) sub.memcpy_8cb 491 │ 0 x080488cb 83 ec5c sub esp , 0x5c │ 0 x080488ce c744244c0000. mov dword [ esp + 0x4c ], 0 │ 0 x080488d6 c74424480000. mov dword [ esp + 0x48 ], 0 │ 0 x080488de 8 b442464 mov eax , dword [ esp + 0x64 ] │ 0 x080488e2 83 e001 and eax , 1 │ 0 x080488e5 85 c0 test eax , eax │ ┌─< 0 x080488e7 751 b jne 0x8048904 │ │ 0 x080488e9 837 c246477 cmp dword [ esp + 0x64 ], 0x77 │ ┌──< 0 x080488ee 7614 jbe 0x8048904 │ ││ 0 x080488f0 8 b442464 mov eax , dword [ esp + 0x64 ] │ ││ 0 x080488f4 83 e00f and eax , 0xf │ ││ 0 x080488f7 85 c0 test eax , eax │ ┌───< 0 x080488f9 7509 jne 0x8048904 │ │││ 0 x080488fb 8 b442468 mov eax , dword [ esp + 0x68 ] │ │││ 0 x080488ff 833800 cmp dword [ eax ], 0 │ ┌────< 0 x08048902 750 d jne 0x8048911 │ │└└└─> 0 x08048904 c74424100000. mov dword [ esp + 0x10 ], 0 │ │ ┌─< 0 x0804890c e99d010000 jmp 0x8048aae │ └────> 0 x08048911 8 b442460 mov eax , dword [ esp + 0x60 ] │ │ 0 x08048915 8 a00 mov al , byte [ eax ] [0 x080485ba ] >

So it's checking that the size of the file is even, superior to 0x77 , that and'ing it with 0xf is equal to zero, and it's checking that our struct->uid isn't zero (you can check that it's indeed our structure using with a breakpoint, as done previously.).

Moving to the next block:

[0 x080485ba ] > pdb @ 0x08048911 │ 0 x08048911 8 b442460 mov eax , dword [ esp + 0x60 ] │ 0 x08048915 8 a00 mov al , byte [ eax ] │ 0 x08048917 25 ff000000 and eax , 0xff │ 0 x0804891c 8944244 c mov dword [ esp + 0x4c ], eax │ 0 x08048920 8 b442460 mov eax , dword [ esp + 0x60 ] │ 0 x08048924 40 inc eax │ 0 x08048925 8 a00 mov al , byte [ eax ] │ 0 x08048927 25 ff000000 and eax , 0xff │ 0 x0804892c 89 c2 mov edx , eax │ 0 x0804892e c1e208 shl edx , 8 │ 0 x08048931 8 d44244c lea eax , [ esp + 0x4c ] │ 0 x08048935 0110 add dword [ eax ], edx [0 x080485ba ] >

What is at [esp + 0x60] ?

$ r2 - d . / collide Process with PID 18794 started... attach 18794 18794 bin.baddr 0x08048000 Assuming filepath . / collide asm.bits 32 -- No fix , no sleep [0 xf76f4ac0 ] > db 0x08048911 [0 xf76f4ac0 ] > dc [ collide.crp - ] stage0: OK stage1: hit breakpoint at : 8048911 attach 18794 1 [0 x08048911 ] > pxr 4 @ esp + 0x60 0 xffe51cf4 0x092bd9d8 .. + . ecx ( 1234 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP ) [0 x08048911 ] > cat .key 1234 AAAAAAAAAA [ … ] [0 x08048911 ] >

So it's reading the first two bytes of the .key file, shifting them by 8 , and adding this to the pointer to the file's start: looks like an offset.

We're now entering in the main loop of the function:

[0 x080485ba ] > pd 44 @ sub.malloc_4f2 + 0x445 │ ; JMP XREF from 0x08048a86 (sub.memcpy_8cb) │ 0 x08048937 8 b44244c mov eax , dword [ esp + 0x4c ] │ 0 x0804893b 03442460 add eax , dword [ esp + 0x60 ] │ 0 x0804893f 89442444 mov dword [ esp + 0x44 ], eax │ 0 x08048943 83 ec0c sub esp , 0xc │ 0 x08048946 ff742450 push dword [ esp + 0x50 ] │ 0 x0804894a e8ddfdffff call fcn.0804872c │ 0 x0804894f 83 c410 add esp , 0x10 │ 0 x08048952 89442440 mov dword [ esp + 0x40 ], eax │ 0 x08048956 8 d442444 lea eax , [ esp + 0x44 ] │ 0 x0804895a 830010 add dword [ eax ], 0x10 │ 0 x0804895d 83 ec0c sub esp , 0xc │ 0 x08048960 ff742450 push dword [ esp + 0x50 ] │ 0 x08048964 e8c3fdffff call fcn.0804872c │ 0 x08048969 83 c410 add esp , 0x10 │ 0 x0804896c 8944243 c mov dword [ esp + 0x3c ], eax │ 0 x08048970 8 d442444 lea eax , [ esp + 0x44 ] │ 0 x08048974 830010 add dword [ eax ], 0x10 │ 0 x08048977 83 ec0c sub esp , 0xc │ 0 x0804897a ff742450 push dword [ esp + 0x50 ] │ 0 x0804897e e8a9fdffff call fcn.0804872c │ 0 x08048983 83 c410 add esp , 0x10 │ 0 x08048986 89442438 mov dword [ esp + 0x38 ], eax │ 0 x0804898a 8 b442440 mov eax , dword [ esp + 0x40 ] │ 0 x0804898e 03442460 add eax , dword [ esp + 0x60 ] │ 0 x08048992 8 b542468 mov edx , dword [ esp + 0x68 ] │ 0 x08048996 8 b00 mov eax , dword [ eax ] │ 0 x08048998 3 b02 cmp eax , dword [ edx ] │ ┌─< 0 x0804899a 7528 jne 0x80489c4 │ │ 0 x0804899c 8 b44243c mov eax , dword [ esp + 0x3c ] │ │ 0 x080489a0 03442460 add eax , dword [ esp + 0x60 ] │ │ 0 x080489a4 8 b542468 mov edx , dword [ esp + 0x68 ] │ │ 0 x080489a8 8 b00 mov eax , dword [ eax ] │ │ 0 x080489aa 3 b4204 cmp eax , dword [ edx + 4 ] │ ┌──< 0 x080489ad 7515 jne 0x80489c4 │ ││ 0 x080489af 8 b442438 mov eax , dword [ esp + 0x38 ] │ ││ 0 x080489b3 03442460 add eax , dword [ esp + 0x60 ] │ ││ 0 x080489b7 8 b542468 mov edx , dword [ esp + 0x68 ] │ ││ 0 x080489bb 8 b00 mov eax , dword [ eax ] │ ││ 0 x080489bd 3 b4208 cmp eax , dword [ edx + 8 ] │ ┌───< 0 x080489c0 7502 jne 0x80489c4 │ ┌────< 0 x080489c2 eb0d jmp 0x80489d1 │ │└└└─> 0 x080489c4 c74424100000. mov dword [ esp + 0x10 ], 0 │ │ ┌─< 0 x080489cc e9dd000000 jmp 0x8048aae │ │ │ ; JMP XREF from 0x080489c2 (sub.memcpy_8cb) │ └────> 0 x080489d1 8 d442448 lea eax , [ esp + 0x48 ] ; 0x48 ; 'H' [0 x080485ba ] >

So, there is a function, fcn.0804872c , called tree times, with offsets distant of 0x10 as arguments, and its return value is used, as an offset, to compare it against our struct .

[0 x080485ba ] > pdf @ fcn.0804872c ╒ ( fcn ) fcn.0804872c 36 │ 0 x0804872c 53 push ebx │ 0 x0804872d 8 b4c2408 mov ecx , dword [ esp + 8 ] │ 0 x08048731 8 b442408 mov eax , dword [ esp + 8 ] │ 0 x08048735 8 b542408 mov edx , dword [ esp + 8 ] │ 0 x08048739 8 b4004 mov eax , dword [ eax + 4 ] │ 0 x0804873c 0 faf4208 imul eax , dword [ edx + 8 ] │ 0 x08048740 89 c2 mov edx , eax │ 0 x08048742 8 b442408 mov eax , dword [ esp + 8 ] │ 0 x08048746 8 b580c mov ebx , dword [ eax + 0xc ] │ 0 x08048749 8 d0413 lea eax , [ ebx + edx ] │ 0 x0804874c 3301 xor eax , dword [ ecx ] │ 0 x0804874e 5 b pop ebx ╘ 0 x0804874f c3 ret [0 x080485ba ] >

This is roughly equivalent to return (((param[4] * param[8])) + param[0xc]) ^ param[0] .

So, it means that in our .key file, the uid , gid and value field of our struct must be present a specific offsets.

Then, we have our aforementioned condition, to exit (or not) the loop.

And finally, the end of the function:

│ │└────> 0 x080489eb 8 b542464 mov edx , dword [ esp + 0x64 ] │ │ │││ 0 x080489ef 89 d0 mov eax , edx │ │ │││ 0 x080489f1 c1f81f sar eax , 0x1f │ │ │││ 0 x080489f4 c1e81f shr eax , 0x1f │ │ │││ 0 x080489f7 8 d0410 lea eax , [ eax + edx ] │ │ │││ 0 x080489fa d1f8 sar eax , 1 │ │ │││ 0 x080489fc 03442460 add eax , dword [ esp + 0x60 ] │ │ │││ 0 x08048a00 8944241 c mov dword [ esp + 0x1c ], eax │ │ │││ 0 x08048a04 8 b542464 mov edx , dword [ esp + 0x64 ] │ │ │││ 0 x08048a08 89 d0 mov eax , edx │ │ │││ 0 x08048a0a c1f81f sar eax , 0x1f │ │ │││ 0 x08048a0d c1e81f shr eax , 0x1f │ │ │││ 0 x08048a10 8 d0410 lea eax , [ eax + edx ] │ │ │││ 0 x08048a13 d1f8 sar eax , 1 │ │ │││ 0 x08048a15 89442418 mov dword [ esp + 0x18 ], eax │ │ │││ 0 x08048a19 83 ec04 sub esp , 4 │ │ │││ 0 x08048a1c 6 a0c push 0xc │ │ │││ 0 x08048a1e ff742470 push dword [ esp + 0x70 ] │ │ │││ 0 x08048a22 8 d44242c lea eax , [ esp + 0x2c ] │ │ │││ 0 x08048a26 50 push eax │ │ │││ 0 x08048a27 e854f9ffff call sym.imp.memcpy │ │ │││ 0 x08048a2c 83 c410 add esp , 0x10 │ │ │││ 0 x08048a2f 8 b442420 mov eax , dword [ esp + 0x20 ] │ │ │││ 0 x08048a33 0 faf442424 imul eax , dword [ esp + 0x24 ] │ │ │││ 0 x08048a38 33442428 xor eax , dword [ esp + 0x28 ] │ │ │││ 0 x08048a3c 8944242 c mov dword [ esp + 0x2c ], eax │ │ │││ 0 x08048a40 8 b442418 mov eax , dword [ esp + 0x18 ] │ │ │││ 0 x08048a44 83 e007 and eax , 7 │ │ │││ 0 x08048a47 85 c0 test eax , eax │ │┌────< 0 x08048a49 740 a je 0x8048a55 │ │││││ 0 x08048a4b c74424100000. mov dword [ esp + 0x10 ], 0 │ ┌──────< 0 x08048a53 eb59 jmp 0x8048aae │ ││└────> 0 x08048a55 8 b442418 mov eax , dword [ esp + 0x18 ] │ ││ │││ 0 x08048a59 8944240 c mov dword [ esp + 0xc ], eax │ ││ │││ 0 x08048a5d 837 c240c00 cmp dword [ esp + 0xc ], 0 │ ││┌────< 0 x08048a62 7905 jns 0x8048a69 │ ││││││ 0 x08048a64 8344240 c07 add dword [ esp + 0xc ], 7 │ ││└────> 0 x08048a69 8 b44240c mov eax , dword [ esp + 0xc ] │ ││ │││ 0 x08048a6d c1f803 sar eax , 3 │ ││ │││ 0 x08048a70 89442418 mov dword [ esp + 0x18 ], eax │ ││ │││ 0 x08048a74 c74424140000. mov dword [ esp + 0x14 ], 0 │ ││┌────> 0 x08048a7c 8 b442414 mov eax , dword [ esp + 0x14 ] │ ││││││ 0 x08048a80 3 b442418 cmp eax , dword [ esp + 0x18 ] │ ┌───────< 0 x08048a84 7 c05 jl 0x8048a8b │ │││││└──< 0 x08048a86 e9acfeffff jmp 0x8048937 │ └───────> 0 x08048a8b 83 ec08 sub esp , 8 │ ││││ │ 0 x08048a8e 8 d442428 lea eax , [ esp + 0x28 ] │ ││││ │ 0 x08048a92 50 push eax │ ││││ │ 0 x08048a93 ff742428 push dword [ esp + 0x28 ] │ ││││ │ 0 x08048a97 e83e020000 call fcn.08048cda │ ││││ │ 0 x08048a9c 83 c410 add esp , 0x10 │ ││││ │ 0 x08048a9f 8 d44241c lea eax , [ esp + 0x1c ] │ ││││ │ 0 x08048aa3 830008 add dword [ eax ], 8 │ ││││ │ 0 x08048aa6 8 d442414 lea eax , [ esp + 0x14 ] │ ││││ │ 0 x08048aaa ff00 inc dword [ eax ] │ ││└────< 0 x08048aac ebce jmp 0x8048a7c │ └└─└─└─> 0 x08048aae 8 b442410 mov eax , dword [ esp + 0x10 ] │ 0 x08048ab2 83 c45c add esp , 0x5c ╘ 0 x08048ab5 c3 ret [0 x080485ba ] >

You may be wondering what this snippet is doing:

0 x080489ef 89 d0 mov eax , edx 0 x080489f1 c1f81f sar eax , 0x1f 0 x080489f4 c1e81f shr eax , 0x1f 0 x080489f7 8 d0410 lea eax , [ eax + edx ] 0 x080489fa d1f8 sar eax , 1

It's a classic compiler trick to implement a signed division.

I fail to see the point of sar eax, 0x1f , but shr eax, 0x1f will only keep the highest bit of eax , aka the sign bit, and lea eax, [eax + edx] will add it to edx , and sar eax, 1 will finally perform the division by two (a right shift or one is equivalent to a division by two.).

Take -7 for example: -7 / 2 = (-7 + 1) >> 1 = -3 , while -7 >> 1 = -4 .

So the size of the file .key is divided by 2, this value is stored at [esp + x10] , then divided again by two, stored at [esp + 0x18] . There is then a call to memcpy , that is copying 0xC bytes of the content of the .key file into a local variable, then the program is computing var = (struct.uid * struct.gid) ^ struct.value .

It's then checking if the size of the file .key divided by two and and'ed by 7 is equal to zero. It it is, it'll jump to a BADBOY location.

Some stuffs are moved around, and there is finally an internal loop, that does call fcn.08048cda amongst doing other computations, roughly equivalent to:

struct our_struct keydata = key_file_content + ( size_of_the_key_file / 2 ); for ( uint32_t i = 0 ; i < sizeof ( keydata ) / 8 ; i ++ ) { fcn08048cda (((( uint32_t * ) & keydata ) + i * 2 ), & our_struct ); }

Lets hope that it's not another computation loop…

[0 x080485ba ] > pdf @ fcn.08048cda ╒ ( fcn ) fcn.08048cda 230 │ 0 x08048cda 53 push ebx │ 0 x08048cdb 83 ec10 sub esp , 0x10 │ 0 x08048cde a1d89f0408 mov eax , dword [ 0x8049fd8 ] │ 0 x08048ce3 c1e005 shl eax , 5 │ 0 x08048ce6 89442404 mov dword [ esp + 4 ], eax │ 0 x08048cea 8 b442418 mov eax , dword [ esp + 0x18 ] │ 0 x08048cee 8 b00 mov eax , dword [ eax ] │ 0 x08048cf0 8944240 c mov dword [ esp + 0xc ], eax │ 0 x08048cf4 8 b442418 mov eax , dword [ esp + 0x18 ] │ 0 x08048cf8 83 c004 add eax , 4 │ 0 x08048cfb 8 b00 mov eax , dword [ eax ] │ 0 x08048cfd 89442408 mov dword [ esp + 8 ], eax │ 0 x08048d01 c70424000000. mov dword [ esp ], 0 │ ┌─> 0 x08048d08 8 b0424 mov eax , dword [ esp ] │ │ 0 x08048d0b 3 b05dc9f0408 cmp eax , dword [ 0x8049fdc ] │ ┌──< 0 x08048d11 7 c05 jl 0x8048d18 │ ┌───< 0 x08048d13 e98c000000 jmp 0x8048da4 │ │└──> 0 x08048d18 8 b44240c mov eax , dword [ esp + 0xc ] │ │ │ 0 x08048d1c 89 c2 mov edx , eax │ │ │ 0 x08048d1e c1e204 shl edx , 4 │ │ │ 0 x08048d21 8 b44241c mov eax , dword [ esp + 0x1c ] │ │ │ 0 x08048d25 83 c008 add eax , 8 │ │ │ 0 x08048d28 0310 add edx , dword [ eax ] │ │ │ 0 x08048d2a 8 b442404 mov eax , dword [ esp + 4 ] │ │ │ 0 x08048d2e 0344240 c add eax , dword [ esp + 0xc ] │ │ │ 0 x08048d32 89 d1 mov ecx , edx │ │ │ 0 x08048d34 31 c1 xor ecx , eax │ │ │ 0 x08048d36 8 b44240c mov eax , dword [ esp + 0xc ] │ │ │ 0 x08048d3a 89 c2 mov edx , eax │ │ │ 0 x08048d3c c1ea05 shr edx , 5 │ │ │ 0 x08048d3f 8 b44241c mov eax , dword [ esp + 0x1c ] │ │ │ 0 x08048d43 83 c00c add eax , 0xc │ │ │ 0 x08048d46 8 b18 mov ebx , dword [ eax ] │ │ │ 0 x08048d48 8 d0413 lea eax , [ ebx + edx ] │ │ │ 0 x08048d4b 89 ca mov edx , ecx │ │ │ 0 x08048d4d 31 c2 xor edx , eax │ │ │ 0 x08048d4f 8 d442408 lea eax , [ esp + 8 ] │ │ │ 0 x08048d53 2910 sub dword [ eax ], edx │ │ │ 0 x08048d55 8 b442408 mov eax , dword [ esp + 8 ] │ │ │ 0 x08048d59 89 c2 mov edx , eax │ │ │ 0 x08048d5b c1e204 shl edx , 4 │ │ │ 0 x08048d5e 8 b44241c mov eax , dword [ esp + 0x1c ] │ │ │ 0 x08048d62 0310 add edx , dword [ eax ] │ │ │ 0 x08048d64 8 b442404 mov eax , dword [ esp + 4 ] │ │ │ 0 x08048d68 03442408 add eax , dword [ esp + 8 ] │ │ │ 0 x08048d6c 89 d1 mov ecx , edx │ │ │ 0 x08048d6e 31 c1 xor ecx , eax │ │ │ 0 x08048d70 8 b442408 mov eax , dword [ esp + 8 ] │ │ │ 0 x08048d74 89 c2 mov edx , eax │ │ │ 0 x08048d76 c1ea05 shr edx , 5 │ │ │ 0 x08048d79 8 b44241c mov eax , dword [ esp + 0x1c ] │ │ │ 0 x08048d7d 83 c004 add eax , 4 │ │ │ 0 x08048d80 8 b18 mov ebx , dword [ eax ] │ │ │ 0 x08048d82 8 d0413 lea eax , [ ebx + edx ] │ │ │ 0 x08048d85 89 ca mov edx , ecx │ │ │ 0 x08048d87 31 c2 xor edx , eax │ │ │ 0 x08048d89 8 d44240c lea eax , [ esp + 0xc ] │ │ │ 0 x08048d8d 2910 sub dword [ eax ], edx │ │ │ 0 x08048d8f 8 b15d89f0408 mov edx , dword [ 0x8049fd8 ] │ │ │ 0 x08048d95 8 d442404 lea eax , [ esp + 4 ] │ │ │ 0 x08048d99 2910 sub dword [ eax ], edx │ │ │ 0 x08048d9b 89 e0 mov eax , esp │ │ │ 0 x08048d9d ff00 inc dword [ eax ] │ │ └─< 0 x08048d9f e964ffffff jmp 0x8048d08 │ └───> 0 x08048da4 8 b542418 mov edx , dword [ esp + 0x18 ] │ 0 x08048da8 8 b44240c mov eax , dword [ esp + 0xc ] │ 0 x08048dac 8902 mov dword [ edx ], eax │ 0 x08048dae 8 b542418 mov edx , dword [ esp + 0x18 ] │ 0 x08048db2 83 c204 add edx , 4 │ 0 x08048db5 8 b442408 mov eax , dword [ esp + 8 ] │ 0 x08048db9 8902 mov dword [ edx ], eax │ 0 x08048dbb 83 c410 add esp , 0x10 │ 0 x08048dbe 5 b pop ebx ╘ 0 x08048dbf c3 ret [0 x080485ba ] >

Another computation loop, hurray!

It seems that eax is used as a counter, and that the number of iteration is stored at 0x8049fdc . There are multiple ways of displaying its value: pf like we did previously, but radare2's asm.comments is also working:

0 x080485ba ] > pf i @ 0x8049fdc 0 x08049fdc = 32 [0 x080485ba ] > e asm.comments = true [0 x080485ba ] > pd 1 @ 0x08048d0b │ 0 x08048d0b 3 b05dc9f0408 cmp eax , dword [ 0x8049fdc ] ; [0x8049fdc:4]=32 0 x080485ba ] >

So, 32 iterations. The first block is likely initializing some data:

[0 x080485ba ] > pdb @ fcn.08048cda ╒ ( fcn ) fcn.08048cda 230 │ ; CALL XREF from 0x08048a97 (sub.memcpy_8cb) │ 0 x08048cda 53 push ebx │ 0 x08048cdb 83 ec10 sub esp , 0x10 │ 0 x08048cde a1d89f0408 mov eax , dword [ 0x8049fd8 ] ; [0x8049fd8:4]=0x9e3779b9 │ 0 x08048ce3 c1e005 shl eax , 5 │ 0 x08048ce6 89442404 mov dword [ esp + 4 ], eax │ 0 x08048cea 8 b442418 mov eax , dword [ esp + 0x18 ] ; [0x18:4]=0x80485ba entry0 │ 0 x08048cee 8 b00 mov eax , dword [ eax ] │ 0 x08048cf0 8944240 c mov dword [ esp + 0xc ], eax │ 0 x08048cf4 8 b442418 mov eax , dword [ esp + 0x18 ] ; [0x18:4]=0x80485ba entry0 │ 0 x08048cf8 83 c004 add eax , 4 │ 0 x08048cfb 8 b00 mov eax , dword [ eax ] │ 0 x08048cfd 89442408 mov dword [ esp + 8 ], eax │ 0 x08048d01 c70424000000. mov dword [ esp ], 0 [0 x080485ba ] >

0x9e3779b9 << 5 is moved at [esp + 4] , looks like this value will be used as read-only at 0x08048d2a and 0x08048d64 , then decremented by 0x9e3779b9 on each iteration.

[esp + 0x18] is the content of the second part of our .key file, and you can see that the first dword will be stored at [esp + 0xc] while the second will be at [esp + 8] .

Then, the big calculation block!

It's actually pretty simple, since there are only basic arithmetic options. The only trick is to remember the offsets of our struct , but you can always renamed local variables with the afvn local_1ch new_name command.

This whole function is doing something like:

uint32_t fcn08048cda ( const uint8_t * keyfile_second_part , const struct * our_struct ){ #define INCREMENT 0x9e3779b9 << 5; uint32_t key = init ; uint32_t * second_part1 = * keyfile_second_part ; uint32_t * second_part2 = * keyfile_second_part + 1 ; for ( uint32_t i = 0 ; i < 32 ; i ++ ){ second_part2 -= ((( * ( secon_part1 ) << 4 ) + pwhash1 -> hash ) ^ ( key2 + * ( secon_part1 ))) ^ ((( * ( secon_part1 ) >> 5 ) + pwhash2 -> hash )); * ( secon_part1 ) -= (((( * ( second_part2 ) << 4 ) + pwhash2 -> uid ) ^ ( key2 + * ( second_part2 )))) ^ ((( * ( second_part2 ) >> 5 ) + pwhash2 -> gid )); key += init ; } * keyfile_second_part = second_part1 ; * ( keyfile_second_part + 1 ) = second_part2 ; #undef INCREMENT }

Now, if you remember what we said about sub.memcpy_8cb , after this function is called in a loop size_of_the_key_file / 16 times, and after than, it checks again that our uid , gid and value are still matching.

We need to reverse (and simplify a bit) this scheme to forge a key, fortunately, it's not that hard, you only have to revert the << 5 operation on the key , and to permute the second_part and second_part + 1 operations.

static void fcn08048cda_reversed ( uint32_t * second_part , const our_struct_t * our_struct ) { #define INCREMENT 0x9e3779b9 uint32_t key = INCREMENT ; for ( uint32_t i = 0 ; i < 32 ; i ++ ) { * ( second_part ) += ((( * ( second_part + 1 ) >> 5 ) + our_struct -> gid )) ^ (( * ( second_part + 1 ) << 4 ) + our_struct -> uid ) ^ ( key + * ( second_part + 1 )) ; * ( second_part + 1 ) += (( * ( second_part ) << 4 ) + our_struct -> value ) ^ ( key + * ( second_part )) ^ ( ( * ( second_part ) >> 5 ) + (( our_struct -> uid * our_struct -> gid ) ^ our_struct -> value ) ); key += INCREMENT ; } #undef INCREMENT }

It seems that it'll be a real pain to construct a file to validate this crackme…

The stage2 only consists in a call to fcn.08048ab6 . If you launch radare2 with analysis, and manually analyse this function (to avoid renaming), you should be able to do this:

[0 x080485ba ] > af @ 0x08048ab6 [0 x080485ba ] > pdfs @ fcn.08048ab6~fcn 0 x08048afd fcn.08048dc0 0 x08048b0d fcn.08048dc0 0 x08048b2e fcn.08048e02 0 x08048b54 fcn.08048e02 0 x08048b6c fcn.08048f18 0 x08048b81 fcn.08048f18 0 x08048b98 fcn.08049ec6 0 x08048bce fcn.08049ec6 [0 x080485ba ] >

Remember fcn.08049ec6 ? It's a custom memcmp . Every function is called twice, this is weird…

[0 x080485ba ] > pdf @ fcn.08048dc0 ╒ ( fcn ) fcn.08048dc0 66 │ 0 x08048dc0 8 b542404 mov edx , dword [ esp + 4 ] │ 0 x08048dc4 8 b442404 mov eax , dword [ esp + 4 ] │ 0 x08048dc8 c74014000000. mov dword [ eax + 0x14 ], 0 │ 0 x08048dcf c74210000000. mov dword [ edx + 0x10 ], 0 │ 0 x08048dd6 8 b442404 mov eax , dword [ esp + 4 ] │ 0 x08048dda c70001234567 mov dword [ eax ], 0x67452301 │ 0 x08048de0 8 b442404 mov eax , dword [ esp + 4 ] │ 0 x08048de4 c7400489abcd. mov dword [ eax + 4 ], 0xefcdab89 │ 0 x08048deb 8 b442404 mov eax , dword [ esp + 4 ] │ 0 x08048def c74008fedcba. mov dword [ eax + 8 ], 0x98badcfe │ 0 x08048df6 8 b442404 mov eax , dword [ esp + 4 ] │ 0 x08048dfa c7400c765432. mov dword [ eax + 0xc ], 0x10325476 ╘ 0 x08048e01 c3 ret [0 x080485ba ] > pdfs @ fcn.0x8048e02 0 x08048e81 sym.imp.memcpy 0 x08048e98 fcn.08048fcb 0 x08048ec6 fcn.08048fcb

If you take a look at fcn.08048fcb , you'll see a lot of mov , rol , and some sub and add . This really looks like a hash function, with fcn.08048dc0 being the initialization, fcn.08048e02 the computation, and fcn.08048f18 the final pass.

Time to find for what hash the constants 0x67452301 , 0xefcdab89 , 0x98badcfe and 0x10325476 are used. Keep in mind that we're dealing with little-endian, so the constants are in fact 0x76543210 , 0xfedcba98 , 89ab cdef , 0x01234567 , or even better, in another order:

0x01234567

0x89abcdef

0xfedcba98

0x76543210

If you ask a search engine about those, you'll find that they are used in the MD5 function.

After the md5 computations, there are two calls to memcmp , and since the binary is called collide , I guess we'll have to find a collision in md5, Two different plaintexts, with the same md5 hash. Since there is only a single call (well, done twice, on different data) to the computation part of the hash function, it's really looks like a single-block collision.

I tried to use the code from Vlastimil Klima, porting it to Linux, outputting the vectors as C code, … but since the code is awful (and with a lot of comments in Czech), I gave a try to fastcoll, but it didn't compile with a recent boost version, so I went back in time and used md5coll.c, that I had used to pass a infamous level11 on a well-known two-letters wargame ;)

Now the only remaining thing to do is to create our .key file, that will look like a my_struct1 || encrypt (my_struct2) , with the only differences between my_struct1 and my_struct2 being the md5 collision vector.

The my_struct will look like this:

struct my_struct { uint32_t vector1 [ 32 ]; // first vector for the collision uint32_t padding [ 0x1d13 - sizeof ( v1 )] uint32_t uid_var1 ; uint32_t uid_var2 ; uint32_t uid_var3 ; uint32_t uid_var4 ; uint32_t gid_var1 ; uint32_t gid_var2 ; uint32_t gid_var3 ; uint32_t gid_var4 ; uint32_t value_var1 ; uint32_t value_var2 ; uint32_t value_var3 ; uint32_t value_var4 ; uint32_t uid ; uint32_t gid ; int32_t value ; uint32_t padding_end []; }

Since I didn't want to compute the padding to make the file size a multiple of 0xf (because I'm lazy), I just bruteforced it by hand (since it's 1 in my case, it didn't take long :D).

Remember that the var stuff are used like this (var1 * var2 + var3) ^ var4 , to find the offset of their corresponding fields. So we can zero them all (or just var1 or var2 and var3 ), and use only var4 as offset indicator.

You can get my keygen here:

$ wget https://dustri.org/b/files/collide_keygen.c $ gcc ./keygen.c $ ./a.out $ ./collide [ collide.crp- ] stage0: OK stage1: OK stage2: OK [ key accepted ] $

I was a bit disappointed by this crackme: too much boring reversing, and too few interesting tricks, but still above the average.

Shout out to oddcoder for his successful GSoC with radare2, implementing and improving variables detection/handling/typing/manipulation/…