Reverse Engineering Techniques • Windows

Automated Unpacking: Dynamic Extraction of Obfuscated Payloads with mal_unpack

Automate malware unpacking with mal_unpack to dynamically intercept, dump, and reconstruct obfuscated position-independent shellcode and PE payloads.

Tool: https://github.com/hasherezade/mal_unpack

Executive Summary

The sample is position-independent shellcode (PIC) for 32-bit Windows. It implements runtime API resolution by hash, decompression of an embedded payload, CPUID-based environment checks, and thread-based execution with a state machine. No import table is present; the only static string is KERNEL32.dll, indicating manual resolution of Windows APIs from kernel32. The code is consistent with loader/dropper or first-stage shellcode commonly seen in exploit payloads, packed malware, or modular implants.

▪Risk level: High — capable of loading and executing arbitrary second-stage payloads, evading static import analysis, and adapting execution based on CPU environment.

File Properties

PE Sections

Property.text (Section 0).rdata (Section 1).data (Section 2).rsrc (Section 3)
Entropy6.7236.2793.5134.072
SHA-2567DEA6528B5B...D57077C9F62...4B6A56676EF...43C67A2F650...
File Ratio40.11%55.30%3.44%0.57%
Raw Address (Start)0x000004000x00011C000x00029E000x0002B600
Raw Address (End)0x00011C000x00029E000x0002B6000x0002BA00
Raw Size71,680 bytes98,816 bytes6,144 bytes1,024 bytes
Virtual Address0x000010000x000130000x0002C0000x00030000
Virtual Size71,471 bytes98,306 bytes13,148 bytes874 bytes

Observations

▪Entropy: The .text section (where the executable code lives) has an entropy of 6.723. While high, it is generally below the typical threshold for packed malware (usually >7.0), suggesting the code is likely obfuscated or compressed but perhaps not fully packed with a tool like UPX.
▪Virtual vs. Raw Size: In the .data section, the virtual size (13,148 bytes) is significantly larger than the raw size (6,144 bytes). This is common for uninitialized data (BSS) that occupies space in memory but not on disk.

IAT

