No cON Name CTF Quals 2014 - cannaBINoid (300pts) writeup

The challenge description was: Get the key. The flag is: "NcN_" + sha1sum(key)

Let's look at the file:

mrt:~/ctf$ file cannabinoid
cannabinoid: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV),
dynamically linked (uses shared libs),
for GNU/Linux 2.6.32,
BuildID[sha1]=0x66d4f3074c860f8e84c7cd5c036d327a00f84bd0,
stripped

mrt:~/ctf$ strings cannabinoid
/lib/ld-linux.so.2
libc.so.6
_IO_stdin_used
puts
stdin
mmap
fread
open
__libc_start_main
__gmon_start__
GLIBC_2.0
PTRh
[^_]
;*2$"4

mrt:~/ctf$ ./cannabinoid
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

mrt:~/ctf$

It's a 32 bit binary file asking for some input (a lot of it) from the user and silently failing if the key is invalid. We know the binary is probably using fread to get user input from the strings output. Let's dump the binary and see what is going on:

mrt:~/ctf$ objdump -d -M intel cannabinoid > cannabinoid.dmp

Let's look for any mention of fread in the dump:

...
187 8048539: e8 12 fe ff ff call 8048350 <fread@plt> ; our call to fread
188 804853e: 83 c4 10 add esp,0x10
189 8048541: 3d 80 00 00 00 cmp eax,0x80 ; is it 0x80 bytes long?
190 8048546: 74 0a je 8048552 <__libc_start_main@plt+0x1b2>
191 8048548: b8 01 00 00 00 mov eax,0x1
192 804854d: e9 ac 00 00 00 jmp 80485fe <__libc_start_main@plt+0x25e>
193 8048552: 8b 43 04 mov eax,DWORD PTR [ebx+0x4] ; get here if 0x80
194 8048555: 8b 00 mov eax,DWORD PTR [eax]
195 8048557: 83 ec 08 sub esp,0x8
196 804855a: 6a 00 push 0x0
197 804855c: 50 push eax
198 804855d: e8 1e fe ff ff call 8048380 <open@plt> ; open file?
...

There we go, we can see a call to fread and then a check if eax is equal to 0x80 (128). Remember we had to enter a lot of input to get the program to silently quit? This is our input size: 128 bytes. If the input is 0x80 bytes it then jumps to 0x8048552 and then use open to read a file apparently. Let's just snoop around in the dump if we notice something interesting before going into gdb. A couple lines before the call to fread I noticed the program was putting some hex values into memory, it was too short to be our valid key but it could still be a lead to what we need to find:

...
156 80484ab: 8d 4c 24 04 lea ecx,[esp+0x4]
157 80484af: 83 e4 f0 and esp,0xfffffff0
158 80484b2: ff 71 fc push DWORD PTR [ecx-0x4]
159 80484b5: 55 push ebp
160 80484b6: 89 e5 mov ebp,esp
161 80484b8: 53 push ebx
162 80484b9: 51 push ecx
163 80484ba: 81 ec a0 00 00 00 sub esp,0xa0
164 80484c0: 89 cb mov ebx,ecx
165 80484c2: c6 85 5c ff ff ff 59 mov BYTE PTR [ebp-0xa4],0x59 ; starting here
166 80484c9: c6 85 5d ff ff ff 6f mov BYTE PTR [ebp-0xa3],0x6f ;
167 80484d0: c6 85 5e ff ff ff 75 mov BYTE PTR [ebp-0xa2],0x75 ;
168 80484d7: c6 85 5f ff ff ff 20 mov BYTE PTR [ebp-0xa1],0x20 ;
169 80484de: c6 85 60 ff ff ff 67 mov BYTE PTR [ebp-0xa0],0x67 ;
170 80484e5: c6 85 61 ff ff ff 6f mov BYTE PTR [ebp-0x9f],0x6f ;
171 80484ec: c6 85 62 ff ff ff 74 mov BYTE PTR [ebp-0x9e],0x74 ;
172 80484f3: c6 85 63 ff ff ff 20 mov BYTE PTR [ebp-0x9d],0x20 ;
173 80484fa: c6 85 64 ff ff ff 69 mov BYTE PTR [ebp-0x9c],0x69 ;
174 8048501: c6 85 65 ff ff ff 74 mov BYTE PTR [ebp-0x9b],0x74 ;
175 8048508: c6 85 66 ff ff ff 21 mov BYTE PTR [ebp-0x9a],0x21 ;
176 804850f: c6 85 67 ff ff ff 00 mov BYTE PTR [ebp-0x99],0x0 ; null terminated
177 8048516: 83 3b 01 cmp DWORD PTR [ebx],0x1
178 8048519: 74 0a je 8048525 <__libc_start_main@plt+0x185>

