diff --git a/types/frida-gum/frida-gum-tests.ts b/types/frida-gum/frida-gum-tests.ts index 6d5ae8dc837364..69552eca5788ff 100644 --- a/types/frida-gum/frida-gum-tests.ts +++ b/types/frida-gum/frida-gum-tests.ts @@ -281,6 +281,42 @@ Interceptor.attach({ }, }); +Interceptor.attach({ + target: puts, + writeRedirect(details) { + // $ExpectType DefaultInstructionWriter + details.writer; + // $ExpectType NativePointer + details.target; + // $ExpectType number + details.capacity; + + // Same register type as carried by InstrumentationTarget. + const scratch: InstrumentationTarget["scratchRegister"] = details.scratchRegister; + void scratch; + + const writer = details.writer as Arm64Writer; + writer.putBImm(details.target); + }, + redirectSpaceHint: 16, +}, { + onEnter(args) { + // $ExpectType InvocationArguments + args; + }, +}); + +// $ExpectType InstrumentationOptions +Interceptor.defaults; + +Interceptor.defaults = { + scratchRegister: "x15", + writeRedirect(details) { + details.writer.flush(); + }, + redirectSpaceHint: 256, +}; + Interceptor.flush(); // $ExpectType void diff --git a/types/frida-gum/index.d.ts b/types/frida-gum/index.d.ts index c6acfee2769d29..409d411bf964b2 100644 --- a/types/frida-gum/index.d.ts +++ b/types/frida-gum/index.d.ts @@ -3296,6 +3296,15 @@ declare namespace Interceptor { */ function flush(): void; + /** + * Default instrumentation options applied to every subsequent + * `attach()` / `replace()` / `replaceFast()` call. + * + * Options specified per call through `InstrumentationTarget` take + * precedence over these. + */ + let defaults: InstrumentationOptions; + /** * The kind of breakpoints to use for non-inline hooks. * @@ -3308,12 +3317,20 @@ declare namespace Interceptor { * Target to instrument, carrying the address alongside optional knobs that * control how the inline hook is set up. */ -interface InstrumentationTarget { +interface InstrumentationTarget extends InstrumentationOptions { /** * Address of function/instruction to instrument. */ target: NativePointerValue; +} +/** + * Knobs that control how an inline hook is set up. + * + * May be specified per call through `InstrumentationTarget`, or globally + * through `Interceptor.defaults`. + */ +interface InstrumentationOptions { /** * Register that Interceptor may clobber when building the trampoline. * @@ -3343,8 +3360,85 @@ interface InstrumentationTarget { * Defaults to `checked`. */ relocation?: RelocationPolicy | undefined; + + /** + * Callback that emits a custom redirect from the instrumented function or + * instruction to Interceptor's trampoline. + * + * The primary use-case is defeating fingerprinting: emitting a redirect + * that a RASP implementation won't recognize as an inline hook. It is also + * useful when space is tight and you want to locate a nearby code cave + * reachable through a short immediate branch, and then branch from there to + * the trampoline farther away. + * + * Throwing from the callback declines the redirect. There is no fallback to + * the default strategy in that case: the `attach()` / `replace()` / + * `replaceFast()` call fails as if the target had a signature that could + * not be instrumented. + */ + writeRedirect?: WriteRedirectCallback | undefined; + + /** + * Upper bound on the number of bytes that `writeRedirect` will need. + * + * Your callback may end up using less. Specifying a larger value means + * Interceptor has to explore further to determine that it is safe to use + * that much space — looking for back-branches, call return sites, etc. — + * which is more expensive. + * + * Defaults to the size needed for a full redirect, e.g. 16 bytes on arm64. + */ + redirectSpaceHint?: number | undefined; +} + +/** + * Callback that emits a custom redirect from the target to Interceptor's + * trampoline. + */ +type WriteRedirectCallback = (details: WriteRedirectDetails) => void; + +/** + * Details passed to a `WriteRedirectCallback`. + */ +interface WriteRedirectDetails { + /** + * Code writer to emit the redirect with. + * + * The concrete type depends on the architecture, e.g. an `X86Writer` on + * x86 and an `Arm64Writer` on arm64. On 32-bit ARM it may be either an + * `ArmWriter` or a `ThumbWriter`, depending on the instruction set at the + * instrumented site. + */ + writer: DefaultInstructionWriter; + + /** + * Address of Interceptor's trampoline, i.e. where your redirect should + * branch to. + */ + target: NativePointer; + + /** + * Scratch register that the redirect may clobber. + * + * Only present on architectures that expose scratch registers, i.e. + * arm64 and mips. + */ + scratchRegister?: Arm64Register | MipsRegister | undefined; + + /** + * Number of bytes available for the redirect. + */ + capacity: number; } +/** + * Default code writer for the current architecture. + * + * On 32-bit ARM this is either an `ArmWriter` or a `ThumbWriter`, depending on + * the instruction set at the instrumented site. + */ +type DefaultInstructionWriter = X86Writer | ArmWriter | ThumbWriter | Arm64Writer | MipsWriter; + type InstrumentationScenario = "online" | "offline"; /** diff --git a/types/frida-gum/package.json b/types/frida-gum/package.json index 62c9d593a90b37..5c995206f43540 100644 --- a/types/frida-gum/package.json +++ b/types/frida-gum/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@types/frida-gum", - "version": "19.2.9999", + "version": "19.3.9999", "nonNpm": true, "nonNpmDescription": "frida-gum", "projects": [