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,
*pHeapChunkis filled withread(0, pHeapChunk, 0x20) - If the input is 2,
&pHeapChunkis printed - If the input is 3, write double quadword of
*pHeapChunkto&vulnVarwithmovdqu. Intuitively, this is effectivelymemcpy(&vulnVar, pHeapChunk, 0x10) - If the input is 0, if
pHeapChunkisNULL, 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
&pHeapChunkwith option 2 and calculate&returnAddr - Set
*(pHeapChunk + 8) = &returnAddr with option 1 - Set
pHeapChunk = (*pHeapChunk + 8)with option 3. NowpHeapChunk = &returnAddr - Set
*pHeapChunk = &winAddrand*(pHeapChunk + 8) = &fakeChunk - 8with 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) = &fakeChunkwith option 1. Now the fake chunk’s size is set to0x40 - Set
pHeapChunk = (*pHeapChunk + 8)with option 3. NowpheapChunkpoints 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