Covers: technique identification by API sequence (process hollowing, code injection, DLL injection, .NET reflective loading, hook-based injection, resource droppers), packer recognition (UPX, entropy, section names, tail jump, breakpoint strategies), anti-analysis patterns (IsDebuggerPresent, PEB, SEH, TLS, RDTSC, tool detection), shellcode indicators (NOP sled, GetEIP, PEB walk), document malware indicators (PDF keywords, VBA triggers, RTF exploits), and two quick-reference tables mapping APIs→techniques and assembly→behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
12 KiB
FOR610 Malware Patterns & Recognition Cheat Sheet
Technique Identification: API Sequences
Process Hollowing (T1055.012)
CreateProcess(SUSPENDED) ← dwCreationFlags = 0x4
NtUnmapViewOfSection() ← gut the target process
VirtualAllocEx() ← allocate space for payload
WriteProcessMemory() ← inject malicious code
ResumeThread() ← wake up with injected code
How to spot: capa shows "Create Suspended Process". In Ghidra: CreateProcess with 0x4 flag, followed by NtUnmapViewOfSection (often loaded dynamically via GetProcAddress to hide from IAT). Lab: WinHost32.exe (S5, Lab 5.4)
Classic Code Injection — CreateRemoteThread (T1055.001)
OpenProcess() ← get handle to target
VirtualAllocEx(RWX) ← allocate executable memory (0x40 = PAGE_EXECUTE_READWRITE)
WriteProcessMemory() ← write shellcode to target
CreateRemoteThread() ← execute in target process
How to spot: Search imports for CreateRemoteThread. VirtualAllocEx with flProtect=0x40 is a red flag. Lab: great.exe (S4, Lab 4.9)
DLL Injection (T1055.001)
OpenProcess() ← target process
VirtualAllocEx() ← allocate space for DLL path string
WriteProcessMemory() ← write DLL path into target
CreateRemoteThread( ← start address = LoadLibraryA
lpStartAddress=LoadLibraryA,
lpParameter=remote_dll_path)
How to spot: CreateRemoteThread where lpStartAddress points to LoadLibrary.
DLL Side-Loading / Sideloading (T1574.002)
Trusted.exe (signed) ← legitimate executable
→ LoadLibraryW("evil.dll") ← loads malicious DLL from same directory
→ DllRegisterServer() ← DLL export runs malicious code
→ CreateProcessW() ← spawns payload
How to spot: Signed executable loading DLL from non-standard path. DLL exports suspicious functions. PeStudio shows expired but valid signature. Lab: Package.exe + iviewers.dll (S3, Lab 3.10)
.NET Reflective Loading (T1620)
byte[] payload = DecodePayload(); // XOR/Base64 decode
Assembly asm = Assembly.Load(payload); // Load from byte[] in memory
asm.GetTypes()[0].InvokeMember(...); // Execute
How to spot: Search ILSpy decompiled code for "Assembly.Load" or ".Load". Set breakpoint AFTER Assembly.Load in dnSpyEx → extract byte[] from Locals window. Lab: chatroom.exe (S4, Lab 4.8)
This is the answer to "what technique is being used when a .NET binary extracts malicious code directly into the memory of its running process?" → Reflective Loading via Assembly.Load(byte[])
.NET In-Memory Compilation
CSharpCodeProvider csc = new CSharpCodeProvider();
string source = Decode(encoded_source); // Base64+XOR decode
CompilerResults result = csc.CompileAssemblyFromSource(params, source);
result.CompiledAssembly.EntryPoint.Invoke(...);
How to spot: Search for "CSharpCodeProvider" or "CompileAssemblyFromSource" in decompiled code. Lab: rwvg1.exe (S3, Lab 3.12)
Hook-Based Injection — SetWindowsHookEx (T1056)
SetWindowsHookExA(
0x0E, ← WH_MOUSE_LL (low-level mouse hook)
hook_function, ← pointer to malicious code
GetModuleHandleA(0), ← own module
0) ← all threads
How to spot: SetWindowsHookExA with idHook=0x0E (mouse) or 0x0D (keyboard). Anti-sandbox: waits for real user activity before executing. Lab: vbprop.exe (S5, Lab 5.5)
Resource Dropper Pattern
FindResourceW() ← locate embedded resource
SizeofResource() ← get payload size
LoadResource() ← load into memory
LockResource() ← get pointer to data
CreateFileA() ← create output file
WriteFile() ← write payload to disk
CreateProcessA() ← execute dropped file
How to spot: Ghidra Symbol Table shows FindResource + WriteFile + CreateProcess sequence. Resources visible in PeStudio. Lab: ishelp.dll dropper (S2, Lab 2.7)
Packer Recognition
How to Identify a Packed Binary
| Indicator | What to check | Tool |
|---|---|---|
| High entropy | Sections >7.0 entropy | PeStudio, peframe |
| Few imports | Only LoadLibrary + GetProcAddress | PeStudio, peframe |
| No readable strings | Barely any strings extracted | strings, FLOSS |
| Unusual section names | UPX0, UPX1, .packed, .code | PeStudio, diec |
| Small IAT | Very few imported functions | PeStudio |
| Packer signature | Known packer detected | DIE/diec, ExeInfo PE |
UPX Packer Indicators
Section names: UPX0 (empty), UPX1 (compressed code), .rsrc
Entry point: In UPX1 section (not .text)
Assembly: PUSHAD → [decompression loop] → POPAD → JMP OEP
Unpack: upx -d packed.exe (fails if modified)
The Unpacking Tail Jump (assembly)
; End of unpacker — restore registers and jump to real code
POPAD ; restore all saved registers
JMP 0x140003F94 ; ← this is the OEP (Original Entry Point)
How to find it: Set breakpoint at end of UPX1 section. Look for JMP to address in UPX0 (or .text).
Breakpoint Strategies for Manual Unpacking
| Strategy | When to use | How |
|---|---|---|
| Tail jump | Known packer structure | Find JMP at end of unpacker |
| VirtualAlloc | Unpacker allocates new memory | BP on VirtualAlloc, watch for RWX allocation |
| VirtualProtect | Unpacker changes permissions | BP on VirtualProtect with 0x40 (RWX) |
| Stack breakpoint (ESP trick) | SEH-based packers | Set hardware BP on value written to stack after PUSH |
| LoadLibrary | Unpacker resolves imports | BP on LoadLibraryA when loading unexpected DLLs |
| RtlDecompressBuffer | Compression-based packer | BP on decompression API |
Anti-Analysis Recognition
Debugger Detection Patterns
IsDebuggerPresent:
CALL IsDebuggerPresent
TEST EAX, EAX ; zero = no debugger
JNE debugger_found ; non-zero = debugger!
Bypass: Set EAX=0 in debugger, or NOP the JNE. Lab: getdown.exe (S5)
PEB.BeingDebugged (avoid API call):
MOV EAX, FS:[30h] ; get PEB address
MOVZX EAX, [EAX+2] ; offset 0x2 = BeingDebugged byte
TEST EAX, EAX
JNE debugger_found
Bypass: Zero the byte at PEB+2 in debugger. Lab: lansrv.exe (S5)
NtGlobalFlag check:
MOV EAX, FS:[30h] ; PEB
TEST [EAX+68h], 0x70 ; NtGlobalFlag (0x70 when debugger-launched)
JNE debugger_found
Timing check (RDTSC):
RDTSC ; read timestamp → EDX:EAX
MOV [saved], EAX ; save
; ... code to time ...
RDTSC ; read again
SUB EAX, [saved] ; compute delta
CMP EAX, threshold ; if too large → stepping detected
JA debugger_found
SEH Anti-Debug Pattern
PUSH handler_addr ; push exception handler
PUSH FS:[0] ; push current SEH chain
MOV FS:[0], ESP ; install new handler
XOR ECX, ECX
DIV ECX ; ← deliberate divide-by-zero exception!
; execution jumps to handler_addr
; Analyst expects code to continue here, but it doesn't!
How to handle: Set breakpoint on handler_addr. In x32dbg: Options → Preferences → Exceptions → "Do not break on DIV0". Lab: want.exe (S5, Lab 5.7)
TLS Callback Anti-Debug
PE Header → TLS Directory → TLS Callback address
Code at TLS callback runs BEFORE entry point
Typically contains: IsDebuggerPresent + XOR decryption loop
How to handle: PeStudio shows TLS section. Set hardware breakpoint at TLS callback address. Lab: lansrv.exe (S5, Lab 5.9)
Tool Detection Patterns
; Check for security DLLs
CALL GetModuleHandleW, "avghookx.dll" ; AVG
TEST EAX, EAX
JNE tool_found
; Check for debugger windows
CALL FindWindowW, "OLLYDBG", NULL ; OllyDbg
CALL FindWindowW, "WinDbgFrameClass", NULL ; WinDbg
TEST EAX, EAX
JNE tool_found
; Enumerate processes for analysis tools
CALL CreateToolhelp32Snapshot
CALL Process32FirstW / Process32NextW ; loop comparing names
Bypass: ScyllaHide plugin hides all of these. Lab: raas.exe (S5, Lab 5.6)
Shellcode Indicators
| Pattern | Hex/Assembly | Meaning |
|---|---|---|
| NOP sled | 90 90 90 90 90... |
Shellcode padding — buffer overflow indicator |
| GetEIP (CALL+POP) | E8 00 00 00 00 58 |
CALL next + POP EAX — shellcode locates itself |
| PEB walk | MOV EAX, FS:[30h] |
Find kernel32.dll base address |
| API hash loop | Hash compare + loop | Resolve API addresses without strings |
| XOR decode loop | XOR [ESI], AL; INC ESI; DEC ECX; JNZ |
Self-decrypting shellcode |
XORSearch detects: XORSearch -W -d 3 <file> scans for GetEIP, kernel32 finder, NOP sled
Document Malware Indicators
PDF Suspicious Keywords
| Keyword | Risk | What it does |
|---|---|---|
/JavaScript |
HIGH | Embedded script execution |
/OpenAction |
HIGH | Auto-execute on open |
/Launch |
HIGH | Launch external program |
/AA |
HIGH | Additional actions (multiple triggers) |
/AcroForm |
MEDIUM | Interactive form (can execute) |
/URI |
LOW-MED | Clickable URL (phishing) |
/EmbeddedFile |
MEDIUM | Embedded file content |
/RichMedia |
MEDIUM | Flash/media content |
Scan: pdfid.py <file> — any non-zero count for HIGH keywords = investigate
Office VBA Auto-Execute Triggers
| Function | Trigger |
|---|---|
AutoOpen() |
Document opened (Word) |
Document_Open() |
Document opened (Word) |
Auto_Open() |
Workbook opened (Excel) |
Workbook_Open() |
Workbook opened (Excel) |
AutoExec() |
Application start |
AutoClose() |
Document closed |
Detect: oledump.py <file> → streams marked "M" contain macros → extract with -s <n> -v
RTF Exploit Indicators
| Indicator | What to look for |
|---|---|
| Deeply nested groups | rtfdump.py shows nesting level >3 |
| Large hex data | Group with many hex bytes |
\objdata keyword |
Embedded OLE object |
\*\objclass |
Object class identifier |
PowerShell Encoded Command Pattern
powershell.exe -WindowStyle Hidden -EncodedCommand <Base64>
Decode: echo <Base64> | base64 -d or CyberChef (From Base64 → Decode UTF-16LE)
API → Technique Quick Reference
| If you see these APIs... | Technique |
|---|---|
| CreateProcess(SUSPENDED) + NtUnmapViewOfSection + WriteProcessMemory + ResumeThread | Process Hollowing |
| VirtualAllocEx(0x40) + WriteProcessMemory + CreateRemoteThread | Code Injection |
| CreateRemoteThread(LoadLibraryA, dll_path) | DLL Injection |
| Assembly.Load(byte[]) + InvokeMember() | .NET Reflective Loading |
| CSharpCodeProvider + CompileAssemblyFromSource | .NET In-Memory Compilation |
| SetWindowsHookExA(0x0E) | Mouse Hook Anti-Sandbox |
| FindResource + WriteFile + CreateProcess | Resource Dropper |
| LoadLibraryW (DLL not in IAT, from same dir) | DLL Side-Loading |
| InternetOpen + HttpSendRequest + InternetReadFile | HTTP C2 |
| RegSetValueEx("...\Run", malware_path) | Registry Persistence |
| IsDebuggerPresent / FS:[30h]+2 / NtGlobalFlag | Anti-Debugging |
| GetModuleHandle("avghookx.dll") / FindWindow("OLLYDBG") | Tool Detection |
| RDTSC + delta comparison | Timing Anti-Debug |
| FS:[0] manipulation + intentional exception | SEH Anti-Debug |
| BlockInput(TRUE) | Analyst Lockout |
| CreateToolhelp32Snapshot + Process32First/Next | Process Enumeration |
| VirtualAlloc + memcpy/rep movsb + JMP | Runtime Unpacking |
| PUSHAD...POPAD...JMP OEP | UPX-Style Packer |
Assembly Pattern Quick Reference
| Assembly Pattern | What It Means |
|---|---|
XOR EAX, EAX |
Zero out register (common idiom) |
TEST EAX, EAX then JZ/JNZ |
Check if value is zero |
PUSH EBP; MOV EBP,ESP; SUB ESP,n |
Function prologue |
LEAVE; RET |
Function epilogue |
CALL; ADD ESP,n |
cdecl call (caller cleans stack) |
FS:[30h] |
PEB access (anti-debug or API resolution) |
FS:[0] |
SEH chain access |
XOR [reg], key in loop |
String/code decryption |
PUSHAD...POPAD...JMP addr |
Packer wrapper (UPX-style) |
MOV byte ptr [EBP-x], imm8 (repeated) |
Stack string building |
E8 00 00 00 00 then POP reg |
GetEIP shellcode technique |
REP MOVSB (ESI→EDI) |
Memory copy (memcpy) |
VirtualAlloc then REP MOVSB then JMP EAX |
Unpack and execute |
90 90 90 90... |
NOP sled (shellcode indicator) |