Mobile Development 12 min read

Understanding Variadic Function Hooking and Stack Context Pollution with TrampolineHook

This article explains why adding parameter‑reading code to a variadic‑function hook causes a deterministic crash due to stack context pollution, analyzes the calling‑stack layout and register usage with ARM64 assembly, and presents a heap‑based context‑saving solution using TrampolineHook to safely intercept variadic methods on iOS.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Understanding Variadic Function Hooking and Stack Context Pollution with TrampolineHook

In a previous article the author introduced a full‑bridge Hook solution called TrampolineHook and mentioned a subtle bug related to context pollution . While most developers think of register pollution, the stack itself can also be corrupted when intercepting variadic functions.

To illustrate the problem, a simple Objective‑C class @interface TestObject : NSObject with a variadic method - (void)method:(int *)value,... is shown, followed by the TrampolineHook interception code that replaces the original IMP with a custom interceptor void wzq_check_variadic(id a, char *methodName, int *v, ...) . The interceptor works fine when it only logs the call, but crashes on the second read of the variadic arguments.

The crash is caused by the way the caller places the addresses of the variadic arguments on the stack. The interceptor saves the current context on the stack before calling the original function, which overwrites the memory region where the caller’s argument addresses reside. When the interceptor later tries to read the next argument with v = va_arg(args, int *) , it accesses corrupted stack data and crashes.

Detailed ARM64 assembly of the caller shows the stack frame allocation, storing of local variables, and pushing the addresses of the arguments onto the stack. The method’s prologue and the subsequent call to objc_msgSend are also displayed, highlighting that the first variadic argument is passed in a register (the “anchor” parameter) while the remaining arguments live on the caller’s stack.

To fix the issue, the article proposes preserving the entire execution context on the heap instead of the stack. A struct typedef struct _THPageVariadicContext { int64_t gR[10]; int64_t vR[16]; int64_t linkRegister; int64_t originIMPRegister; } THPageVariadicContext; is used as a conceptual model for the saved registers.

The solution consists of three steps:

In the pre‑interceptor ( THPageVariadicContextPre ), save registers, allocate heap memory with malloc , store the allocation address in a callee‑saved register, and write all general‑purpose and floating‑point registers to the heap.

After the original function returns, the post‑interceptor ( THPageVariadicContextPost ) restores the registers from the heap, frees the allocated memory, and jumps back to the original return address.

Both interceptors are declared with __attribute__((__naked__)) to avoid automatic prologue/epilogue code that would otherwise modify the stack.

By moving the context to the heap, the stack layout used by the variadic arguments remains untouched, eliminating the crash. The article concludes that this approach enables reliable variadic Hooking and points readers to the GitHub repository for the latest THVariadicInterceptor implementation.

iOSassemblyHookingObjective-CTrampolineHookVariadic Functions
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.