There is a function called "shell". This function loads rdi into rbp-8, checks it against the value 0xdeadcafebabebeef
, and if the check is successful it pops a shell for us.
Clearly, our goal is to call this function.
In main, there is a gets call. It reads as much input as we want into rbp-0xa
, opening up an avenue for buffer overflow. As the saved rbp is 8 bytes long, our padding will be 0xa + 8 bytes of junk.
We could use a pop rdi gadget so that the check works properly, but there's no need. Instead, we can jump straight to the instruction inside of shell that pops a shell for us.
So our payload is just padding + address of instruction in shell that pops a shell
There's no NX, AND a gets call. On top of this, the program sends us the buffer address. A simple ret to shellcode, right?
Wrong, in fact. We have to buffer overflow from main, which makes things more difficult. Instead of the classic leave ; ret, it pops a value into ecx, then loads ecx - 4 into esp. This removes our classic buffer overflow where we change EIP. Instead, we must stack pivot.
We have control of ESP. When you call ret, the program jumps to the address stored at ESP. We can set esp to the address of the buffer. Then, we change the beginning of our buffer to be the address of our shellcode.I put the shellcode after the esp.
We can use pattern.py to figure out the amount of bytes until esp, which is 32.
I created custom shellcode that had to have the address of a string /bin/sh which I also put in the input.
This has been explained in countless writeups, so I won't go over it largely, but there's NX and no PIE. So we can use ret2plt, like always, to leak a libc address.
Main is quite a complex function. For the purposes of buffer overflow, this is irrelevant to us. However, if we just overwrite RBP with a bunch of As, it means that errors will be caused at an instruction like mov DWORD PTR [rbp-0x8], 0x9
, and whenever pop is called(after rsp is set to rbp). Thus, we must give rbp some form of authentic value so we can basically move the stack somewhere else. There is a page mapped read and write inside of the binary at around 0x602000-0x603000
. We can set rbp to a value around here to create a nice fake stack, then execute a ret2libc attack.
NX, no PIE. The whole cookie thing is largely irrelevant so I'm not gonna comment on it.
It calls gets on rbp-0x50, opening an avenue for buffer overflow. We can do a ret2plt attack, calling puts@plt on puts@got in order to leak a libc address, specifically that of puts. Then, we can subtract the appropriate offset to get the libc base.
From there, it's a simple ret2libc attack. We call system("/bin/sh") using pop rdi. The problem is, this doesn't seem to work. To make it work, we have to set rsi and rdx to 0. We don't have a pop rdx gadget inside of the binary, so it's impossible... right?
Wrong, actually. At this point in the exploit, we know the libc base, so we can use rop gadgets inside of the libc. So the full exploit is:
It gets a bunch of input from us using a function called input that does some multiplication a value, lets call this number "num", and then calls fgets(var,num,stdin), basically reading num bytes into "var". By stepping through the program and looking at every function call of input, we see the last one has the largest value, so I just went with that even though they'd all probably work.
Later on, there are some interesting instructions. Namely these two:
Note that these two lines are at main+247
- that's quite a jump. It compares some variable on the stack to 0xc0d3d00d
and jumps away if they aren't equal. If they are equal, it goes on to execute some instructions including fopen, fgets, puts etc. - probably printing the flag from flag.txt.
Using gdb, I set a breakpoint at the instruction that compares ebp-0xc
to 0xc0d3d00d
. Then at the fourth input I pasted in a de brujin pattern generated by pattern.py. At the breakpoint, I read ebp-0xc
, and pasted this value back into pattern.py. This gives 116 bytes until we overwrite this variable, so our payload is just
There is literally zero protections.
We could do ret to shellcode, but there's no jmp esp and the shifts with the buffer can be very temperamental. Instead, I used ret2libc, which is a lot more reliable.
Via the GOT and PLT, we can execute a simple leak of the address of puts. Feeding this value into libc database find, their libc version is libc6-i386_2.27-3ubuntu1_amd64
So, we can use the GOT and PLT to leak the address of puts and call main again. Then, we can subtract the appropriate value from this to get the libc base. Since we called main again, we get a second input, to which we can deliver the main payload of system("/bin/sh").