mirror of
https://gitlab.com/BinaryHardening/cfgrip
synced 2026-07-26 12:41:08 +00:00
Function boundary detection: multi-pass prolog, tail-call, .pdata, data pointers
- Extended prolog patterns: MSVC x64 callee-saves, sub rsp >= 0x20, enter - Tail-call detection: jmp to prolog candidates seeds new functions - endbr64/endbr32 CET skipping at function starts - PE .pdata exception table parsing for precise start/end boundaries - Data-section function pointer scanning (vtables, callbacks) - PLT stub detection with is_thunk tag - end_address and is_thunk fields in Function struct and JSON output - getDataRanges() and getRuntimeFunctions() in Binary interface - README docs for all new features + research paper reference
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
build/
|
||||
build_wsl/
|
||||
tests/*.subs.cfg
|
||||
tests/*.clean.cfg
|
||||
tests/*.subs.clean.cfg
|
||||
|
||||
@@ -12,11 +12,12 @@ cfgrip gives you the map. What you do with it is up to you.
|
||||
|
||||
For every binary cfgrip processes, it produces:
|
||||
|
||||
- **Function list** with addresses and names (entry point, exports, discovered via `call` instructions)
|
||||
- **Function list** with addresses, optional end address (from `.pdata`), names (entry point, exports, discovered), and thunk tagging (PLT stubs)
|
||||
- **Basic blocks** per function — instruction sequences terminated by branches, calls, returns, or traps
|
||||
- **Control flow edges** — successors of each block (direct branches, fall-through, indirect targets)
|
||||
- **Import table** — resolved library imports with addresses
|
||||
- **Indirect targets** — GOT-resolved calls, jump table entries, register-traced branches, and unresolved ones (marked as such)
|
||||
- **Function boundaries** — multiple detection passes (prolog patterns, call targets, tail calls, `.pdata` entries, data-section function pointers) for maximum coverage
|
||||
|
||||
With `--clean`, it additionally:
|
||||
|
||||
@@ -133,8 +134,8 @@ imports: 85
|
||||
0x140020290 HeapSize (KERNEL32.dll)
|
||||
0x140020298 CreateFileW (KERNEL32.dll)
|
||||
0x1400202a0 RtlUnwind (KERNEL32.dll)
|
||||
functions: 414
|
||||
indirect targets: 1372
|
||||
functions: 1975
|
||||
indirect targets: 3453
|
||||
cfg written to: tests\example1.exe.cfg
|
||||
```
|
||||
---
|
||||
@@ -384,6 +385,10 @@ The `.cfg` file is structured JSON. Here's what it looks like:
|
||||
...
|
||||
```
|
||||
|
||||
Each function now includes optional fields:
|
||||
- `end_address` — precise function end when available (from PE `.pdata` exception table), otherwise computed as the maximum instruction address across all blocks
|
||||
- `is_thunk` — `true` for PLT stubs and import thunks (functions that only redirect to another address)
|
||||
|
||||
### `--subs-only` output
|
||||
|
||||
```
|
||||
@@ -501,6 +506,20 @@ Every instruction in `--clean` mode includes `stack_offset` — the RSP delta fr
|
||||
|
||||
Same structure as `--clean`, but with `"mode": "subs-only+clean"` to indicate both filters were applied. Function count is reduced to only entry-point-reachable functions, and remaining functions have stack offsets and xrefs.
|
||||
|
||||
## function boundary detection
|
||||
|
||||
cfgrip discovers functions through multiple detection passes:
|
||||
|
||||
| Pass | What it detects | Covers |
|
||||
|---|---|---|
|
||||
| **Prolog scanning** | `push rbp`, `push r15`/`r14`/`r13`/`r12`/`rbx`/`rdi`/`rsi`, `sub rsp, >=0x20`, `enter` | MSVC x64, GCC, leaf functions, CET (`endbr64`) |
|
||||
| **Call targets** | Every `call` instruction target is a function start | Direct and GOT-resolved indirect calls |
|
||||
| **Tail calls** | `jmp` instructions targeting prolog candidates | Optimized tail-call chains |
|
||||
| **`.pdata` (PE)** | Runtime function entries from the exception handler table | Precise start/end for every x64 PE function |
|
||||
| **Data pointers** | 8-byte values in `.rdata`/`.data` pointing into executable code | Function pointers, vtables, callbacks |
|
||||
|
||||
Functions with `is_thunk: true` are PLT stubs or import thunks — single-block functions that redirect to another address.
|
||||
|
||||
## building
|
||||
|
||||
Requires CMake and a C++17 compiler. Capstone is fetched automatically.
|
||||
@@ -541,8 +560,31 @@ cmake --build build --config Release
|
||||
| **Function discovery** | Entry point | YES |
|
||||
| | Exports | YES |
|
||||
| | `call` targets | YES |
|
||||
| | Prolog scanning (`push rbp`, CET `endbr64`) | YES |
|
||||
| | Prolog scanning (MSVC x64, GCC, CET `endbr64`) | YES |
|
||||
| | Tail-call detection (`jmp` → function) | YES |
|
||||
| | PE `.pdata` (exception handler table) | YES |
|
||||
| | Data-section function pointer scan | YES |
|
||||
| **Thunk detection** | PLT stubs / import thunks (`is_thunk`) | YES |
|
||||
| **Function boundaries** | `end_address` from `.pdata` or max instruction | YES |
|
||||
| **Subs-only mode** | `--subs-only` flag | YES |
|
||||
| **CFG cleaning** | `--clean` (jump-thread, dead-block prune, stack deltas, xrefs) | YES |
|
||||
|
||||
## research
|
||||
|
||||
Function boundary detection in this tool is based on the approach described in **"Function Boundary Detection in Stripped Binaries"** (Alves-Foss & Song, 2019), which introduces a multi-heuristic algorithm for locating function starts and ends in stripped x86/x64 binaries.
|
||||
|
||||
The paper is available at `papers/Function_Boundary_Detection_in_Stripped_Binaries.pdf`.
|
||||
|
||||
**How our implementation maps to the paper's heuristics:**
|
||||
|
||||
| Heuristic | Paper description | Our implementation |
|
||||
|---|---|---|
|
||||
| H1–H4 | Prolog signatures (`push rbp`, callee-saved regs, stack sub, `enter`) | `isProlog()` in `disasm/engine.cpp` — detects `push rbp`, `push r15..rbx`, `sub rsp >= 0x20`, `enter` |
|
||||
| H5 | Call-target seeding | Every direct `call` target is a function start |
|
||||
| H6 | Jump-to-function (tail call) detection | `jmp` to prolog candidates adds target to function queue |
|
||||
| H7 | Exception table parsing | PE `.pdata` RUNTIME_FUNCTION entries give precise start/end |
|
||||
| H8 | Data reference analysis | `scanDataPointers()` walks data sections for code pointers |
|
||||
|
||||
The paper's key insight is that algorithmic heuristics — without machine learning — can achieve high accuracy on stripped binaries. Our implementation follows this philosophy, using a multi-pass approach where each pass catches functions the others might miss.
|
||||
|
||||
|
||||
|
||||
+150
-22
@@ -20,6 +20,20 @@ static bool regMatch(const string& op, const string& target)
|
||||
CFGBuilder::CFGBuilder(Binary* binary, Disassembler* disasm, bool subsOnly)
|
||||
: m_bin(binary), m_dis(disasm), m_subs_only(subsOnly) {}
|
||||
|
||||
addr_t CFGBuilder::skipEndbr64(addr_t addr)
|
||||
{
|
||||
auto bytes = m_bin->readBytes(addr, 4);
|
||||
if (bytes.size() >= 4)
|
||||
{
|
||||
// endbr64: F3 0F 1E FA
|
||||
// endbr32: F3 0F 1E FB
|
||||
if (bytes[0] == 0xF3 && bytes[1] == 0x0F && bytes[2] == 0x1E &&
|
||||
(bytes[3] == 0xFA || bytes[3] == 0xFB))
|
||||
return addr + 4;
|
||||
}
|
||||
return addr;
|
||||
}
|
||||
|
||||
bool CFGBuilder::isVisited(addr_t addr)
|
||||
{
|
||||
return m_blocks.count(addr) > 0;
|
||||
@@ -239,7 +253,12 @@ BasicBlock CFGBuilder::disassembleBlock(addr_t start, addr_t& next)
|
||||
{
|
||||
addr_t t = m_dis->getBranchTarget(inst);
|
||||
if (t && !m_dis->isIndirectBranch(inst))
|
||||
{
|
||||
t = skipEndbr64(t);
|
||||
block.successors.push_back(t);
|
||||
if (m_prologs.count(t))
|
||||
m_funcs.insert(t);
|
||||
}
|
||||
else if (m_dis->isIndirectBranch(inst))
|
||||
{
|
||||
addr_t resolved = resolveGOT(inst);
|
||||
@@ -322,6 +341,7 @@ BasicBlock CFGBuilder::disassembleBlock(addr_t start, addr_t& next)
|
||||
addr_t t = m_dis->getBranchTarget(inst);
|
||||
if (t && !m_dis->isIndirectBranch(inst))
|
||||
{
|
||||
t = skipEndbr64(t);
|
||||
m_funcs.insert(t);
|
||||
recordIndirect(inst, t, true, "", "");
|
||||
}
|
||||
@@ -342,6 +362,7 @@ BasicBlock CFGBuilder::disassembleBlock(addr_t start, addr_t& next)
|
||||
((uint64_t)gb[6] << 48) | ((uint64_t)gb[7] << 56);
|
||||
if (target && target < 0x100000000ULL) resolved = target;
|
||||
}
|
||||
resolved = skipEndbr64(resolved);
|
||||
m_funcs.insert(resolved);
|
||||
recordIndirect(inst, resolved, true, nm, lb);
|
||||
}
|
||||
@@ -352,6 +373,7 @@ BasicBlock CFGBuilder::disassembleBlock(addr_t start, addr_t& next)
|
||||
resolved = traceReg(reg, block.instructions, block.instructions.size() - 1);
|
||||
if (resolved)
|
||||
{
|
||||
resolved = skipEndbr64(resolved);
|
||||
m_funcs.insert(resolved);
|
||||
recordIndirect(inst, resolved, true, "traced", "");
|
||||
}
|
||||
@@ -411,8 +433,10 @@ Function CFGBuilder::buildFunction(addr_t start, const string& name)
|
||||
func.address = start;
|
||||
func.name = name;
|
||||
|
||||
addr_t actual = skipEndbr64(start);
|
||||
|
||||
queue<addr_t> q;
|
||||
q.push(start);
|
||||
q.push(actual);
|
||||
|
||||
while (!q.empty())
|
||||
{
|
||||
@@ -428,9 +452,66 @@ Function CFGBuilder::buildFunction(addr_t start, const string& name)
|
||||
func.blocks.push_back(block);
|
||||
}
|
||||
|
||||
// Compute end_address as max instruction address across all blocks
|
||||
for (const auto& b : func.blocks)
|
||||
for (const auto& inst : b.instructions)
|
||||
if (inst.address + inst.size > func.end_address)
|
||||
func.end_address = inst.address + inst.size;
|
||||
|
||||
// Tag PLT stubs
|
||||
if (isPLTStub(func)) func.is_thunk = true;
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
bool CFGBuilder::isPLTStub(const Function& func) const
|
||||
{
|
||||
if (func.blocks.size() != 1) return false;
|
||||
const auto& block = func.blocks[0];
|
||||
if (block.instructions.empty()) return false;
|
||||
const auto& first = block.instructions[0];
|
||||
if (first.mnemonic == "jmp" && first.operands.find('[') != string::npos)
|
||||
{
|
||||
int64_t disp = m_dis->getRIPDisp(first);
|
||||
if (disp != 0)
|
||||
{
|
||||
addr_t target = first.address + first.size + disp;
|
||||
if (m_got.count(target)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CFGBuilder::scanDataPointers()
|
||||
{
|
||||
auto ranges = m_bin->getDataRanges();
|
||||
auto exec = m_bin->getExecutableRanges();
|
||||
|
||||
auto isInExec = [&](addr_t addr) -> bool {
|
||||
for (const auto& r : exec)
|
||||
if (addr >= r.first && addr < r.second) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
for (const auto& r : ranges)
|
||||
{
|
||||
addr_t a = r.first;
|
||||
// Align to 8 bytes
|
||||
if (a & 7) a = (a + 7) & ~7ULL;
|
||||
for (; a + 8 <= r.second; a += 8)
|
||||
{
|
||||
auto bytes = m_bin->readBytes(a, 8);
|
||||
if (bytes.size() < 8) continue;
|
||||
uint64_t val = bytes[0] | ((uint64_t)bytes[1] << 8) |
|
||||
((uint64_t)bytes[2] << 16) | ((uint64_t)bytes[3] << 24) |
|
||||
((uint64_t)bytes[4] << 32) | ((uint64_t)bytes[5] << 40) |
|
||||
((uint64_t)bytes[6] << 48) | ((uint64_t)bytes[7] << 56);
|
||||
if (val && isInExec(val))
|
||||
m_funcs.insert(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CFG CFGBuilder::build()
|
||||
{
|
||||
CFG cfg;
|
||||
@@ -458,36 +539,67 @@ CFG CFGBuilder::build()
|
||||
set<addr_t> built;
|
||||
queue<addr_t> pending;
|
||||
|
||||
// Apply .pdata end_address to functions
|
||||
auto pdata = m_bin->getRuntimeFunctions();
|
||||
auto setEndAddress = [&](Function& func) {
|
||||
for (const auto& rf : pdata)
|
||||
if (rf.first == func.address)
|
||||
{
|
||||
func.end_address = rf.second;
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
auto isEmpty = [](const Function& f) -> bool {
|
||||
for (const auto& b : f.blocks)
|
||||
if (!b.instructions.empty()) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
auto scanCallAndJmpTargets = [&](const Function& func) {
|
||||
for (const auto& b : func.blocks)
|
||||
for (const auto& inst : b.instructions)
|
||||
{
|
||||
if (m_dis->isCall(inst))
|
||||
{
|
||||
addr_t t = m_dis->getBranchTarget(inst);
|
||||
if (t && !m_dis->isIndirectBranch(inst))
|
||||
{
|
||||
t = skipEndbr64(t);
|
||||
m_funcs.insert(t);
|
||||
}
|
||||
}
|
||||
if (m_dis->isUnconditionalBranch(inst))
|
||||
{
|
||||
addr_t t = m_dis->getBranchTarget(inst);
|
||||
if (t && !m_dis->isIndirectBranch(inst))
|
||||
{
|
||||
t = skipEndbr64(t);
|
||||
if (m_prologs.count(t))
|
||||
m_funcs.insert(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto process = [&](addr_t addr, const string& name)
|
||||
{
|
||||
addr = skipEndbr64(addr);
|
||||
if (built.count(addr)) return;
|
||||
built.insert(addr);
|
||||
|
||||
auto func = buildFunction(addr, name);
|
||||
if (isEmpty(func)) return;
|
||||
setEndAddress(func);
|
||||
cfg.functions.push_back(func);
|
||||
|
||||
for (const auto& b : func.blocks)
|
||||
for (const auto& inst : b.instructions)
|
||||
if (m_dis->isCall(inst))
|
||||
{
|
||||
addr_t t = m_dis->getBranchTarget(inst);
|
||||
if (t && !m_dis->isIndirectBranch(inst))
|
||||
m_funcs.insert(t);
|
||||
}
|
||||
scanCallAndJmpTargets(func);
|
||||
|
||||
for (auto d : m_funcs)
|
||||
if (!built.count(d)) pending.push(d);
|
||||
};
|
||||
|
||||
{
|
||||
addr_t ep = m_bin->getEntryPoint();
|
||||
addr_t ep = skipEndbr64(m_bin->getEntryPoint());
|
||||
string ep_name = "entry";
|
||||
for (const auto& exp : m_bin->getExportedFunctions())
|
||||
if (exp.first == ep) { ep_name = exp.second; break; }
|
||||
@@ -497,7 +609,10 @@ CFG CFGBuilder::build()
|
||||
if (!m_subs_only)
|
||||
{
|
||||
for (const auto& exp : m_bin->getExportedFunctions())
|
||||
pending.push(exp.first);
|
||||
pending.push(skipEndbr64(exp.first));
|
||||
for (const auto& rf : pdata)
|
||||
if (rf.first != m_bin->getEntryPoint())
|
||||
pending.push(skipEndbr64(rf.first));
|
||||
}
|
||||
|
||||
map<addr_t, string> imp_map;
|
||||
@@ -507,6 +622,7 @@ CFG CFGBuilder::build()
|
||||
while (!pending.empty())
|
||||
{
|
||||
addr_t a = pending.front(); pending.pop();
|
||||
a = skipEndbr64(a);
|
||||
if (built.count(a)) continue;
|
||||
|
||||
string name;
|
||||
@@ -518,19 +634,11 @@ CFG CFGBuilder::build()
|
||||
if (it != imp_map.end()) name = it->second;
|
||||
}
|
||||
|
||||
built.insert(a);
|
||||
auto func = buildFunction(a, name);
|
||||
if (isEmpty(func)) continue;
|
||||
setEndAddress(func);
|
||||
cfg.functions.push_back(func);
|
||||
|
||||
for (const auto& b : func.blocks)
|
||||
for (const auto& inst : b.instructions)
|
||||
if (m_dis->isCall(inst))
|
||||
{
|
||||
addr_t t = m_dis->getBranchTarget(inst);
|
||||
if (t && !m_dis->isIndirectBranch(inst))
|
||||
if (!built.count(t)) pending.push(t);
|
||||
}
|
||||
scanCallAndJmpTargets(func);
|
||||
|
||||
if (!m_subs_only)
|
||||
{
|
||||
@@ -539,6 +647,26 @@ CFG CFGBuilder::build()
|
||||
}
|
||||
}
|
||||
|
||||
// Add data-section function pointers as seeds (if not subs-only)
|
||||
if (!m_subs_only)
|
||||
{
|
||||
scanDataPointers();
|
||||
for (auto d : m_funcs)
|
||||
if (!built.count(d)) pending.push(d);
|
||||
while (!pending.empty())
|
||||
{
|
||||
addr_t a = pending.front(); pending.pop();
|
||||
a = skipEndbr64(a);
|
||||
if (built.count(a)) continue;
|
||||
|
||||
auto func = buildFunction(a, "");
|
||||
if (isEmpty(func)) continue;
|
||||
setEndAddress(func);
|
||||
cfg.functions.push_back(func);
|
||||
scanCallAndJmpTargets(func);
|
||||
}
|
||||
}
|
||||
|
||||
cfg.indirect_targets = m_targets;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ class CFGBuilder
|
||||
Function buildFunction(addr_t start, const string& name);
|
||||
BasicBlock disassembleBlock(addr_t start, addr_t& next);
|
||||
set<addr_t> findPrologs();
|
||||
void scanDataPointers();
|
||||
addr_t skipEndbr64(addr_t addr);
|
||||
bool isPLTStub(const Function& func) const;
|
||||
void markVisited(addr_t addr);
|
||||
bool isVisited(addr_t addr);
|
||||
|
||||
|
||||
@@ -57,6 +57,32 @@ bool Disassembler::isProlog(const vector<Instruction>& insts) const
|
||||
{
|
||||
if (inst.mnemonic == "push" && (inst.operands == "rbp" || inst.operands == "ebp"))
|
||||
return true;
|
||||
if (inst.mnemonic == "push")
|
||||
{
|
||||
const string& ops = inst.operands;
|
||||
if (ops == "r15" || ops == "r14" || ops == "r13" || ops == "r12" ||
|
||||
ops == "rbx" || ops == "rdi" || ops == "rsi")
|
||||
return true;
|
||||
}
|
||||
if (inst.mnemonic == "sub")
|
||||
{
|
||||
const string& ops = inst.operands;
|
||||
size_t p = ops.find("rsp");
|
||||
if (p == string::npos) p = ops.find("esp");
|
||||
if (p != string::npos && (p == 0 || ops[p-1] == ' ' || ops[p-1] == ','))
|
||||
{
|
||||
size_t x = ops.rfind("0x");
|
||||
if (x != string::npos && x > p)
|
||||
{
|
||||
try {
|
||||
uint64_t val = stoull(ops.substr(x), nullptr, 16);
|
||||
if (val >= 0x20) return true;
|
||||
} catch (...) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (inst.mnemonic == "enter")
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,14 @@ class Binary
|
||||
virtual addr_t getImageBase() const = 0;
|
||||
virtual vector<uint8_t> readBytes(addr_t vaddr, size_t size) const = 0;
|
||||
virtual vector<pair<addr_t, addr_t>> getExecutableRanges() const = 0;
|
||||
virtual vector<pair<addr_t, addr_t>> getDataRanges() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
virtual vector<pair<addr_t, addr_t>> getRuntimeFunctions() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
virtual vector<pair<addr_t, string>> getExportedFunctions() const = 0;
|
||||
virtual vector<ImportEntry> getImportedFunctions() const
|
||||
{
|
||||
|
||||
@@ -290,6 +290,19 @@ bool ELFLoader::parseHeader()
|
||||
}
|
||||
}
|
||||
|
||||
// Collect data section ranges (allocatable, non-executable)
|
||||
for (uint16_t i = 0; i < shnum; i++)
|
||||
{
|
||||
uint64_t sh_flags = 0;
|
||||
size_t so = shoff + i * 64;
|
||||
if (so + 64 > m_data.size()) continue;
|
||||
sh_flags = r64(m_data, so + 8);
|
||||
bool is_exec = (sh_flags & 0x4) != 0;
|
||||
bool is_alloc = (sh_flags & 0x2) != 0;
|
||||
if (is_alloc && !is_exec && sections[i].addr && sections[i].size)
|
||||
m_data_ranges.push_back({sections[i].addr, sections[i].addr + sections[i].size});
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < shnum; i++)
|
||||
{
|
||||
string sname = sectionName(sections[i].name_idx);
|
||||
@@ -391,6 +404,11 @@ vector<uint8_t> ELFLoader::readBytes(addr_t vaddr, size_t size) const
|
||||
return {};
|
||||
}
|
||||
|
||||
vector<pair<addr_t, addr_t>> ELFLoader::getDataRanges() const
|
||||
{
|
||||
return m_data_ranges;
|
||||
}
|
||||
|
||||
vector<pair<addr_t, addr_t>> ELFLoader::getExecutableRanges() const
|
||||
{
|
||||
return m_exec_ranges;
|
||||
|
||||
@@ -16,6 +16,7 @@ class ELFLoader : public Binary
|
||||
addr_t getEntryPoint() const override { return m_entry; }
|
||||
addr_t getImageBase() const override { return 0; }
|
||||
vector<uint8_t> readBytes(addr_t vaddr, size_t size) const override;
|
||||
vector<pair<addr_t, addr_t>> getDataRanges() const override;
|
||||
vector<pair<addr_t, addr_t>> getExecutableRanges() const override;
|
||||
vector<pair<addr_t, string>> getExportedFunctions() const override;
|
||||
vector<ImportEntry> getImportedFunctions() const override;
|
||||
@@ -39,6 +40,7 @@ class ELFLoader : public Binary
|
||||
addr_t m_entry;
|
||||
vector<uint8_t> m_data;
|
||||
vector<pair<addr_t, addr_t>> m_exec_ranges;
|
||||
vector<pair<addr_t, addr_t>> m_data_ranges;
|
||||
vector<tuple<uint64_t, addr_t, uint64_t>> m_file_map;
|
||||
vector<pair<addr_t, string>> m_exports;
|
||||
vector<ImportEntry> m_imports;
|
||||
|
||||
+60
-1
@@ -120,9 +120,58 @@ bool PELoader::parseHeaders()
|
||||
}
|
||||
}
|
||||
|
||||
// Collect data section ranges (non-executable readable)
|
||||
for (const auto& s : sections)
|
||||
{
|
||||
if (s.raw_ptr == 0) continue;
|
||||
bool is_exec = (s.characteristics & 0x20000000) != 0;
|
||||
bool is_read = (s.characteristics & 0x40000000) != 0;
|
||||
if (!is_exec && is_read)
|
||||
{
|
||||
addr_t start = s.vaddr;
|
||||
addr_t end = start + (s.vsize ? s.vsize : s.raw_size);
|
||||
m_data_ranges.push_back({start, end});
|
||||
}
|
||||
}
|
||||
|
||||
// Parse .pdata (exception handler table) for precise function boundaries
|
||||
uint32_t pdata_rva = 0;
|
||||
uint32_t pdata_size = 0;
|
||||
uint32_t data_dir_off = (magic == 0x10B) ? opt_hdr_off + 96 : opt_hdr_off + 112;
|
||||
if (magic == 0x20B)
|
||||
{
|
||||
pdata_rva = r32(m_data, data_dir_off + 24);
|
||||
pdata_size = r32(m_data, data_dir_off + 28);
|
||||
}
|
||||
if (pdata_rva && pdata_size >= 12)
|
||||
{
|
||||
uint32_t pdata_off = 0;
|
||||
for (const auto& s : sections)
|
||||
{
|
||||
addr_t va = pdata_rva + m_image_base;
|
||||
if (va >= s.vaddr && va < s.vaddr + (s.vsize ? s.vsize : s.raw_size))
|
||||
{
|
||||
pdata_off = s.raw_ptr + static_cast<uint32_t>(va - s.vaddr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pdata_off)
|
||||
{
|
||||
size_t nentries = pdata_size / 12;
|
||||
for (size_t i = 0; i < nentries; i++)
|
||||
{
|
||||
uint32_t off = pdata_off + i * 12;
|
||||
if (off + 12 > m_data.size()) break;
|
||||
uint32_t begin_rva = r32(m_data, off);
|
||||
uint32_t end_rva = r32(m_data, off + 4);
|
||||
if (begin_rva && end_rva && end_rva > begin_rva)
|
||||
m_pdata.push_back({begin_rva + m_image_base, end_rva + m_image_base});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_64 = (m_arch == Arch::X64);
|
||||
uint32_t import_rva = 0;
|
||||
uint32_t data_dir_off = (magic == 0x10B) ? opt_hdr_off + 96 : opt_hdr_off + 112;
|
||||
import_rva = r32(m_data, data_dir_off + 8);
|
||||
|
||||
auto rvaToOffset = [&](uint32_t rva) -> uint32_t {
|
||||
@@ -260,11 +309,21 @@ vector<uint8_t> PELoader::readBytes(addr_t vaddr, size_t size) const
|
||||
return result;
|
||||
}
|
||||
|
||||
vector<pair<addr_t, addr_t>> PELoader::getDataRanges() const
|
||||
{
|
||||
return m_data_ranges;
|
||||
}
|
||||
|
||||
vector<pair<addr_t, addr_t>> PELoader::getExecutableRanges() const
|
||||
{
|
||||
return m_exec_ranges;
|
||||
}
|
||||
|
||||
vector<pair<addr_t, addr_t>> PELoader::getRuntimeFunctions() const
|
||||
{
|
||||
return m_pdata;
|
||||
}
|
||||
|
||||
vector<pair<addr_t, string>> PELoader::getExportedFunctions() const
|
||||
{
|
||||
return m_exports;
|
||||
|
||||
@@ -16,7 +16,9 @@ class PELoader : public Binary
|
||||
addr_t getEntryPoint() const override { return m_entry; }
|
||||
addr_t getImageBase() const override { return m_image_base; }
|
||||
vector<uint8_t> readBytes(addr_t vaddr, size_t size) const override;
|
||||
vector<pair<addr_t, addr_t>> getDataRanges() const override;
|
||||
vector<pair<addr_t, addr_t>> getExecutableRanges() const override;
|
||||
vector<pair<addr_t, addr_t>> getRuntimeFunctions() const override;
|
||||
vector<pair<addr_t, string>> getExportedFunctions() const override;
|
||||
vector<ImportEntry> getImportedFunctions() const override;
|
||||
const string& getPath() const override { return m_path; }
|
||||
@@ -32,6 +34,8 @@ class PELoader : public Binary
|
||||
addr_t m_image_base;
|
||||
vector<uint8_t> m_data;
|
||||
vector<pair<addr_t, addr_t>> m_exec_ranges;
|
||||
vector<pair<addr_t, addr_t>> m_data_ranges;
|
||||
vector<pair<addr_t, addr_t>> m_pdata;
|
||||
vector<pair<addr_t, string>> m_exports;
|
||||
vector<ImportEntry> m_imports;
|
||||
};
|
||||
|
||||
Binary file not shown.
+322252
-79879
File diff suppressed because it is too large
Load Diff
@@ -56,8 +56,10 @@ struct BasicBlock
|
||||
struct Function
|
||||
{
|
||||
addr_t address;
|
||||
addr_t end_address = 0;
|
||||
string name;
|
||||
vector<BasicBlock> blocks;
|
||||
bool is_thunk = false;
|
||||
};
|
||||
|
||||
struct CFG
|
||||
@@ -135,7 +137,11 @@ struct CFG
|
||||
const auto& f = functions[i];
|
||||
os << " {\n";
|
||||
os << " \"address\": \"0x" << hex << f.address << "\",\n";
|
||||
if (f.end_address)
|
||||
os << " \"end_address\": \"0x" << hex << f.end_address << "\",\n";
|
||||
os << " \"name\": \"" << escapeJSON(f.name) << "\",\n";
|
||||
if (f.is_thunk)
|
||||
os << " \"is_thunk\": true,\n";
|
||||
os << " \"blocks\": [\n";
|
||||
for (size_t j = 0; j < f.blocks.size(); ++j) {
|
||||
const auto& b = f.blocks[j];
|
||||
|
||||
Reference in New Issue
Block a user