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,99 @@
|
||||
---
|
||||
name: analyzing-elf-binaries-on-linux
|
||||
description: 'Statically analyzes Linux ELF malware: ELF header and sections, dynamic symbols
|
||||
and imports, segment permissions, embedded strings, and packing indicators to infer
|
||||
capability without execution. Activates for requests to analyze an ELF binary, Linux
|
||||
malware, or shared object.'
|
||||
domain: cybersecurity
|
||||
subdomain: malware-analysis
|
||||
tags:
|
||||
- malware
|
||||
- static-analysis
|
||||
- elf
|
||||
- linux
|
||||
- symbols
|
||||
- entropy
|
||||
version: 1.0.0
|
||||
author: analyst-ai-pack
|
||||
license: Apache-2.0
|
||||
mitre_attack:
|
||||
- T1027
|
||||
- T1059.004
|
||||
- T1129
|
||||
d3fend:
|
||||
- D3-FA
|
||||
- D3-FCR
|
||||
references:
|
||||
- 'ELF specification (System V ABI) — https://refspecs.linuxfoundation.org/elf/elf.pdf'
|
||||
- 'pyelftools — https://github.com/eliben/pyelftools'
|
||||
---
|
||||
|
||||
# Analyzing ELF Binaries on Linux
|
||||
|
||||
## When to Use
|
||||
|
||||
- You have a Linux executable or shared object and need to assess it statically.
|
||||
- You want to read the ELF header, segments, dynamic symbols, and strings to infer behavior.
|
||||
- You are checking for static linking, stripped symbols, or packing.
|
||||
|
||||
**Do not use** symbol absence as proof of nothing — stripped or statically linked Go/Rust
|
||||
binaries hide structure; switch to disassembly when static metadata is thin.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `readelf`/`nm`/`strings` (binutils) or `pyelftools` (`pip install pyelftools`).
|
||||
- The sample in neutralized form inside the lab.
|
||||
|
||||
## Safety & Handling
|
||||
|
||||
- Static only: parse the file; do not set it executable or run it.
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Read the ELF header
|
||||
|
||||
```bash
|
||||
python scripts/analyst.py analyze sample.elf
|
||||
```
|
||||
|
||||
Note class (ELF32/64), endianness, type (EXEC/DYN/REL), machine (x86-64, ARM, MIPS), and
|
||||
whether it is stripped.
|
||||
|
||||
### Step 2: Examine segments and section permissions
|
||||
|
||||
Writable+executable segments, or a single large segment, suggest packing or self-modifying
|
||||
code. Compare `PT_LOAD` permissions against expectations.
|
||||
|
||||
### Step 3: Inspect dynamic symbols and needed libraries
|
||||
|
||||
Imported functions hint at capability: `socket`/`connect` (network), `ptrace` (anti-debug or
|
||||
injection), `fork`/`execve` (process control), `crypt`/`EVP_*` (encryption).
|
||||
|
||||
### Step 4: Detect static linking and packing
|
||||
|
||||
Static binaries lack a dynamic symbol table and `NEEDED` entries. High whole-file entropy and
|
||||
a tiny section table suggest a packer (e.g. UPX leaves `UPX!` markers).
|
||||
|
||||
### Step 5: Skim strings and constructors
|
||||
|
||||
Check `.init_array`/constructors (code before `main`), and strings for paths, URLs, and shell
|
||||
commands.
|
||||
|
||||
## Validation
|
||||
|
||||
- Header `type` and segment permissions are consistent with the inferred behavior.
|
||||
- Imported symbols correspond to plausible capabilities.
|
||||
- Packing call (entropy + missing sections) matches the strings observed.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Assuming dynamic symbols are complete — they only cover imported/exported names, not
|
||||
internal functions.
|
||||
- Treating a stripped Go binary as "empty"; its structure lives in runtime metadata, not the
|
||||
symbol table.
|
||||
- Ignoring constructors/`.init_array`, which run before `main`.
|
||||
|
||||
## References
|
||||
|
||||
- See [`references/api-reference.md`](references/api-reference.md) for the ELF analyzer.
|
||||
- ELF/System V ABI spec and pyelftools (linked in frontmatter).
|
||||
@@ -0,0 +1,43 @@
|
||||
# API Reference: Analyzing ELF Binaries on Linux
|
||||
|
||||
## Overview
|
||||
|
||||
`scripts/analyst.py` reports ELF header facts, dynamic symbols and `NEEDED` libraries, a strip
|
||||
check, and capability groupings (network/process/crypto). It uses `pyelftools` when present
|
||||
and falls back to a minimal header/entropy parse otherwise.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Package | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| pyelftools | >= 0.30 | Full ELF parsing (optional) |
|
||||
| (stdlib) | — | Header/entropy fallback |
|
||||
|
||||
## External tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| readelf / nm (binutils) | Authoritative ELF inspection |
|
||||
| strings | Embedded string review |
|
||||
|
||||
## Core functions
|
||||
|
||||
### `analyze(path)`
|
||||
Dispatches to the pyelftools or minimal parser; returns header, symbols, needed libs, and
|
||||
capability groupings.
|
||||
|
||||
### `entropy(data)`
|
||||
Byte-level Shannon entropy for packing assessment.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
pip install pyelftools # optional
|
||||
python scripts/analyst.py analyze sample.elf
|
||||
```
|
||||
|
||||
## Sources
|
||||
|
||||
- ELF / System V ABI specification — https://refspecs.linuxfoundation.org/elf/elf.pdf
|
||||
- pyelftools — https://github.com/eliben/pyelftools
|
||||
- UPX section/marker conventions (vendor docs)
|
||||
@@ -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