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 withread(0, pHeapChunk, 0x20)
- If the input is 2,
&pHeapChunk
is printed - If the input is 3, write double quadword of
*pHeapChunk
to&vulnVar
withmovdqu
. Intuitively, this is effectivelymemcpy(&vulnVar, pHeapChunk, 0x10)
- If the input is 0, if
pHeapChunk
isNULL
, then exit with error. Else, dofree(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
- Leak
&pHeapChunk
with option 2 and calculate&returnAddr
- Set
*(pHeapChunk + 8) = &returnAdd
r with option 1 - Set
pHeapChunk = (*pHeapChunk + 8)
with option 3. NowpHeapChunk = &returnAddr
- Set
*pHeapChunk = &winAddr
and*(pHeapChunk + 8) = &fakeChunk - 8
with option 1. NowreturnAddr = &winAddr
- Set
pHeapChunk = (*pHeapChunk + 8)
with option 3. NowpHeapChunk = &fakeChunk - 8
, the fake chunk’s size field address - Set
*pHeapChunk = 0x40 and *(pHeapChunk + 8) = &fakeChunk
with option 1. Now the fake chunk’s size is set to0x40
- Set
pHeapChunk = (*pHeapChunk + 8)
with option 3. NowpheapChunk
points to a valid chunk - 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