Runtime heap exploitation primitive detector for Linux binaries.
HeapSentinel instruments a target process with Frida and classifies heap bugs — use-after-free, double-free, heap overflow, and tcache poisoning — as they happen, mapping each finding to the exploitation technique it enables. A second static analysis layer (angr) covers code paths that never execute at runtime.
$ python3 main.py -t ./targets/vuln_heap -m both -T 10
[FINDING] Use-After-Free @ 0x1ffe27d0 → Tcache Poisoning High
[FINDING] Double-Free @ 0x1ffe27d0 → Tcache Dup High
[FINDING] Heap-Overflow @ 0x1ffe2840 → Chunk Overlap High
- Dynamic analysis — Frida agent hooks
malloc,free,memset,memcpy,strcpyand monitors heap state in real time - Static analysis — angr CFG analysis finds double-free and UAF paths in code that doesn't execute
- Technique mapping — each primitive is mapped to the exploitation technique it enables (tcache poisoning, fastbin dup, house of force, etc.)
- Two output layers — live terminal table + HTML/JSON reports with call stacks
- Crash-resistant — per-event flushing means findings survive process crashes before return
- Zero false positives on the how2heap benchmark (100% precision, dynamic layer)
| Primitive | Detection method | Example technique |
|---|---|---|
| Use-After-Free | Write to freed chunk via libc hook | Tcache Poisoning |
| Double-Free (A→A) | Consecutive frees, no malloc between | Tcache Dup |
| Double-Free (A→B→A) | Fastbin bypass pattern | Fastbin Dup |
| Heap Overflow | Write past chunk boundary | Chunk Overlap via Size Corruption |
| Overlap malloc | New allocation overlaps live chunk | overlapping_chunks technique |
| Suspicious malloc | malloc returns untracked address | Tcache Poisoning (crash-before-return) |
| Non-heap free (static) | Stack pointer passed to free() | House of Spirit |
Tested against the how2heap benchmark (glibc 2.31 + 2.35) and three CTF-style validation targets.
| Mode | Precision | Recall | F1 |
|---|---|---|---|
| Dynamic only | 100% | 68.4% | 81.2% |
| Combined (dynamic + static) | 94.1% | 84.2% | 88.9% |
Static analysis contributes a +15.8% recall improvement by catching primitives on crash-before-return paths and code that is never reached at runtime.
Requirements: Linux x86-64, Python 3.10+, root or ptrace capability for Frida.
git clone https://github.com/userIssa/heapsentinel
cd heapsentinel
python3 -m venv .venv
source .venv/bin/activate
# Core (dynamic analysis)
pip install frida frida-tools rich click jinja2
# Optional (static analysis)
pip install angrangr is optional —
--mode dynamicworks without it. Static analysis adds significant install size (~500MB) but pushes recall from 68% to 84%.
# Compile the included test target
gcc -o targets/vuln_heap targets/vuln_heap.c -no-pie -fno-stack-protector -g
# Run dynamic analysis (10 second timeout)
python3 main.py -t targets/vuln_heap -m dynamic -T 10
# Run static analysis only (no Frida, no root needed)
python3 main.py -t targets/vuln_heap -m static
# Run both layers together
python3 main.py -t targets/vuln_heap -m both -T 10
# Attach to a running process
python3 main.py -p <PID> -m dynamic -T 30
# Verbose — see every malloc/free/write event live
python3 main.py -t targets/vuln_heap -m dynamic -v
# No binary? Try the built-in demo
python3 main.py --demopython3 main.py [OPTIONS]
Options:
-t, --target TEXT Path to target binary
-p, --pid INTEGER Attach to running process by PID
-a, --args TEXT Arguments for target binary
-m, --mode [dynamic|static|both] Analysis mode [default: dynamic]
-T, --timeout INTEGER Monitor timeout in seconds [default: 30]
-o, --output TEXT Report output name [default: output]
-v, --verbose Print every heap event as it arrives
--demo Run built-in demo, no binary needed
-h, --help Show this message and exit
Reports are written to reports/<name>.html and reports/<name>.json.
Target binary
│
▼
Frida JS agent ← hooks malloc/free/memset/memcpy/strcpy
│ event_batch
▼
FridaController ← ingests events, maintains ordering
│
▼
HeapStateTracker ← shadow heap map, chunk lifecycle
│
▼
PrimitiveClassifier ← UAF / DF / overflow / overlap detection
│
▼
TechniqueMapper ← maps primitive → exploitation technique
│
▼
HTML + JSON report
angr static layer (parallel)
│
▼
CFGFast + callgraph ← function discovery via nm + angr
│
▼
Reachability analysis ← double-free paths, UAF paths, non-heap free
│
▼
Static findings merged into report
# Compile how2heap targets (glibc 2.35 recommended)
cd /path/to/how2heap
for f in glibc_2.35/fastbin_dup.c glibc_2.35/overlapping_chunks.c \
glibc_2.35/house_of_botcake.c glibc_2.35/tcache_poisoning.c \
glibc_2.35/house_of_spirit.c glibc_2.35/house_of_lore.c \
glibc_2.35/large_bin_attack.c glibc_2.35/poison_null_byte.c; do
name=g2.35_$(basename $f .c)
gcc -o $name $f -g 2>/dev/null && echo "✓ $name"
done
# Run evaluation harnesses
cd /path/to/heapsentinel
bash run_eval.sh /path/to/how2heap
bash run_eval_combined.sh /path/to/how2heapheapsentinel/
├── main.py # CLI entry point
├── core/
│ ├── heap_state.py # Shadow heap map + event log
│ ├── primitive_classifier.py # UAF / DF / overflow detectors
│ └── technique_mapper.py # Primitive → technique mapping
├── instrumentation/
│ ├── frida_agent.js # JS agent injected into target
│ └── frida_controller.py # Python Frida session manager
├── analysis/
│ └── static_analyser.py # angr CFG-based static analysis
├── reporting/
│ └── report_generator.py # HTML + JSON report generation
├── targets/
│ ├── vuln_heap.c # Deliberately vulnerable test binary
│ ├── ctf_uaf_vtable.c # CTF-style UAF validation target
│ ├── ctf_heap_feng_shui.c # CTF-style overflow validation target
│ └── ctf_tcache_chain.c # CTF-style tcache validation target
├── tests/
│ └── run_tests.py # 27-test stdlib-only test suite
├── run_eval.sh # Dynamic-only evaluation harness
└── run_eval_combined.sh # Combined dynamic+static evaluation
- Direct memory writes — raw pointer stores (
*(ptr) = val) bypass all libc hooks. Techniques likeunsafe_unlinkthat corrupt fd/bk directly require CPU-level instrumentation (Frida Stalker or hardware watchpoints) to detect dynamically. - Pointer provenance — UAF via a stale pointer used after reallocation to the same address is not detected, as the memory is live at that point. Full coverage requires taint tracking.
- Static FP rate — the static layer trades some precision for recall. Double-free paths flagged statically may involve different pointers that alias to the same address.
- glibc only — the Frida agent targets glibc's malloc implementation. musl, jemalloc, and tcmalloc are not supported.
- Kali Linux (glibc 2.35 + 2.31)
- Python 3.12
- Frida 16.x
- angr 9.2.x
MIT