GCHQ Challenge Part 1 - http://www.canyoucrackit.co.uk/
Hello,
As I have seen that many people already posted their solutions … I do not see the point of keeping mine :). Here it is.
Today I stumbled upon a challenge that seems to come from GCHQ itself. GCHQ is basically part of the UK’s Secret Service.
An article describing a bit the recruitment campaign: GCHQ challenges codebreakers via social networks
Anyway, the challenge is located here: Can You Crack It!
The challenge is divided in 3 parts, the first part of which we see on the front page of the website.
Let’s break it!
The challenge
We have a picture with hex digits:
From there, I’ve got 2 theories of what that is:
- raw data
- machine code
First of all, we need to get those “hex digits” down to binary form and hell yeah … we’re lazy:
1
$ gocr cyber.png
You get most of the digits but you have to fix it to get this:
1
2
3
4
5
6
7
8
9
10
eb 04 af c2 bf a3 81 ec OO 01 OO OO 31 c9 88 Oc
Oc fe c1 75 f9 31 cO ba ef be ad de 02 04 Oc OO
dO c1 ca 08 8a 1c Oc 8a 3c 04 88 1c 04 88 3c Oc
fe c1 75 e8 e9 5c OO OO OO 89 e3 81 c3 04 OO OO
OO 5c 58 3d 41 41 41 41 75 43 58 3d 42 42 42 42
75 3b 5a 89 d1 89 e6 89 df 29 cf f3 a4 89 de 89
d1 89 df 29 cf 31 cO 31 db 31 d2 fe cO 02 1c 06
8a 14 06 8a 34 1e 88 34 06 88 14 1e OO f2 30 f6
8a 1c 16 8a 17 30 da 88 17 47 49 75 de 31 db 89
d8 fe cO cd 80 9O 9O e8 9d ff ff ff 41 41 41 41
Okay, let’s check the raw data we have:
1
2
3
4
5
6
7
8
9
10
11
$ hexdump -C gchq-part1.bin
00000000 eb 04 af c2 bf a3 81 ec 00 01 00 00 31 c9 88 0c |............1...|
00000010 0c fe c1 75 f9 31 c0 ba ef be ad de 02 04 0c 00 |...u.1..........|
00000020 d0 c1 ca 08 8a 1c 0c 8a 3c 04 88 1c 04 88 3c 0c |........<.....<.|
00000030 fe c1 75 e8 e9 5c 00 00 00 89 e3 81 c3 04 00 00 |..u..\..........|
00000040 00 5c 58 3d 41 41 41 41 75 43 58 3d 42 42 42 42 |.\X=AAAAuCX=BBBB|
00000050 75 3b 5a 89 d1 89 e6 89 df 29 cf f3 a4 89 de 89 |u;Z......)......|
00000060 d1 89 df 29 cf 31 c0 31 db 31 d2 fe c0 02 1c 06 |...).1.1.1......|
00000070 8a 14 06 8a 34 1e 88 34 06 88 14 1e 00 f2 30 f6 |....4..4......0.|
00000080 8a 1c 16 8a 17 30 da 88 17 47 49 75 de 31 db 89 |.....0...GIu.1..|
00000090 d8 fe c0 cd 80 90 90 e8 9d ff ff ff 41 41 41 41 |............AAAA|
I do not recognize any particular magic header value (no MZ or ELF or any executable related. Let’s go on our second hypothesis.
Is it machine code?
The first thing that set up red flags about machine code is the first byte: 0xeb! 0xeb for anyone used to shellcode is the opcode corresponding to an inconditionnal jmp on x86 processors.
I believe there must be some construct like this (as usual) or not too far from it:
1
2
3
4
5
6
7
8
9
10
jmp savepc
getpc:
pop pc
jmp payload
savepc:
call getpc
payload:
For disassembling the file, I used ndisasm and cleaned a bit with some custom tools and vim.
Let’s see the reconstructed code:
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
bits 32
section .text
global main
main:
jmp short begin
key0: dd 0xa3bfc2af
; let's make some space for our buffer!
begin:
sub esp, 0x100
; we init our buffer with value from 0 to 255
xor ecx, ecx
init_array:
mov [esp+ecx], cl
inc cl
jnz init_array
; shuffle values in the array
xor eax, eax
mov edx, 0xdeadbeef
shuffle:
add al, [esp+ecx] ; al += array[ecx]
add al, dl ; index = al + dl
ror edx, 0x8 ; every 4 rotations we get our original value ;)
; we swap values
mov bl, [esp+ecx] ; bl = array[ecx]
mov bh, [esp+eax] ; bh = array[ecx]
mov [esp+eax], bl ; array[eax] = bl
mov [esp+ecx], bh ; array[ecx] = bh
inc cl ; go forward in our array
jnz shuffle
jmp dword save_pc
get_encoded:
mov ebx, esp ; pointer ebx to ret address on stack
add ebx, strict dword 0x4 ; address of array (ignoring the ret address on the stack ;))
pop esp ; esp = program counter (point after the call to get_encoded)
pop eax ; first 4 bytes after the call
cmp eax, 0x41414141
jnz exit
pop eax ; next 4 bytes after the call
cmp eax, 0x42424242
jnz exit
; copy message to buffer in stack
pop edx ; get length of message
mov ecx, edx ; ecx = len(msg)
mov esi, esp ; esi = &msg
mov edi, ebx ; edi = address of array
sub edi, ecx ; edi = ebx - len(msg) = start of dest area
rep movsb ; copying the message to the stack (writing over array)
; init for decoding encoded message
mov esi, ebx ; esi = &buffer
mov ecx, edx ; ecx = len(msg)
mov edi, ebx ; edi = &buffer
sub edi, ecx ; edi = ebx - len(msg) = start of dest area
xor eax, eax
xor ebx, ebx
xor edx, edx
; loop for decoding the secret message
decode:
inc al
add bl, [esi+eax] ; get one byte of encoded message
mov dl, [esi+eax] ; get one byte of encoded message
mov dh, [esi+ebx] ; get one byte of encoded message
; swap values back
mov [esi+eax], dh
mov [esi+ebx], dl
add dl, dh ; get index
; decode byte
xor dh, dh
mov bl, [esi+edx] ; get key
mov dl, [edi] ; get encoded byte
xor dl, bl ; decode byte
; save byte
mov [edi], dl
; loop
inc edi
dec ecx
jnz decode
exit:
xor ebx, ebx
mov eax, ebx
inc al
int 0x80
save_pc:
nop
nop
call dword get_encoded
junk1: dd 0x41414141
The first part (init_array and shuffle) looks like some RC4 variant. Then we decode it using our RC4 variant … but wait … there is data missing! Where is the encoded message?
If you look at the beginning of the PNG file you can see an encoded base64 string:
1
2
3
4
5
6
7
8
9
10
11
12
13
00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR|
00000010 00 00 02 e4 00 00 01 04 08 02 00 00 00 ef 6a b6 |..............j.|
00000020 2d 00 00 00 01 73 52 47 42 00 ae ce 1c e9 00 00 |-....sRGB.......|
00000030 00 09 70 48 59 73 00 00 0b 13 00 00 0b 13 01 00 |..pHYs..........|
00000040 9a 9c 18 00 00 00 07 74 49 4d 45 07 db 08 05 0e |.......tIME.....|
00000050 12 33 7e 39 c1 70 00 00 00 5d 69 54 58 74 43 6f |.3~9.p...]iTXtCo|
00000060 6d 6d 65 6e 74 00 00 00 00 00 51 6b 4a 43 51 6a |mment.....QkJCQj|
00000070 49 41 41 41 43 52 32 50 46 74 63 43 41 36 71 32 |IAAACR2PFtcCA6q2|
00000080 65 61 43 38 53 52 2b 38 64 6d 44 2f 7a 4e 7a 4c |eaC8SR+8dmD/zNzL|
00000090 51 43 2b 74 64 33 74 46 51 34 71 78 38 4f 34 34 |QC+td3tFQ4qx8O44|
000000a0 37 54 44 65 75 5a 77 35 50 2b 30 53 73 62 45 63 |7TDeuZw5P+0SsbEc|
000000b0 59 52 0a 37 38 6a 4b 4c 77 3d 3d 32 ca be f1 00 |YR.78jKLw==2....|
000000c0 00 20 00 49 44 41 54 78 da ec bd 79 74 1c d5 b5 |. .IDATx...yt...|
The base64 string “QkJCQjIAAACR2PFtcCA6q2eaC8SR+8dmD/zNzLQC+td3tFQ4qx8O447TDeuZw5P+0SsbEcYR78jKLw=” decodes to the following:
1
2
3
junk2: dd 0x42424242
msg_length: dd 0x00000032
msg_encoded: db `\x91\xd8\xf1\x6d\x70\x20\x3a\xab\x67\x9a\x0b\xc4\x91\xfb\xc7\x66\x0f\xfc\xcd\xcc\xb4\x02\xfa\xd7\x77\xb4\x54\x38\xab\x1f\x0e\xe3\x8e\xd3\x0d\xeb\x99\xc3\x93\xfe\xd1\x2b\x1b\x11\xc6\x11\xef\xc8\xca\x2f`
It is a NASM source which correspond bit to bit to the original gchq.bin ;).:
1
$ nasm gchq.asm
So the first challenge basically consist in executing the code and dumping the decoded string.
Let’s dump the decoded string!
For this part, I did no want to patch the binary code as I would deem it inelegant. Instead, I chose to write a “launcher” and ptrace our gchq code :).
Here it is:
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// @author m_101
// @year 2011
// @desc Program for launching GCHQ code
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
// for memalign, mprotect
#include <sys/mman.h>
#include <errno.h>
#include <malloc.h>
#include <signal.h>
// ptrace
#include <sys/ptrace.h>
// for registers
#include <sys/user.h>
#define __NR_exit 1
#define MSG_LENGTH 0x32
// launch a file
int launch_file (char *filename) {
//
char *mem;
void (*func)();
// file related
FILE *fp;
int szFile;
if (!filename)
return -1;
// open file
fp = fopen(filename, "r");
if (!fp) {
fprintf(stderr, "error: Failed opening (r): %s\n", filename);
exit(1);
}
// get file size
fseek(fp, 0, SEEK_END);
szFile = ftell(fp);
fseek(fp, 0, SEEK_SET);
printf("[+] File size: %d\n", szFile);
// alloc aligned memory for file
mem = memalign(PAGE_SIZE, szFile * sizeof(*mem));
if (!mem) {
printf("[-] error: %s\n", strerror(errno));
return 1;
}
memset(mem, 0, szFile * sizeof(*mem));
// fill mem
fread(mem, sizeof(*mem), szFile, fp);
// set permissions
if (mprotect(mem, szFile * sizeof(*mem), PROT_READ | PROT_WRITE | PROT_EXEC)) {
printf("[-] error: %s\n", strerror(errno));
return 1;
}
// close file
fclose(fp);
// execute code
printf("[+] Executing code at address %p\n", mem);
func = (void *) mem;
func();
return 0;
}
void regs_show (struct user_regs_struct *regs) {
if (!regs)
return;
printf("eax: 0x%08lx\n", regs->orig_eax);
printf("ebx: 0x%08lx\n", regs->ebx);
printf("ecx: 0x%08lx\n", regs->ecx);
printf("edx: 0x%08lx\n", regs->edx);
printf("esi: 0x%08lx\n", regs->esi);
printf("edi: 0x%08lx\n", regs->edi);
printf("ebp: 0x%08lx\n", regs->ebp);
printf("esp: 0x%08lx\n", regs->esp);
printf("eip: 0x%08lx\n", regs->eip);
printf("eflags: 0x%08lx\n", regs->eflags);
printf("cs: 0x%08lx\n", regs->xcs);
printf("ds: 0x%08lx\n", regs->xds);
printf("es: 0x%08lx\n", regs->xes);
printf("fs: 0x%08lx\n", regs->xfs);
printf("gs: 0x%08lx\n", regs->xgs);
printf("ss: 0x%08lx\n", regs->xss);
}
int main (int argc, char *argv[]) {
int retcode;
pid_t cpid;
struct user_regs_struct regs = {0};
int cstatus;
// for ptrace
int syscall;
// dump memory of child process
char decoded[MSG_LENGTH * 2] = {0};
uint32_t word;
uint32_t addr;
int offset;
// check number of arguments
if (argc < 2) {
printf("Usage: %s filename\n", argv[0]);
return -1;
}
cpid = fork();
// child
if (cpid == 0) {
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
raise(SIGTRAP);
launch_file(argv[1]);
}
// parent
else if (cpid > 0) {
// wait for child
waitpid(cpid, &cstatus, 0);
// trace the process to arrive to the exit syscall
do {
// get at syscall entry
ptrace(PTRACE_SYSCALL, cpid, NULL, NULL);
waitpid(cpid, &cstatus, 0);
// get registers of child process
ptrace(PTRACE_GETREGS, cpid, NULL, ®s);
syscall = regs.orig_eax;
// we are at exit syscall
// then we finish the loop and do more processing
if (syscall != __NR_exit) {
// wait for syscall to complete
// and avoid exiting process
ptrace(PTRACE_SYSCALL, cpid, NULL, NULL);
waitpid(cpid, &cstatus, 0);
}
} while (syscall != __NR_exit);
// get registers of child process
ptrace(PTRACE_GETREGS, cpid, NULL, ®s);
printf("== Registers\n");
regs_show(®s);
// dump decoded string
addr = regs.esi - MSG_LENGTH;
for (offset = 0; offset < MSG_LENGTH; offset += 4) {
word = ptrace(PTRACE_PEEKDATA, cpid, addr + offset, NULL);
// printf("word: 0x%08x\n", word);
*((uint32_t *)(decoded+offset)) = word;
}
printf("\n[+] Decoded string: '%s'\n", decoded);
// continue process (so it exits)
ptrace(PTRACE_CONT, cpid, NULL, NULL);
}
// error
else {
printf("Failed fork\n");
}
return 0;
}
Now compile it using this command line:
1
$ gcc -m32 -g -o launcher launcher.c
You can now launch it:
1
2
3
4
$ ./launcher ./gchq
[+] File size: 218
[+] Executing code at address 0x832f000
[+] Decoded string: 'GET /15b436de1f9107f3778aad525e5d0b20.js HTTP/1.1'
Pawned :).
Conclusion
Here we saw the first challenge of the GCHQ recruitment campaign, not too hard or not to bad for a first level :).
To be continued on the next level ;),
m_101