Function NameFlagBindingExtraAddress 1Address 2Library
WriteFileximplicit-0x0002AC8A0x0002AC8AKERNEL32.dll
WriteConsoleW-implicit-0x0002AF8E0x0002AF8EKERNEL32.dll
WriteConsoleA-implicit-0x0002AF680x0002AF68KERNEL32.dll
WideCharToMultiByte-implicit-0x0002ADFC0x0002ADFCKERNEL32.dll
VirtualFree-implicit-0x0002AC500x0002AC50KERNEL32.dll
VirtualAllocximplicit-0x0002AC5E0x0002AC5EKERNEL32.dll
UnhandledExceptionFilter-implicit-0x0002AB420x0002AB42KERNEL32.dll
TlsSetValue-implicit-0x0002AD2A0x0002AD2AKERNEL32.dll
TlsGetValue-implicit-0x0002AD100x0002AD10KERNEL32.dll
TlsFree-implicit-0x0002AD380x0002AD38KERNEL32.dll
TlsAlloc-implicit-0x0002AD1E0x0002AD1EKERNEL32.dll
TerminateProcess-implicit-0x0002AB1A0x0002AB1AKERNEL32.dll
Sleep-implicit-0x0002AACE0x0002AACEKERNEL32.dll
SetUnhandledExceptionFilter-implicit-0x0002AB5E0x0002AB5EKERNEL32.dll
SetStdHandle-implicit-0x0002AEAA0x0002AEAAKERNEL32.dll
SetLastError-implicit-0x0002AD420x0002AD42KERNEL32.dll
SetHandleCount-implicit-0x0002ABC00x0002ABC0KERNEL32.dll
SetFilePointer-implicit-0x0002AF560x0002AF56KERNEL32.dll
RtlUnwind-implicit-0x0002AC080x0002AC08KERNEL32.dll
RaiseExceptionximplicit-0x0002AC3E0x0002AC3EKERNEL32.dll
QueryPerformanceCounter-implicit-0x0002AE2C0x0002AE2CKERNEL32.dll
MultiByteToWideChar-implicit-0x0002AE860x0002AE86KERNEL32.dll
LoadStringW-implicit-0x0002AA940x0002AA94USER32.dll
LoadLibraryA-implicit-0x0002AD780x0002AD78KERNEL32.dll
LeaveCriticalSection-implicit-0x0002ABA80x0002ABA8KERNEL32.dll
LCMapStringW-implicit-0x0002AD680x0002AD68KERNEL32.dll
LCMapStringA-implicit-0x0002AEFC0x0002AEFCKERNEL32.dll
IsValidCodePage-implicit-0x0002ACFE0x0002ACFEKERNEL32.dll
IsDebuggerPresent-implicit-0x0002AB7C0x0002AB7CKERNEL32.dll
InterlockedIncrement-implicit-0x0002ACB80x0002ACB8KERNEL32.dll
InterlockedDecrement-implicit-0x0002ACD00x0002ACD0KERNEL32.dll
InitializeCriticalSectionAndSpinCount-implicit-0x0002AD880x0002AD88KERNEL32.dll
HeapSize-implicit-0x0002AEF00x0002AEF0KERNEL32.dll
HeapReAlloc-implicit-0x0002AC6E0x0002AC6EKERNEL32.dll
HeapFree-implicit-0x0002AC240x0002AC24KERNEL32.dll
HeapCreate-implicit-0x0002AC7C0x0002AC7CKERNEL32.dll
HeapAlloc-implicit-0x0002AAAE0x0002AAAEKERNEL32.dll
GetTickCount-implicit-0x0002AE460x0002AE46KERNEL32.dll
GetSystemTimeAsFileTime-implicit-0x0002AE6C0x0002AE6CKERNEL32.dll
GetStringTypeW-implicit-0x0002AF1E0x0002AF1EKERNEL32.dll
GetStringTypeA-implicit-0x0002AF0C0x0002AF0CKERNEL32.dll
GetStdHandle-implicit-0x0002ABD20x0002ABD2KERNEL32.dll
GetStartupInfoA-implicit-0x0002AB080x0002AB08KERNEL32.dll
GetProcAddress-implicit-0x0002AAD60x0002AAD6KERNEL32.dll
GetOEMCP-implicit-0x0002ACF20x0002ACF2KERNEL32.dll
GetModuleHandleW-implicit-0x0002AABA0x0002AABAKERNEL32.dll
GetModuleHandleA-implicit-0x0002AF420x0002AF42KERNEL32.dll
GetModuleFileNameA-implicit-0x0002AC960x0002AC96KERNEL32.dll
GetLocaleInfoA-implicit-0x0002AF300x0002AF30KERNEL32.dll
GetLastError-implicit-0x0002AC140x0002AC14KERNEL32.dll
GetFileType-implicit-0x0002ABE20x0002ABE2KERNEL32.dll
GetEnvironmentStringsWximplicit-0x0002AE120x0002AE12KERNEL32.dll
GetEnvironmentStringsximplicit-0x0002ADCA0x0002ADCAKERNEL32.dll
GetCurrentThreadIdximplicit-0x0002AD520x0002AD52KERNEL32.dll
GetCurrentProcessIdximplicit-0x0002AE560x0002AE56KERNEL32.dll
GetCurrentProcessximplicit-0x0002AB2E0x0002AB2EKERNEL32.dll
GetConsoleOutputCP-implicit-0x0002AF780x0002AF78KERNEL32.dll
GetConsoleMode-implicit-0x0002AECA0x0002AECAKERNEL32.dll
GetConsoleCP-implicit-0x0002AEBA0x0002AEBAKERNEL32.dll
GetCommandLineA-implicit-0x0002AAF60x0002AAF6KERNEL32.dll
GetCPInfo-implicit-0x0002ACAC0x0002ACACKERNEL32.dll
GetACP-implicit-0x0002ACE80x0002ACE8KERNEL32.dll
FreeEnvironmentStringsW-implicit-0x0002ADE20x0002ADE2KERNEL32.dll
FreeEnvironmentStringsA-implicit-0x0002ADB00x0002ADB0KERNEL32.dll
FlushFileBuffers-implicit-0x0002AEDC0x0002AEDCKERNEL32.dll
ExitProcess-implicit-0x0002AAE80x0002AAE8KERNEL32.dll
EnterCriticalSection-implicit-0x0002AB900x0002AB90KERNEL32.dll
DeleteCriticalSection-implicit-0x0002ABF00x0002ABF0KERNEL32.dll
CreateFileA-implicit-0x0002AE9C0x0002AE9CKERNEL32.dll
CloseHandle-implicit-0x0002AC300x0002AC30KERNEL32.dll

Description

