GTK KeyGen M3 !!!!!! -- mrmacete's solution =========================================== 1. Identification of executable ------------------------------- $ file GTK_K3YG3NME GTK_K3YG3NME: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, BuildID[sha1]=45df5d38da520a617dd7f6df2441cb3660c9d5e5, stripped Tested on Ubuntu, should work in every 32 bits linux with GTK 2 installed. 2. Solution plan ---------------- + use radare2 + use radare2 debugger In order to use radare2 debugger, r2 must be invoked with the -d option. While in visual mode ('V' command), it is possible to step into with 's' and step over with 'S'. Occasionally it is necessary to inspect some memory area, then it is possible to invoke the r2 prompt using ':' and use commands such as: :> pxc @ eax ; prints hexdump chars starting at the address pointed by eax :> pxw @ edx ; prints hexdump words starting at the address pointed by edx All the debugger commands start with 'd', try typing 'd?' for help. 3. Obfuscation and anti-debugging --------------------------------- There are two tricks to deal with, while reversing this piece of code: * one is the "jump in the middle" trick, that permits to hide real instructions from the disassembler's eyes by jumping in the middle of an instruction causing all the following to be re-decoded. Fortunately radare2 is able to do the re-interpretation on the fly while debugging so it's only a little annoying but it is not a big deal, here is an example: BEFORE JMP: │ │┌─< 0x080490a9 eb05 jmp 0x80490b0 ;[1] │ └──< 0x080490ab ebf9 jmp 0x80490a6 ;[2] │ │ 0x080490ad 6983b80155e0. imul eax, dword [ebx - 0x1faafe48], 0x51e44d11 │ 0x080490b7 52 push edx │ 0x080490b8 50 push eax (note how radare2 nicely signals the "jump in the middle" with the broken arrow) AFTER JMP: │ 0x080490b0 0155e0 add dword [ebp - 0x20], edx │ 0x080490b3 114de4 adc dword [ebp - 0x1c], ecx │ 0x080490b6 51 push ecx │ 0x080490b7 52 push edx │ 0x080490b8 50 push eax * the other is the use of RDTSC to measure time passed between checkpoints scattered all across the code, then compare the elapsed time with a fixed constant in an attempt of detecting the use of a debugger. Fortunately again, with any debugger including radare2's it is possible to set the value of registers at runtime. Here is an example of checkpoint: | | 0x08048f77 0f31 rdtsc | | 0x08048f79 8bc8 mov ecx, eax | | 0x08048f7b e807000000 call 0x8048f87 ;[4] [...] and here is the corresponding comparison, before which we smartly set EAX value to zero, making it pass: | | 0x08048f8b 0f31 rdtsc | | 0x08048f8d 2bc1 sub eax, ecx | | | ;-- eip: | | | 0x08048f8f 3d00300000 cmp eax, 0x3000 | | `=< 0x08048f94 73d4 jae 0x8048f6a ;[2] Press to return to Visual mode. :> dr eax=0 0xeec66977 ->0x00000000 :> 4. Finding the relevant code ---------------------------- As usual, one useful way to find the relevant code is to start searching from the end, i.e. from the output strings: $ r2 GTK_K3YG3NME -- This is fine. [0x08048e90]> aa [0x08048e90]> iz vaddr=0x08049b50 paddr=0x00001b50 ordinal=000 sz=72 len=71 section=.rodata type=ascii string=\nName is less than 3 characters !!!!\n vaddr=0x08049b98 paddr=0x00001b98 ordinal=001 sz=56 len=55 section=.rodata type=ascii string=\nLength is invalid !\n vaddr=0x08049bd0 paddr=0x00001bd0 ordinal=002 sz=77 len=76 section=.rodata type=ascii string=\nThe Serial is not valid !!!! , Try hard!\n vaddr=0x08049c20 paddr=0x00001c20 ordinal=003 sz=90 len=89 section=.rodata type=ascii string=\nThe serial number is correct.\n You Are A winner !! :D\n Let's find the usage of the first, for example: [0x08048e90]> /r 0x08049b50 axd 0x08049b50 0x08049460 [0x08048e90]> s 0x08049460 [0x08049460]> V [0x08049440 51% 145 GTK_K3YG3NME]> pd $r @ fcn.08048fe9+1111 # 0x8049440 │││ ; JMP XREF from 0x0804931e (fcn.08049103) │ │││ 0x08049440 c74424100000. mov dword [esp + 0x10], 0 │ │││ 0x08049448 c744240c0000. mov dword [esp + 0xc], 0 │ │││ 0x08049450 c74424080000. mov dword [esp + 8], 0 │ │││ 0x08049458 c7442404ffff. mov dword [esp + 4], sym.imp._Jv_R │ │││ 0x08049460 c70424509b04. mov dword [esp], str._span_foregro │ │││ 0x08049467 e8c0f9ffff call sym.imp.g_locale_to_utf8 [...] Let's see how we get here, there is a JMP XREF, so press 'x' and then '0' in visual mode: [GOTO XREF]> 1% 145 GTK_K3YG3NME]> pd $r @ fcn.08048fe9+1111 # 0x8049440 [0] 0x08049440 CODE (JMP) XREF 0x0804931e (fcn.08049103) And this will lead us straight in the middle of the function which reads and validates the input strings before sending them to the serial checking code: ; get the username | | 0x0804927a e89dfaffff call sym.imp.gtk_entry_get_text ;[2] | | ^- sym.imp.gtk_entry_get_text() | | ;-- eip: | | 0x0804927f 8b55d4 mov edx, dword [ebp-local_11] | | 0x08049282 8b5204 mov edx, dword [edx + 4] ; [0x4:4]=-1 ; 4 | | 0x08049285 89c3 mov ebx, eax [...] ; get the password | 0x080492c9 e84efaffff call sym.imp.gtk_entry_get_text ;[4] | ^- sym.imp.gtk_entry_get_text() | .----> 0x080492ce 89c7 mov edi, eax ; check username length (must be > 2) | 0x08049311 891c24 mov dword [esp], ebx | 0x08049314 89c6 mov esi, eax | 0x08049316 e871faffff call sym.imp.strlen ;[3] | 0x0804931b 83f802 cmp eax, 2 | ,==< 0x0804931e 0f861c010000 jbe 0x8049440 ; convert password to an unsigned long | | 0x08049324 803f00 cmp byte [edi], 0 | ,==< 0x08049327 0f84e3000000 je 0x8049410 ;[4] | || 0x0804932d 8d45e4 lea eax, [ebp-local_7] | || 0x08049330 893c24 mov dword [esp], edi | || 0x08049333 c74424080a00. mov dword [esp + 8], 0xa | || 0x0804933b 89442404 mov dword [esp + 4], eax | || 0x0804933f e8f8f9ffff call sym.imp.strtoull ;[5] ; finally call the serial checking function | || 0x08049478 894dd4 mov dword [ebp-local_11], ecx | || 0x0804947b 891c24 mov dword [esp], ebx ; (function renamed using radare2 '_' command in visual mode) | || 0x0804947e e8cdfaffff call check_serial_with_number_and_user ; check that the returned integer is equal to the provided serial | || 0x08049483 8b4dd4 mov ecx, dword [ebp-local_11] | || 0x08049486 31fa xor edx, edi | || 0x08049488 31c8 xor eax, ecx | || 0x0804948a 09c2 or edx, eax \ |`=< 0x0804948c 0f85f5feffff jne 0x8049387 5. The serial checking function ------------------------------- Basically the serial check is performed by generating a 64 bit integer from the username, and then checking that the provided serial number (converted to unsigned long long) is equal to it. Here is the heart of it, the calculation executed for each char of the username: | 0x08049001 8b5508 mov edx, dword [ebp+username] ; [0x8:4]=-1 ; 8 | 0x08049004 bf02000000 mov edi, 2 | 0x08049009 0fb602 movzx eax, byte [edx] | 0x0804900c c745e0000000. mov dword [ebp-local_8], 0 | 0x08049013 c745e4000000. mov dword [ebp-local_7], 0 | 0x0804901a 84c0 test al, al | ,==< 0x0804901c 0f84d3000000 je 0x80490f5 ;[2] | | 0x08049022 8db600000000 lea esi, [esi] | 0x08049045 b 31c9 xor ecx, ecx | 0x08049047 85ff test edi, edi | 0x08049049 ba01000000 mov edx, 1 | ,=< 0x0804904e 744b je 0x804909b ;[1] | | 0x08049050 0fbec0 movsx eax, al | | 0x08049053 31c9 xor ecx, ecx | | 0x08049055 89c2 mov edx, eax | | 0x08049057 31db xor ebx, ebx | | 0x08049059 c1fa1f sar edx, 0x1f | | 0x0804905c 8955ec mov dword [ebp - 0x14], edx | | 0x0804905f ba01000000 mov edx, 1 | | 0x08049064 8945e8 mov dword [ebp - 0x18], eax | | 0x08049067 8955d8 mov dword [ebp - 0x28], edx | | 0x0804906a 894ddc mov dword [ebp - 0x24], ecx | | 0x0804906d 8d7600 lea esi, [esi] | ; JMP XREF from 0x08049093 (unk) |.--> 0x08049070 8b4de8 mov ecx, dword [ebp - 0x18] ||| 0x08049073 83c301 add ebx, 1 ||| 0x08049076 8b75ec mov esi, dword [ebp - 0x14] ||| 0x08049079 0faf4ddc imul ecx, dword [ebp - 0x24] ||| 0x0804907d 0faf75d8 imul esi, dword [ebp - 0x28] ||| 0x08049081 8b45e8 mov eax, dword [ebp - 0x18] ||| 0x08049084 f765d8 mul dword [ebp - 0x28] ||| 0x08049087 01f1 add ecx, esi ||| 0x08049089 01ca add edx, ecx ||| 0x0804908b 39df cmp edi, ebx ||| 0x0804908d 8945d8 mov dword [ebp - 0x28], eax ||| 0x08049090 8955dc mov dword [ebp - 0x24], edx |`=< 0x08049093 77db ja 0x8049070 ;[1] | 0x08049095 8b55d8 mov edx, dword [ebp - 0x28] | 0x08049098 8b4ddc mov ecx, dword [ebp - 0x24] 0x080490b0 0155e0 add dword [ebp - 0x20], edx 0x080490b3 114de4 adc dword [ebp - 0x1c], ecx [...] || 0x080490e2 8b5508 mov edx, dword [ebp + 8] ; [0x8:4]=-1 ; 8 || 0x080490e5 0fb6443aff movzx eax, byte [edx + edi - 1] | 0x080490ea 83c701 add edi, 1 | 0x080490ed 84c0 test al, al `===< 0x080490ef 0f8533ffffff jne 0x8049028 This, merely translated into python is: def keygen(username): serial = 0 for i in xrange(len(username)): counter = 0 mul_accum = 1 a = ord(username[i]) for j in xrange(i+2): c = a * counter b = a * mul_accum mul_accum = b & 0xffffffff counter = c + ((b & 0xffffffff00000000) >> 32) serial += mul_accum serial += counter << 32 Which is the heart of my solution, included in the keygen.py