reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
Apache License 2.0 — see the repository root LICENSE file for the full text.
|
||||
|
||||
Copyright the AnalystAIPack contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: extracting-encryption-keys-from-binaries
|
||||
description: 'Locates candidate encryption keys in a binary by finding high-entropy fixed-size
|
||||
regions (16/24/32 bytes) near cryptographic constants or crypto API references, and surfacing
|
||||
printable key/passphrase strings. Activates for requests to extract encryption keys, find an AES
|
||||
key in a sample, or recover hardcoded crypto material.'
|
||||
domain: cybersecurity
|
||||
subdomain: reverse-engineering
|
||||
tags:
|
||||
- reverse-engineering
|
||||
- key-extraction
|
||||
- cryptography
|
||||
- entropy
|
||||
- config-extraction
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1573
|
||||
- T1486
|
||||
d3fend:
|
||||
- D3-SDA
|
||||
- D3-DA
|
||||
references:
|
||||
- 'FIPS 197 (AES key sizes) — https://csrc.nist.gov/publications/detail/fips/197/final'
|
||||
- 'MITRE ATT&CK T1573 Encrypted Channel — https://attack.mitre.org/techniques/T1573/'
|
||||
---
|
||||
|
||||
# Extracting Encryption Keys From Binaries
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a sample with hardcoded crypto and want to surface candidate keys/IVs (16/24/32-byte
|
||||
high-entropy blocks) and passphrase strings.
|
||||
- You are recovering material to decrypt config, C2 traffic, or ransomware test vectors.
|
||||
|
||||
**Do not use** entropy alone as proof a region is a key — corroborate with proximity to crypto
|
||||
constants/APIs and by attempting decryption. This skill reads the binary statically and executes
|
||||
nothing.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- The binary (read inertly). Pairs well with the crypto-constant scanner.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Read bytes statically; handle recovered keys as sensitive and store securely.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Find high-entropy key-sized regions
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py keys sample.bin
|
||||
```
|
||||
|
||||
Slides a window of 16/24/32 bytes and reports regions whose entropy exceeds a threshold (likely
|
||||
random key material), with offsets.
|
||||
|
||||
### Step 2: Surface printable secrets
|
||||
|
||||
Extract printable strings that look like passphrases/hex keys (e.g., 32/64-hex, base64 of 16/32
|
||||
bytes).
|
||||
|
||||
### Step 3: Corroborate
|
||||
|
||||
Rank candidates by proximity to crypto constants/API references and confirm by decrypting a known
|
||||
ciphertext.
|
||||
|
||||
### Step 4: Document
|
||||
|
||||
Record candidate keys, sizes, offsets, and the corroborating evidence.
|
||||
|
||||
## Validation
|
||||
|
||||
- Candidate regions are exactly key-sized (16/24/32 bytes) and high-entropy.
|
||||
- Hex/base64 key strings are length-consistent with a real key size.
|
||||
- A candidate is confirmed only by successful decryption, not entropy alone.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Compressed/packed data producing many false high-entropy regions — unpack first.
|
||||
- Keys derived at runtime (KDF) not present statically.
|
||||
- Endianness/encoding of the key (raw vs hex vs base64) before use.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the extractor.
|
||||
- FIPS 197 and ATT&CK T1573 (linked in frontmatter).
|
||||
@@ -0,0 +1,40 @@
|
||||
# API Reference: Extracting Encryption Keys From Binaries
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` surfaces candidate encryption keys: it finds key-sized (16/24/32-byte)
|
||||
high-entropy, high-diversity regions and extracts printable hex (32–64 chars) and base64 (24–44
|
||||
chars) strings matching common key lengths. It reads bytes statically and executes nothing.
|
||||
Entropy is a heuristic — confirm by decryption.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| (stdlib) | — | math, regex, Counter, JSON |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `keys(path)`
|
||||
Returns high-entropy key candidates (offset/size/entropy/hex), hex-string keys, and base64-string
|
||||
keys.
|
||||
|
||||
### `_high_entropy_regions(data)`
|
||||
Sliding-window search requiring near-maximal byte diversity to reduce false positives.
|
||||
|
||||
## Tuning
|
||||
|
||||
- `KEY_SIZES = (16, 24, 32)` for AES-128/192/256.
|
||||
- `ENTROPY_THRESHOLD` over short windows; raise to reduce noise on packed binaries.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py keys sample.bin
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- FIPS 197 (AES key sizes 128/192/256) — https://csrc.nist.gov/publications/detail/fips/197/final
|
||||
- MITRE ATT&CK T1573 Encrypted Channel — https://attack.mitre.org/techniques/T1573/
|
||||
- High-entropy key-region heuristic from public key-recovery research; confirm by decryption
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Surface candidate encryption keys in a binary.
|
||||
|
||||
Finds key-sized (16/24/32-byte) high-entropy regions and extracts printable hex/base64 strings
|
||||
that match common key lengths. Reports offsets and candidate types. Reads bytes statically;
|
||||
executes nothing. Entropy is a heuristic — confirm candidates by attempting decryption.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
KEY_SIZES = (16, 24, 32)
|
||||
ENTROPY_THRESHOLD = 3.7 # bits over a small window; tuned to flag random-looking blocks
|
||||
RE_HEXKEY = re.compile(rb"\b[0-9a-fA-F]{32,64}\b")
|
||||
RE_B64KEY = re.compile(rb"\b[A-Za-z0-9+/]{24,44}={0,2}\b")
|
||||
|
||||
|
||||
def _entropy(chunk: bytes) -> float:
|
||||
if not chunk:
|
||||
return 0.0
|
||||
counts = Counter(chunk)
|
||||
total = len(chunk)
|
||||
return -sum((c / total) * math.log2(c / total) for c in counts.values())
|
||||
|
||||
|
||||
def _high_entropy_regions(data: bytes) -> list[dict]:
|
||||
out = []
|
||||
step = 8
|
||||
seen_offsets: set[int] = set()
|
||||
for size in KEY_SIZES:
|
||||
for i in range(0, len(data) - size, step):
|
||||
window = data[i:i + size]
|
||||
# require byte diversity close to the window size (random key material)
|
||||
if len(set(window)) >= size - 2 and _entropy(window) >= ENTROPY_THRESHOLD:
|
||||
if all(abs(i - o) >= size for o in seen_offsets):
|
||||
out.append({"offset": i, "size": size,
|
||||
"entropy": round(_entropy(window), 3),
|
||||
"hex": window.hex()})
|
||||
seen_offsets.add(i)
|
||||
if len(out) > 200:
|
||||
return out
|
||||
return out
|
||||
|
||||
|
||||
def keys(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
hex_keys = sorted({m.decode("ascii") for m in RE_HEXKEY.findall(data)})[:50]
|
||||
b64_keys = sorted({m.decode("ascii") for m in RE_B64KEY.findall(data)})[:50]
|
||||
return {
|
||||
"high_entropy_key_candidates": _high_entropy_regions(data)[:50],
|
||||
"hex_string_keys": hex_keys,
|
||||
"base64_string_keys": b64_keys,
|
||||
"note": "confirm any candidate by decrypting known ciphertext; entropy alone is weak",
|
||||
}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Extract candidate keys from a binary")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("keys")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(keys(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user