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 :

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:

  1. ****Stack reading: read the stack to leak canaries and a return address to defeat ASLR.

  2. Blind ROP: find enough gadgets to invoke write and control its arguments.

  3. 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.

   Buffer   | return address | stack...
0x4141...41 | 0x40000000     | 0xdead 0xdead 0xdead ... | --> Crash
0x4141...41 | 0x40000000     | 0xstop 0xdead 0xdead ... | --> No crash
                   |              |
                   |              V
                   V            sleep
          Tested gadget address
                   

To find a pop X; ret; gadget, the stop gadgets must be placed one slot further to handle the pop instruction:

   Buffer   | return address | stack...
0x4141...41 | 0x40000000     | 0xdead 0xdead 0xdead ... | --> Crash
0x4141...41 | 0x40000000     | 0xdead 0xstop 0xdead ... | --> No crash
                   |                     |
                   |                     V
                   V                   sleep
          Tested gadget address
                   

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

   Buffer   | return address     | stack...
0x4141...41 | 0x???????????????? | 0xdead *6     0xSTOP ... | --> Will no Crash
0x4141...41 | 0x???????????????? | 0xdead 0xdead 0xdead ... | --> Will crash
                   |              
                   |              
                   V            
          Tested gadget address
                   

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 crash

  • b'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 crash

  • b'A' * offset + canary + rbp + (BROP + 0x9) + p64(0x300) + (BROP + 0x7) + RIP + p64(0x0) + (PLT + 0xb ) + p64(ENTRY) + STOP --> Will crash

  • b'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 found

  • b'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 found

  • b'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