Now we need to call read(0, some_writable_address, 8) to read back the address of system. We then call puts to print the GOT entry for read (using write would've been better, but I was lazy and assumed there were no NULs in the bottom 48 bits): POP_RDI The exploit buffer starts with 24 junk bytes to get to the return address. In the following listings each line is a 64-bit qword. Say our jump address is stored at addr: to prepare the jump we place the dword (addr - 0x401528) / 8 at a known location eax_addr, then we use POP_RBP to set rbp to eax_addr + 4. This allows us to jump to any address if we can place it somewhere in memory between 0x401528 and 0x800401520 at a 8-byte aligned offset from 0x401528. It loads the dword eax from rbp - 4, then jumps to the address read from ((uint64_t) eax) * 8 + 0x401528. Then it does a jump through some indirections. First, it pops rdx off the stack, allowing us to set it. The last one is a bit more complex and serves two purposes. The first three are self-explanatory (we'll see why we need POP_RBP shortly). I used the following gadgets: # pop rdi ret We need gadgets to set the first three arguments ( rdi, rsi, rdx) and to jump to an arbitrary location read off memory.
#Stack g code soft scnells full#
Note also that, due to full RELRO, we can't overwite the GOT, so we can't return to PLT and we'll need a gadget to jump to an arbitrary address stored in a register or memory.ĭue to the small gadget selection I couldn't find a way to calculate the address of system inside the ROP chain, so the plan became sending the address of read over the socket to my script, doing the calculation there and sending the address of system back to the ROP chain. The plan is to read the address of a libc function (I chose read) off the GOT, calculate the address of system and call system("/bin/sh"). We have the libc, so we can figure out the offset of e.g. Also, the binary has a small selection of gadgets and there are no syscall gadgets. Unfortunately libc is dynamically linked, so we can only call imported functions via PLT, and there's no system or exec* imported. The binary has NX mitigation, so we need to build a ROP chain. We assume the lower 6 bytes don't contain NULs, so we can leak them (and if they do, we just need to try again). We know the top two bytes will be zero (because 64-bit addressing is really 48-bit). the address at which read writes our data. In our out-of-bounds situation this will be the address of buf in the new_message frame, i.e. The function prints the address of the memo buffer as a string instead of its content. There's also an address leak in edit_message, which is useful as the stack is randomized. This will result in read(0, g_msg_stack, ((size_t) (g_msg_buf)) & 0xFFFFFFFF). We'll create a memo with index 1, then set the index to 6, then edit sending our exploit buffer. Also, the edit index has to be >= 5 to get to g_msg_stack. the edit index has to be even), otherwise we'll get a zero-length read. The heap addresses in g_msg_buf are low and use the lower 32 bits, so we'll need the g_msg_len out-of-bounds to line up at a g_msg_buf element boundary (i.e. Notice that g_msg_len is an array of ints, i.e. Since the return address is at bp + 8 and buf in new_message is at bp - 16 we need 8 - (-16) = 24 filler bytes to reach the return address of edit_message. The base pointer for new_message and edit_message will be the same because they're both called from main and have the same arguments. g_msg_stack follows g_msg_buf in memory, so we can make read write to a stack address in the now-defunct new_message stack frame. Note that read reads up to the specified amount of bytes, so we can control how much we write. G_msg_buf immediately follows g_msg_len in memory, so (provided we pass the !g_msg_buf check) we can pass read an address as size, which will be a big number. Also note that g_msg_len is of the wrong size, 4 instead of 5. new_message sets it before checking it, so by trying to create a new memo with a bogus index and then editing it we can trigger out-of-bound reads for g_msg_buf and g_msg_len. the index of the most recent memo we worked on). Instead, we see that edit_message trusts the value of g_msg_index (i.e. There's an obvious heap overflow in new_message when len > 32, we're going to ignore that (there are more heap vulnerabilities in other places, I didn't use them). New_message and edit_message are called by main when 1 or 2 (respectively) are entered as choices in the main menu. Puts( "edited! this is edited message! ") Puts( "message too long, you can leave on memo though ") 0x400C04 int read_decimal( const char *prompt)