From arc-probe
Detects function parameters in Windows x64 binaries by analyzing prologue, register usage, and instruction patterns to infer count, types, and calling convention.
npx claudepluginhub vzco/arc-probe --plugin arc-probeThis skill uses the workspace's default tool permissions.
Detect the parameters of a function by analyzing its prologue, register usage, and instruction patterns. Infers parameter count, types, and calling convention for Windows x64 (__fastcall).
Analyzes a single x64 function in a binary: disassembles code, identifies arguments and calling convention, finds string refs and xrefs, detects RTTI, generates signatures, labels in GUI.
Reconstruct data structures by analyzing memory access patterns across functions
Triages and audits IDA binaries to detect suspicious behavior, crypto/network activity, review decompiled code against source, and run multi-table queries.
Share bugs, ideas, or general feedback.
Detect the parameters of a function by analyzing its prologue, register usage, and instruction patterns. Infers parameter count, types, and calling convention for Windows x64 (__fastcall).
address (required): Address of the function start (hex). Can be absolute or module+offset.probe.exe "disasm func <address>"
If disasm func fails or returns too many instructions (>500), fall back to a fixed count:
probe.exe "disasm <address> 80"
Record the full disassembly output. You will scan it for register usage patterns.
Look at the first 5-10 instructions for the function prologue pattern:
Non-volatile register saves (tells you how many locals the function needs):
mov [rsp+0x08], rcx ; saves arg1 to shadow space
mov [rsp+0x10], rdx ; saves arg2 to shadow space
mov [rsp+0x18], r8 ; saves arg3 to shadow space
push rbx ; saves non-volatile register
sub rsp, 0x40 ; allocate stack frame
Key prologue patterns:
mov [rsp+0x08], rcx early -- function saves first arg, confirms RCX is usedmov [rsp+0x10], rdx early -- function saves second arg, confirms RDX is usedmovaps [rsp+...], xmm6 or movups -- saves non-volatile XMM registers (function uses floats internally)sub rsp, 0x?? -- many locals or large stack-allocated structsWalk through the disassembly and find the first occurrence of each argument register. The key rule: if a register is read before it is written, it is a parameter.
Integer argument registers (Windows x64 ABI):
| Register | Arg # | Notes |
|---|---|---|
| RCX/ECX/CX/CL | 1 | Also this pointer for member functions |
| RDX/EDX/DX/DL | 2 | |
| R8/R8D/R8W/R8B | 3 | |
| R9/R9D/R9W/R9B | 4 |
Float argument registers (shadow the same slots):
| Register | Arg # | Notes |
|---|---|---|
| XMM0 | 1 | Replaces RCX when arg1 is float/double |
| XMM1 | 2 | Replaces RDX when arg2 is float/double |
| XMM2 | 3 | Replaces R8 when arg3 is float/double |
| XMM3 | 4 | Replaces R9 when arg4 is float/double |
For each register, classify the first instruction that references it:
Read (parameter confirmed):
mov rax, [rcx] -- reads from RCX (pointer dereference)test rcx, rcx -- tests RCX (null check)cmp edx, 0x10 -- compares RDX (integer comparison)mov [rsp+...], rcx -- saves RCX (shadow space store)movss xmm4, xmm0 -- copies XMM0 (float parameter save)Write (NOT a parameter -- register is being assigned):
xor ecx, ecx -- zeroes RCXmov rcx, rax -- overwrites RCXlea rcx, [rip+...] -- loads address into RCX (preparing a call)Stop scanning a register once you find its first read or write. If the first usage is a write, that register slot is NOT a parameter.
For each confirmed parameter register, determine the type from how it is used:
Pointer type (most common for RCX):
mov rax, [rcx] -- dereferences as pointer (vtable load = this pointer)mov eax, [rcx+0x354] -- struct field access at known offsetlea rax, [rcx+0x10] -- address arithmetic on pointertest rcx, rcx followed by je -- null check (definitely a pointer)call [rax+0x??] after mov rax, [rcx] -- virtual function call (RCX is object pointer)Integer type:
cmp edx, <immediate> -- range checkand edx, 0xFF -- masking to byteshl edx, 2 -- bit shift (index calculation)add edx, eax -- arithmeticmov [rax+...], edx -- stored as 32-bit valueBoolean type:
test dl, dl -- boolean testmovzx eax, dl -- zero-extend bytecmp cl, 0 / cmp cl, 1 -- explicit boolean checkFloat type:
movss [rsp+...], xmm0 -- saved as floatcomiss xmm0, xmm1 -- float comparisonmulss xmm0, [rcx+...] -- float multiplyaddss xmm0, xmm1 -- float addcvtsi2ss xmm0, eax -- int-to-float conversion (function receives int, converts)String type (const char):*
strlen, strcmp, or prints itmovzx eax, byte [rcx] -- reading charactersthis pointer (C++ member function)A function is likely a member function if RCX shows these patterns:
mov rax, [rcx] as the first or second instruction -- vtable load (definitive)[rcx+0x10], [rcx+0x354], [rcx+0x3F3] -- reading struct fieldsthis is passed through to callees: mov rcx, rbx where RBX was saved from the original RCXrtti vtable results include this address)If RCX is a this pointer, the effective parameter count starts from RDX (arg2 becomes the "first visible" parameter).
If the function accesses [rsp+0x28] or higher offsets early in the function (before allocating locals), these are stack-passed arguments:
mov rax, [rsp+0x28] ; 5th argument (after 0x20 shadow space)
mov rax, [rsp+0x30] ; 6th argument
mov rax, [rsp+0x38] ; 7th argument
Important: After sub rsp, N, the stack offsets shift. Stack parameters are at [rsp+N+0x28], [rsp+N+0x30], etc. Only check for stack params in instructions BEFORE the sub rsp or adjust offsets accordingly.
Scan the function's epilogue (instructions before ret):
xor eax, eax then ret -- returns 0 (bool false or int 0)mov eax, 1 then ret -- returns true or successmov eax, [rbx+0x??] then ret -- returns an int32 field (getter)mov rax, rbx then ret -- returns a pointerlea rax, [rbx+0x??] then ret -- returns pointer to sub-objectmovss xmm0, [rbx+0x??] then ret -- returns floateax/rax/xmm0 assignment before ret -- likely void returnCheck multiple return paths (the function may have early returns with different patterns).
Find callers to see what arguments they actually pass:
probe.exe "xref scan <address> <module> --type CALL --limit 5"
For each caller, disassemble the setup before the call:
probe.exe "disasm <call_site - 0x20> 12"
The argument setup at call sites confirms the parameter count and types:
lea rdx, [rip+...] (string), the second parameter is const char*xmm1 instead of RDX, the second parameter is floatBuild a C-style function signature:
Function: sub_7FF612345678 (client.dll + 0x5678)
Size: ~0x1A0 bytes
Detected Signature:
int __fastcall sub_7FF612345678(void* this, int damage, const char* source, bool notify)
Parameters:
RCX this void* vtable at [rcx], field access [rcx+0x354], [rcx+0x3F3]
RDX damage int32 compared with immediate, used in arithmetic
R8 source const char* lea r8, [rip+...] -> "player" at call sites
R9 notify bool test r9b, r9b -> conditional branch
Return:
EAX int32 xor eax,eax before early ret; mov eax,[rbx+0x354] before normal ret
Stack frame: 0x40 bytes (sub rsp, 0x40)
Saved registers: RBX, RDI (non-volatile)
If using the GUI bridge, label the function with the detected signature:
curl -s -X POST http://localhost:9996 -H "Content-Type: application/json" \
-d '{"action":"store","store":"label","method":"setLabel","args":["0x<address>","int sub_XXXX(void* this, int arg2, char* arg3, bool arg4)"]}'
Small functions have obvious signatures:
mov eax, [rcx+0x354]; ret -- int GetField(void* this) -- 1 param (this)mov [rcx+0x354], edx; ret -- void SetField(void* this, int value) -- 2 paramsmovzx eax, byte [rcx+0x103]; ret -- bool GetFlag(void* this) -- 1 paramjmp <other> -- follow the jump and analyze the real functionSometimes a register is both read and written in complex ways. Fallback strategies:
Check .pdata for unwind info -- it tells you the parameter count for SEH:
probe.exe "call info <address>"
Count shadow space stores -- the number of mov [rsp+0x??], reg instructions in the prologue (where offset is 0x08, 0x10, 0x18, 0x20) directly indicates the parameter count.
Check callers -- the most reliable way to confirm parameter count is to see what callers actually pass.
sub rsp, no register savesprintf-style) store all 4 register args to shadow space unconditionallythis pointer convention means C++ methods have one fewer "visible" parameter than register analysis suggests