Recipes¶
All recipes use --json with jq on Linux/macOS, or ConvertFrom-Json in PowerShell. Add --no-similarity to batch runs unless you want every file recorded in the similarity DB.
Reusable jq helpers¶
PPEE writes values from the file as hex strings (see JSON conventions), and jq has no bitwise operators. These two helpers cover both. Save them as ~/.jq, where jq loads them automatically:
# "C1C0" -> 49600
def hex: ascii_downcase | explode
| reduce .[] as $c (0; . * 16 + (if $c >= 97 then $c - 87 else $c - 48 end));
# true if flag value $n is set
def bit($n): (. / $n | floor) % 2 == 1;
jq one-liners¶
# SHA-256 only
ppee-cli --json --hashes f.exe | jq -r .fileInfo.sha256
# Imported DLL names
ppee-cli --json --imports f.exe | jq -r '.imports[].name'
# module!function for every named import
ppee-cli --json --imports f.exe | jq -r '.imports[] | .name as $m | .functions[] | select(.name) | "\($m)!\(.name)"'
# Hardening flags from DllCharacteristics
ppee-cli --json --headers f.exe | jq '.headers["OptionalHeader.DllCharacteristics"] | hex
| {aslr: bit(64), highEntropyVA: bit(32), nx: bit(256), cfg: bit(16384)}'
# PDB path(s)
ppee-cli --json --debug f.exe | jq -r '.debug[].codeView.pdbPath // empty'
# Resource types with their variant counts
ppee-cli --json --resources f.exe | jq -r '.resources.types[] | "\(.typeName // .name // .id)\t\([.names[].languages | length] | add)"'
# Signer subject (empty if unsigned)
ppee-cli --json --security f.exe | jq -r '.security.signatures[0].signerCertificate.subjectName // "UNSIGNED"'
# URLs found in the file
ppee-cli --json --strings f.exe | jq -r '.strings.url[].text' | sort -u
# Is it .NET?
ppee-cli --json --net f.exe | jq .net.present
Batch processing¶
ppee-cli takes one file per run. Use your shell or xargs/parallel to fan out; each process is independent.
#!/usr/bin/env bash
set -uo pipefail
echo "path,sha256,imphash,ssdeep"
find "${1:-.}" -type f \( -iname '*.exe' -o -iname '*.dll' -o -iname '*.sys' \) -print0 |
xargs -0 -P "$(nproc)" -I{} sh -c \
'ppee-cli --no-update-check --no-similarity --json --hashes "$1" 2>/dev/null \
| jq -r "[.path, .fileInfo.sha256, .fileInfo.impHash, .fileInfo.ssdeep] | @csv"' _ {}
Get-ChildItem -Recurse -Include *.exe,*.dll,*.sys -Path C:\Samples |
ForEach-Object {
$j = & ppee-cli.exe --no-update-check --no-similarity --json --hashes $_.FullName | ConvertFrom-Json
[pscustomobject]@{ Path = $j.path; SHA256 = $j.fileInfo.sha256; ImpHash = $j.fileInfo.impHash }
} | Export-Csv hashes.csv -NoTypeInformation
Parallelism
A single ppee-cli already uses several threads for hashing and string scanning. When you run many files at once, set PPEE_JOBS=1 to avoid oversubscribing the CPU: PPEE_JOBS=1 xargs -P "$(nproc)" …
Export imports to CSV¶
ppee-cli --json --imports --delay-imports f.exe | jq -r '
(.imports[] | .name as $m | .functions[] | ["static", $m, (.name // "#\(.ordinal)")]),
(.delayImports[] | .name as $m | .functions[] | ["delay", $m, (.name // "#\(.ordinal)")])
| @csv' > imports.csv
Find unsigned binaries in a folder¶
find dist -type f \( -name '*.exe' -o -name '*.dll' \) | while read -r f; do
ppee-cli --no-update-check --no-similarity --json --security "$f" \
| jq -e '.security.present' > /dev/null || echo "UNSIGNED: $f"
done
Detect tampering after signing¶
When the file changes after signing, the digest embedded in the signature no longer matches the file's Authentihash:
ppee-cli --json --security f.exe | jq -r '.security.signatures[]
| if .embeddedDigest == .authentihash then "OK \(.programName // "-")" else "MISMATCH (modified after signing)" end'
Build an IOC record¶
ppee-cli --json --hashes --imports --debug --richheader --no-similarity sample.exe | jq '{
sha256: .fileInfo.sha256, md5: .fileInfo.md5, imphash: .fileInfo.impHash,
ssdeep: .fileInfo.ssdeep, tlsh: .fileInfo.tlsh,
pdb: [.debug[].codeView.pdbPath // empty],
dlls: [.imports[].name],
rich: (.richHeader.present)
}'
Compare two builds¶
diff <(ppee-cli --headers --sections old.exe | tail -n +2) \
<(ppee-cli --headers --sections new.exe | tail -n +2)
tail -n +2 drops the first line, which contains the path.
Reproducible-build check¶
for f in a/app.exe b/app.exe; do
ppee-cli --no-similarity --set FileHeader.TimeDateStamp=0 --set 'DataDirectory[6].Size=0' \
--save -o "/tmp/$(basename "$(dirname "$f")").exe" "$f" > /dev/null
done
cmp /tmp/a.exe /tmp/b.exe && echo "identical apart from timestamp/debug dir"
Related: Docker batch scanning · CI policy gates · JSON reference
References¶
- jq manual: the filter language used in the JSON examples on this page.