The Import Address Table (IAT) is a key structure in PE files that lists the external functions the program needs to run.

▪Evasion Indicators: The presence of IsDebuggerPresent suggests the file may have anti-debugging capabilities to detect if it's being analyzed.
▪Memory Manipulation: Functions like VirtualAlloc and VirtualFree are often used by malware for shellcode injection or unpacking.
▪Discovery: GetModuleFileNameA and GetCommandLineA are commonly used to gather environment data or verify its own location before executing further stages.

Automated unpacking using malunpack

Fig 1 - Unpacking using mal_unpack
Figure: Fig 1 - Unpacking using mal_unpack Click to zoom ↗

Fig 1 - Unpacking using mal_unpack

This tool unpacked the sample and put it in mount.exe.out directory.

Fig 2 - Output dir
Figure: Fig 2 - Output dir Click to zoom ↗

Fig 2 - Output dir

There’s a lot of files we’ll be focusing on .shc shellcode file.

Binary Ninja Analysis

Open the shellcode file in binary ninja. Choose appropriate platform windows-x86

Fig 3 - Opening shellcode in binary ninja
Figure: Fig 3 - Opening shellcode in binary ninja Click to zoom ↗

Fig 3 - Opening shellcode in binary ninja

Fig 4 - binary ninja Triage summary
Figure: Fig 4 - binary ninja Triage summary Click to zoom ↗

Fig 4 - binary ninja Triage summary

Switch to linear for high level representation.

Fig 5 - Linear View of binary ninja
Figure: Fig 5 - Linear View of binary ninja Click to zoom ↗

Fig 5 - Linear View of binary ninja

The sample utilizes a custom implementation of dynamic API resolution to hide its true capabilities from static analysis tools and the Import Address Table (IAT).

Mechanism: PEB Traversing & ROR13 Hashing

Instead of calling Windows APIs directly, the malware manually locates the Process Environment Block (PEB) via the Thread Environment Block (TEB) at fs:[30h]. It then iterates through the InLoadOrderModuleList to find loaded system DLLs (typically kernel32.dll or ntdll.dll).

Once a DLL is located, the malware parses the PE Export Directory to find function names.

Fig 6 - Function **`sub_467(0x726774c)`**
Figure: Fig 6 - Function **`sub_467(0x726774c)`** Click to zoom ↗

Fig 6 - Function sub_467(0x726774c)

Shellcode Information

Technical Analysis

Memory layout

ComponentDescription
Single SegmentOne contiguous executable/data region (ram) from base to 0xEFFF.
API TableFunction pointers stored at offset 0xD000. The first slot acts as an API trampoline for dynamic calls.
Hash TablesAPI hash lists located at offsets 0xEF00 and 0xFFC0, referenced during the initialization phase.

Anti-Analysis & Evasion

TechniqueImplementation
No Import TableAll Windows APIs are resolved at runtime; static analysis cannot identify imported functions.
API HashingExport names are hashed using the formula: $hash = hash \times 0x1003f + *name$.
Single Static StringOnly KERNEL32.dll exists in plaintext; no other DLL or API names are present.
CPUID ChecksVerifies "GenuineIntel" and specific Intel family/model/stepping (e.g., 0x106C0, 0x20660). Likely used to detect and avoid VM/Sandbox environments.
TimingImplements tick-count-based delays (3–8 second ranges) to bypass automated sandbox analysis.

API Resolution (PEB-based)

The shellcode utilizes a multi-step process to interact with the Windows OS without static linked dependencies:

1.Kernel32 Base Location: Obtained via the PEB (Process Environment Block) by traversing PEB -> Ldr -> InMemoryOrderModuleList.
2.Export Parsing: The shellcode manually parses the PE header to locate the Export Directory (RVA 0x78).
3.Hashing: Each exported name is processed through the hashing algorithm at offset 0x00000A6A.
4.Table Population: Resolved addresses are written into the API table at 0x40D000. The specific APIs selected are driven by hash lists at 0x40EF00 and 0x40FFC0.
5.Invocation via Trampoline: Execution is passed through an API trampoline at 0x0000C7B1 (JMP [0x40D000]), ensuring no direct API references exist at the actual call sites.

Execution flow