All of these hex values are valid characters:

mrt:~/ctf$ echo -e "\x59\x6f\x75\x20\x67\x6f\x74\x20\x69\x74\x21"
You got it!

Nice! So now we know that we have a confirmation message when the key is valid, and puts is called to output it. Just by curiosity let's check one last time the dump before going into gdb and find the call to puts.

...                                                                         ; loop starts here
222 80485b3: 8b 55 f0 mov edx,DWORD PTR [ebp-0x10] ; edx = counter (ebp-0x10)
223 80485b6: 8b 45 e8 mov eax,DWORD PTR [ebp-0x18] ; eax = good key address
224 80485b9: 01 d0 add eax,edx ; next byte (eax+edx)
225 80485bb: 0f b6 10 movzx edx,BYTE PTR [eax] ; store byte in edx
226 80485be: 8d 8d 68 ff ff ff lea ecx,[ebp-0x98] ; ecx = our input address
227 80485c4: 8b 45 f0 mov eax,DWORD PTR [ebp-0x10] ; eax = counter (ebp-0x10)
228 80485c7: 01 c8 add eax,ecx ; next byte (eax+ecx)
229 80485c9: 0f b6 00 movzx eax,BYTE PTR [eax] ; store byte in eax
230 80485cc: 38 c2 cmp dl,al ; compare values
231 80485ce: 0f 94 c0 sete al
232 80485d1: 0f b6 c0 movzx eax,al
233 80485d4: 21 45 f4 and DWORD PTR [ebp-0xc],eax
234 80485d7: 83 45 f0 01 add DWORD PTR [ebp-0x10],0x1 ; next byte
235 80485db: 83 7d f0 7f cmp DWORD PTR [ebp-0x10],0x7f ; 0x7f bytes long?
236 80485df: 7e d2 jle 80485b3 <__libc_start_main@plt+0x213>
237 80485e1: 83 7d f4 00 cmp DWORD PTR [ebp-0xc],0x0 ; null terminated?
238 80485e5: 74 12 je 80485f9 <__libc_start_main@plt+0x259>
239 80485e7: 83 ec 0c sub esp,0xc
240 80485ea: 8d 85 5c ff ff ff lea eax,[ebp-0xa4]
241 80485f0: 50 push eax
242 80485f1: e8 6a fd ff ff call 8048360 <puts@plt> ; outputs message
243 80485f6: 83 c4 10 add esp,0x10
244 80485f9: b8 00 00 00 00 mov eax,0x0
245 80485fe: 8d 65 f8 lea esp,[ebp-0x8]
246 8048601: 59 pop ecx
247 8048602: 5b pop ebx
248 8048603: 5d pop ebp
249 8048604: 8d 61 fc lea esp,[ecx-0x4]
250 8048607: c3 ret

So before calling fputs and outputting the You got it! message we have a couple calls to compare values, then checking if it's 0x7f bytes long (remember our key should be 0x80 bytes long) which makes this part even more interesting. Let's start gdb, run the program with 0x80 bytes long input, and put a breakpoint on address 0x80485cc where values are being compared 0x7f times.

mrt:~/ctf$ python -c 'print "A"*0x80' > cankey

