From arc-probe
Discovers C++ classes by name via RTTI in target processes, maps vtables, disassembles virtual functions, explores inheritance hierarchies, and labels structures for analysis.
npx claudepluginhub vzco/arc-probe --plugin arc-probeThis skill uses the workspace's default tool permissions.
Given a class name (or partial name), use RTTI to find the class in the target process, map its vtable, disassemble key virtual functions, explore the inheritance hierarchy, find related classes, and build a complete class map with labels in the GUI.
Analyzes C++ virtual function tables: detects parameters, finds string references, measures sizes, labels entries. Resolves via RTTI class names or direct addresses in modules.
Reconstruct data structures by analyzing memory access patterns across functions
Performs depth-first reverse engineering on Ghidra binaries, answering questions like function behavior, crypto usage, or C2 addresses via iterative analysis and database improvements.
Share bugs, ideas, or general feedback.
Given a class name (or partial name), use RTTI to find the class in the target process, map its vtable, disassemble key virtual functions, explore the inheritance hierarchy, find related classes, and build a complete class map with labels in the GUI.
class_name (required): Class name or partial name to search for (e.g., "BaseEntity", "PlayerPawn", "Render")module (optional): Module to search in (e.g., "client.dll"). If omitted, searches all modules.probe.exe "rtti find <class_name> <module>"
This searches RTTI type descriptors for class names containing the search term. Returns:
name — the demangled class namevtable — vtable addressmodule — which module contains itIf multiple matches are found, present them all and let the user choose. Common pattern: searching for "Entity" might match CEntityInstance, C_BaseEntity, CEntitySystem, etc.
If no matches are found:
"Entity" instead of "CBaseEntity""BaseEntity" instead of "Game::CBaseEntity"/GR-). See "If RTTI is stripped" below.probe.exe "rtti hierarchy <class_name> <module>"
This walks the RTTI Class Hierarchy Descriptor to find all base classes. Example output:
C_CitadelPlayerPawn
<- C_BasePlayerPawn
<- C_BaseModelEntity
<- C_BaseEntity
<- CEntityInstance
Record the full chain. Each base class contributes fields and virtual functions to the derived class. Fields from base classes appear at lower offsets.
probe.exe "rtti vtable <class_name> <module>"
This reads the vtable entries — each entry is a pointer to a virtual function. The response includes:
vtable_address — base of the vtable in memoryentries — array of function pointers with disassembly previewsFor each vtable entry, you get:
index — vtable slot number (0, 1, 2, ...)address — function pointer valuepreview — first disassembled instructionScan through the vtable entries and categorize each function:
Tiny functions (1-5 instructions) — analyze inline:
probe.exe "disasm <vtable_entry_address> 8"
Recognize these patterns:
Destructor (usually index 0 or 1):
push rbx
sub rsp, 0x20
...
call operator_delete ; or just deallocates and returns
Getter (returns a field):
mov eax, [rcx+0x354] ; int32 getter at offset 0x354
ret
or
movss xmm0, [rcx+0x40] ; float getter at offset 0x40
ret
Boolean getter:
movzx eax, byte ptr [rcx+0x103] ; bool at offset 0x103
ret
Setter:
mov [rcx+0x354], edx ; sets field at 0x354 from second arg
ret
Type check / IsA:
xor eax, eax ; return false
ret
or
mov eax, 1 ; return true
ret
Thunk (redirects to another function):
jmp <other_address>
Pure virtual stub (should never be called):
int3 ; or call __purecall
Large functions (>20 instructions) — deeper analysis for the most interesting ones:
probe.exe "disasm func <vtable_entry_address>"
Look for string references, system calls, and field access patterns to understand the function's purpose.
From the getters and setters found in step 4, compile a field map:
Known fields for C_BaseEntity:
+0x103 bool (getter at vf[12])
+0x330 pointer (getter at vf[18], likely GameSceneNode*)
+0x350 int32 (getter at vf[22], max health?)
+0x354 int32 (getter at vf[23], current health?)
+0x35C int32 (getter at vf[25], life state?)
+0x3F3 uint8 (getter at vf[31], team number?)
Search for classes that share namespace or naming patterns:
probe.exe "rtti find <namespace_or_prefix> <module>"
For example, if the target class is C_BaseEntity, search for:
C_Base — all base classesCEntity — entity system classesCGame — game system classesFor each related class, check if it inherits from the target class:
probe.exe "rtti hierarchy <related_class> <module>"
Build a tree of all related classes:
C_BaseEntity
├── C_BaseModelEntity
│ ├── C_BasePlayerPawn
│ │ └── C_CitadelPlayerPawn
│ └── C_BaseFlex
├── C_BaseToggle
└── C_BaseTrigger
To find live instances of the class in memory, search for the vtable pointer. Every instance of the class has the vtable address as its first 8 bytes:
probe.exe "pattern <vtable_bytes_as_pattern> <module>"
Convert the vtable address to little-endian bytes. For example, vtable at 0x7FFB1A2B3C40:
probe.exe "pattern 40 3C 2B 1A FB 7F 00 00"
Each match is a pointer to an instance of the class (or a subclass). This is useful for finding objects without knowing where they're stored.
Note: This scans the module's memory, which might not include heap allocations. Heap-allocated objects won't be found this way.
For the most interesting vtable entries identified in step 4, do a full function analysis:
probe.exe "disasm func <function_address>"
Look for:
lea rcx, [rip+...] followed by reading the stringmov rax, [rcx]; call [rax+0x??] reveals interactions with other virtual classesmov eax, [rcx+offset] reveals struct layoutFind what calls this virtual function:
probe.exe "xref scan <function_address> <module> --type CALL --limit 10"
curl -s -X POST http://localhost:9996 -H "Content-Type: application/json" -d '{
"action":"batch","actions":[
{"action":"activity","status":"working","message":"Labeling <class_name> class map..."},
{"action":"store","store":"label","method":"setLabel","args":["0x<vtable_addr>","vtable: <class_name>"]},
{"action":"store","store":"label","method":"setLabel","args":["0x<vf0_addr>","<class_name>::~<class_name> (destructor)"]},
{"action":"store","store":"label","method":"setLabel","args":["0x<vf1_addr>","<class_name>::GetHealth (getter +0x354)"]},
{"action":"store","store":"label","method":"setLabel","args":["0x<vf5_addr>","<class_name>::vf5 (large, refs \"error\")"]},
{"action":"navigate","tab":"disasm","address":"0x<vtable_addr>"},
{"action":"activity","status":"idle","message":"Done — mapped <class_name> with N vtable entries"}
]
}'
If an object instance was found, also create a struct in the GUI:
curl -s -X POST http://localhost:9996 -H "Content-Type: application/json" -d '{
"action":"batch","actions":[
{"action":"store","store":"struct","method":"createStruct","args":["<class_name>","0x<instance_addr>",1024]},
{"action":"store","store":"struct","method":"addField","args":["<class_name>",0,"pointer","vtable"]},
{"action":"store","store":"struct","method":"addField","args":["<class_name>",259,"bool","m_bFlag (+0x103)"]},
{"action":"store","store":"struct","method":"addField","args":["<class_name>",816,"pointer","m_pGameSceneNode (+0x330)"]},
{"action":"store","store":"struct","method":"addField","args":["<class_name>",852,"int32","m_iHealth (+0x354)"]},
{"action":"navigate","tab":"structs"}
]
}'
Note: addField offsets are in decimal (0x103 = 259, 0x330 = 816, 0x354 = 852).
Class Discovery: C_BaseEntity
==============================
Module: client.dll
Vtable: 0x7FFB1A2B3C40 (client.dll + 0x1E93C40, .rdata)
Inheritance:
C_BaseEntity <- CEntityInstance <- IHandleEntity
Vtable (42 entries):
[0] 0x7FFB0B234000 ~C_BaseEntity (destructor)
[1] 0x7FFB0B234100 GetRefEHandle — returns [rcx+0x10]
[2] 0x7FFB0B234200 IsAlive — movzx eax, byte [rcx+0x35C]; test/cmp
[3] 0x7FFB0B234300 GetHealth — mov eax, [rcx+0x354]; ret
[4] 0x7FFB0B234400 GetMaxHealth — mov eax, [rcx+0x350]; ret
[5] 0x7FFB0B234500 TakeDamage — large (0x1A0 bytes), refs "damage"
...
[18] 0x7FFB0B236000 GetGameSceneNode — mov rax, [rcx+0x330]; ret
...
[31] 0x7FFB0B237000 GetTeamNumber — movzx eax, byte [rcx+0x3F3]; ret
...
Field Map (from vtable getters):
+0x010 pointer GetRefEHandle return (entity handle)
+0x103 bool IsAlive flag
+0x330 pointer GameSceneNode*
+0x350 int32 MaxHealth
+0x354 int32 Health
+0x35C int32 LifeState
+0x3F3 uint8 TeamNum
Related Classes (same hierarchy):
C_BaseModelEntity (58 vtable entries) <- C_BaseEntity
C_BasePlayerPawn (89 vtable entries) <- C_BaseModelEntity
C_CitadelPlayerPawn (112 vtable entries) <- C_BasePlayerPawn
Live Instances Found: 3
0x055EA1C28000 (heap)
0x055EA1C29000 (heap)
0x055EA1C30000 (heap)
Some binaries are compiled with /GR- which strips RTTI metadata. Signs:
rtti find returns zero results for any searchrtti scan <module> returns few or no types.?AV strings in the module's .rdataFallback approaches:
PE exports — the class might be exported by name. Check pe exports <module> for class-related symbols.
String references — constructor/destructor functions often reference the class name in assert or log messages. Search for the class name as a string:
probe.exe "strings find <class_name> <module>"
Debug symbols — if a PDB file is available (same directory as the binary, or symbol server), it contains full type information.
Manual vtable discovery — scan .rdata for pointer arrays where every entry points into .text. These are likely vtables. Disassemble entries to identify the class.
Cross-reference from known code — if you know a function that creates instances of the class, breakpoint it and read RCX after construction to get the vtable pointer, then work backwards to the class layout.
this pointer is always in RCX for member functions (Microsoft x64 ABI)mov rax, [rcx]; call [rax+index*8] — the index tells you which vtable slot is being calledjmp <addr>), follow the jump to find the real implementation__purecall) indicate abstract classes — the derived class must override them