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
+8
View File
@@ -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
{
+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++)
{
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;
+2
View File
@@ -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
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);
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;
+4
View File
@@ -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;
};