mrt:~/ctf$ gdb cannabinoid
Reading symbols from /home/mrt/ctf/cannabinoid...(no debugging symbols found)...done.
gdb$ b * 0x80485cc
Breakpoint 1 at 0x80485cc
gdb$ r < cankey
--------------------------------------------------------------------------[regs]
EAX: 0x00000041 EBX: 0xFFFFD7A0 ECX: 0xFFFFD6F0 EDX: 0x0000007F o d I t S z a P c
ESI: 0x00000000 EDI: 0x00000000 EBP: 0xFFFFD788 ESP: 0xFFFFD6E0 EIP: 0x080485CC
CS: 0023 DS: 002B ES: 002B FS: 0000 GS: 0063 SS: 002B
[0x002B:0xFFFFD6E0]------------------------------------------------------[stack]
0xFFFFD730 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD720 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD710 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD700 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD6F0 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD6E0 : E8 62 FD F7 59 6F 75 20 - 67 6F 74 20 69 74 21 00 .b..You got it!.
[0x002B:0xFFFFD6E0]-------------------------------------------------------[data]
0xFFFFD6E0 : E8 62 FD F7 59 6F 75 20 - 67 6F 74 20 69 74 21 00 .b..You got it!.
0xFFFFD6F0 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD700 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD710 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD720 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD730 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD740 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD750 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
--------------------------------------------------------------------------[code]
=> 0x80485cc: cmp dl,al
0x80485ce: sete al
0x80485d1: movzx eax,al
0x80485d4: and DWORD PTR [ebp-0xc],eax
0x80485d7: add DWORD PTR [ebp-0x10],0x1
0x80485db: cmp DWORD PTR [ebp-0x10],0x7f
0x80485df: jle 0x80485b3
0x80485e1: cmp DWORD PTR [ebp-0xc],0x0
--------------------------------------------------------------------------------

Breakpoint 1, 0x080485cc in ?? ()
gdb$

Here we can see the message stored in case our key is valid and our input, let's check the content of dl and al which are compared:

gdb$ p/x $dl
$1 = 0x7f
gdb$ p/x $al
$2 = 0x41

The register dl contains the byte we should have input (0x7f) and al contains our actual input (0x41) which is the hex value of the letter A. From now on it's pretty easy, let's just dump 0x80 bytes when the loop starts at the address stored in eax and we should have our key. Let's make gdb output all the values for us by putting a breakpoint where the loop starts and running the binary again:

gdb$ b * 0x80485cc
Breakpoint 1 at 0x80485cc
gdb$ b * 0x80485b9
Breakpoint 2 at 0x80485b9
gdb$ r < cankey
--------------------------------------------------------------------------[regs]
EAX: 0xF7FDC000 EBX: 0xFFFFD7A0 ECX: 0x00000080 EDX: 0x00000000 o d I t S z A P C
ESI: 0x00000000 EDI: 0x00000000 EBP: 0xFFFFD788 ESP: 0xFFFFD6E0 EIP: 0x080485B9
CS: 0023 DS: 002B ES: 002B FS: 0000 GS: 0063 SS: 002B
[0x002B:0xFFFFD6E0]------------------------------------------------------[stack]
0xFFFFD730 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD720 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD710 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD700 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD6F0 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD6E0 : E8 62 FD F7 59 6F 75 20 - 67 6F 74 20 69 74 21 00 .b..You got it!.
[0x002B:0xFFFFD6E0]-------------------------------------------------------[data]
0xFFFFD6E0 : E8 62 FD F7 59 6F 75 20 - 67 6F 74 20 69 74 21 00 .b..You got it!.
0xFFFFD6F0 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD700 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD710 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD720 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD730 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD740 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0xFFFFD750 : 41 41 41 41 41 41 41 41 - 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
--------------------------------------------------------------------------[code]
=> 0x80485b9: add eax,edx
0x80485bb: movzx edx,BYTE PTR [eax]
0x80485be: lea ecx,[ebp-0x98]
0x80485c4: mov eax,DWORD PTR [ebp-0x10]
0x80485c7: add eax,ecx
0x80485c9: movzx eax,BYTE PTR [eax]
0x80485cc: cmp dl,al
0x80485ce: sete al
--------------------------------------------------------------------------------

Breakpoint 2, 0x080485b9 in ?? ()

And now let's check the 128 (0x80) bytes stored at the address stored in eax :

