Blind Return Oriented Programming - BROP
This technique was found by Andrea BITTAU from Stanford in 2014.
Blind Return Oriented Programming (BROP) is basically a ROP attack where the attacker doesn't had access to the binary.
How it works ?
In order to use the BROP attacks two requirements are needed :
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 by leaking enough gadgets to perform the write system call, after which the binary is transferred from memory to the attacker's socket. Following that, a standard ROP attack can be carried out.
In short, BROP attack has the following phases:
****Stack reading: read the stack to leak canaries and a return address to defeat ASLR.
Blind ROP: find enough gadgets to invoke write and control its arguments.
Build the exploit: dump enough of the binary to find enough gadgets and then doing a standard ROP exploit
If the server is compiled with the PIE flag or if there is a stack canary, the server must be a forking daemon.
Needed gadgets
Stop gadget
Because of "blind" exploit, something is needed to highlight when something useful was found. Here take place the stop gadget.
Essentially, this gadget does not cause the program to crash, and either prints out something or stays in an infinite loop.
From the perspective of the testing, a service crashing or just finishing execution looks the same. By setting the stop gadget as the return address of an address being tested, it can be determined whether the tested instructions actually caused the service to crash or if they simply finished executing.
To find a pop X; ret;
gadget, the stop gadgets must be placed one slot further to handle the pop
instruction:
BROP gadget
As the end of the __libc_csu_init
function, there is a gadget that pops 6 registers from the stack. (Ret2csu)
To retrieve this gadget, 2 request are executed for each tested address :
The first request is :
b'A' * offset + canary + rbp + ADDR + 0xdead * 6 + STOP
If the binary doesn't crash the tested address is maybe the BROP gadget, because it will pop the 6 0xdead
addresses and return into the STOP
gadget.
But there is a chance that the tested address is a STOP gadget
itself, so a second request had to be executed, and it must crash to confirm that the tested address isn't a stop gadget
The second request is :
b'A' * offset + canary + rbp + ADDR
PLT address
Retrieving the base address of the PLT permit to make it easier to found the followed gadgets by using the ret2dl_resolve technique.
In order to retrieve a PLT address, 2 requests have to be executed to confirm that the tested address is a PLT address:
b'A' * offset + canary + rbp + ADDR + STOP
--> will no crashb'A' * offset + canary + rbp + (ADDR + 0x6) + STOP
--> will no crash
To be certain that it's a PLT address, 2 more requests can be done to try if the next possible PLT address (+0x10
) it's also a valid PLT address
A PLT address is always a 0x10 multiple, so it's not necessary to try each possible address, just try every 0x10
addresses.
STRCMP
Most of the time, there is no gadget pop RDX; ret;
However, the strcmp
function will set RDX
to 0
or a value greater than 0
depends on the strings provided as arguments.
This gadget can be needed to retrieve and use the Write gadget (basicaly if the function used in the binary is write()).
The PLT entries can be identified by exercising each entry with different arguments and seeing how the function performs. The first two arguments can be controlled thanks to the BROP gadget.
strcmp
has the following behavior and signature, where “bad” is an invalid memory location (e.g., 0x0) and “readable” is a readable pointer (e.g., an address in .text : RIP):
strcmp(bad, readable): crash
strcmp(readable, bad): crash
strcmp(readable, readable): no crash
To retrieve **** STRCMP gadget
3 requests are executed for each tested entry (based on ret2dl_resolve exploit):
b'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0x300) + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
--> Will crashb'A' * offset + canary + rbp + (BROP + 0x9) + p64(0x300) + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
--> Will crashb'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP
--> Will no crash
BROP + 0x7 point to
pop RSI; pop R15; ret;
BROP + 0x9 point to
pop RDI; ret;
PLT + 0xb point to a call to dl_resolve.
Write / Puts
A gadget that permit to write to the output is needed in order to leak datas.
As for strcmp
this function will be retrieve using the ret2dl_resolve technique.
There is several function that permit to write to the output and the signature may differ following which function is used.
Also, the used file descriptor had to be retrieve.
There is the 3 common signatures :
puts(data)
dprintf(fd, data)
write(fd, data, len(data)
For each entry, it's necessary to try several file descriptor and see if the binary respond more data than before.
To retrieve the write gadget
3 requests are executed for each possible file descriptor for each possible entry :
b'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + p64(0) + p64(0) + (PLT + 0xb) + p64(ENTRY) + STOP
--> If there is data printed, then puts was foundb'A' * offset + canary + rbp + (BROP + 0x9) + FD + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb) + p64(ENTRY) + STOP
--> If there is data printed, then dprintf was foundb'A' * offset + canary + rbp + (BROP + 0x9) + RIP + (BROP + 0x7) + (RIP + 0x1) + p64(0x0) + (PLT + 0xb ) + p64(STRCMP ENTRY) + (BROP + 0x9) + FD + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb) + p64(ENTRY) + STOP
--> If there is data printed, then write was found
Exploiting
At this point the attacker can leak arbitrary data from the binary. Then it's possible to try each addresses until the ELF magic bytes are printed ( \xb1ELF\x02\x01
). Here is the binary base address.
Finally, it's possible to print each addresses from this point and if there is data printed, then add it as leaked data, if not, in fact there is a null byte ( 0x00
) at the targeted address, so add it as leaked data and proceed with the next address.
Resources
Last updated