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:
x86byte
2026-06-30 10:04:56 +01:00
parent 236e428dce
commit 6c2dca78ab
13 changed files with 322576 additions and 79906 deletions
+1
View File
@@ -1,4 +1,5 @@
build/ build/
build_wsl/
tests/*.subs.cfg tests/*.subs.cfg
tests/*.clean.cfg tests/*.clean.cfg
tests/*.subs.clean.cfg tests/*.subs.clean.cfg
+46 -4
View File
@@ -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: 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 - **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) - **Control flow edges** — successors of each block (direct branches, fall-through, indirect targets)
- **Import table** — resolved library imports with addresses - **Import table** — resolved library imports with addresses
- **Indirect targets** — GOT-resolved calls, jump table entries, register-traced branches, and unresolved ones (marked as such) - **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: With `--clean`, it additionally:
@@ -133,8 +134,8 @@ imports: 85
0x140020290 HeapSize (KERNEL32.dll) 0x140020290 HeapSize (KERNEL32.dll)
0x140020298 CreateFileW (KERNEL32.dll) 0x140020298 CreateFileW (KERNEL32.dll)
0x1400202a0 RtlUnwind (KERNEL32.dll) 0x1400202a0 RtlUnwind (KERNEL32.dll)
functions: 414 functions: 1975
indirect targets: 1372 indirect targets: 3453
cfg written to: tests\example1.exe.cfg 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 ### `--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. 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 ## building
Requires CMake and a C++17 compiler. Capstone is fetched automatically. 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 | | **Function discovery** | Entry point | YES |
| | Exports | YES | | | Exports | YES |
| | `call` targets | 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 | | **Subs-only mode** | `--subs-only` flag | YES |
| **CFG cleaning** | `--clean` (jump-thread, dead-block prune, stack deltas, xrefs) | 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 |
|---|---|---|
| H1H4 | 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
View File
@@ -20,6 +20,20 @@ static bool regMatch(const string& op, const string& target)
CFGBuilder::CFGBuilder(Binary* binary, Disassembler* disasm, bool subsOnly) CFGBuilder::CFGBuilder(Binary* binary, Disassembler* disasm, bool subsOnly)
: m_bin(binary), m_dis(disasm), m_subs_only(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) bool CFGBuilder::isVisited(addr_t addr)
{ {
return m_blocks.count(addr) > 0; 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); addr_t t = m_dis->getBranchTarget(inst);
if (t && !m_dis->isIndirectBranch(inst)) if (t && !m_dis->isIndirectBranch(inst))
{
t = skipEndbr64(t);
block.successors.push_back(t); block.successors.push_back(t);
if (m_prologs.count(t))
m_funcs.insert(t);
}
else if (m_dis->isIndirectBranch(inst)) else if (m_dis->isIndirectBranch(inst))
{ {
addr_t resolved = resolveGOT(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); addr_t t = m_dis->getBranchTarget(inst);
if (t && !m_dis->isIndirectBranch(inst)) if (t && !m_dis->isIndirectBranch(inst))
{ {
t = skipEndbr64(t);
m_funcs.insert(t); m_funcs.insert(t);
recordIndirect(inst, t, true, "", ""); 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); ((uint64_t)gb[6] << 48) | ((uint64_t)gb[7] << 56);
if (target && target < 0x100000000ULL) resolved = target; if (target && target < 0x100000000ULL) resolved = target;
} }
resolved = skipEndbr64(resolved);
m_funcs.insert(resolved); m_funcs.insert(resolved);
recordIndirect(inst, resolved, true, nm, lb); 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); resolved = traceReg(reg, block.instructions, block.instructions.size() - 1);
if (resolved) if (resolved)
{ {
resolved = skipEndbr64(resolved);
m_funcs.insert(resolved); m_funcs.insert(resolved);
recordIndirect(inst, resolved, true, "traced", ""); recordIndirect(inst, resolved, true, "traced", "");
} }
@@ -411,8 +433,10 @@ Function CFGBuilder::buildFunction(addr_t start, const string& name)
func.address = start; func.address = start;
func.name = name; func.name = name;
addr_t actual = skipEndbr64(start);
queue<addr_t> q; queue<addr_t> q;
q.push(start); q.push(actual);
while (!q.empty()) while (!q.empty())
{ {
@@ -428,9 +452,66 @@ Function CFGBuilder::buildFunction(addr_t start, const string& name)
func.blocks.push_back(block); 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; 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 CFGBuilder::build()
{ {
CFG cfg; CFG cfg;
@@ -458,36 +539,67 @@ CFG CFGBuilder::build()
set<addr_t> built; set<addr_t> built;
queue<addr_t> pending; 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 { auto isEmpty = [](const Function& f) -> bool {
for (const auto& b : f.blocks) for (const auto& b : f.blocks)
if (!b.instructions.empty()) return false; if (!b.instructions.empty()) return false;
return true; 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) auto process = [&](addr_t addr, const string& name)
{ {
addr = skipEndbr64(addr);
if (built.count(addr)) return; if (built.count(addr)) return;
built.insert(addr); built.insert(addr);
auto func = buildFunction(addr, name); auto func = buildFunction(addr, name);
if (isEmpty(func)) return; if (isEmpty(func)) return;
setEndAddress(func);
cfg.functions.push_back(func); cfg.functions.push_back(func);
scanCallAndJmpTargets(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);
}
for (auto d : m_funcs) for (auto d : m_funcs)
if (!built.count(d)) pending.push(d); if (!built.count(d)) pending.push(d);
}; };
{ {
addr_t ep = m_bin->getEntryPoint(); addr_t ep = skipEndbr64(m_bin->getEntryPoint());
string ep_name = "entry"; string ep_name = "entry";
for (const auto& exp : m_bin->getExportedFunctions()) for (const auto& exp : m_bin->getExportedFunctions())
if (exp.first == ep) { ep_name = exp.second; break; } if (exp.first == ep) { ep_name = exp.second; break; }
@@ -497,7 +609,10 @@ CFG CFGBuilder::build()
if (!m_subs_only) if (!m_subs_only)
{ {
for (const auto& exp : m_bin->getExportedFunctions()) 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; map<addr_t, string> imp_map;
@@ -507,6 +622,7 @@ CFG CFGBuilder::build()
while (!pending.empty()) while (!pending.empty())
{ {
addr_t a = pending.front(); pending.pop(); addr_t a = pending.front(); pending.pop();
a = skipEndbr64(a);
if (built.count(a)) continue; if (built.count(a)) continue;
string name; string name;
@@ -518,19 +634,11 @@ CFG CFGBuilder::build()
if (it != imp_map.end()) name = it->second; if (it != imp_map.end()) name = it->second;
} }
built.insert(a);
auto func = buildFunction(a, name); auto func = buildFunction(a, name);
if (isEmpty(func)) continue; if (isEmpty(func)) continue;
setEndAddress(func);
cfg.functions.push_back(func); cfg.functions.push_back(func);
scanCallAndJmpTargets(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);
}
if (!m_subs_only) 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; cfg.indirect_targets = m_targets;
return cfg; return cfg;
} }
+3
View File
@@ -18,6 +18,9 @@ class CFGBuilder
Function buildFunction(addr_t start, const string& name); Function buildFunction(addr_t start, const string& name);
BasicBlock disassembleBlock(addr_t start, addr_t& next); BasicBlock disassembleBlock(addr_t start, addr_t& next);
set<addr_t> findPrologs(); set<addr_t> findPrologs();
void scanDataPointers();
addr_t skipEndbr64(addr_t addr);
bool isPLTStub(const Function& func) const;
void markVisited(addr_t addr); void markVisited(addr_t addr);
bool isVisited(addr_t addr); bool isVisited(addr_t addr);
+26
View File
@@ -57,6 +57,32 @@ bool Disassembler::isProlog(const vector<Instruction>& insts) const
{ {
if (inst.mnemonic == "push" && (inst.operands == "rbp" || inst.operands == "ebp")) if (inst.mnemonic == "push" && (inst.operands == "rbp" || inst.operands == "ebp"))
return true; 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; return false;
} }
+8
View File
@@ -28,6 +28,14 @@ class Binary
virtual addr_t getImageBase() const = 0; virtual addr_t getImageBase() const = 0;
virtual vector<uint8_t> readBytes(addr_t vaddr, size_t size) 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>> 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<pair<addr_t, string>> getExportedFunctions() const = 0;
virtual vector<ImportEntry> getImportedFunctions() const virtual vector<ImportEntry> getImportedFunctions() const
{ {
+18
View File
@@ -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++) for (uint16_t i = 0; i < shnum; i++)
{ {
string sname = sectionName(sections[i].name_idx); string sname = sectionName(sections[i].name_idx);
@@ -391,6 +404,11 @@ vector<uint8_t> ELFLoader::readBytes(addr_t vaddr, size_t size) const
return {}; return {};
} }
vector<pair<addr_t, addr_t>> ELFLoader::getDataRanges() const
{
return m_data_ranges;
}
vector<pair<addr_t, addr_t>> ELFLoader::getExecutableRanges() const vector<pair<addr_t, addr_t>> ELFLoader::getExecutableRanges() const
{ {
return m_exec_ranges; return m_exec_ranges;
+2
View File
@@ -16,6 +16,7 @@ class ELFLoader : public Binary
addr_t getEntryPoint() const override { return m_entry; } addr_t getEntryPoint() const override { return m_entry; }
addr_t getImageBase() const override { return 0; } addr_t getImageBase() const override { return 0; }
vector<uint8_t> readBytes(addr_t vaddr, size_t size) const override; 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>> getExecutableRanges() const override;
vector<pair<addr_t, string>> getExportedFunctions() const override; vector<pair<addr_t, string>> getExportedFunctions() const override;
vector<ImportEntry> getImportedFunctions() const override; vector<ImportEntry> getImportedFunctions() const override;
@@ -39,6 +40,7 @@ class ELFLoader : public Binary
addr_t m_entry; addr_t m_entry;
vector<uint8_t> m_data; vector<uint8_t> m_data;
vector<pair<addr_t, addr_t>> m_exec_ranges; 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<tuple<uint64_t, addr_t, uint64_t>> m_file_map;
vector<pair<addr_t, string>> m_exports; vector<pair<addr_t, string>> m_exports;
vector<ImportEntry> m_imports; vector<ImportEntry> m_imports;
+60 -1
View File
@@ -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); bool is_64 = (m_arch == Arch::X64);
uint32_t import_rva = 0; 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); import_rva = r32(m_data, data_dir_off + 8);
auto rvaToOffset = [&](uint32_t rva) -> uint32_t { 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; return result;
} }
vector<pair<addr_t, addr_t>> PELoader::getDataRanges() const
{
return m_data_ranges;
}
vector<pair<addr_t, addr_t>> PELoader::getExecutableRanges() const vector<pair<addr_t, addr_t>> PELoader::getExecutableRanges() const
{ {
return m_exec_ranges; return m_exec_ranges;
} }
vector<pair<addr_t, addr_t>> PELoader::getRuntimeFunctions() const
{
return m_pdata;
}
vector<pair<addr_t, string>> PELoader::getExportedFunctions() const vector<pair<addr_t, string>> PELoader::getExportedFunctions() const
{ {
return m_exports; return m_exports;
+4
View File
@@ -16,7 +16,9 @@ class PELoader : public Binary
addr_t getEntryPoint() const override { return m_entry; } addr_t getEntryPoint() const override { return m_entry; }
addr_t getImageBase() const override { return m_image_base; } addr_t getImageBase() const override { return m_image_base; }
vector<uint8_t> readBytes(addr_t vaddr, size_t size) const override; 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>> getExecutableRanges() const override;
vector<pair<addr_t, addr_t>> getRuntimeFunctions() const override;
vector<pair<addr_t, string>> getExportedFunctions() const override; vector<pair<addr_t, string>> getExportedFunctions() const override;
vector<ImportEntry> getImportedFunctions() const override; vector<ImportEntry> getImportedFunctions() const override;
const string& getPath() const override { return m_path; } const string& getPath() const override { return m_path; }
@@ -32,6 +34,8 @@ class PELoader : public Binary
addr_t m_image_base; addr_t m_image_base;
vector<uint8_t> m_data; vector<uint8_t> m_data;
vector<pair<addr_t, addr_t>> m_exec_ranges; 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<pair<addr_t, string>> m_exports;
vector<ImportEntry> m_imports; vector<ImportEntry> m_imports;
}; };
+322252 -79879
View File
File diff suppressed because it is too large Load Diff
+6
View File
@@ -56,8 +56,10 @@ struct BasicBlock
struct Function struct Function
{ {
addr_t address; addr_t address;
addr_t end_address = 0;
string name; string name;
vector<BasicBlock> blocks; vector<BasicBlock> blocks;
bool is_thunk = false;
}; };
struct CFG struct CFG
@@ -135,7 +137,11 @@ struct CFG
const auto& f = functions[i]; const auto& f = functions[i];
os << " {\n"; os << " {\n";
os << " \"address\": \"0x" << hex << f.address << "\",\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"; os << " \"name\": \"" << escapeJSON(f.name) << "\",\n";
if (f.is_thunk)
os << " \"is_thunk\": true,\n";
os << " \"blocks\": [\n"; os << " \"blocks\": [\n";
for (size_t j = 0; j < f.blocks.size(); ++j) { for (size_t j = 0; j < f.blocks.size(); ++j) {
const auto& b = f.blocks[j]; const auto& b = f.blocks[j];