TELEMETRY / DISASSEMBLY
shellcode_entry (0xB78D)
  ├── Resolve kernel32 (PEB)
  ├── Get system directory / build path
  ├── CreateThread(..., FUN_0000A524, ...)
  └── FUN_0000A524 (message/wait loop)
        └── Wait → FUN_0000A465 (state machine)
              State 0: FUN_00005C69, FUN_00006B55, FUN_0000C04A (init, resolve APIs, create thread)
              State 1: FUN_00007EF1 … FUN_00009A2F, FUN_00005A04 (setup)
              State 2: FUN_0000A358 (main logic)
                    ├── GetTickCount, timing
                    ├── FUN_0000A5D7
                    ├── FUN_00005A43 (decompression)
                    │     └── Inflate (zlib-style) → decompressed payload
                    ├── FUN_00005BD6 / FUN_0000C0FC / FUN_0000A679 (payload execution path)
                    └── Optional: FUN_0000C384, FUN_0000C0B7, FUN_0000C45D
              State 3: Cleanup (e.g. close handle)

Payload Handling

Execution Logic: Routine FUN_00005A43 initializes the inflate pipeline. The resulting decompressed data is then passed to FUN_0000A679, which likely handles the execution of the next-stage binary or the parsing of a decrypted configuration file.

Environment & Capability Detection

The sample performs deep hardware inspection to ensure it is running on a specific target environment and to avoid detection by security researchers using generic virtual machines.

▪CPUID Validation: The malware executes the CPUID instruction to confirm the "GenuineIntel" vendor string.
▪Signature Matching: It compares specific Intel family/model/stepping signatures against internal globals (_DAT_00411AD8).
▪Feature Bit Inspection: Uses Extended CPUID (Leaf 7) to check for specific hardware features (bits 0x200 and 0x20).
▪Environment Flags: Based on these checks, it assigns a "capability level" (0–5). This likely dictates which malicious features are activated or whether the malware terminates to avoid a sandbox.

Inferred Windows API Usage

Since the malware uses dynamic resolution via ROR13 hashing, the following APIs are inferred based on the operational context of the code and observed memory constants:

Inferred RoleUsage Context
GetProcAddress / LoadLibraryCore resolution of kernel32.dll exports (e.g., trampoline index 10).
GetTickCountImplements anti-sandbox timing and execution pacing (3–8s delays).
VirtualAlloc / HeapAllocMemory management for the decompressed payload (allocator at 0x40FD8C).
CreateThreadAsynchronous execution; spawns a worker thread for the main loop (FUN_0000A524).
WaitForSingleObjectEvent synchronization and loop timing (4–8s intervals).
GetSystemDirectoryEnvironment discovery; likely used to locate target paths for dropped files.
memcpy / memsetNative memory manipulation for block copying and zeroing out state.

Behavioral Summary

The following table summarizes the execution lifecycle of the shellcode from initial entry to final payload delivery.

PhaseBehavior
1. LoadShellcode is injected or executed via an exploit, packed stub, or reflective loader.
2. InitManually traverses the PEB to locate kernel32.dll; resolves APIs by hash and populates the jump table.
3. SetupHardware inspection (CPUID) is performed; memory is allocated; environment paths are gathered.
4. RunEnters a timed wait loop. Once sandbox/timing checks pass, the embedded payload is decompressed (Zlib) and executed in memory.
5. CleanupReaches "State 3": Closes open handles, clears sensitive memory regions, and terminates the process.

Indicators of Compromise (IOCs)

Static Indicators

TypeValue
StringKERNEL32.dll
Hash ConstantAPI hash multiplier 0x1003f
Code PatternIndirect call via JMP [0x40D000] (or equivalent at runtime base)
CPUID Check"GenuineIntel" (0x49656E69, 0x6C65746E, 0x756E6547)
Key OffsetsEntry: 0xB78D; Trampoline: 0xC7B1; Hash: 0x0A6A; Resolver: 0x0B63

Capabilities Assessment

This assessment provides a high-level overview of the functions currently integrated into the analyzed shellcode.

CapabilityPresentNotes
Runtime API ResolutionYesHash-based; focuses on kernel32.dll in this specific sample.
Payload DecompressionYesUses a Zlib-style inflate engine for secondary stage delivery.
Thread CreationYesMain malicious logic is offloaded to a separate worker thread.
Environment CheckYesRigorous Intel CPUID and feature flag verification.
Anti-Sandbox TimingYesUses GetTickCount to implement execution delays.
PersistenceNoNot observed; likely handled by the second-stage payload.
Network / C2NoNot observed in this loader; likely resides in the decompressed data.
File / Registry OpsNoLikely implemented in the next stage after API resolution.
Copied