
Dynamic shellcode loader with sophisticated evasion capabilities
AsmLdr is an advanced shellcode loader implemented in x64 assembly language, designed for Windows x64 environments. Its primary purpose is to execute encrypted payloads while minimizing detection by advanced antivirus software, endpoint detection and response (EDR) systems, sandboxes, and debuggers. It achieves this by resolving modules, APIs, and system calls dynamically at runtime, eliminating static dependencies like import tables or hardcoded addresses. This approach creates a compact binary with a low detection profile.
The loader uses module stomping to place shellcode into an existing DLL's executable section, decrypts the payload in memory, and runs it via indirect syscalls with stack spoofing. It includes evasion features such as anti-debugging measures, timing adjustments to mimic normal application patterns, and bypassing of Windows event tracing (ETW). These capabilities make it a tool for red teaming and security research. Ethical Disclaimer: This project is for educational and research purposes only. It demonstrates low-level Windows internals, including PEB traversal, export table parsing, syscall unhooking, and memory protection manipulation. Use in controlled environments (e.g., VMs). Misuse for malicious activities is illegal and unethical. The author disclaims liability for any misuse.
AsmLdr offers a range of features for stealthy payload execution and evasion. Each is described below, focusing on its function and benefits:
Module Resolution via PEB Traversal: The loader lists running modules by following the PEB's module chain. It matches module names through Unicode comparisons to find and return the base address of the desired DLL. This method bypasses standard loading functions, lowering the risk of interception.
API Resolution from Export Tables: The loader examines a DLL's export directory to locate functions. It matches APIs using hashing or optionally direct name checks with length verification to prevent confusion between similar function names. This provides the function's address without relying on pre-linked imports.
Anti-Debugging Protections: The loader detects debugging through multiple checks and exits cleanly if any indicate analysis.
Junk Code Generation for Analysis Resistance: The loader runs sequences of non-essential operations to create confusing patterns in disassembly, making static analysis harder without impacting runtime efficiency.
Timing and Behavioral Normalization: The loader measures system speed to run simulated workloads that match normal application patterns, helping it blend with legitimate processes. It also pauses briefly before key actions to normalize execution timing.
ETW Logging Bypass: The loader setup hardware breakpoints on ETW logging APIs to intercept and skip event logging calls, preventing traces in system logs.
Multi-Layer Unhooking: The loader dynamically retrieves valid system call numbers (SSNs) by checking functions directly, scanning nearby code if hooked, or counting patterns in ntdll to estimate the number, adjusting for known exceptions.
Techniques used:
Indirect Syscall and Stack Spoofing: The loader finds clean code paths in ntdll for system calls and chains up to 32 operations, hiding the call stack to evade tracing. It restores the original state after completion.
DLL Hollowing: The loader brings in a target DLL, temporarily allows writing to its code section, inserts the encrypted shellcode, decrypts it, and resets protections to run-only.
In-Memory Decryption: The loader decrypts the shellcode by applying a repeating key pattern directly in the injected memory.
Utility Functions: The loader includes tools for measuring string lengths, comparing strings (standard or wide characters), hashing names for lookups, validating memory pointers, and reading timestamps for timing controls.
The Python script generate.py encrypts the raw shellcode file. It adds padding for alignment, creates or uses a key, and XORs each byte with the key in a repeating cycle. The output file shellcode.asm defines sizes, arrays for the encrypted data and key, and a routine to load the data at runtime. The loader's decryption matches this process in memory. Note that longer keys increase wait time.
Prepare a raw shellcode binary (e.g., payload.bin). Run python generate.py payload.bin [optional_key] to produce shellcode.asm.
Modify szDllName (Unicode hex words in evader.asm) for alternative DLLs like urlmon.dll. Recompute DJB2 hashes for new APIs using the str_hash logic. Comment out macros (e.g., KILL_DEBUGGERS) for debugging.
AsmLdr is optimized for Windows 10 and 11 x64; kernel updates may alter offsets (e.g., PEB+0x18) or NTDLL patterns (e.g., 0xb8d18b4c), requiring validation. Error handling relies on NTSTATUS returns (e.g., <0 failure), with no built-in logging. Junk and fake operations consume CPU cycles, potentially alerting resource monitors on constrained systems. Use solely in controlled environments.