gdb$ x/128bx $eax
0xf7fdc000: 0x7f 0x45 0x4c 0x46 0x01 0x01 0x01 0x00
0xf7fdc008: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xf7fdc010: 0x02 0x00 0x03 0x00 0x01 0x00 0x00 0x00
0xf7fdc018: 0xb0 0x83 0x04 0x08 0x34 0x00 0x00 0x00
0xf7fdc020: 0xe8 0x09 0x00 0x00 0x00 0x00 0x00 0x00
0xf7fdc028: 0x34 0x00 0x20 0x00 0x08 0x00 0x28 0x00
0xf7fdc030: 0x1c 0x00 0x1b 0x00 0x06 0x00 0x00 0x00
0xf7fdc038: 0x34 0x00 0x00 0x00 0x34 0x80 0x04 0x08
0xf7fdc040: 0x34 0x80 0x04 0x08 0x00 0x01 0x00 0x00
0xf7fdc048: 0x00 0x01 0x00 0x00 0x05 0x00 0x00 0x00
0xf7fdc050: 0x04 0x00 0x00 0x00 0x03 0x00 0x00 0x00
0xf7fdc058: 0x34 0x01 0x00 0x00 0x34 0x81 0x04 0x08
0xf7fdc060: 0x34 0x81 0x04 0x08 0x13 0x00 0x00 0x00
0xf7fdc068: 0x13 0x00 0x00 0x00 0x04 0x00 0x00 0x00
0xf7fdc070: 0x01 0x00 0x00 0x00 0x01 0x00 0x00 0x00
0xf7fdc078: 0x00 0x00 0x00 0x00 0x00 0x80 0x04 0x08

As you can see if starts with 0x7f, the first value compared during the loop with our own input we noticed earlier, let's dump all these bytes into a file and try feeding the binary with it to see if the key is correct.

gdb$ dump binary memory key.bin 0xf7fdc000 0xf7fdc080
gdb$ q

mrt:~/ctf$ xxd key.bin
0000000: 7f45 4c46 0101 0100 0000 0000 0000 0000 .ELF............
0000010: 0200 0300 0100 0000 b083 0408 3400 0000 ............4...
0000020: e809 0000 0000 0000 3400 2000 0800 2800 ........4. ...(.
0000030: 1c00 1b00 0600 0000 3400 0000 3480 0408 ........4...4...
0000040: 3480 0408 0001 0000 0001 0000 0500 0000 4...............
0000050: 0400 0000 0300 0000 3401 0000 3481 0408 ........4...4...
0000060: 3481 0408 1300 0000 1300 0000 0400 0000 4...............
0000070: 0100 0000 0100 0000 0000 0000 0080 0408 ................

We have our key now, let's try it with the binary:

mrt:~/ctf$ cat key.bin | ./cannabinoid
You got it!

Interesting, the key is an ELF binary. But wait.. could it be.. the first 128 bytes of the binary itself?

mrt:~/ctf$ xxd -l 128 cannabinoid
0000000: 7f45 4c46 0101 0100 0000 0000 0000 0000 .ELF............
0000010: 0200 0300 0100 0000 b083 0408 3400 0000 ............4...
0000020: e809 0000 0000 0000 3400 2000 0800 2800 ........4. ...(.
0000030: 1c00 1b00 0600 0000 3400 0000 3480 0408 ........4...4...
0000040: 3480 0408 0001 0000 0001 0000 0500 0000 4...............
0000050: 0400 0000 0300 0000 3401 0000 3481 0408 ........4...4...
0000060: 3481 0408 1300 0000 1300 0000 0400 0000 4...............
0000070: 0100 0000 0100 0000 0000 0000 0080 0408 ................

mrt:~/ctf$ cat cannabinoid | ./cannabinoid
You got it!

Facepalm self.. If you get back to the beginning of this writeup, you would notice that the binary makes some calls to mmap and open, I didn't pay much attention but what it actually does is opening itself and mapping the file into memory to compare the 128 bytes with the user input.

mrt:~/ctf$ apropos mmap
mmap (2) - map or unmap files or devices into memory

Let's get our flag:

mrt:~/ctf$ cat key.bin | sha1sum | awk '{print "NcN_"$1}'
NcN_effaf80a641b28a8d8a750b99ef740593bb3dcbd

We got our flag:

NcN_effaf80a641b28a8d8a750b99ef740593bb3dcbd