| name | re-source |
| description | Re-source IDA binaries. Use when asked for recursive annotation, structure recovery, type reconstruction, or bottom-up program understanding. |
| metadata | {"argument-hint":"[function-name or address]"} |
| allowed-tools | ["Bash","Read","Glob","Grep"] |
For structure recovery patterns, see: references/struct-recovery-patterns.md
This skill teaches a methodology for recovering source-level understanding from compiled binaries using idasql. It orchestrates the other skills into a systematic workflow.
Core Workflow: Recursive Re-Sourcing
1. Start at a Function
Pick a function — an entry point, a function referenced by an interesting string, or a callee of a known function.
SELECT decompile(0x401000);
SELECT decompile('DriverEntry');
2. Annotate the Function
Use the annotations skill to edit the decompilation into something readable:
UPDATE ctree_lvars SET name = 'driver_object' WHERE func_addr = 0x401000 AND idx = 0;
UPDATE ctree_lvars SET name = 'registry_path' WHERE func_addr = 0x401000 AND idx = 1;
UPDATE ctree_lvars SET type = 'PDRIVER_OBJECT'
WHERE func_addr = 0x401000 AND idx = 0;
SELECT line_num, addr, line, comment
FROM pseudocode
WHERE func_addr = 0x401000
ORDER BY line_num;
UPDATE pseudocode SET comment = 'initialize dispatch table'
WHERE func_addr = 0x401000 AND addr = 0x401020;
SELECT decompile(0x401000, 1);
3. Set a Function Comment
Write a concise summary describing what the function does. This makes the function indexable for later queries.
For exact trigger semantics (function summary / func-summary / singular add function comment), follow the annotations skill's Function Summary contract.
SELECT addr, name, comment, rpt_comment
FROM funcs
WHERE addr = 0x401000;
UPDATE funcs
SET rpt_comment = 'DriverEntry: initializes driver dispatch routines and device object'
WHERE addr = 0x401000;
4. Recurse into Callees
Follow calls inside the function. Annotate each callee the same way, building understanding bottom-up.
SELECT callee_name, printf('0x%X', callee_addr) as addr
FROM disasm_calls WHERE func_addr = 0x401000;
SELECT func_name, depth FROM call_graph
WHERE start = 0x401000 AND direction = 'down' AND max_depth = 5;
SELECT decompile(0x401050);
5. Recurse into Callers
Follow callers to build the bigger picture: how is this function used?
SELECT caller_name, printf('0x%X', caller_addr) as addr
FROM callers WHERE func_addr = 0x401000;
SELECT func_name, depth FROM call_graph
WHERE start = 0x401000 AND direction = 'up' AND max_depth = 10;
SELECT step, func_name FROM shortest_path
WHERE from_addr = (SELECT addr FROM funcs WHERE name = 'main')
AND to_addr = 0x401000 AND max_depth = 20;
SELECT decompile(0x400F00);
6. Structure Recovery
The hardest part. Decompiled code often shows casts like *(DWORD *)(a1 + 0x10) — these are structure field accesses.
Step-by-step Process
a) Identify offset patterns in a single function:
SELECT decompile(0x401000);
SELECT addr, op_name, num_value
FROM ctree WHERE func_addr = 0x401000
AND op_name IN ('cot_add', 'cot_idx')
AND num_value IS NOT NULL;
b) Cross-function correlation — find more fields:
SELECT DISTINCT (SELECT name FROM funcs WHERE dc.func_addr >= addr AND dc.func_addr < end_addr LIMIT 1) as caller
FROM disasm_calls dc
WHERE dc.callee_addr = 0x401000;
c) Callee correlation — let callees reveal field types:
SELECT COALESCE(call_obj_name, call_helper_name) AS callee_name,
arg_idx, arg_op, arg_num_value
FROM ctree_call_args
WHERE func_addr = 0x401000
AND arg_var_name = 'a1';
d) Build the struct incrementally:
INSERT INTO types (name, kind) VALUES ('MY_CONTEXT', 'struct');
SELECT ordinal FROM types WHERE name = 'MY_CONTEXT';
INSERT INTO types_members (type_ordinal, member_name, member_type)
SELECT ordinal, 'handle', 'void *' FROM types WHERE name = 'MY_CONTEXT';
INSERT INTO types_members (type_ordinal, member_name, member_type)
SELECT ordinal, 'buffer_ptr', 'void *' FROM types WHERE name = 'MY_CONTEXT';
INSERT INTO types_members (type_ordinal, member_name, member_type)
SELECT ordinal, 'buffer_size', 'unsigned int' FROM types WHERE name = 'MY_CONTEXT';
e) Apply the recovered struct:
SELECT parse_decls('struct MY_CONTEXT { void *handle; void *buffer_ptr; unsigned int buffer_size; };');
UPDATE funcs SET prototype = 'int __fastcall process_context(MY_CONTEXT *ctx);'
WHERE addr = 0x401000;
UPDATE ctree_lvars SET type = 'MY_CONTEXT *'
WHERE func_addr = 0x401000 AND idx = 0;
SELECT decompile(0x401000, 1);
7. Track Progress
Use netnode_kv to persist progress across sessions:
INSERT INTO netnode_kv(key, value)
VALUES('re_source:0x401000', '{"status":"done","summary":"DriverEntry init"}');
SELECT key, value FROM netnode_kv WHERE key LIKE 're_source:%';
Advanced Re-Sourcing Patterns (CTEs)
Transitive caller discovery
Find all functions that transitively pass a struct through a chain of calls — who ultimately provides the data?
Prefer call_graph for simple traversal: SELECT func_name, depth FROM call_graph WHERE start = 0x401000 AND direction = 'up' AND max_depth = 5 replaces the CTE below. Use the CTE only when you need to JOIN caller context (e.g. offset accesses) at each step.
WITH RECURSIVE caller_chain AS (
SELECT c.caller_func_addr AS func_addr,
c.caller_name AS func_name,
1 AS depth
FROM callers c
WHERE c.func_addr = 0x401000
UNION ALL
SELECT c.caller_func_addr,
c.caller_name,
cc.depth + 1
FROM caller_chain cc
JOIN callers c ON c.func_addr = cc.func_addr
WHERE cc.depth < 5
)
SELECT DISTINCT func_name, printf('0x%X', func_addr) AS addr, MIN(depth) AS min_depth
FROM caller_chain
GROUP BY func_addr
ORDER BY min_depth;
Aggregate offset accesses across all callers
Build a comprehensive struct field map by collecting offset patterns from every function that touches the struct:
WITH callers_of AS (
SELECT DISTINCT func_addr
FROM disasm_calls
WHERE callee_addr = 0x401000
),
offset_accesses AS (
SELECT func_addr,
(SELECT name FROM funcs WHERE func_addr >= addr AND func_addr < end_addr LIMIT 1) AS func_name,
num_value AS field_offset,
op_name
FROM ctree
WHERE func_addr IN (SELECT func_addr FROM callers_of)
AND op_name IN ('cot_add', 'cot_idx')
AND num_value IS NOT NULL
AND num_value BETWEEN 0 AND 0x1000
)
SELECT field_offset,
printf('0x%X', field_offset) AS hex_offset,
COUNT(DISTINCT func_addr) AS seen_in_funcs,
GROUP_CONCAT(DISTINCT func_name) AS functions
FROM offset_accesses
GROUP BY field_offset
ORDER BY field_offset;
Find struct-heavy functions (candidates for structure recovery)
Functions with the most cot_add offset patterns are likely manipulating structs through raw pointer arithmetic:
WITH offset_funcs AS (
SELECT func_addr,
COUNT(*) AS offset_accesses,
COUNT(DISTINCT num_value) AS unique_offsets
FROM ctree
WHERE func_addr IN (SELECT addr FROM funcs ORDER BY size DESC LIMIT 100)
AND op_name = 'cot_add'
AND num_value IS NOT NULL
AND num_value BETWEEN 1 AND 0x1000
GROUP BY func_addr
)
SELECT (SELECT name FROM funcs WHERE func_addr >= addr AND func_addr < end_addr LIMIT 1) AS name,
printf('0x%X', func_addr) AS addr,
offset_accesses,
unique_offsets
FROM offset_funcs
ORDER BY unique_offsets DESC
LIMIT 15;
Cross-reference struct field offsets with known type sizes
Match observed offsets against field sizes of existing types to guess field types:
WITH observed AS (
SELECT DISTINCT num_value AS offset
FROM ctree
WHERE func_addr = 0x401000
AND op_name = 'cot_add'
AND num_value IS NOT NULL
AND num_value BETWEEN 0 AND 0x200
),
candidate_types AS (
SELECT t.name AS type_name, t.size AS type_size
FROM types t
WHERE t.is_struct = 1 AND t.size > 0
)
SELECT o.offset, printf('0x%X', o.offset) AS hex_offset,
ct.type_name, ct.type_size
FROM observed o
LEFT JOIN candidate_types ct ON ct.type_size = o.offset
ORDER BY o.offset;
Key Principles
-
Bottom-up understanding: Start with leaf callees, annotate them, then work up to callers. Each annotated callee makes the caller easier to understand.
-
Cross-function struct correlation: A single function rarely reveals the full struct layout. Look at multiple callers/callees of the same function to discover different fields.
-
Iterative refinement: Apply what you know, re-decompile, see if the output improves. Add more fields/types as you discover them.
-
Verify every edit: Follow the Mandatory Mutation Loop (read → edit → refresh → verify) from the annotations skill.
-
Save periodically: Use SELECT save_database() to persist your work.
Related Skills
annotations — The editing/annotation workflow: how to rename, retype, comment
decompiler — Deep decompiler reference: ctree, types, parse_decls, union selection
types — Type system mechanics: struct/union/enum creation and manipulation
xrefs — Caller/callee traversal, call_graph / shortest_path tables, string_refs view
disassembly — cfg_edges for control flow understanding during struct recovery
storage — netnode_kv for tracking progress across sessions