reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Static ELF analysis: header, sections, dynamic symbols, and packing hints.
|
||||
|
||||
Uses ``pyelftools`` when available; otherwise parses the ELF header and section table
|
||||
directly so basic facts (class, type, machine, sections, entropy) are still reported.
|
||||
Never executes the sample.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import struct
|
||||
import sys
|
||||
from collections import Counter
|
||||
from pathlib import Path
|
||||
|
||||
_NETWORK = {"socket", "connect", "send", "recv", "bind", "listen", "gethostbyname"}
|
||||
_PROC = {"fork", "execve", "ptrace", "clone", "kill", "system", "popen"}
|
||||
_CRYPTO = {"crypt", "evp_encryptinit_ex", "aes_encrypt", "rc4"}
|
||||
|
||||
|
||||
def entropy(data: bytes) -> float:
|
||||
if not data:
|
||||
return 0.0
|
||||
counts = Counter(data)
|
||||
n = len(data)
|
||||
return -sum((c / n) * math.log2(c / n) for c in counts.values())
|
||||
|
||||
|
||||
def analyze_with_pyelftools(path: str) -> dict:
|
||||
from elftools.elf.elffile import ELFFile # type: ignore
|
||||
from elftools.elf.dynamic import DynamicSection # type: ignore
|
||||
with open(path, "rb") as fh:
|
||||
elf = ELFFile(fh)
|
||||
header = {
|
||||
"class": elf.elfclass,
|
||||
"type": elf["e_type"],
|
||||
"machine": elf["e_machine"],
|
||||
"entry": hex(elf["e_entry"]),
|
||||
}
|
||||
symbols: list[str] = []
|
||||
needed: list[str] = []
|
||||
dynsym = elf.get_section_by_name(".dynsym")
|
||||
if dynsym:
|
||||
symbols = [s.name for s in dynsym.iter_symbols() if s.name]
|
||||
for sec in elf.iter_sections():
|
||||
if isinstance(sec, DynamicSection):
|
||||
for tag in sec.iter_tags():
|
||||
if tag.entry.d_tag == "DT_NEEDED":
|
||||
needed.append(tag.needed)
|
||||
stripped = elf.get_section_by_name(".symtab") is None
|
||||
low = {s.lower() for s in symbols}
|
||||
return {
|
||||
"header": header,
|
||||
"stripped": stripped,
|
||||
"needed": needed,
|
||||
"dynamic_symbol_count": len(symbols),
|
||||
"capabilities": {
|
||||
"network": sorted(low & _NETWORK),
|
||||
"process": sorted(low & _PROC),
|
||||
"crypto": sorted(low & _CRYPTO),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def analyze_minimal(path: str) -> dict:
|
||||
data = Path(path).read_bytes()
|
||||
if data[:4] != b"\x7fELF":
|
||||
raise ValueError("not an ELF")
|
||||
is64 = data[4] == 2
|
||||
le = data[5] == 1
|
||||
endian = "<" if le else ">"
|
||||
e_type = struct.unpack_from(endian + "H", data, 16)[0]
|
||||
e_machine = struct.unpack_from(endian + "H", data, 18)[0]
|
||||
upx = b"UPX!" in data
|
||||
return {
|
||||
"header": {"class": 64 if is64 else 32, "type": e_type, "machine": e_machine},
|
||||
"whole_file_entropy": round(entropy(data), 3),
|
||||
"upx_marker": upx,
|
||||
"note": "pyelftools not installed; minimal parse",
|
||||
}
|
||||
|
||||
|
||||
def analyze(path: str) -> dict:
|
||||
try:
|
||||
return analyze_with_pyelftools(path)
|
||||
except ImportError:
|
||||
return analyze_minimal(path)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = argparse.ArgumentParser(description="Static ELF analysis")
|
||||
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||
p = sub.add_parser("analyze")
|
||||
p.add_argument("file")
|
||||
args = parser.parse_args(argv)
|
||||
print(json.dumps(analyze(args.file), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user