
Desmonta binários PE/ELF x86/x64, resolve ramos indiretos via GOT, tabelas de salto e rastreamento de registradores, e exporta o grafo de fluxo de controle completo como JSON estruturado para análise estática e correção.
Extrator de CFG para PE/ELF x86/x64. Recebe um binário, desmonta-o, resolve cada salto e chamada (GOT, tabelas de salto, rastreamento de registradores) e exporta o grafo de fluxo de controle completo como JSON estruturado.
Você precisa saber exatamente para onde cada desvio vai. Não para leitura — para patch. Alimente o JSON no Zydis ou AsmJit, localize a instrução exata que precisa interceptar ou modificar e escreva de volta. Equipes anti-trapaça o usam para mapear binários de jogos. Pessoas de RE o usam para extrair código para seus próprios pipelines de análise. Analistas de software rastreiam caminhos de execução sem executar o binário.
cfgrip fornece o mapa. O que você faz com ele é por sua conta.
Para cada binário que o cfgrip processa, ele produz:
.pdata), nomes (ponto de entrada, exportações, descobertas) e marcação de thunks (stubs PLT).pdata, ponteiros de função em seção de dados) para cobertura máximaCom --clean, adicionalmente:
jmp → jmp em arestas diretasstack_offset (delta RSP a partir da entrada da função)xrefs mapeando cada destino de chamada/salto de volta para seus chamadoresEle lida com desvios indiretos por:
mov/lea para encontrar endereços concretoscfgrip [--subs-only] [--clean] <binary>
Alimente com um binário, obtenha <binary>.cfg como saída.
Use --subs-only para extrair apenas funções alcançáveis a partir do ponto de entrada através do grafo de chamadas — ignora exportações e candidatos a prólogo inalcançáveis.
Use --clean para aplicar jump-threading, poda de blocos mortos, rastreamento de deslocamento de pilha e análise de referências cruzadas.
Exemplo:
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: 1975
indirect targets: 3453
cfg written to: tests\example1.exe.cfg
O arquivo .cfg é JSON estruturado. Aqui está como ele se parece:
{
"binary": "tests\\example1.exe",
"mode": "full",
"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"
]
},
...
...
...
Cada função agora inclui campos opcionais:
end_address — fim preciso da função quando disponível (da tabela de exceção .pdata do PE), caso contrário calculado como o endereço máximo de instrução em todos os blocosis_thunk — true para stubs PLT e thunks de importação (funções que apenas redirecionam para outro endereço)--subs-only{
"binary": "C:\\binaries\\target.exe",
"mode": "subs-only",
"arch": "x86-64",
"format": "PE",
"entry_point": "0x1400054bc",
"imports": [ ... ],
"indirect_targets": [ ... ],
"functions": [
{
"address": "0x1400054bc",
"name": "entry",
"blocks": [ ... ]
},
...
]
}
O campo "mode": "subs-only" informa às ferramentas downstream que este CFG contém apenas funções alcançáveis a partir do ponto de entrada. Exportações e candidatos a prólogo inalcançáveis são excluídos — menos funções, superfície de análise mais limpa.
--clean{
"binary": "C:\\binaries\\target.exe",
"mode": "full+clean",
"arch": "x86-64",
"format": "PE",
"entry_point": "0x1400054bc",
"imports": [ ... ],
"indirect_targets": [ ... ],
"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",
"stack_offset": 0
},
{
"address": "0x1400054c0",
"size": 5,
"mnemonic": "call",
"operands": "0x140005d30",
"stack_offset": -40
},
{
"address": "0x1400054c5",
"size": 4,
"mnemonic": "add",
"operands": "rsp, 0x28",
"stack_offset": -40
},
{
"address": "0x1400054c9",
"size": 5,
"mnemonic": "jmp",
"operands": "0x140005340",
"stack_offset": 0
}
],
"successors": [ "0x140005340" ]
},
...
]
},
...
],
"xrefs": [
{
"target": "0x140011b00",
"callers": [
{ "address": "0x14001a30f", "type": "call" },
{ "address": "0x140019ec1", "type": "call" },
...
]
},
{
"target": "0x140007394",
"callers": [
{ "address": "0x14001a31a", "type": "call" },
...
]
},
...
]
}
Cada instrução no modo --clean inclui stack_offset — o delta RSP a partir da entrada da função naquele ponto de instrução. A seção xrefs mapeia cada destino de chamada/salto de volta para cada instrução que o referencia.
--subs-only --clean{
"binary": "C:\\binaries\\target.exe",
"mode": "subs-only+clean",
...
}
Mesma estrutura que --clean, mas com "mode": "subs-only+clean" para indicar que ambos os filtros foram aplicados. O número de funções é reduzido para apenas funções alcançáveis pelo ponto de entrada, e as funções restantes têm deslocamentos de pilha e referências cruzadas.
O cfgrip descobre funções através de múltiplas passagens de detecção:
Funções com is_thunk: true são stubs PLT ou thunks de importação — funções de bloco único que redirecionam para outro endereço.
Requer CMake e um compilador C++17. Capstone é obtido automaticamente.
cmake -B build
cmake --build build --config Release
./build/cfgrip <binary>
Ou no Windows com Visual Studio:
cmake -B build -S .
cmake --build build --config Release
.\build\Release\cfgrip.exe <binary>

