-
Notifications
You must be signed in to change notification settings - Fork 3
Signatures
cc_signature is the cross-platform / cross-arch signature scan and address resolution library.
It finds code and data in loaded modules by IDA-style byte patterns, follows operands to the real addresses, and caches results by key.
Patterns are written like in IDA, with ?? wildcards: "48 8B ?? C3", the maximum pattern length is 128 bytes.
Module names are basenames without extension, e.g. "server", "vphysics".
Everything lives in the CrashCapture::Sig namespace.
The quickest way to find something:
-
Sig::Scan(const char* module, const char* ida): uintptr_t- compile the pattern, scan the named module, return the first hit (or
0if the module is absent or nothing matches).
- compile the pattern, scan the named module, return the first hit (or
-
Sig::Compile(const char* ida, CCPattern* out): bool- parse an IDA pattern once into a
CCPattern.
- parse an IDA pattern once into a
-
Sig::Find(const CCModule* m, const CCPattern* p): uintptr_t- first match of a compiled pattern in a module, or
0.
- first match of a compiled pattern in a module, or
-
Sig::FindAll(const CCModule* m, const CCPattern* p, uintptr_t* out, int max): int- up to
maxmatches, returns the count written.
- up to
uintptr_t hit = CrashCapture::Sig::Scan("vphysics", "48 8B ?? C3");
if (!hit) {
// not found in this build
}Often the pattern hits an instruction that references the thing you want (a mov reg, [rip+X] or an x86 absolute operand).
Resolve steps follow that reference:
-
Sig::RelTarget(uintptr_t at, int opOff, int insnLen): uintptr_t- resolve a rip-relative / rel32 operand at
at(opOff= operand offset inside the instruction,insnLen= full instruction length).
- resolve a rip-relative / rel32 operand at
-
Sig::Abs32(uintptr_t at, int opOff): uintptr_t- resolve an x86 absolute 32-bit operand.
-
Sig::Deref(uintptr_t at): uintptr_t-
*(void**)at.
-
-
Sig::stepCC_STEP_ADD-
cur += a.
-
These appear as entries in a target's steps[] array, which is CC_STEP_END-terminated and at most 4 steps long.
Available ops:
| op | meaning |
|---|---|
CC_STEP_END |
terminates the step list |
CC_STEP_REL |
Sig::RelTarget(cur, a, b) |
CC_STEP_ABS32 |
Sig::Abs32(cur, a) |
CC_STEP_DEREF |
Sig::Deref(cur) |
CC_STEP_ADD |
cur += a |
For targets with no clean signature, the anchor helpers find code by the string literals it references:
-
Sig::FindLiteral(const CCModule* m, const char* text): uintptr_t- find a NUL-terminated literal in the module's data,
0if absent.
- find a NUL-terminated literal in the module's data,
-
Sig::FindRefs(const CCModule* m, uintptr_t target, uintptr_t* out, int max): int- code sites whose operand resolves to
target.
- code sites whose operand resolves to
-
Sig::FuncStart(const CCModule* m, uintptr_t inside): uintptr_t- walk back to the enclosing function's first byte.
-
Sig::AnchorAll(const char* module, const char* literal, uintptr_t* out, int max): int- distinct functions referencing
literal.
- distinct functions referencing
-
Sig::Anchor(const char* module, const char* literal): uintptr_t- the innermost one (smallest ref-to-start delta).
For anything used more than once, register targets once and resolve them all in one pass.
A CCTarget tries the symbol first (when the binary exports it), and falls back to the signature, the resolve steps are then applied to the signature hit.
Lifecycle:
-
Sig::Register(const CCTarget* targets, int count): void- register the table (static lifetime), once, before
Init.
- register the table (static lifetime), once, before
-
Sig::Init(): void- resolve and cache every registered target.
-
Sig::Get(const char* key): uintptr_t- cached address by key, or
0; resolves on demand if not cached.
- cached address by key, or
-
Sig::Resolve(const CCTarget* t): uintptr_t- resolve one target directly, no cache.
struct CCTarget {
const char* key; // stable id
const char* module; // basename: "server", "vphysics"
const char* symbol; // tried first, or NULL
const char* sig; // IDA pattern fallback, or NULL
CCResolveStep steps[4]; // applied to the sig hit; END-terminated
};Example, declaring the two common shapes:
#include "tools/cc_signature.h"
static const CrashCapture::CCTarget my_targets[] = {
// resolves straight to the symbol
{"my_func", "server", "_ZSomeSymbol", NULL, {{CC_STEP_END, 0, 0}}},
// finds the sig, then follows the operand to the real address
{"my_data", "vphysics", NULL, "80 3D ?? ?? ?? ?? 00", {{CC_STEP_ABS32, 2, 0}, {CC_STEP_END, 0, 0}}},
};Example, registering and initializing:
CrashCapture::Sig::Register(my_targets, 2);
CrashCapture::Sig::Init();
uintptr_t addr = CrashCapture::Sig::Get("my_func");
if (!addr) {
// signature drifted or engine changed, degrade gracefully
}A 0 return always means "not found in this build", never an error to crash over: callers check and degrade, which is exactly how the patcher skips drifted fixes.
Getting started
Usage
Features
For module developers