Prerequisites
If you don't want to get lost you need to know about calling conventions. Through this part arguments are pushed on the stack in the reverse order. There is one additional trick : arguments are not really pushed on the stack. Actually the necessary space for function arguments is already reserved and they aremov
ed directly to the stack to the correct offset relative to esp
.Analysis of the executable file
On part 2 we found a link to this file : www.canyoucrackit.co.uk/da75370fe15c4148bd4ceec861fbdaa5.exe(I made a local copy in case the original file is not available anymore. I just changed the extension to .bin : da75370fe15c4148bd4ceec861fbdaa5.bin)
Let's give it a nice name and examine it first.
$ mv da75370fe15c4148bd4ceec861fbdaa5.exe keygen.exe $ objdump -x keygen.exe
keygen.exe: file format pei-i386 keygen.exe architecture: i386, flags 0x00000102: EXEC_P, D_PAGED start address 0x00401000 Characteristics 0x30f relocations stripped executable line numbers stripped symbols stripped 32 bit words debugging information removed Time/Date Fri Aug 5 15:29:54 2011 Magic 010b (PE32) MajorLinkerVersion 2 MinorLinkerVersion 20 SizeOfCode 00000c00 SizeOfInitializedData 00000600 SizeOfUninitializedData 00000200 AddressOfEntryPoint 00001000 BaseOfCode 00001000 BaseOfData 00002000 ImageBase 00400000 SectionAlignment 00001000 FileAlignment 00000200 MajorOSystemVersion 4 MinorOSystemVersion 0 MajorImageVersion 1 MinorImageVersion 0 MajorSubsystemVersion 4 MinorSubsystemVersion 0 Win32Version 00000000 SizeOfImage 00005000 SizeOfHeaders 00000400 CheckSum 00005050 Subsystem 00000003 (Windows CUI) DllCharacteristics 00000000 SizeOfStackReserve 00200000 SizeOfStackCommit 00001000 SizeOfHeapReserve 00100000 SizeOfHeapCommit 00001000 LoaderFlags 00000000 NumberOfRvaAndSizes 00000010 The Data Directory Entry 0 00000000 00000000 Export Directory [.edata (or where ever we found it)] Entry 1 00004000 00000338 Import Directory [parts of .idata] Entry 2 00000000 00000000 Resource Directory [.rsrc] Entry 3 00000000 00000000 Exception Directory [.pdata] Entry 4 00000000 00000000 Security Directory Entry 5 00000000 00000000 Base Relocation Directory [.reloc] Entry 6 00000000 00000000 Debug Directory Entry 7 00000000 00000000 Description Directory Entry 8 00000000 00000000 Special Directory Entry 9 00000000 00000000 Thread Storage Directory [.tls] Entry a 00000000 00000000 Load Configuration Directory Entry b 00000000 00000000 Bound Import Directory Entry c 00000000 00000000 Import Address Table Directory Entry d 00000000 00000000 Delay Import Directory Entry e 00000000 00000000 CLR Runtime Header Entry f 00000000 00000000 Reserved There is an import table in .idata at 0x404000 The Import Tables (interpreted .idata section contents) vma: Hint Time Forward DLL First Table Stamp Chain Name Thunk 00004000 00004054 00000000 00000000 000042a4 000040d0 DLL Name: cygcrypt-0.dll vma: Hint/Ord Member-Name Bound-To 4148 0 crypt 00004014 0000405c 00000000 00000000 00004318 000040d8 DLL Name: cygwin1.dll vma: Hint/Ord Member-Name Bound-To 4150 59 __main 415c 182 _dll_crt0@0 416c 275 _fopen64 4178 367 _impure_ptr 4188 741 calloc 4194 788 connect 41a0 839 cygwin_detach_dll 41b4 841 cygwin_internal 41c8 862 dll_dllcrt0 41d8 929 fclose 41e4 999 free 41ec 1006 fscanf 41f8 1073 gethostbyname 4208 1147 htons 4210 1277 malloc 421c 1295 memcpy 4228 1299 memset 4234 1385 printf 4240 1493 realloc 424c 1496 recv 4254 1575 send 425c 1663 socket 4268 1673 sprintf 4274 1691 strcmp 4280 1702 strlen 00004028 000040c4 00000000 00000000 00004328 00004140 DLL Name: KERNEL32.dll vma: Hint/Ord Member-Name Bound-To 428c 529 GetModuleHandleA 0000403c 00000000 00000000 00000000 00000000 00000000 Sections: Idx Name Size VMA LMA File off Algn 0 .text 00000a50 00401000 00401000 00000400 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .data 000001a0 00402000 00402000 00001000 2**5 CONTENTS, ALLOC, LOAD, DATA 2 .bss 00000120 00403000 00403000 00000000 2**3 ALLOC 3 .idata 00000338 00404000 00404000 00001200 2**2 CONTENTS, ALLOC, LOAD, DATA SYMBOL TABLE: no symbolsThis file is in Portable Executable format and it imports functions from cygwin DLLs.
We need to boot under Windows and use cygwin to run it. But first let's use a good Win32 disassembler to see exactly what it is doing. Personally I used W32dasm which is quite good.
I won't kill you with a full listing here since it is more than 1000 lines long. I will only dump the full listing of the
main
function. In the comments I explicitly reconstructed function calls.:004010BA C745F400000000 mov [ebp-0C], 00000000 ;Sets a flag to 0 :004010C1 C704244E204000 mov dword ptr [esp], 0040204E * Reference To: cygwin1.printf, Ord:0569h | :004010C8 E88B040000 Call 00401558 ; printf("keygen.exe") :004010CD 837D0802 cmp dword ptr [ebp+08], 00000002 ; if (argc != 2) {... :004010D1 7418 je 004010EB * Possible StringData Ref from Data Obj ->"usage: keygen.exe hostname" | :004010D3 C704245C204000 mov dword ptr [esp], 0040205C * Reference To: cygwin1.printf, Ord:0569h | :004010DA E879040000 Call 00401558 ; printf("usage: keygen.exe hostname") :004010DF C745B0FFFFFFFF mov [ebp-50], FFFFFFFF :004010E6 E919010000 jmp 00401204 ; jump to exit ... } * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004010D1(C) | * Possible StringData Ref from Data Obj ->"r" | :004010EB C744240478204000 mov [esp+04], 00402078 * Possible StringData Ref from Data Obj ->"license.txt" | :004010F3 C704247A204000 mov dword ptr [esp], 0040207A * Reference To: cygwin1._fopen64, Ord:0113h | :004010FA E851040000 Call 00401550 ; fopen("license.txt","r") :004010FF 8945B4 mov dword ptr [ebp-4C], eax ; The result is stored in [ebp-4C], this is a file descriptor :00401102 837DB400 cmp dword ptr [ebp-4C], 00000000 ; if ([ebp-4c] == 0) { ... :00401106 7518 jne 00401120 * Possible StringData Ref from Data Obj ->"error: license.txt not found" | :00401108 C7042486204000 mov dword ptr [esp], 00402086 * Reference To: cygwin1.printf, Ord:0569h | :0040110F E844040000 Call 00401558 ; printf("error: license.txt not found") :00401114 C745B0FFFFFFFF mov [ebp-50], FFFFFFFF :0040111B E9E4000000 jmp 00401204 ; jump to exit ... } * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:00401106(C) | :00401120 C744240818000000 mov [esp+08], 00000018 :00401128 C744240400000000 mov [esp+04], 00000000 :00401130 8D45C8 lea eax, dword ptr [ebp-38] :00401133 890424 mov dword ptr [esp], eax * Reference To: cygwin1.memset, Ord:0513h | :00401136 E80D040000 Call 00401548 ; memset([ebp-38],0,0x18) //[ebp-38] = buffer of size 0x18=24 :0040113B 8D45C8 lea eax, dword ptr [ebp-38] :0040113E 89442408 mov dword ptr [esp+08], eax * Possible StringData Ref from Data Obj ->"%s" | :00401142 C7442404A4204000 mov [esp+04], 004020A4 :0040114A 8B45B4 mov eax, dword ptr [ebp-4C] :0040114D 890424 mov dword ptr [esp], eax * Reference To: cygwin1.fscanf, Ord:03EEh | :00401150 E8EB030000 Call 00401540 ; fscanf([ebp-4C], %s, &[ebp-38]) //[ebp-4c] = file descriptor :00401155 8B45B4 mov eax, dword ptr [ebp-4C] :00401158 890424 mov dword ptr [esp], eax * Reference To: cygwin1.fclose, Ord:03A1h | :0040115B E8D8030000 Call 00401538 ; fclose([ebp-4C]); :00401160 C745B400000000 mov [ebp-4C], 00000000 :00401167 817DC867636871 cmp dword ptr [ebp-38], 71686367 ; if ([ebp-38] != 0x71686367) goto error; //it is 'gchq' :0040116E 755F jne 004011CF * Possible StringData Ref from Data Obj ->"hqDTK7b8K2rvw" | :00401170 A100204000 mov eax, dword ptr [00402000] :00401175 89442404 mov dword ptr [esp+04], eax :00401179 8D45C8 lea eax, dword ptr [ebp-38] :0040117C 83C004 add eax, 00000004 :0040117F 890424 mov dword ptr [esp], eax * Reference To: cygcrypt-0.crypt, Ord:0000h | :00401182 E8A5020000 Call 0040142C ; crypt(&[ebp-38]+4,"hqDTK7b8K2rvw") :00401187 89C2 mov edx, eax * Possible StringData Ref from Data Obj ->"hqDTK7b8K2rvw" | :00401189 A100204000 mov eax, dword ptr [00402000] :0040118E 89442404 mov dword ptr [esp+04], eax :00401192 891424 mov dword ptr [esp], edx * Reference To: cygwin1.strcmp, Ord:069Bh | :00401195 E896030000 Call 00401530 ; strcmp(result,"hqDTK7b8K2rvw") //Compare crypt result with hqDTK7b8K2rvw :0040119A 85C0 test eax, eax :0040119C 7507 jne 004011A5 :0040119E C745F401000000 mov [ebp-0C], 00000001 ; Set [ebp-0C] to 1 if crypt's result was ok. * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0040119C(C) | * Possible StringData Ref from Data Obj ->"loading stage1 license key(s)..." | :004011A5 C70424A8204000 mov dword ptr [esp], 004020A8 * Reference To: cygwin1.printf, Ord:0569h | :004011AC E8A7030000 Call 00401558 ; printf("loading stage1 license key(s)..."); :004011B1 8B45D4 mov eax, dword ptr [ebp-2C] :004011B4 8945B8 mov dword ptr [ebp-48], eax ; [ebp-48] = [ebp-2C]. 4 bytes are copied from the fscanfed buffer * Possible StringData Ref from Data Obj ->"loading stage2 license key(s)..." | :004011B7 C70424CC204000 mov dword ptr [esp], 004020CC * Reference To: cygwin1.printf, Ord:0569h | :004011BE E895030000 Call 00401558 ; printf("loading stage2 license key(s)..."); :004011C3 8B45D8 mov eax, dword ptr [ebp-28] :004011C6 8945BC mov dword ptr [ebp-44], eax :004011C9 8B45DC mov eax, dword ptr [ebp-24] :004011CC 8945C0 mov dword ptr [ebp-40], eax ; [ebp-40]=[ebp-24] and [ebp-44]=[ebp-28]. 8 bytes are copied for stage 2 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:0040116E(C) | :004011CF 837DF400 cmp dword ptr [ebp-0C], 00000000 ; if ([ebp-0C] != 0) { ... //Checks if crypt result was 0 :004011D3 7515 jne 004011EA * Possible StringData Ref from Data Obj ->"error: license.txt invalid" | :004011D5 C70424EF204000 mov dword ptr [esp], 004020EF * Reference To: cygwin1.printf, Ord:0569h | :004011DC E877030000 Call 00401558 ; printf("error: license.txt invalid") :004011E1 C745B0FFFFFFFF mov [ebp-50], FFFFFFFF :004011E8 EB1A jmp 00401204 ; jump to exit ... } * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:004011D3(C) | :004011EA 8D45B8 lea eax, dword ptr [ebp-48] :004011ED 89442404 mov dword ptr [esp+04], eax :004011F1 8B450C mov eax, dword ptr [ebp+0C] :004011F4 83C004 add eax, 00000004 :004011F7 8B00 mov eax, dword ptr [eax] :004011F9 890424 mov dword ptr [esp], eax :004011FC E808000000 call 00401209 ; Calls a procedure with params (&[ebp+0C]+4, &[ebp-48]) ; It is something like build_request(argv[1], &stage1_license)
Basically it reads a file
license.txt
and performs some checks before calling a function that I named build_request
. I will spare you the details about this function. All it does is build a HTTP GET request with the parameters passed to it and send it. The request and reply are printed to the terminal.First thing we notice is that the program should be called with 1 argument. The hostname to which the request will be sent. Obviously we will try with
www.canyoucrackit.co.uk
.Bypassing the password check
Second thing is that there is a kind of password check. The program reads it from the file and runs it through the libc function crypt. It is acrypt(password, salt)
in DES mode so only the first 8 bytes of the password and the 2 first bytes of the salt are used.There are two ways to pass the password check :
- Find the actual password with a password cracker. We should find a password that resolves to
hqDTK7b8K2rvw
whencrypt
ed withhq
as a salt. This can take some time. - Or since the original password is used nowhere else than for this check we can change the x86 assembler opcode to inverse the test and continue the normal execution path if the password is incorrect.
jne
which opcode is 0x75
and the binary for the inverse test je
is 0x74
. We are going to change:0040119C 7507 jne 004011A5into
:0040119C 7407 je 004011A5
We can change it with our disassembler if it can do so or simply with the shell.
$ xxd -p keygen.exe | sed s/7507/7407/ | xxd -p -r > keygen.exe
Moreover with this first analysis we can draw a draft of the stack for the
main()
function.ebp-50 0000 0000 ; Return code variable ebp-4C 0000 0000 ; filedescriptor for fopen("license.txt") ebp-48 ???? ???? ; Contains stage1 license key gotten from [ebp-2C] ebp-44 ???? ???? ; Contains stage2 license key part1 gotten from [ebp-28] ebp-40 ???? ???? ; Contains stage2 license key part2 gotten from [ebp-24] ebp-3C ???? ???? ebp-38 6763 6871 ; buffer of size 0x18 = 24, first 2 bytes : magic number ebp-34 ???? ???? ; password for which crypt(password,"hq") should be "hqDTK7b8K2rvw" ebp-30 ???? ???? ; next 4 bytes of password ebp-2C 0000 0000 ; Stage1 license key, 4 bytes ebp-28 0000 0000 ; Stage2 license key first 4 bytes ebp-24 0000 0000 ; Stage2 license key last 4 bytes ebp-20 ???? ???? ebp-1C ???? ???? ebp-18 ???? ???? ebp-14 ???? ???? ebp-10 ???? ???? ebp-C ???? ???? ; Variable that contains 1 is the password is correct ebp-8 ???? ???? ebp-4 ???? ???? ebp xxxx xxxx ; Saved EBP ebp+4 xxxx xxxx ; Saved EIP ebp+8 0000 0000 ; argc ebp+C 0000 0000 ; argv ebp+10 0000 0000 ; argv[1]
Forging the file license.txt
The next step is to create the filelicense.txt
. With our analysis we know that the file should contain the following.<4 bytes Magic Number><8 bytes password><4 bytes Stage1 license key><8 bytes Stage2 license key>
Magic Number
We know that it needs to begin withgchq
.$ printf "gchq" > license.txt
Password
It is followed by 8 bytes of password for which we can put anything we want since we have bypassed the check.$ printf "PASSWORD" >> license.txt
Stage 1 License Key
For that step the analysis didn't give us the value we should use. But remember that on part 2 of the challenge there was 4 bytes that were jumped over just at the beginning of the program.00000000 EB04 jmp short 0x6 00000002 AF scasd ;Unused byte 00000003 C2BFA3 ret 0xa3bf ;Unused bytes [...]Those 4 bytes are
AFC2 BFA3
. Let's use them for stage 1 license key.$ echo "AFC2 BFA3" | xxd -p -r >> license.txt
Stage 2 License Key
On part 2 there was 8 bytes of data inVM.cpu.firmware
that we could not make sense of.firmware: [0xd2ab1f05, 0xda13f110]Let's not forget about the endianness. It gives us stage 2 license key :
051F ABD2 10F1 13DA
.$ echo "051F ABD2 10F1 13DA" | xxd -p -r >> license.txt
Running the program
Time to run the program$ ./keygen.exe www.canyoucrackit.co.uk
keygen.exe loading stage1 license key(s)... loading stage2 license key(s)... request: GET /hqDTK7b8K2rvw/a3bfc2af/d2ab1f05/da13f110/key.txt HTTP/1.0 response: HTTP/1.1 404 Not Found Content-Type: text/html; charset=us-ascii Server: Microsoft-HTTPAPI/2.0 Date: Fri, 30 Dec 2011 14:48:44 GMT Connection: close Content-Length: 315 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN""http://www.w3.org/TR/html4/strict.dtd"> <HTML><HEAD><TITLE>Not Found</TITLE> <META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii"></HEAD> <BODY><h2>Not Found</h2> <hr><p>HTTP Error 404. The requested resource is not found.</p> </BODY></HTML>
We get a 404 response. We could think that our license file is wrong but it is not. Strangely the HTTP GET request sent by the program misses a very important part : the Host header.
In order for it to work it should be
GET /hqDTK7b8K2rvw/a3bfc2af/d2ab1f05/da13f110/key.txt HTTP/1.0 Host: www.canyoucrackit.co.uk
Let's try it away with netcat (install the package for cygwin if you don't have it).
$ printf "GET /hqDTK7b8K2rvw/a3bfc2af/d2ab1f05/da13f110/key.txt HTTP/1.0\r\n\ Host: www.canyoucrackit.co.uk\r\n\r\n" | nc www.canyoucrackit.co.uk 80
HTTP/1.1 200 OK Content-Type: text/plain Last-Modified: Wed, 26 Oct 2011 08:40:14 GMT Accept-Ranges: bytes ETag: "bc46bae1ba93cc1:0" Server: Microsoft-IIS/7.5 Date: Fri, 30 Dec 2011 15:06:15 GMT Connection: close Content-Length: 37 Pr0t3ct!on#cyber_security@12*12.2011+Here is the password ! (We could also have gotten it by visiting : http://www.canyoucrackit.co.uk/hqDTK7b8K2rvw/a3bfc2af/d2ab1f05/da13f110/key.txt)
Finally we just have to enter it to the front page of the website canyoucrackit. It leads us to this page http://www.canyoucrackit.co.uk/soyoudidit.asp#code
The button register your interest just sends us to gchq-careers.co.uk on a page where we can apply for a position of Cyber Specialist. This page is publicly accessible through their search engine. A bit disappointing.
Final Note
I didn't crack the password myself but for anyone interested the crypted password iscyberwin
. It is easy to check.$ crypt hq cyberwin
hqDTK7b8K2rvw