mirror of
https://gitlab.com/BinaryHardening/cfgrip
synced 2026-07-26 12:41:08 +00:00
cfgrip: standalone CFG extraction tool from PE/ELF binaries
This commit is contained in:
@@ -0,0 +1 @@
|
||||
build/
|
||||
@@ -0,0 +1,31 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(cfgrip)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
capstone
|
||||
GIT_REPOSITORY https://github.com/capstone-engine/capstone.git
|
||||
GIT_TAG 5.0.7
|
||||
)
|
||||
set(CAPSTONE_BUILD_TESTS OFF)
|
||||
set(CAPSTONE_BUILD_SHARED OFF)
|
||||
set(CAPSTONE_BUILD_CSTOOL OFF)
|
||||
set(CAPSTONE_BUILD_CSVIEW OFF)
|
||||
FetchContent_MakeAvailable(capstone)
|
||||
|
||||
add_executable(cfgrip
|
||||
main.cpp
|
||||
loader/binary.cpp
|
||||
loader/pe.cpp
|
||||
loader/elf.cpp
|
||||
disasm/engine.cpp
|
||||
cfg/builder.cpp
|
||||
)
|
||||
|
||||
target_include_directories(cfgrip PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_include_directories(cfgrip PRIVATE ${capstone_SOURCE_DIR}/include)
|
||||
|
||||
target_link_libraries(cfgrip capstone_static)
|
||||
@@ -0,0 +1,14 @@
|
||||
# code of conduct
|
||||
|
||||
## the rule
|
||||
|
||||
Don't be an asshole.
|
||||
|
||||
## what that means
|
||||
|
||||
- No personal attacks, no harassment, no discrimination.
|
||||
- Assume good faith. People make mistakes, including you.
|
||||
- If someone asks you to stop, stop.
|
||||
- If you see shitty behavior, call it out.
|
||||
|
||||
That's it. No long document. Just don't be an asshole.
|
||||
@@ -0,0 +1,39 @@
|
||||
# contributing
|
||||
|
||||
Pull requests are welcome. If you're planning something big, open an issue first so we don't step on each other.
|
||||
|
||||
## building
|
||||
|
||||
```sh
|
||||
cmake -B build
|
||||
cmake --build build --config Release
|
||||
```
|
||||
|
||||
Capstone is fetched automatically via FetchContent. You don't need to install it.
|
||||
|
||||
## what needs work
|
||||
|
||||
- **More architectures** — right now it's x86/x86-64 only. ARM, AArch64, etc would be nice.
|
||||
- **Better indirect call resolution** — the register tracer is basic. It could be smarter.
|
||||
- **More binary formats** — Mach-O isn't supported yet.
|
||||
|
||||
## style
|
||||
|
||||
- No tabs. 4 spaces.
|
||||
- No comments unless the code is genuinely confusing.
|
||||
- Match the existing style. If you're changing something, make it look like it belongs.
|
||||
- Keep it simple. This is not a framework.
|
||||
|
||||
## committing
|
||||
|
||||
Write short commit messages. Use `git rebase` to keep the history clean. No merge commits.
|
||||
|
||||
## testing
|
||||
|
||||
There's a test binary in `tests/`. Run cfgrip on it and make sure the output is valid JSON:
|
||||
|
||||
```sh
|
||||
./build/cfgrip tests/example1.exe
|
||||
```
|
||||
|
||||
If your change affects the output format, update `tests/example1.exe.cfg` too.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 BinaryHardening
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,419 @@
|
||||
# cfgrip
|
||||
|
||||
PE/ELF x86/x64 CFG extractor. Takes a binary in, disassembles it, resolves every jump and call (GOT, jump tables, register tracing), and exports the full control flow graph as structured JSON.
|
||||
|
||||
## why
|
||||
|
||||
You need to know exactly where every branch goes. Not for reading — for patching. Feed the JSON into Zydis or AsmJit, locate the exact instruction you need to hook or modify, and write back. Anti-cheat teams use it to map out game binaries. RE people use it to lift code into their own analysis pipelines. Software analysts trace execution paths without running the binary.
|
||||
|
||||
cfgrip gives you the map. What you do with it is up to you.
|
||||
|
||||
## what it extracts
|
||||
|
||||
For every binary cfgrip processes, it produces:
|
||||
|
||||
- **Function list** with addresses and names (entry point, exports, discovered via `call` instructions)
|
||||
- **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)
|
||||
|
||||
It handles indirect branches by:
|
||||
1. Checking the **GOT** (Global Offset Table) for known imports
|
||||
2. Scanning backward for **LEA instructions** to locate jump tables, then reading table entries
|
||||
3. **Tracing registers** backward through `mov`/`lea` chains to find concrete addresses
|
||||
|
||||
## usage
|
||||
|
||||
```
|
||||
cfgrip <binary>
|
||||
```
|
||||
|
||||
That's it. Feed it a binary, get `<binary>.cfg` as output.
|
||||
|
||||
---
|
||||
Example:
|
||||
```
|
||||
cfgrip.exe tests\example1.exe
|
||||
format: PE
|
||||
arch: x86-64
|
||||
entry: 0x1400054bc
|
||||
imports: 85
|
||||
0x140020000 EncodePointer (KERNEL32.dll)
|
||||
0x140020008 DecodePointer (KERNEL32.dll)
|
||||
0x140020010 EnterCriticalSection (KERNEL32.dll)
|
||||
0x140020018 LeaveCriticalSection (KERNEL32.dll)
|
||||
0x140020020 InitializeCriticalSectionEx (KERNEL32.dll)
|
||||
0x140020028 DeleteCriticalSection (KERNEL32.dll)
|
||||
0x140020030 MultiByteToWideChar (KERNEL32.dll)
|
||||
0x140020038 WideCharToMultiByte (KERNEL32.dll)
|
||||
0x140020040 LCMapStringEx (KERNEL32.dll)
|
||||
0x140020048 GetStringTypeW (KERNEL32.dll)
|
||||
0x140020050 GetCPInfo (KERNEL32.dll)
|
||||
0x140020058 RtlCaptureContext (KERNEL32.dll)
|
||||
0x140020060 RtlLookupFunctionEntry (KERNEL32.dll)
|
||||
0x140020068 RtlVirtualUnwind (KERNEL32.dll)
|
||||
0x140020070 UnhandledExceptionFilter (KERNEL32.dll)
|
||||
0x140020078 SetUnhandledExceptionFilter (KERNEL32.dll)
|
||||
0x140020080 GetCurrentProcess (KERNEL32.dll)
|
||||
0x140020088 TerminateProcess (KERNEL32.dll)
|
||||
0x140020090 IsProcessorFeaturePresent (KERNEL32.dll)
|
||||
0x140020098 QueryPerformanceCounter (KERNEL32.dll)
|
||||
0x1400200a0 GetCurrentProcessId (KERNEL32.dll)
|
||||
0x1400200a8 GetCurrentThreadId (KERNEL32.dll)
|
||||
0x1400200b0 GetSystemTimeAsFileTime (KERNEL32.dll)
|
||||
0x1400200b8 InitializeSListHead (KERNEL32.dll)
|
||||
0x1400200c0 IsDebuggerPresent (KERNEL32.dll)
|
||||
0x1400200c8 GetStartupInfoW (KERNEL32.dll)
|
||||
0x1400200d0 GetModuleHandleW (KERNEL32.dll)
|
||||
0x1400200d8 WriteConsoleW (KERNEL32.dll)
|
||||
0x1400200e0 RtlPcToFileHeader (KERNEL32.dll)
|
||||
0x1400200e8 RaiseException (KERNEL32.dll)
|
||||
0x1400200f0 RtlUnwindEx (KERNEL32.dll)
|
||||
0x1400200f8 GetLastError (KERNEL32.dll)
|
||||
0x140020100 SetLastError (KERNEL32.dll)
|
||||
0x140020108 InitializeCriticalSectionAndSpinCount (KERNEL32.dll)
|
||||
0x140020110 TlsAlloc (KERNEL32.dll)
|
||||
0x140020118 TlsGetValue (KERNEL32.dll)
|
||||
0x140020120 TlsSetValue (KERNEL32.dll)
|
||||
0x140020128 TlsFree (KERNEL32.dll)
|
||||
0x140020130 FreeLibrary (KERNEL32.dll)
|
||||
0x140020138 GetProcAddress (KERNEL32.dll)
|
||||
0x140020140 LoadLibraryExW (KERNEL32.dll)
|
||||
0x140020148 GetStdHandle (KERNEL32.dll)
|
||||
0x140020150 WriteFile (KERNEL32.dll)
|
||||
0x140020158 GetModuleFileNameW (KERNEL32.dll)
|
||||
0x140020160 ExitProcess (KERNEL32.dll)
|
||||
0x140020168 GetModuleHandleExW (KERNEL32.dll)
|
||||
0x140020170 GetCommandLineA (KERNEL32.dll)
|
||||
0x140020178 GetCommandLineW (KERNEL32.dll)
|
||||
0x140020180 HeapAlloc (KERNEL32.dll)
|
||||
0x140020188 HeapFree (KERNEL32.dll)
|
||||
0x140020190 FlsAlloc (KERNEL32.dll)
|
||||
0x140020198 FlsGetValue (KERNEL32.dll)
|
||||
0x1400201a0 FlsSetValue (KERNEL32.dll)
|
||||
0x1400201a8 FlsFree (KERNEL32.dll)
|
||||
0x1400201b0 VirtualProtect (KERNEL32.dll)
|
||||
0x1400201b8 CompareStringW (KERNEL32.dll)
|
||||
0x1400201c0 LCMapStringW (KERNEL32.dll)
|
||||
0x1400201c8 GetLocaleInfoW (KERNEL32.dll)
|
||||
0x1400201d0 IsValidLocale (KERNEL32.dll)
|
||||
0x1400201d8 GetUserDefaultLCID (KERNEL32.dll)
|
||||
0x1400201e0 EnumSystemLocalesW (KERNEL32.dll)
|
||||
0x1400201e8 GetFileType (KERNEL32.dll)
|
||||
0x1400201f0 CloseHandle (KERNEL32.dll)
|
||||
0x1400201f8 FlushFileBuffers (KERNEL32.dll)
|
||||
0x140020200 GetConsoleOutputCP (KERNEL32.dll)
|
||||
0x140020208 GetConsoleMode (KERNEL32.dll)
|
||||
0x140020210 ReadFile (KERNEL32.dll)
|
||||
0x140020218 GetFileSizeEx (KERNEL32.dll)
|
||||
0x140020220 SetFilePointerEx (KERNEL32.dll)
|
||||
0x140020228 ReadConsoleW (KERNEL32.dll)
|
||||
0x140020230 HeapReAlloc (KERNEL32.dll)
|
||||
0x140020238 FindClose (KERNEL32.dll)
|
||||
0x140020240 FindFirstFileExW (KERNEL32.dll)
|
||||
0x140020248 FindNextFileW (KERNEL32.dll)
|
||||
0x140020250 IsValidCodePage (KERNEL32.dll)
|
||||
0x140020258 GetACP (KERNEL32.dll)
|
||||
0x140020260 GetOEMCP (KERNEL32.dll)
|
||||
0x140020268 GetEnvironmentStringsW (KERNEL32.dll)
|
||||
0x140020270 FreeEnvironmentStringsW (KERNEL32.dll)
|
||||
0x140020278 SetEnvironmentVariableW (KERNEL32.dll)
|
||||
0x140020280 SetStdHandle (KERNEL32.dll)
|
||||
0x140020288 GetProcessHeap (KERNEL32.dll)
|
||||
0x140020290 HeapSize (KERNEL32.dll)
|
||||
0x140020298 CreateFileW (KERNEL32.dll)
|
||||
0x1400202a0 RtlUnwind (KERNEL32.dll)
|
||||
functions: 422
|
||||
indirect targets: 1372
|
||||
cfg written to: tests\example1.exe.cfg
|
||||
```
|
||||
---
|
||||
|
||||
## the output format
|
||||
|
||||
The `.cfg` file is structured JSON. Here's what it looks like:
|
||||
```
|
||||
{
|
||||
"binary": "tests\\example1.exe",
|
||||
"arch": "x86-64",
|
||||
"format": "PE",
|
||||
"entry_point": "0x1400054bc",
|
||||
"imports": [
|
||||
{
|
||||
"address": "0x140020000",
|
||||
"name": "EncodePointer",
|
||||
"library": "KERNEL32.dll"
|
||||
},
|
||||
{
|
||||
"address": "0x140020008",
|
||||
"name": "DecodePointer",
|
||||
"library": "KERNEL32.dll"
|
||||
},
|
||||
{
|
||||
"address": "0x140020010",
|
||||
"name": "EnterCriticalSection",
|
||||
"library": "KERNEL32.dll"
|
||||
},
|
||||
{
|
||||
"address": "0x140020018",
|
||||
"name": "LeaveCriticalSection",
|
||||
"library": "KERNEL32.dll"
|
||||
},
|
||||
{
|
||||
"address": "0x140020020",
|
||||
"name": "InitializeCriticalSectionEx",
|
||||
"library": "KERNEL32.dll"
|
||||
},
|
||||
...
|
||||
...
|
||||
...
|
||||
"functions": [
|
||||
{
|
||||
"address": "0x1400054bc",
|
||||
"name": "entry",
|
||||
"blocks": [
|
||||
{
|
||||
"address": "0x1400054bc",
|
||||
"size": 4,
|
||||
"is_prolog": false,
|
||||
"is_epilog": false,
|
||||
"instructions": [
|
||||
{
|
||||
"address": "0x1400054bc",
|
||||
"size": 4,
|
||||
"mnemonic": "sub",
|
||||
"operands": "rsp, 0x28"
|
||||
},
|
||||
{
|
||||
"address": "0x1400054c0",
|
||||
"size": 5,
|
||||
"mnemonic": "call",
|
||||
"operands": "0x140005d30"
|
||||
},
|
||||
{
|
||||
"address": "0x1400054c5",
|
||||
"size": 4,
|
||||
"mnemonic": "add",
|
||||
"operands": "rsp, 0x28"
|
||||
},
|
||||
{
|
||||
"address": "0x1400054c9",
|
||||
"size": 5,
|
||||
"mnemonic": "jmp",
|
||||
"operands": "0x140005340"
|
||||
}
|
||||
],
|
||||
"successors": [
|
||||
"0x140005340"
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": "0x140005340",
|
||||
"size": 8,
|
||||
"is_prolog": false,
|
||||
"is_epilog": false,
|
||||
"instructions": [
|
||||
{
|
||||
"address": "0x140005340",
|
||||
"size": 5,
|
||||
"mnemonic": "mov",
|
||||
"operands": "qword ptr [rsp + 8], rbx"
|
||||
},
|
||||
{
|
||||
"address": "0x140005345",
|
||||
"size": 5,
|
||||
"mnemonic": "mov",
|
||||
"operands": "qword ptr [rsp + 0x10], rsi"
|
||||
},
|
||||
{
|
||||
"address": "0x14000534a",
|
||||
"size": 1,
|
||||
"mnemonic": "push",
|
||||
"operands": "rdi"
|
||||
},
|
||||
{
|
||||
"address": "0x14000534b",
|
||||
"size": 4,
|
||||
"mnemonic": "sub",
|
||||
"operands": "rsp, 0x30"
|
||||
},
|
||||
{
|
||||
"address": "0x14000534f",
|
||||
"size": 5,
|
||||
"mnemonic": "mov",
|
||||
"operands": "ecx, 1"
|
||||
},
|
||||
{
|
||||
"address": "0x140005354",
|
||||
"size": 5,
|
||||
"mnemonic": "call",
|
||||
"operands": "0x14000550c"
|
||||
},
|
||||
{
|
||||
"address": "0x140005359",
|
||||
"size": 2,
|
||||
"mnemonic": "test",
|
||||
"operands": "al, al"
|
||||
},
|
||||
{
|
||||
"address": "0x14000535b",
|
||||
"size": 6,
|
||||
"mnemonic": "je",
|
||||
"operands": "0x140005497"
|
||||
}
|
||||
],
|
||||
"successors": [
|
||||
"0x140005497",
|
||||
"0x140005361"
|
||||
]
|
||||
},
|
||||
{
|
||||
"address": "0x140005497",
|
||||
"size": 15,
|
||||
"is_prolog": false,
|
||||
"is_epilog": false,
|
||||
"instructions": [
|
||||
{
|
||||
"address": "0x140005497",
|
||||
"size": 5,
|
||||
"mnemonic": "mov",
|
||||
"operands": "ecx, 7"
|
||||
},
|
||||
{
|
||||
"address": "0x14000549c",
|
||||
"size": 5,
|
||||
"mnemonic": "call",
|
||||
"operands": "0x140005e44"
|
||||
},
|
||||
{
|
||||
"address": "0x1400054a1",
|
||||
"size": 1,
|
||||
"mnemonic": "nop",
|
||||
"operands": ""
|
||||
},
|
||||
{
|
||||
"address": "0x1400054a2",
|
||||
"size": 5,
|
||||
"mnemonic": "mov",
|
||||
"operands": "ecx, 7"
|
||||
},
|
||||
{
|
||||
"address": "0x1400054a7",
|
||||
"size": 5,
|
||||
"mnemonic": "call",
|
||||
"operands": "0x140005e44"
|
||||
},
|
||||
{
|
||||
"address": "0x1400054ac",
|
||||
"size": 2,
|
||||
"mnemonic": "mov",
|
||||
"operands": "ecx, ebx"
|
||||
},
|
||||
{
|
||||
"address": "0x1400054ae",
|
||||
"size": 5,
|
||||
"mnemonic": "call",
|
||||
"operands": "0x14000ec14"
|
||||
},
|
||||
{
|
||||
"address": "0x1400054b3",
|
||||
"size": 1,
|
||||
"mnemonic": "nop",
|
||||
"operands": ""
|
||||
},
|
||||
{
|
||||
"address": "0x1400054b4",
|
||||
"size": 2,
|
||||
"mnemonic": "mov",
|
||||
"operands": "ecx, ebx"
|
||||
},
|
||||
{
|
||||
"address": "0x1400054b6",
|
||||
"size": 5,
|
||||
"mnemonic": "call",
|
||||
"operands": "0x14000ebcc"
|
||||
},
|
||||
{
|
||||
"address": "0x1400054bb",
|
||||
"size": 1,
|
||||
"mnemonic": "nop",
|
||||
"operands": ""
|
||||
},
|
||||
{
|
||||
"address": "0x1400054bc",
|
||||
"size": 4,
|
||||
"mnemonic": "sub",
|
||||
"operands": "rsp, 0x28"
|
||||
},
|
||||
{
|
||||
"address": "0x1400054c0",
|
||||
"size": 5,
|
||||
"mnemonic": "call",
|
||||
"operands": "0x140005d30"
|
||||
},
|
||||
{
|
||||
"address": "0x1400054c5",
|
||||
"size": 4,
|
||||
"mnemonic": "add",
|
||||
"operands": "rsp, 0x28"
|
||||
},
|
||||
{
|
||||
"address": "0x1400054c9",
|
||||
"size": 5,
|
||||
"mnemonic": "jmp",
|
||||
"operands": "0x140005340"
|
||||
}
|
||||
],
|
||||
"successors": [
|
||||
"0x140005340"
|
||||
]
|
||||
},
|
||||
...
|
||||
...
|
||||
...
|
||||
```
|
||||
|
||||
## building
|
||||
|
||||
Requires CMake and a C++17 compiler. Capstone is fetched automatically.
|
||||
|
||||
```
|
||||
cmake -B build
|
||||
cmake --build build --config Release
|
||||
./build/cfgrip <binary>
|
||||
```
|
||||
|
||||
Or on Windows with Visual Studio:
|
||||
|
||||
```
|
||||
cmake -B build -S .
|
||||
cmake --build build --config Release
|
||||
.\build\Release\cfgrip.exe <binary>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
<img width="855" height="976" alt="image" src="https://github.com/user-attachments/assets/69018295-e3a7-4e86-a66a-4a9de448ad00" />
|
||||
|
||||
<img width="774" height="654" alt="image" src="https://github.com/user-attachments/assets/a2918f3b-caa9-4bbc-be19-bb06d74bd177" />
|
||||
|
||||
---
|
||||
|
||||
## what it supports
|
||||
|
||||
| | | |
|
||||
|---|---|:-:|
|
||||
| **Formats** | PE (32/64-bit) | YES |
|
||||
| | ELF (64-bit) | YES |
|
||||
| **Architectures** | x86 | YES |
|
||||
| | x86-64 | YES |
|
||||
| **Indirect calls** | GOT resolution | YES |
|
||||
| | Jump table detection | YES |
|
||||
| | Backward register tracing | YES |
|
||||
| **Function discovery** | Entry point | YES |
|
||||
| | Exports | YES |
|
||||
| | `call` targets | YES |
|
||||
| | Prolog scanning (`push rbp`, CET `endbr64`) | YES |
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# security
|
||||
|
||||
This tool parses untrusted binaries. There's always a risk of bugs in the parsers (PE, ELF) or the disassembler that could crash or do worse.
|
||||
|
||||
If you find something, don't open a public issue. Email me directly or reach out on the repo's security tab.
|
||||
|
||||
If you're integrating cfgrip into something that processes untrusted files, consider sandboxing it.
|
||||
+530
@@ -0,0 +1,530 @@
|
||||
#include "cfg/builder.hpp"
|
||||
#include <queue>
|
||||
#include <algorithm>
|
||||
|
||||
static bool isMemOp(const string& op)
|
||||
{
|
||||
return op.find('[') != string::npos || op.find('+') != string::npos;
|
||||
}
|
||||
|
||||
static bool regMatch(const string& op, const string& target)
|
||||
{
|
||||
auto norm = [](const string& s) -> string {
|
||||
if (s.size() >= 3 && s[0] == 'r' && isdigit(s[1])) return s.substr(1);
|
||||
if (s.size() >= 3 && s[0] == 'e' && isdigit(s[1])) return s.substr(1);
|
||||
return s;
|
||||
};
|
||||
return norm(op) == norm(target) || op == target;
|
||||
}
|
||||
|
||||
CFGBuilder::CFGBuilder(Binary* binary, Disassembler* disasm)
|
||||
: m_bin(binary), m_dis(disasm) {}
|
||||
|
||||
bool CFGBuilder::isVisited(addr_t addr)
|
||||
{
|
||||
return m_blocks.count(addr) > 0;
|
||||
}
|
||||
|
||||
void CFGBuilder::markVisited(addr_t addr)
|
||||
{
|
||||
m_blocks.insert(addr);
|
||||
}
|
||||
|
||||
void CFGBuilder::buildGotMap()
|
||||
{
|
||||
auto imports = m_bin->getImportedFunctions();
|
||||
for (const auto& imp : imports)
|
||||
{
|
||||
if (imp.address)
|
||||
m_got[imp.address] = {imp.name, imp.library};
|
||||
}
|
||||
}
|
||||
|
||||
addr_t CFGBuilder::resolveGOT(const Instruction& inst)
|
||||
{
|
||||
if (!m_dis->isIndirectBranch(inst)) return 0;
|
||||
int64_t disp = m_dis->getRIPDisp(inst);
|
||||
if (disp == 0) return 0;
|
||||
|
||||
addr_t got = inst.address + inst.size + disp;
|
||||
auto it = m_got.find(got);
|
||||
if (it != m_got.end())
|
||||
{
|
||||
if (!it->second.first.empty()) return got;
|
||||
}
|
||||
|
||||
auto bytes = m_bin->readBytes(got, 8);
|
||||
if (bytes.size() < 4) return 0;
|
||||
|
||||
uint64_t target = 0;
|
||||
if (bytes.size() >= 8)
|
||||
{
|
||||
target = 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);
|
||||
}
|
||||
else
|
||||
{
|
||||
target = bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
addr_t CFGBuilder::resolveJumpTable(const vector<Instruction>& block, const Instruction& inst)
|
||||
{
|
||||
size_t n = block.size();
|
||||
if (n < 3) return 0;
|
||||
|
||||
int idx = -1;
|
||||
for (int i = (int)n - 1; i >= 0; i--)
|
||||
{
|
||||
if (block[i].address == inst.address) { idx = i; break; }
|
||||
}
|
||||
if (idx < 2) return 0;
|
||||
|
||||
for (int i = idx - 1; i >= max(0, idx - 8); i--)
|
||||
{
|
||||
if (block[i].mnemonic == "lea")
|
||||
{
|
||||
int64_t disp = m_dis->getRIPDisp(block[i]);
|
||||
if (disp == 0) continue;
|
||||
addr_t table = block[i].address + block[i].size + disp;
|
||||
|
||||
int64_t bound = -1;
|
||||
for (int j = i - 1; j >= max(0, i - 4); j--)
|
||||
{
|
||||
if (block[j].mnemonic == "cmp")
|
||||
{
|
||||
size_t pos = block[j].operands.find("0x");
|
||||
if (pos != string::npos)
|
||||
{
|
||||
try { bound = stoll(block[j].operands.substr(pos), nullptr, 16); }
|
||||
catch (...) {}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bound <= 0) bound = 32;
|
||||
|
||||
for (int64_t e = 0; e < bound && e < 256; e++)
|
||||
{
|
||||
auto ent = m_bin->readBytes(table + e * 8, 8);
|
||||
if (ent.size() < 4) break;
|
||||
uint64_t entry = 0;
|
||||
if (ent.size() >= 8)
|
||||
entry = ent[0] | ((uint64_t)ent[1] << 8) | ((uint64_t)ent[2] << 16) |
|
||||
((uint64_t)ent[3] << 24) | ((uint64_t)ent[4] << 32) |
|
||||
((uint64_t)ent[5] << 40) | ((uint64_t)ent[6] << 48) | ((uint64_t)ent[7] << 56);
|
||||
else
|
||||
entry = ent[0] | (ent[1] << 8) | (ent[2] << 16) | (ent[3] << 24);
|
||||
|
||||
if (entry == 0 || entry > 0x100000000ULL) break;
|
||||
auto ranges = m_bin->getExecutableRanges();
|
||||
bool ok = false;
|
||||
for (const auto& r : ranges)
|
||||
if (entry >= r.first && entry < r.second) { ok = true; break; }
|
||||
if (ok) return table;
|
||||
else break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
addr_t CFGBuilder::traceReg(const string& reg, const vector<Instruction>& block, size_t idx)
|
||||
{
|
||||
if (idx >= block.size() || idx == 0) return 0;
|
||||
string cur = reg;
|
||||
|
||||
for (int i = (int)idx - 1; i >= 0; i--)
|
||||
{
|
||||
const auto& inst = block[i];
|
||||
if (inst.mnemonic == "nop" || inst.mnemonic == "cmp" || inst.mnemonic == "test")
|
||||
continue;
|
||||
|
||||
size_t comma = inst.operands.find(',');
|
||||
string dst = comma != string::npos ? inst.operands.substr(0, comma) : inst.operands;
|
||||
while (!dst.empty() && dst[0] == ' ') dst = dst.substr(1);
|
||||
while (!dst.empty() && dst.back() == ' ') dst.pop_back();
|
||||
|
||||
if (inst.mnemonic == "mov" || inst.mnemonic == "lea")
|
||||
{
|
||||
if (dst == cur || dst == "r" + cur.substr(1) || regMatch(dst, cur))
|
||||
{
|
||||
if (inst.mnemonic == "mov" && !inst.operands.empty())
|
||||
{
|
||||
size_t pos = inst.operands.rfind("0x");
|
||||
if (pos != string::npos)
|
||||
{
|
||||
try { return stoull(inst.operands.substr(pos), nullptr, 16); }
|
||||
catch (...) {}
|
||||
}
|
||||
}
|
||||
if (inst.mnemonic == "lea")
|
||||
{
|
||||
int64_t disp = m_dis->getRIPDisp(inst);
|
||||
if (disp != 0) return inst.address + inst.size + disp;
|
||||
}
|
||||
if (comma != string::npos)
|
||||
{
|
||||
string src = inst.operands.substr(comma + 1);
|
||||
while (!src.empty() && src[0] == ' ') src = src.substr(1);
|
||||
while (!src.empty() && src.back() == ' ') src.pop_back();
|
||||
if (!src.empty() && src[0] != '0' && !isMemOp(src))
|
||||
{
|
||||
cur = src;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (dst == cur || regMatch(dst, cur)) return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CFGBuilder::recordIndirect(const Instruction& inst, addr_t target, bool ok, const string& name, const string& lib)
|
||||
{
|
||||
IndirectTarget it;
|
||||
it.instruction_addr = inst.address;
|
||||
it.resolved_target = ok ? target : 0;
|
||||
it.resolved = ok;
|
||||
it.name = name;
|
||||
it.library = lib;
|
||||
m_targets.push_back(it);
|
||||
}
|
||||
|
||||
BasicBlock CFGBuilder::disassembleBlock(addr_t start, addr_t& next)
|
||||
{
|
||||
BasicBlock block;
|
||||
block.address = start;
|
||||
block.is_prolog = false;
|
||||
block.is_epilog = false;
|
||||
|
||||
addr_t cur = start;
|
||||
auto ranges = m_bin->getExecutableRanges();
|
||||
|
||||
auto inExec = [&](addr_t a) -> bool {
|
||||
for (const auto& r : ranges)
|
||||
if (a >= r.first && a < r.second) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < 5000; ++i)
|
||||
{
|
||||
if (!inExec(cur)) { next = 0; break; }
|
||||
|
||||
auto bytes = m_bin->readBytes(cur, 15);
|
||||
if (bytes.empty()) { next = 0; break; }
|
||||
|
||||
auto insts = m_dis->disassemble(bytes, cur, 1);
|
||||
if (insts.empty()) { next = 0; break; }
|
||||
|
||||
auto& inst = insts[0];
|
||||
block.instructions.push_back(inst);
|
||||
|
||||
if (i <= 3 && !block.is_prolog && m_dis->isProlog(block.instructions))
|
||||
block.is_prolog = true;
|
||||
|
||||
if (m_dis->isEpilog(inst))
|
||||
block.is_epilog = true;
|
||||
|
||||
if (m_dis->isReturn(inst)) { next = 0; break; }
|
||||
if (m_dis->isTrap(inst)) { next = 0; break; }
|
||||
|
||||
if (m_dis->isUnconditionalBranch(inst))
|
||||
{
|
||||
addr_t t = m_dis->getBranchTarget(inst);
|
||||
if (t && !m_dis->isIndirectBranch(inst))
|
||||
block.successors.push_back(t);
|
||||
else if (m_dis->isIndirectBranch(inst))
|
||||
{
|
||||
addr_t resolved = resolveGOT(inst);
|
||||
if (!resolved)
|
||||
{
|
||||
resolved = resolveJumpTable(block.instructions, inst);
|
||||
if (resolved)
|
||||
{
|
||||
for (int j = (int)block.instructions.size() - 2; j >= 0; j--)
|
||||
{
|
||||
if (block.instructions[j].mnemonic == "lea")
|
||||
{
|
||||
int64_t ld = m_dis->getRIPDisp(block.instructions[j]);
|
||||
if (ld != 0)
|
||||
{
|
||||
addr_t tbl = block.instructions[j].address + block.instructions[j].size + ld;
|
||||
for (int64_t e = 0; e < 64; e++)
|
||||
{
|
||||
auto eb = m_bin->readBytes(tbl + e * 8, 8);
|
||||
if (eb.size() < 4) break;
|
||||
uint64_t entry = 0;
|
||||
if (eb.size() >= 8)
|
||||
entry = eb[0] | ((uint64_t)eb[1] << 8) | ((uint64_t)eb[2] << 16) |
|
||||
((uint64_t)eb[3] << 24) | ((uint64_t)eb[4] << 32) |
|
||||
((uint64_t)eb[5] << 40) | ((uint64_t)eb[6] << 48) | ((uint64_t)eb[7] << 56);
|
||||
else
|
||||
entry = eb[0] | (eb[1] << 8) | (eb[2] << 16) | (eb[3] << 24);
|
||||
if (entry == 0 || entry > 0x100000000ULL) break;
|
||||
bool ok = false;
|
||||
for (const auto& r : ranges)
|
||||
if (entry >= r.first && entry < r.second) { ok = true; break; }
|
||||
if (ok) block.successors.push_back(entry);
|
||||
else break;
|
||||
}
|
||||
resolved = tbl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!resolved)
|
||||
{
|
||||
string reg = inst.operands;
|
||||
while (!reg.empty() && reg[0] == ' ') reg = reg.substr(1);
|
||||
resolved = traceReg(reg, block.instructions, block.instructions.size() - 1);
|
||||
if (resolved)
|
||||
{
|
||||
block.successors.push_back(resolved);
|
||||
recordIndirect(inst, resolved, true, "traced", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
recordIndirect(inst, 0, false, "unresolved", "");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto git = m_got.find(resolved);
|
||||
string nm = git != m_got.end() ? git->second.first : "";
|
||||
string lb = git != m_got.end() ? git->second.second : "";
|
||||
block.successors.push_back(resolved);
|
||||
recordIndirect(inst, resolved, true, nm, lb);
|
||||
}
|
||||
}
|
||||
next = 0; break;
|
||||
}
|
||||
|
||||
if (m_dis->isConditionalBranch(inst))
|
||||
{
|
||||
addr_t t = m_dis->getBranchTarget(inst);
|
||||
if (t && !m_dis->isIndirectBranch(inst))
|
||||
block.successors.push_back(t);
|
||||
block.successors.push_back(cur + inst.size);
|
||||
next = cur + inst.size; break;
|
||||
}
|
||||
|
||||
if (m_dis->isCall(inst))
|
||||
{
|
||||
addr_t t = m_dis->getBranchTarget(inst);
|
||||
if (t && !m_dis->isIndirectBranch(inst))
|
||||
{
|
||||
m_funcs.insert(t);
|
||||
recordIndirect(inst, t, true, "", "");
|
||||
}
|
||||
else if (m_dis->isIndirectBranch(inst))
|
||||
{
|
||||
addr_t resolved = resolveGOT(inst);
|
||||
if (resolved)
|
||||
{
|
||||
auto git = m_got.find(resolved);
|
||||
string nm = git != m_got.end() ? git->second.first : "";
|
||||
string lb = git != m_got.end() ? git->second.second : "";
|
||||
auto gb = m_bin->readBytes(resolved, 8);
|
||||
if (gb.size() >= 8)
|
||||
{
|
||||
uint64_t target = gb[0] | ((uint64_t)gb[1] << 8) |
|
||||
((uint64_t)gb[2] << 16) | ((uint64_t)gb[3] << 24) |
|
||||
((uint64_t)gb[4] << 32) | ((uint64_t)gb[5] << 40) |
|
||||
((uint64_t)gb[6] << 48) | ((uint64_t)gb[7] << 56);
|
||||
if (target && target < 0x100000000ULL) resolved = target;
|
||||
}
|
||||
m_funcs.insert(resolved);
|
||||
recordIndirect(inst, resolved, true, nm, lb);
|
||||
}
|
||||
else
|
||||
{
|
||||
string reg = inst.operands;
|
||||
while (!reg.empty() && reg[0] == ' ') reg = reg.substr(1);
|
||||
resolved = traceReg(reg, block.instructions, block.instructions.size() - 1);
|
||||
if (resolved)
|
||||
{
|
||||
m_funcs.insert(resolved);
|
||||
recordIndirect(inst, resolved, true, "traced", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
recordIndirect(inst, 0, false, "unresolved", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cur += inst.size;
|
||||
next = cur;
|
||||
}
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
set<addr_t> CFGBuilder::findPrologs()
|
||||
{
|
||||
set<addr_t> cand;
|
||||
auto ranges = m_bin->getExecutableRanges();
|
||||
|
||||
for (const auto& r : ranges)
|
||||
{
|
||||
addr_t a = r.first;
|
||||
while (a < r.second)
|
||||
{
|
||||
auto bytes = m_bin->readBytes(a, 15);
|
||||
if (bytes.empty()) { a += 1; continue; }
|
||||
auto insts = m_dis->disassemble(bytes, a, 1);
|
||||
if (insts.empty()) { a += 1; continue; }
|
||||
auto& inst = insts[0];
|
||||
|
||||
if (inst.mnemonic == "endbr64")
|
||||
{
|
||||
auto nb = m_bin->readBytes(a + inst.size, 15);
|
||||
auto ni = m_dis->disassemble(nb, a + inst.size, 2);
|
||||
if (!ni.empty() && m_dis->isProlog(ni)) cand.insert(a);
|
||||
}
|
||||
else
|
||||
{
|
||||
vector<Instruction> tmp{inst};
|
||||
if (m_dis->isProlog(tmp)) cand.insert(a);
|
||||
}
|
||||
|
||||
a += inst.size;
|
||||
}
|
||||
}
|
||||
|
||||
return cand;
|
||||
}
|
||||
|
||||
Function CFGBuilder::buildFunction(addr_t start, const string& name)
|
||||
{
|
||||
Function func;
|
||||
func.address = start;
|
||||
func.name = name;
|
||||
|
||||
queue<addr_t> q;
|
||||
q.push(start);
|
||||
|
||||
while (!q.empty())
|
||||
{
|
||||
addr_t a = q.front(); q.pop();
|
||||
if (isVisited(a)) continue;
|
||||
markVisited(a);
|
||||
|
||||
addr_t nxt = a;
|
||||
auto block = disassembleBlock(a, nxt);
|
||||
for (auto s : block.successors)
|
||||
if (!isVisited(s)) q.push(s);
|
||||
|
||||
func.blocks.push_back(block);
|
||||
}
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
CFG CFGBuilder::build()
|
||||
{
|
||||
CFG cfg;
|
||||
cfg.binary_path = m_bin->getPath();
|
||||
cfg.entry_point = m_bin->getEntryPoint();
|
||||
|
||||
switch (m_bin->getArch())
|
||||
{
|
||||
case Arch::X86: cfg.arch_str = "x86"; break;
|
||||
case Arch::X64: cfg.arch_str = "x86-64"; break;
|
||||
default: cfg.arch_str = "unknown";
|
||||
}
|
||||
switch (m_bin->getFormat())
|
||||
{
|
||||
case BinaryFormat::PE: cfg.format_str = "PE"; break;
|
||||
case BinaryFormat::ELF: cfg.format_str = "ELF"; break;
|
||||
default: cfg.format_str = "unknown";
|
||||
}
|
||||
|
||||
cfg.imports = m_bin->getImportedFunctions();
|
||||
buildGotMap();
|
||||
|
||||
m_prologs = findPrologs();
|
||||
|
||||
set<addr_t> built;
|
||||
queue<addr_t> pending;
|
||||
|
||||
auto process = [&](addr_t addr, const string& name)
|
||||
{
|
||||
if (built.count(addr)) return;
|
||||
built.insert(addr);
|
||||
|
||||
auto func = buildFunction(addr, name);
|
||||
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);
|
||||
}
|
||||
|
||||
for (auto d : m_funcs)
|
||||
if (!built.count(d)) pending.push(d);
|
||||
};
|
||||
|
||||
{
|
||||
addr_t ep = m_bin->getEntryPoint();
|
||||
string ep_name = "entry";
|
||||
for (const auto& exp : m_bin->getExportedFunctions())
|
||||
if (exp.first == ep) { ep_name = exp.second; break; }
|
||||
process(ep, ep_name);
|
||||
}
|
||||
|
||||
for (const auto& exp : m_bin->getExportedFunctions())
|
||||
pending.push(exp.first);
|
||||
|
||||
map<addr_t, string> imp_map;
|
||||
for (const auto& imp : m_bin->getImportedFunctions())
|
||||
imp_map[imp.address] = imp.name;
|
||||
|
||||
while (!pending.empty())
|
||||
{
|
||||
addr_t a = pending.front(); pending.pop();
|
||||
if (built.count(a)) continue;
|
||||
|
||||
string name;
|
||||
for (const auto& exp : m_bin->getExportedFunctions())
|
||||
if (exp.first == a) { name = exp.second; break; }
|
||||
if (name.empty())
|
||||
{
|
||||
auto it = imp_map.find(a);
|
||||
if (it != imp_map.end()) name = it->second;
|
||||
}
|
||||
|
||||
built.insert(a);
|
||||
auto func = buildFunction(a, name);
|
||||
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);
|
||||
}
|
||||
|
||||
for (auto p : m_prologs)
|
||||
if (!built.count(p)) pending.push(p);
|
||||
}
|
||||
|
||||
cfg.indirect_targets = m_targets;
|
||||
return cfg;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include "types.hpp"
|
||||
#include "loader/binary.hpp"
|
||||
#include "disasm/engine.hpp"
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
class CFGBuilder
|
||||
{
|
||||
public:
|
||||
CFGBuilder(Binary* binary, Disassembler* disasm);
|
||||
CFG build();
|
||||
|
||||
private:
|
||||
void buildGotMap();
|
||||
Function buildFunction(addr_t start, const string& name);
|
||||
BasicBlock disassembleBlock(addr_t start, addr_t& next);
|
||||
set<addr_t> findPrologs();
|
||||
void markVisited(addr_t addr);
|
||||
bool isVisited(addr_t addr);
|
||||
|
||||
addr_t resolveGOT(const Instruction& inst);
|
||||
addr_t resolveJumpTable(const vector<Instruction>& block, const Instruction& inst);
|
||||
addr_t traceReg(const string& reg, const vector<Instruction>& block, size_t idx);
|
||||
void recordIndirect(const Instruction& inst, addr_t target, bool ok, const string& name, const string& lib);
|
||||
|
||||
Binary* m_bin;
|
||||
Disassembler* m_dis;
|
||||
set<addr_t> m_funcs;
|
||||
set<addr_t> m_blocks;
|
||||
set<addr_t> m_prologs;
|
||||
map<addr_t, pair<string, string>> m_got;
|
||||
vector<IndirectTarget> m_targets;
|
||||
};
|
||||
@@ -0,0 +1,163 @@
|
||||
#include "disasm/engine.hpp"
|
||||
#include <cstring>
|
||||
|
||||
Disassembler::Disassembler() : m_handle(0), m_initialized(false) {}
|
||||
Disassembler::~Disassembler()
|
||||
{
|
||||
if (m_initialized) cs_close(&m_handle);
|
||||
}
|
||||
|
||||
bool Disassembler::init(Arch arch)
|
||||
{
|
||||
cs_arch a;
|
||||
cs_mode m;
|
||||
if (arch == Arch::X64) { a = CS_ARCH_X86; m = CS_MODE_64; }
|
||||
else if (arch == Arch::X86) { a = CS_ARCH_X86; m = CS_MODE_32; }
|
||||
else return false;
|
||||
|
||||
if (cs_open(a, m, &m_handle) != CS_ERR_OK) return false;
|
||||
cs_option(m_handle, CS_OPT_DETAIL, CS_OPT_ON);
|
||||
m_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
vector<Instruction> Disassembler::disassemble(const vector<uint8_t>& bytes, addr_t base, size_t count)
|
||||
{
|
||||
vector<Instruction> result;
|
||||
if (!m_initialized || bytes.empty()) return result;
|
||||
|
||||
const uint8_t* code = bytes.data();
|
||||
size_t size = bytes.size();
|
||||
addr_t addr = base;
|
||||
size_t n = count ? count : size;
|
||||
|
||||
cs_insn* insn = cs_malloc(m_handle);
|
||||
if (!insn) return result;
|
||||
|
||||
while (cs_disasm_iter(m_handle, &code, &size, &addr, insn))
|
||||
{
|
||||
Instruction inst;
|
||||
inst.address = insn->address;
|
||||
inst.mnemonic = insn->mnemonic;
|
||||
inst.operands = insn->op_str;
|
||||
inst.size = static_cast<uint8_t>(insn->size);
|
||||
inst.bytes.assign(insn->bytes, insn->bytes + insn->size);
|
||||
result.push_back(inst);
|
||||
|
||||
if (count && result.size() >= count) break;
|
||||
}
|
||||
|
||||
cs_free(insn, 1);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Disassembler::isProlog(const vector<Instruction>& insts) const
|
||||
{
|
||||
for (const auto& inst : insts)
|
||||
{
|
||||
if (inst.mnemonic == "push" && (inst.operands == "rbp" || inst.operands == "ebp"))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Disassembler::isEpilog(const Instruction& inst) const
|
||||
{
|
||||
return inst.mnemonic == "leave" || inst.mnemonic == "ret" || inst.mnemonic == "retf";
|
||||
}
|
||||
|
||||
bool Disassembler::isReturn(const Instruction& inst) const
|
||||
{
|
||||
return inst.mnemonic == "ret" || inst.mnemonic == "retf" || inst.mnemonic == "iret" || inst.mnemonic == "iretd";
|
||||
}
|
||||
|
||||
bool Disassembler::isTrap(const Instruction& inst) const
|
||||
{
|
||||
return inst.mnemonic == "hlt" || inst.mnemonic == "ud2" || inst.mnemonic == "int3" || inst.mnemonic == "ud0";
|
||||
}
|
||||
|
||||
bool Disassembler::isCall(const Instruction& inst) const
|
||||
{
|
||||
return inst.mnemonic == "call";
|
||||
}
|
||||
|
||||
bool Disassembler::isUnconditionalBranch(const Instruction& inst) const
|
||||
{
|
||||
return inst.mnemonic == "jmp" || inst.mnemonic == "ljmp";
|
||||
}
|
||||
|
||||
bool Disassembler::isConditionalBranch(const Instruction& inst) const
|
||||
{
|
||||
return inst.mnemonic.size() == 2 && inst.mnemonic[0] == 'j' && inst.mnemonic != "jmp";
|
||||
}
|
||||
|
||||
bool Disassembler::isIndirectBranch(const Instruction& inst) const
|
||||
{
|
||||
const string& ops = inst.operands;
|
||||
if (inst.mnemonic == "call" || inst.mnemonic == "jmp")
|
||||
{
|
||||
if (ops.find('[') != string::npos) return true;
|
||||
if (ops.size() >= 3 && ops[0] == 'r' && isdigit(ops[1])) return true;
|
||||
if (ops.size() >= 4 && ops[0] == 'r' && ops[1] == '1' && isdigit(ops[2])) return true;
|
||||
if (ops.size() >= 3 && ops[0] == 'e' && isdigit(ops[1])) return true;
|
||||
if (ops[0] == '*') return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
addr_t Disassembler::getBranchTarget(const Instruction& inst) const
|
||||
{
|
||||
const string& ops = inst.operands;
|
||||
if (ops.empty()) return 0;
|
||||
|
||||
size_t pos = string::npos;
|
||||
if (ops[0] == '0' && ops.size() > 2 && ops[1] == 'x')
|
||||
pos = 0;
|
||||
else if (ops[0] >= '0' && ops[0] <= '9')
|
||||
pos = 0;
|
||||
else
|
||||
pos = ops.find("0x");
|
||||
|
||||
if (pos == string::npos) return 0;
|
||||
|
||||
try {
|
||||
return stoull(ops.substr(pos), nullptr, 16);
|
||||
} catch (...) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t Disassembler::getRIPDisp(const Instruction& inst) const
|
||||
{
|
||||
if (!m_initialized) return 0;
|
||||
|
||||
auto bytes = inst.bytes;
|
||||
if (bytes.empty()) return 0;
|
||||
|
||||
cs_insn* insn = cs_malloc(m_handle);
|
||||
if (!insn) return 0;
|
||||
|
||||
const uint8_t* code = bytes.data();
|
||||
size_t size = bytes.size();
|
||||
addr_t addr = inst.address;
|
||||
int64_t disp = 0;
|
||||
|
||||
if (cs_disasm_iter(m_handle, &code, &size, &addr, insn))
|
||||
{
|
||||
if (insn->detail)
|
||||
{
|
||||
for (int i = 0; i < insn->detail->x86.op_count; i++)
|
||||
{
|
||||
const auto& op = insn->detail->x86.operands[i];
|
||||
if (op.type == X86_OP_MEM && op.mem.base == X86_REG_RIP)
|
||||
{
|
||||
disp = op.mem.disp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cs_free(insn, 1);
|
||||
return disp;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include "types.hpp"
|
||||
#include "loader/binary.hpp"
|
||||
#include <capstone/capstone.h>
|
||||
|
||||
class Disassembler
|
||||
{
|
||||
public:
|
||||
Disassembler();
|
||||
~Disassembler();
|
||||
|
||||
bool init(Arch arch);
|
||||
vector<Instruction> disassemble(const vector<uint8_t>& bytes, addr_t base, size_t count = 0);
|
||||
bool isProlog(const vector<Instruction>& insts) const;
|
||||
bool isEpilog(const Instruction& inst) const;
|
||||
bool isReturn(const Instruction& inst) const;
|
||||
bool isTrap(const Instruction& inst) const;
|
||||
bool isCall(const Instruction& inst) const;
|
||||
bool isUnconditionalBranch(const Instruction& inst) const;
|
||||
bool isConditionalBranch(const Instruction& inst) const;
|
||||
bool isIndirectBranch(const Instruction& inst) const;
|
||||
addr_t getBranchTarget(const Instruction& inst) const;
|
||||
int64_t getRIPDisp(const Instruction& inst) const;
|
||||
|
||||
private:
|
||||
csh m_handle;
|
||||
bool m_initialized;
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "loader/binary.hpp"
|
||||
#include "loader/elf.hpp"
|
||||
#include "loader/pe.hpp"
|
||||
#include <memory>
|
||||
|
||||
unique_ptr<Binary> Binary::create(const string& path)
|
||||
{
|
||||
auto elf = make_unique<ELFLoader>();
|
||||
if (elf->load(path)) return elf;
|
||||
|
||||
auto pe = make_unique<PELoader>();
|
||||
if (pe->load(path)) return pe;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
#include "types.hpp"
|
||||
|
||||
|
||||
|
||||
enum class BinaryFormat
|
||||
{
|
||||
PE,
|
||||
ELF,
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
enum class Arch
|
||||
{
|
||||
X86,
|
||||
X64,
|
||||
UNKNOWN
|
||||
};
|
||||
|
||||
class Binary
|
||||
{
|
||||
public:
|
||||
virtual ~Binary() = default;
|
||||
virtual bool load(const string& path) = 0;
|
||||
virtual BinaryFormat getFormat() const = 0;
|
||||
virtual Arch getArch() const = 0;
|
||||
virtual addr_t getEntryPoint() const = 0;
|
||||
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, string>> getExportedFunctions() const = 0;
|
||||
virtual vector<ImportEntry> getImportedFunctions() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
virtual const string& getPath() const = 0;
|
||||
|
||||
static unique_ptr<Binary> create(const string& path);
|
||||
};
|
||||
+332
@@ -0,0 +1,332 @@
|
||||
#include "loader/elf.hpp"
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
|
||||
static const uint64_t EI_NIDENT = 16;
|
||||
static const uint32_t PT_LOAD = 1;
|
||||
static const uint32_t PT_DYNAMIC = 2;
|
||||
static const uint32_t DT_NULL = 0;
|
||||
static const uint32_t DT_NEEDED = 1;
|
||||
static const uint32_t DT_INIT = 12;
|
||||
static const uint32_t DT_FINI = 13;
|
||||
static const uint32_t DT_INIT_ARRAY = 25;
|
||||
static const uint32_t DT_FINI_ARRAY = 26;
|
||||
static const uint32_t DT_INIT_ARRAYSZ = 27;
|
||||
static const uint32_t DT_FINI_ARRAYSZ = 28;
|
||||
static const uint32_t DT_STRTAB = 5;
|
||||
static const uint32_t DT_SYMTAB = 6;
|
||||
static const uint32_t DT_STRSZ = 10;
|
||||
static const uint32_t SHT_SYMTAB = 2;
|
||||
static const uint32_t SHT_DYNSYM = 11;
|
||||
|
||||
ELFLoader::ELFLoader()
|
||||
: m_arch(Arch::X64), m_entry(0),
|
||||
m_init_array(0), m_init_array_size(0),
|
||||
m_fini_array(0), m_fini_array_size(0),
|
||||
m_init(0), m_fini(0) {}
|
||||
|
||||
ELFLoader::~ELFLoader() {}
|
||||
|
||||
bool ELFLoader::load(const string& path)
|
||||
{
|
||||
m_path = path;
|
||||
ifstream f(path, ios::binary);
|
||||
if (!f) return false;
|
||||
|
||||
f.seekg(0, ios::end);
|
||||
size_t sz = f.tellg();
|
||||
f.seekg(0, ios::beg);
|
||||
|
||||
m_data.resize(sz);
|
||||
f.read((char*)m_data.data(), sz);
|
||||
f.close();
|
||||
|
||||
if (m_data.size() < EI_NIDENT) return false;
|
||||
if (m_data[0] != 0x7F || m_data[1] != 'E' || m_data[2] != 'L' || m_data[3] != 'F')
|
||||
return false;
|
||||
|
||||
return parseHeader();
|
||||
}
|
||||
|
||||
static uint16_t r16(const vector<uint8_t>& d, size_t off)
|
||||
{
|
||||
if (off + 2 > d.size()) return 0;
|
||||
return d[off] | (d[off+1] << 8);
|
||||
}
|
||||
|
||||
static uint32_t r32(const vector<uint8_t>& d, size_t off)
|
||||
{
|
||||
if (off + 4 > d.size()) return 0;
|
||||
return d[off] | (d[off+1] << 8) | (d[off+2] << 16) | (d[off+3] << 24);
|
||||
}
|
||||
|
||||
static uint64_t r64(const vector<uint8_t>& d, size_t off)
|
||||
{
|
||||
if (off + 8 > d.size()) return 0;
|
||||
return (uint64_t)d[off] | ((uint64_t)d[off+1] << 8) |
|
||||
((uint64_t)d[off+2] << 16) | ((uint64_t)d[off+3] << 24) |
|
||||
((uint64_t)d[off+4] << 32) | ((uint64_t)d[off+5] << 40) |
|
||||
((uint64_t)d[off+6] << 48) | ((uint64_t)d[off+7] << 56);
|
||||
}
|
||||
|
||||
struct Elf64_Ehdr
|
||||
{
|
||||
uint8_t ident[16];
|
||||
uint16_t type;
|
||||
uint16_t machine;
|
||||
uint32_t version;
|
||||
uint64_t entry;
|
||||
uint64_t phoff;
|
||||
uint64_t shoff;
|
||||
uint32_t flags;
|
||||
uint16_t ehsize;
|
||||
uint16_t phentsize;
|
||||
uint16_t phnum;
|
||||
uint16_t shentsize;
|
||||
uint16_t shnum;
|
||||
uint16_t shstrndx;
|
||||
};
|
||||
|
||||
struct Elf64_Phdr
|
||||
{
|
||||
uint32_t type;
|
||||
uint32_t flags;
|
||||
uint64_t offset;
|
||||
uint64_t vaddr;
|
||||
uint64_t paddr;
|
||||
uint64_t filesz;
|
||||
uint64_t memsz;
|
||||
uint64_t align;
|
||||
};
|
||||
|
||||
struct Elf64_Shdr
|
||||
{
|
||||
uint32_t name;
|
||||
uint32_t type;
|
||||
uint64_t flags;
|
||||
uint64_t addr;
|
||||
uint64_t offset;
|
||||
uint64_t size;
|
||||
uint32_t link;
|
||||
uint32_t info;
|
||||
uint64_t addralign;
|
||||
uint64_t entsize;
|
||||
};
|
||||
|
||||
struct Elf64_Dyn
|
||||
{
|
||||
int64_t d_tag;
|
||||
uint64_t d_val;
|
||||
};
|
||||
|
||||
bool ELFLoader::parseHeader()
|
||||
{
|
||||
if (m_data.size() < 64) return false;
|
||||
|
||||
uint8_t cls = m_data[4];
|
||||
bool is_64 = (cls == 2);
|
||||
|
||||
if (is_64)
|
||||
{
|
||||
m_arch = Arch::X64;
|
||||
if (m_data.size() < 64) return false;
|
||||
uint16_t type = r16(m_data, 16);
|
||||
m_entry = r64(m_data, 24);
|
||||
uint64_t phoff = r64(m_data, 32);
|
||||
uint64_t shoff = r64(m_data, 40);
|
||||
uint16_t phnum = r16(m_data, 56);
|
||||
uint16_t shnum = r16(m_data, 60);
|
||||
uint16_t shstrndx = r16(m_data, 62);
|
||||
|
||||
for (uint16_t i = 0; i < phnum; i++)
|
||||
{
|
||||
size_t off = phoff + i * 56;
|
||||
if (off + 56 > m_data.size()) break;
|
||||
uint32_t ptype = r32(m_data, off);
|
||||
uint32_t pflags = r32(m_data, off + 4);
|
||||
uint64_t poffset = r64(m_data, off + 8);
|
||||
uint64_t pvaddr = r64(m_data, off + 16);
|
||||
uint64_t pfilesz = r64(m_data, off + 32);
|
||||
uint64_t pmemsz = r64(m_data, off + 40);
|
||||
|
||||
if (ptype == PT_LOAD && (pflags & 1))
|
||||
{
|
||||
m_exec_ranges.push_back({pvaddr, pvaddr + pmemsz});
|
||||
}
|
||||
|
||||
if (ptype == PT_DYNAMIC)
|
||||
{
|
||||
addr_t dyn_va = pvaddr;
|
||||
size_t dyn_sz = pmemsz;
|
||||
size_t dyn_off = poffset;
|
||||
|
||||
size_t end = dyn_off + dyn_sz;
|
||||
for (size_t j = dyn_off; j + 16 <= end; j += 16)
|
||||
{
|
||||
int64_t tag = (int64_t)r64(m_data, j);
|
||||
uint64_t val = r64(m_data, j + 8);
|
||||
|
||||
if (tag == DT_NULL) break;
|
||||
if (tag == DT_NEEDED && val)
|
||||
{
|
||||
uint64_t strtab = 0;
|
||||
for (size_t k = dyn_off; k + 16 <= end; k += 16)
|
||||
{
|
||||
int64_t t2 = (int64_t)r64(m_data, k);
|
||||
uint64_t v2 = r64(m_data, k + 8);
|
||||
if (t2 == DT_STRTAB) { strtab = v2; break; }
|
||||
}
|
||||
if (strtab)
|
||||
{
|
||||
size_t name_off = strtab + val;
|
||||
if (name_off < m_data.size())
|
||||
{
|
||||
string lib;
|
||||
for (size_t c = name_off; c < m_data.size() && m_data[c]; c++)
|
||||
lib += (char)m_data[c];
|
||||
ImportEntry e;
|
||||
e.address = 0;
|
||||
e.name = lib;
|
||||
e.library = lib;
|
||||
m_imports.push_back(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tag == DT_INIT) m_init = val;
|
||||
if (tag == DT_FINI) m_fini = val;
|
||||
if (tag == DT_INIT_ARRAY) m_init_array = val;
|
||||
if (tag == DT_INIT_ARRAYSZ) m_init_array_size = val;
|
||||
if (tag == DT_FINI_ARRAY) m_fini_array = val;
|
||||
if (tag == DT_FINI_ARRAYSZ) m_fini_array_size = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < shnum; i++)
|
||||
{
|
||||
size_t off = shoff + i * 64;
|
||||
if (off + 64 > m_data.size()) break;
|
||||
uint32_t stype = r32(m_data, off + 4);
|
||||
|
||||
if (stype == SHT_DYNSYM || stype == SHT_SYMTAB)
|
||||
{
|
||||
uint64_t sym_off = r64(m_data, off + 24);
|
||||
uint64_t sym_sz = r64(m_data, off + 32);
|
||||
uint32_t link = r32(m_data, off + 40);
|
||||
uint64_t entsize = r64(m_data, off + 56);
|
||||
if (entsize == 0) entsize = 24;
|
||||
|
||||
uint64_t strtab_off = 0;
|
||||
uint64_t strtab_sz = 0;
|
||||
for (uint16_t k = 0; k < shnum; k++)
|
||||
{
|
||||
size_t soff = shoff + k * 64;
|
||||
if (soff + 64 > m_data.size()) break;
|
||||
if (r32(m_data, soff + 4) == SHT_SYMTAB || r32(m_data, soff + 4) == SHT_DYNSYM)
|
||||
continue;
|
||||
if ((uint32_t)k == link)
|
||||
{
|
||||
strtab_off = r64(m_data, soff + 24);
|
||||
strtab_sz = r64(m_data, soff + 32);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
size_t nsym = sym_sz / entsize;
|
||||
for (size_t j = 0; j < nsym; j++)
|
||||
{
|
||||
size_t sym_ent = sym_off + j * entsize;
|
||||
if (sym_ent + 16 > m_data.size()) break;
|
||||
uint32_t sym_name_off = r32(m_data, sym_ent);
|
||||
uint8_t sym_info = m_data[sym_ent + 4];
|
||||
uint64_t sym_val = r64(m_data, sym_ent + 8);
|
||||
|
||||
uint8_t sym_type = sym_info & 0xF;
|
||||
uint8_t sym_bind = sym_info >> 4;
|
||||
|
||||
if (sym_val && sym_type == 2 && sym_name_off && strtab_off)
|
||||
{
|
||||
size_t name_pos = strtab_off + sym_name_off;
|
||||
if (name_pos < m_data.size())
|
||||
{
|
||||
string sym_name;
|
||||
for (size_t c = name_pos; c < m_data.size() && m_data[c]; c++)
|
||||
sym_name += (char)m_data[c];
|
||||
if (!sym_name.empty())
|
||||
{
|
||||
m_symbols[sym_val] = sym_name;
|
||||
if (sym_bind == 0) m_exports.push_back({sym_val, sym_name});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stype == SHT_DYNSYM) break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_arch = Arch::X86;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ELFLoader::parseDynamic()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ELFLoader::parseSections()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ELFLoader::parseSymbols()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
vector<uint8_t> ELFLoader::readBytes(addr_t vaddr, size_t size) const
|
||||
{
|
||||
vector<uint8_t> result;
|
||||
for (const auto& r : m_exec_ranges)
|
||||
{
|
||||
if (vaddr >= r.first && vaddr < r.second)
|
||||
{
|
||||
addr_t offset = vaddr;
|
||||
for (const auto& r2 : m_exec_ranges)
|
||||
{
|
||||
if (offset >= r2.first && offset < r2.second)
|
||||
{
|
||||
addr_t file_off = 0;
|
||||
size_t max_read = min(size, (size_t)(r2.second - offset));
|
||||
size_t copy_start = offset - r2.first;
|
||||
if (copy_start < m_data.size())
|
||||
{
|
||||
size_t to_copy = min(max_read, m_data.size() - copy_start);
|
||||
result.assign(m_data.begin() + copy_start, m_data.begin() + copy_start + to_copy);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
vector<pair<addr_t, addr_t>> ELFLoader::getExecutableRanges() const
|
||||
{
|
||||
return m_exec_ranges;
|
||||
}
|
||||
|
||||
vector<pair<addr_t, string>> ELFLoader::getExportedFunctions() const
|
||||
{
|
||||
return m_exports;
|
||||
}
|
||||
|
||||
vector<ImportEntry> ELFLoader::getImportedFunctions() const
|
||||
{
|
||||
return m_imports;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include "loader/binary.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
class ELFLoader : public Binary
|
||||
{
|
||||
public:
|
||||
ELFLoader();
|
||||
~ELFLoader() override;
|
||||
|
||||
bool load(const string& path) override;
|
||||
BinaryFormat getFormat() const override { return BinaryFormat::ELF; }
|
||||
Arch getArch() const override { return m_arch; }
|
||||
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>> getExecutableRanges() const override;
|
||||
vector<pair<addr_t, string>> getExportedFunctions() const override;
|
||||
vector<ImportEntry> getImportedFunctions() const override;
|
||||
const string& getPath() const override { return m_path; }
|
||||
|
||||
addr_t getInitArray() const { return m_init_array; }
|
||||
addr_t getFiniArray() const { return m_fini_array; }
|
||||
size_t getInitArraySize() const { return m_init_array_size; }
|
||||
size_t getFiniArraySize() const { return m_fini_array_size; }
|
||||
addr_t getInit() const { return m_init; }
|
||||
addr_t getFini() const { return m_fini; }
|
||||
|
||||
private:
|
||||
bool parseHeader();
|
||||
bool parseDynamic();
|
||||
bool parseSections();
|
||||
bool parseSymbols();
|
||||
|
||||
string m_path;
|
||||
Arch m_arch;
|
||||
addr_t m_entry;
|
||||
vector<uint8_t> m_data;
|
||||
vector<pair<addr_t, addr_t>> m_exec_ranges;
|
||||
vector<pair<addr_t, string>> m_exports;
|
||||
vector<ImportEntry> m_imports;
|
||||
map<addr_t, string> m_symbols;
|
||||
|
||||
addr_t m_init_array;
|
||||
size_t m_init_array_size;
|
||||
addr_t m_fini_array;
|
||||
size_t m_fini_array_size;
|
||||
addr_t m_init;
|
||||
addr_t m_fini;
|
||||
};
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
#include "loader/pe.hpp"
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
|
||||
PELoader::PELoader() : m_arch(Arch::X64), m_entry(0), m_image_base(0) {}
|
||||
PELoader::~PELoader() {}
|
||||
|
||||
bool PELoader::load(const string& path)
|
||||
{
|
||||
m_path = path;
|
||||
ifstream f(path, ios::binary);
|
||||
if (!f) return false;
|
||||
|
||||
f.seekg(0, ios::end);
|
||||
size_t sz = f.tellg();
|
||||
f.seekg(0, ios::beg);
|
||||
|
||||
m_data.resize(sz);
|
||||
f.read((char*)m_data.data(), sz);
|
||||
f.close();
|
||||
|
||||
return parseHeaders();
|
||||
}
|
||||
|
||||
static uint16_t r16(const vector<uint8_t>& d, size_t off)
|
||||
{
|
||||
if (off + 2 > d.size()) return 0;
|
||||
return d[off] | (d[off+1] << 8);
|
||||
}
|
||||
|
||||
static uint32_t r32(const vector<uint8_t>& d, size_t off)
|
||||
{
|
||||
if (off + 4 > d.size()) return 0;
|
||||
return d[off] | (d[off+1] << 8) | (d[off+2] << 16) | (d[off+3] << 24);
|
||||
}
|
||||
|
||||
static uint64_t r64(const vector<uint8_t>& d, size_t off)
|
||||
{
|
||||
if (off + 8 > d.size()) return 0;
|
||||
return (uint64_t)d[off] | ((uint64_t)d[off+1] << 8) |
|
||||
((uint64_t)d[off+2] << 16) | ((uint64_t)d[off+3] << 24) |
|
||||
((uint64_t)d[off+4] << 32) | ((uint64_t)d[off+5] << 40) |
|
||||
((uint64_t)d[off+6] << 48) | ((uint64_t)d[off+7] << 56);
|
||||
}
|
||||
|
||||
bool PELoader::parseHeaders()
|
||||
{
|
||||
if (m_data.size() < 64) return false;
|
||||
if (r16(m_data, 0) != 0x5A4D) return false;
|
||||
|
||||
uint32_t pe_off = r32(m_data, 0x3C);
|
||||
if (pe_off + 4 > m_data.size()) return false;
|
||||
if (r32(m_data, pe_off) != 0x00004550) return false;
|
||||
|
||||
uint16_t machine = r16(m_data, pe_off + 4);
|
||||
uint16_t num_sections = r16(m_data, pe_off + 6);
|
||||
|
||||
if (machine == 0x8664) m_arch = Arch::X64;
|
||||
else if (machine == 0x14C) m_arch = Arch::X86;
|
||||
else return false;
|
||||
|
||||
uint16_t opt_hdr_size = r16(m_data, pe_off + 20);
|
||||
uint32_t opt_hdr_off = pe_off + 24;
|
||||
|
||||
if (opt_hdr_off + 2 > m_data.size()) return false;
|
||||
uint16_t magic = r16(m_data, opt_hdr_off);
|
||||
|
||||
if (magic == 0x10B)
|
||||
{
|
||||
m_arch = Arch::X86;
|
||||
m_image_base = r32(m_data, opt_hdr_off + 28);
|
||||
m_entry = r32(m_data, opt_hdr_off + 16) + m_image_base;
|
||||
}
|
||||
else if (magic == 0x20B)
|
||||
{
|
||||
m_arch = Arch::X64;
|
||||
m_image_base = r64(m_data, opt_hdr_off + 24);
|
||||
m_entry = r32(m_data, opt_hdr_off + 16) + m_image_base;
|
||||
}
|
||||
else return false;
|
||||
|
||||
uint16_t section_hdr_size = 40;
|
||||
uint32_t sections_off = opt_hdr_off + opt_hdr_size;
|
||||
|
||||
struct Section
|
||||
{
|
||||
string name;
|
||||
addr_t vaddr;
|
||||
uint32_t vsize;
|
||||
uint32_t raw_ptr;
|
||||
uint32_t raw_size;
|
||||
uint32_t characteristics;
|
||||
};
|
||||
vector<Section> sections;
|
||||
|
||||
for (uint16_t i = 0; i < num_sections; i++)
|
||||
{
|
||||
uint32_t off = sections_off + i * section_hdr_size;
|
||||
if (off + section_hdr_size > m_data.size()) break;
|
||||
|
||||
char name_buf[9] = {};
|
||||
memcpy(name_buf, &m_data[off], 8);
|
||||
Section s;
|
||||
s.name = name_buf;
|
||||
s.vsize = r32(m_data, off + 8);
|
||||
s.vaddr = r32(m_data, off + 12) + m_image_base;
|
||||
s.raw_size = r32(m_data, off + 16);
|
||||
s.raw_ptr = r32(m_data, off + 20);
|
||||
s.characteristics = r32(m_data, off + 36);
|
||||
sections.push_back(s);
|
||||
}
|
||||
|
||||
for (const auto& s : sections)
|
||||
{
|
||||
if (s.characteristics & 0x20000000)
|
||||
{
|
||||
addr_t start = s.vaddr;
|
||||
addr_t end = start + (s.vsize ? s.vsize : s.raw_size);
|
||||
m_exec_ranges.push_back({start, end});
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
for (const auto& s : sections)
|
||||
{
|
||||
addr_t va = rva + m_image_base;
|
||||
if (va >= s.vaddr && va < s.vaddr + (s.vsize ? s.vsize : s.raw_size))
|
||||
return s.raw_ptr + static_cast<uint32_t>(va - s.vaddr);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
auto readString = [&](uint32_t off) -> string {
|
||||
string s;
|
||||
for (size_t c = off; c < m_data.size() && m_data[c]; c++)
|
||||
s += (char)m_data[c];
|
||||
return s;
|
||||
};
|
||||
|
||||
if (import_rva)
|
||||
{
|
||||
uint32_t desc_off = rvaToOffset(import_rva);
|
||||
|
||||
for (uint32_t i = 0; ; i++)
|
||||
{
|
||||
uint32_t desc = desc_off + i * 20;
|
||||
if (desc + 20 > m_data.size()) break;
|
||||
|
||||
uint32_t oft_rva = r32(m_data, desc);
|
||||
if (oft_rva == 0) break;
|
||||
|
||||
uint32_t name_rva = r32(m_data, desc + 12);
|
||||
string dll_name;
|
||||
if (name_rva)
|
||||
{
|
||||
uint32_t name_off = rvaToOffset(name_rva);
|
||||
if (name_off) dll_name = readString(name_off);
|
||||
}
|
||||
if (dll_name.empty()) continue;
|
||||
|
||||
uint32_t thunk_rva = r32(m_data, desc + 16);
|
||||
if (thunk_rva == 0) thunk_rva = oft_rva;
|
||||
|
||||
uint32_t thunk_off = rvaToOffset(thunk_rva);
|
||||
if (!thunk_off) continue;
|
||||
|
||||
size_t entry_size = is_64 ? 8 : 4;
|
||||
uint64_t ordinal_flag = is_64 ? 0x8000000000000000ULL : 0x80000000;
|
||||
|
||||
for (uint32_t j = 0; ; j++)
|
||||
{
|
||||
uint32_t entry = static_cast<uint32_t>(thunk_off + j * entry_size);
|
||||
if (entry + entry_size > m_data.size()) break;
|
||||
|
||||
uint64_t val = is_64 ? r64(m_data, entry) : r32(m_data, entry);
|
||||
if (val == 0) break;
|
||||
|
||||
string func_name;
|
||||
if (val & ordinal_flag)
|
||||
{
|
||||
func_name = "ord_" + to_string(val & 0xFFFF);
|
||||
}
|
||||
else
|
||||
{
|
||||
addr_t hint_va = val + m_image_base;
|
||||
uint32_t hint_off = rvaToOffset(static_cast<uint32_t>(val));
|
||||
if (hint_off && hint_off + 2 < m_data.size())
|
||||
func_name = readString(hint_off + 2);
|
||||
}
|
||||
|
||||
addr_t iat_addr = thunk_rva + m_image_base + j * entry_size;
|
||||
ImportEntry e;
|
||||
e.address = iat_addr;
|
||||
e.name = func_name;
|
||||
e.library = dll_name;
|
||||
m_imports.push_back(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PELoader::parseImportTable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PELoader::parseExportTable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
vector<uint8_t> PELoader::readBytes(addr_t vaddr, size_t size) const
|
||||
{
|
||||
vector<uint8_t> result;
|
||||
uint32_t off = 0;
|
||||
uint32_t raw_off = 0;
|
||||
uint32_t raw_end = 0;
|
||||
|
||||
uint16_t num_sections = r16(m_data, r32(m_data, 0x3C) + 6);
|
||||
uint32_t pe_off = r32(m_data, 0x3C);
|
||||
uint16_t opt_sz = r16(m_data, pe_off + 20);
|
||||
uint32_t sections_off = pe_off + 24 + opt_sz;
|
||||
|
||||
for (uint16_t i = 0; i < num_sections; i++)
|
||||
{
|
||||
uint32_t so = sections_off + i * 40;
|
||||
if (so + 40 > m_data.size()) break;
|
||||
addr_t sva = r32(m_data, so + 12) + m_image_base;
|
||||
uint32_t svs = r32(m_data, so + 8);
|
||||
uint32_t srs = r32(m_data, so + 16);
|
||||
uint32_t srp = r32(m_data, so + 20);
|
||||
addr_t end = sva + (svs ? svs : srs);
|
||||
|
||||
if (vaddr >= sva && vaddr < end)
|
||||
{
|
||||
off = srp + static_cast<uint32_t>(vaddr - sva);
|
||||
raw_off = srp;
|
||||
raw_end = srp + srs;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!off) return result;
|
||||
|
||||
size_t available = (raw_end > off) ? (raw_end - off) : 0;
|
||||
size_t to_read = min(size, available);
|
||||
if (off + to_read > m_data.size())
|
||||
to_read = m_data.size() - off;
|
||||
|
||||
if (to_read > 0)
|
||||
result.assign(m_data.begin() + off, m_data.begin() + off + to_read);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
vector<pair<addr_t, addr_t>> PELoader::getExecutableRanges() const
|
||||
{
|
||||
return m_exec_ranges;
|
||||
}
|
||||
|
||||
vector<pair<addr_t, string>> PELoader::getExportedFunctions() const
|
||||
{
|
||||
return m_exports;
|
||||
}
|
||||
|
||||
vector<ImportEntry> PELoader::getImportedFunctions() const
|
||||
{
|
||||
return m_imports;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include "loader/binary.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
class PELoader : public Binary
|
||||
{
|
||||
public:
|
||||
PELoader();
|
||||
~PELoader() override;
|
||||
|
||||
bool load(const string& path) override;
|
||||
BinaryFormat getFormat() const override { return BinaryFormat::PE; }
|
||||
Arch getArch() const override { return m_arch; }
|
||||
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>> getExecutableRanges() const override;
|
||||
vector<pair<addr_t, string>> getExportedFunctions() const override;
|
||||
vector<ImportEntry> getImportedFunctions() const override;
|
||||
const string& getPath() const override { return m_path; }
|
||||
|
||||
private:
|
||||
bool parseHeaders();
|
||||
bool parseImportTable();
|
||||
bool parseExportTable();
|
||||
|
||||
string m_path;
|
||||
Arch m_arch;
|
||||
addr_t m_entry;
|
||||
addr_t m_image_base;
|
||||
vector<uint8_t> m_data;
|
||||
vector<pair<addr_t, addr_t>> m_exec_ranges;
|
||||
vector<pair<addr_t, string>> m_exports;
|
||||
vector<ImportEntry> m_imports;
|
||||
};
|
||||
@@ -0,0 +1,74 @@
|
||||
#include "types.hpp"
|
||||
#include "loader/binary.hpp"
|
||||
#include "cfg/builder.hpp"
|
||||
#include "disasm/engine.hpp"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
cerr << "usage: cfgrip <binary>" << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
string path = argv[1];
|
||||
|
||||
auto binary = Binary::create(path);
|
||||
if (!binary)
|
||||
{
|
||||
cerr << "failed to load binary: " << path << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
cout << "format: ";
|
||||
switch (binary->getFormat())
|
||||
{
|
||||
case BinaryFormat::PE: cout << "PE"; break;
|
||||
case BinaryFormat::ELF: cout << "ELF"; break;
|
||||
default: cout << "unknown";
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
cout << "arch: ";
|
||||
switch (binary->getArch())
|
||||
{
|
||||
case Arch::X86: cout << "x86"; break;
|
||||
case Arch::X64: cout << "x86-64"; break;
|
||||
default: cout << "unknown";
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
cout << "entry: 0x" << hex << binary->getEntryPoint() << dec << endl;
|
||||
cout << "imports: " << binary->getImportedFunctions().size() << endl;
|
||||
|
||||
for (const auto& imp : binary->getImportedFunctions())
|
||||
{
|
||||
cout << " 0x" << hex << imp.address << " " << imp.name;
|
||||
if (!imp.library.empty()) cout << " (" << imp.library << ")";
|
||||
cout << dec << endl;
|
||||
}
|
||||
|
||||
Disassembler dis;
|
||||
if (!dis.init(binary->getArch()))
|
||||
{
|
||||
cerr << "failed to init capstone" << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
CFGBuilder builder(binary.get(), &dis);
|
||||
CFG cfg = builder.build();
|
||||
|
||||
cout << "functions: " << cfg.functions.size() << endl;
|
||||
cout << "indirect targets: " << cfg.indirect_targets.size() << endl;
|
||||
|
||||
string out = path + ".cfg";
|
||||
ofstream f(out);
|
||||
if (f)
|
||||
{
|
||||
f << cfg.toJSON();
|
||||
f.close();
|
||||
cout << "cfg written to: " << out << endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Binary file not shown.
+151183
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,169 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
|
||||
|
||||
using namespace std;
|
||||
using addr_t = uint64_t;
|
||||
|
||||
struct ImportEntry
|
||||
{
|
||||
addr_t address;
|
||||
string name;
|
||||
string library;
|
||||
};
|
||||
|
||||
struct IndirectTarget
|
||||
{
|
||||
addr_t instruction_addr;
|
||||
addr_t resolved_target;
|
||||
string name;
|
||||
string library;
|
||||
bool resolved;
|
||||
};
|
||||
|
||||
struct Instruction
|
||||
{
|
||||
addr_t address;
|
||||
vector<uint8_t> bytes;
|
||||
string mnemonic;
|
||||
string operands;
|
||||
uint8_t size;
|
||||
};
|
||||
|
||||
struct BasicBlock
|
||||
{
|
||||
addr_t address;
|
||||
vector<Instruction> instructions;
|
||||
vector<addr_t> successors;
|
||||
bool is_prolog;
|
||||
bool is_epilog;
|
||||
};
|
||||
|
||||
struct Function
|
||||
{
|
||||
addr_t address;
|
||||
string name;
|
||||
vector<BasicBlock> blocks;
|
||||
};
|
||||
|
||||
struct CFG
|
||||
{
|
||||
string binary_path;
|
||||
string arch_str;
|
||||
string format_str;
|
||||
addr_t entry_point;
|
||||
vector<Function> functions;
|
||||
vector<ImportEntry> imports;
|
||||
vector<IndirectTarget> indirect_targets;
|
||||
|
||||
static string escapeJSON(const string& s)
|
||||
{
|
||||
ostringstream os;
|
||||
for (char c : s)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '"': os << "\\\""; break;
|
||||
case '\\': os << "\\\\"; break;
|
||||
case '\n': os << "\\n"; break;
|
||||
case '\r': os << "\\r"; break;
|
||||
case '\t': os << "\\t"; break;
|
||||
default: os << c;
|
||||
}
|
||||
}
|
||||
return os.str();
|
||||
}
|
||||
|
||||
string toJSON() const
|
||||
{
|
||||
ostringstream os;
|
||||
os << "{\n";
|
||||
os << " \"binary\": \"" << escapeJSON(binary_path) << "\",\n";
|
||||
os << " \"arch\": \"" << escapeJSON(arch_str) << "\",\n";
|
||||
os << " \"format\": \"" << escapeJSON(format_str) << "\",\n";
|
||||
os << " \"entry_point\": \"0x" << hex << entry_point << "\",\n";
|
||||
os << " \"imports\": [\n";
|
||||
for (size_t i = 0; i < imports.size(); ++i) {
|
||||
os << " {\n";
|
||||
os << " \"address\": \"0x" << hex << imports[i].address << "\",\n";
|
||||
os << " \"name\": \"" << escapeJSON(imports[i].name) << "\",\n";
|
||||
os << " \"library\": \"" << escapeJSON(imports[i].library) << "\"\n";
|
||||
os << " }";
|
||||
if (i < imports.size() - 1) os << ",";
|
||||
os << "\n";
|
||||
}
|
||||
os << " ],\n";
|
||||
|
||||
os << " \"indirect_targets\": [\n";
|
||||
for (size_t i = 0; i < indirect_targets.size(); ++i) {
|
||||
const auto& it = indirect_targets[i];
|
||||
os << " {\n";
|
||||
os << " \"instruction\": \"0x" << hex << it.instruction_addr << "\",\n";
|
||||
if (it.resolved) {
|
||||
os << " \"resolved_target\": \"0x" << it.resolved_target << "\",\n";
|
||||
} else {
|
||||
os << " \"resolved_target\": null,\n";
|
||||
}
|
||||
os << " \"name\": \"" << escapeJSON(it.name) << "\",\n";
|
||||
os << " \"library\": \"" << escapeJSON(it.library) << "\"\n";
|
||||
os << " }";
|
||||
if (i < indirect_targets.size() - 1) os << ",";
|
||||
os << "\n";
|
||||
}
|
||||
os << " ],\n";
|
||||
|
||||
os << " \"functions\": [\n";
|
||||
for (size_t i = 0; i < functions.size(); ++i) {
|
||||
const auto& f = functions[i];
|
||||
os << " {\n";
|
||||
os << " \"address\": \"0x" << hex << f.address << "\",\n";
|
||||
os << " \"name\": \"" << escapeJSON(f.name) << "\",\n";
|
||||
os << " \"blocks\": [\n";
|
||||
for (size_t j = 0; j < f.blocks.size(); ++j) {
|
||||
const auto& b = f.blocks[j];
|
||||
os << " {\n";
|
||||
os << " \"address\": \"0x" << hex << b.address << "\",\n";
|
||||
os << " \"size\": " << dec << b.instructions.size() << ",\n";
|
||||
os << " \"is_prolog\": " << (b.is_prolog ? "true" : "false") << ",\n";
|
||||
os << " \"is_epilog\": " << (b.is_epilog ? "true" : "false") << ",\n";
|
||||
os << " \"instructions\": [\n";
|
||||
for (size_t k = 0; k < b.instructions.size(); ++k) {
|
||||
const auto& inst = b.instructions[k];
|
||||
os << " {\n";
|
||||
os << " \"address\": \"0x" << hex << inst.address << "\",\n";
|
||||
os << " \"size\": " << dec << (int)inst.size << ",\n";
|
||||
os << " \"mnemonic\": \"" << escapeJSON(inst.mnemonic) << "\",\n";
|
||||
os << " \"operands\": \"" << escapeJSON(inst.operands) << "\"\n";
|
||||
os << " }";
|
||||
if (k < b.instructions.size() - 1) os << ",";
|
||||
os << "\n";
|
||||
}
|
||||
os << " ],\n";
|
||||
os << " \"successors\": [\n";
|
||||
for (size_t k = 0; k < b.successors.size(); ++k) {
|
||||
os << " \"0x" << hex << b.successors[k] << "\"";
|
||||
if (k < b.successors.size() - 1) os << ",";
|
||||
os << "\n";
|
||||
}
|
||||
os << " ]\n";
|
||||
os << " }";
|
||||
if (j < f.blocks.size() - 1) os << ",";
|
||||
os << "\n";
|
||||
}
|
||||
os << " ]\n";
|
||||
os << " }";
|
||||
if (i < functions.size() - 1) os << ",";
|
||||
os << "\n";
|
||||
}
|
||||
os << " ]\n";
|
||||
os << "}\n";
|
||||
return os.str();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user