Skip to content

Cheat sheet — Static Analysis: Strings & PE

Companion to Module 03 — Static Analysis — Strings & PE · CC BY 4.0 — print it, pin it, share it.

Last reviewed: 2026-07

Only handle live malware in an isolated, disposable analysis VM with no bridged network. Static analysis never runs the sample — but the sample still lives on disk.

Strings — the fast first read

strings sample.exe                     # printable runs, min length 4 (default)
strings -n 8 sample.exe                # raise the floor — cut ASCII noise
strings -e l sample.exe                # 16-bit little-endian (UTF-16LE / Windows wide strings)
strings -a sample.exe                  # scan the whole file, not just loaded sections
strings sample.exe | grep -iE 'http|\.exe|\\\\|reg|mutex'   # C2, paths, registry, mutexes
  • High-signal categories: URLs / C2 domains, file paths, registry keys, error messages (often the clearest intent), and mutex names — a mutex like Global\{a37f...} is more unique and searchable than a hash.
  • Default strings misses UTF-16 — always add -e l on Windows binaries or you'll skip half the payload.

FLOSS — obfuscated & stacked strings

floss sample.exe                       # static + decoded + stacked + tight strings
floss --only static sample.exe         # just what plain strings would find
floss -n 6 sample.exe                  # raise minimum length
floss --json sample.exe > strings.json # machine-readable for the metadata report
  • FLOSS emulates the sample's own decode routines to recover strings that malware only builds at runtime — the ones plain strings never sees. Read the output categories (static / decoded / stacked) so you know what each block means.

pefile — the PE metadata report

import pefile
pe = pefile.PE("sample.exe")

# COFF compile timestamp (T1027.005 — often zeroed or faked)
import datetime
ts = pe.FILE_HEADER.TimeDateStamp
print("compiled:", datetime.datetime.utcfromtimestamp(ts))

# Import Address Table — the capability declaration
for entry in pe.DIRECTORY_ENTRY_IMPORT:
    print(entry.dll.decode())
    for imp in entry.imports:
        if imp.name:
            print("   ", imp.name.decode())

# Per-section entropy (packed vs unpacked)
for s in pe.sections:
    print(s.Name.rstrip(b"\x00").decode(), f"{s.get_entropy():.2f}")
  • The IAT is the highest-signal static data — the APIs the binary told the OS it intends to call. Look up unknowns on MalAPI.io; combos declare behaviour:
  • CreateRemoteThread + VirtualAllocEx + WriteProcessMemory = process injection.
  • SetWindowsHookEx / GetAsyncKeyState = keylogging (Agent Tesla).
  • WSAStartup / connect / send / recv = socket C2 client.

YARA — author, then prove it

rule agent_tesla_keylogger {
    meta:
        author = "you"
        hash   = "<sha256>"
        attck  = "T1056.001"
    strings:
        $mutex = "Global\\{a37f78d2-3b1c-4e5a}" ascii wide
        $api1  = "SetWindowsHookEx"
        $api2  = "GetAsyncKeyState"
    condition:
        uint16(0) == 0x5A4D and $mutex and ($api1 and $api2)
}
yara -s rule.yar sample.exe            # -s: print matching strings + offsets
yara rule.yar ./benign_corpus/         # MUST stay quiet — the true-negative half
  • uint16(0) == 0x5A4D anchors on the MZ magic. Modifiers: nocase, ascii, wide, fullword.

Gotchas worth remembering

  • Static analysis only sees what's unpacked. strings/IAT nearly empty ≠ benign — it usually means packed, unpack first (Module 09). The stub's strings still often name the packer.
  • A YARA rule isn't done when it compiles or even when it matches the sample — it's done when it also stays quiet on a benign control. Match without a true-negative is half a rule.
  • The COFF timestamp catches lazy forgers, not careful ones. A future date is a near-certain forgery; a plausible fake tells you nothing.
  • Strings lie after packing — you're reading the unpacker stub, not the payload. Confirm the entropy read from triage before trusting a "quiet" dump.
  • AI maps imports → ATT&CK fast but hallucinates API names — treat its output as leads to confirm on MalAPI.io, never as the finding.

Comments

Sign in with GitHub to comment. Choose the type: Feedback (errors or suggestions on this page) · Hints (help for fellow learners — no spoilers) · General (anything else).