Friday, December 30, 2011

Can you crack it ? A quite detailed solution (Part 3)

This is Part 3/3. You can find the previous parts here : Part 1, Part 2

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 are moved 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 symbols
This 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 a crypt(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 :
  1. Find the actual password with a password cracker. We should find a password that resolves to hqDTK7b8K2rvw when crypted with hq as a salt. This can take some time.
  2. 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.
We are going to choose the second option. This is the fastest way. We need to inverse the test done in the highlighted line in the code above. The original test is jne which opcode is 0x75 and the binary for the inverse test je is 0x74. We are going to change
:0040119C 7507                    jne 004011A5
into
: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 file license.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 with gchq.
$ 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 in VM.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 is cyberwin. It is easy to check.
$ crypt hq cyberwin
hqDTK7b8K2rvw

No comments:

Post a Comment