A detecção de limites de função nesta ferramenta é baseada na abordagem descrita em "Function Boundary Detection in Stripped Binaries" (Alves-Foss & Song, 2019), que introduz um algoritmo multi-heurístico para localizar inícios e fins de funções em binários x86/x64 sem símbolos.
O artigo está disponível em papers/Function_Boundary_Detection_in_Stripped_Binaries.pdf.
Como nossa implementação mapeia para as heurísticas do artigo:
A percepção chave do artigo é que heurísticas algorítmicas — sem aprendizado de máquina — podem alcançar alta precisão em binários sem símbolos. Nossa implementação segue essa filosofia, usando uma abordagem de múltiplas passagens, onde cada passagem captura funções que as outras podem perder.
| Passagem | O que detecta | Abrange |
|---|
| Escaneamento de prólogo | push rbp, push r15/r14/r13/r12/rbx/rdi/rsi, sub rsp, >=0x20, enter | MSVC x64, GCC, funções folha, CET (endbr64) |
| Alvos de chamada | Cada destino de instrução call é um início de função | Chamadas diretas e indiretas resolvidas por GOT |
| Chamadas de cauda | Instruções jmp visando candidatos a prólogo | Cadeias otimizadas de chamada de cauda |
.pdata (PE) | Entradas de função em tempo de execução da tabela de tratadores de exceção | Início/fim preciso para cada função PE x64 |
| Ponteiros de dados | Valores de 8 bytes em .rdata/.data apontando para código executável | Ponteiros de função, vtables, callbacks |
| Formatos | PE (32/64 bits) | SIM |
| ELF (64 bits) | SIM | |
| Arquiteturas | x86 | SIM |
| x86-64 | SIM | |
| Chamadas indiretas | Resolução GOT | SIM |
| Detecção de tabela de salto | SIM | |
| Rastreamento regressivo de registradores | SIM | |
| Descoberta de funções | Ponto de entrada | SIM |
| Exportações | SIM | |
Destinos de call | SIM | |
Escaneamento de prólogo (MSVC x64, GCC, CET endbr64) | SIM | |
Detecção de chamada de cauda (jmp → função) | SIM | |
PE .pdata (tabela de tratadores de exceção) | SIM | |
| Escaneamento de ponteiros de função na seção de dados | SIM | |
| Detecção de thunks | Stubs PLT / thunks de importação (is_thunk) | SIM |
| Limites de funções | end_address de .pdata ou instrução máxima | SIM |
| Modo apenas subs | Flag --subs-only | SIM |
| Limpeza de CFG | --clean (jump-thread, poda de blocos mortos, deltas de pilha, xrefs) | SIM |
| Heurística | Descrição no artigo | Nossa implementação |
|---|
| H1–H4 | Assinaturas de prólogo (push rbp, registradores callee-saved, sub de pilha, enter) | isProlog() em disasm/engine.cpp — detecta push rbp, push r15..rbx, sub rsp >= 0x20, enter |
| H5 | Semeadura de alvos de chamada | Cada destino de call direto é um início de função |
| H6 | Detecção de salto para função (chamada de cauda) | jmp para candidatos a prólogo adiciona destino à fila de funções |
| H7 | Análise de tabela de exceção | Entradas RUNTIME_FUNCTION de .pdata do PE fornecem início/fim precisos |
| H8 | Análise de referência de dados | scanDataPointers() percorre seções de dados em busca de ponteiros de código |