You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Detects direct modification of machine code instructions at function entry points (prologues) used by inline hooking frameworks such as Dobby, ShadowHook, android-inline-hook, and Substrate.
Overview
Inline code patching overwrites the first few bytes of a target function's machine code with a branch or jump instruction that redirects execution to attacker-controlled trampoline code. Unlike GOT/PLT hooking, which modifies function pointers in linkage tables, inline patching alters the actual executable bytes in .text sections. This technique is the backbone of popular native hook engines — Dobby, ShadowHook (ByteDance android-inline-hook), and Substrate — each of which emits distinct trampoline patterns at the function prologue. Detection relies on comparing in-memory code bytes against known-good ELF representations and scanning for instruction patterns that standard compilers never emit at function entry.
Injection Side
How Attackers Use This Technique
Resolve target address — Attacker identifies the target function address via dlsym(), ELF symbol table parsing, or ART method entry_point_ introspection.
Save original prologue — Copy the first 4–20 bytes (architecture and tool dependent) of the target function for later restoration or relocation.
Make code page writable — Call mprotect() on the code page to add write permission (RWX, or RW then back to RX).
Overwrite prologue — Write a branch/jump instruction at the function entry that redirects to a trampoline.
Trampoline execution — The trampoline saves CPU context (registers, flags), invokes the attacker's handler function, and optionally calls the relocated original prologue (the "backup") to preserve original behavior.
Flush instruction cache — Call __builtin___clear_cache() (or equivalent) to ensure the CPU fetches the patched instructions.
Restore page permissions — mprotect() the code page back to RX to reduce forensic footprint.
Artifacts
Artifact
Location
Indicator
Modified prologue
Target function .text
First 4–20 bytes differ from original ELF on disk
Trampoline code
Anonymous mmap or ELF gap
Branch target resides in non-standard memory region
Relocated prologue
Near trampoline
Original instructions with fixed-up PC-relative references
RWX memory (transient)
/proc/self/maps
mprotect to RWX during patching window
Instruction cache flush
Syscall trace
__builtin___clear_cache() or equivalent syscall
mprotect pattern
Syscall trace
mprotect(RW) → memcpy → mprotect(RX) sequence
Architecture-Specific Trampoline Patterns
ARM64 patterns:
Tool
Pattern
Size
Encoding
Dobby (near)
ADRP Xn, #page; ADD Xn, Xn, #off; BR Xn
12B
0x90000000 class + 0x91000000 class + 0xD61F0000 class
Compile trampoline slots into injected SO's .text section — no anonymous pages needed
★★★★
ELF gap injection
Write trampoline to LOAD segment padding — no new memory mappings
★★★★
JIT Code Cache injection
Write trampoline into ART JIT Code Cache free space — blends with legitimate JIT output
★★★★★
Static linking
Link hook engine into main SO — no library name signatures to detect
★★★
Immediate permission restore
mprotect back to RX immediately after write — RWX window is μs
★★
BTI bypass
Use RET Xn instead of BR Xn to pass hardware Branch Target Identification checks
★★★
Detection Side
Mechanism
Function prologues in loaded shared libraries and ART compiled code should match their on-disk ELF representation. Any deviation in the first N bytes of a function indicates patching. Additionally, certain instruction patterns — such as an unconditional branch as the very first instruction, or LDR + BR pairs loading absolute addresses — are never emitted by standard compilers at function entry points. By reading code bytes directly via pointer dereference (no library calls), detection is resistant to libc-level hook interception.
Anti-Evasion Properties
Property
Explanation
Resistant to libc hooks
Function bytes are read directly from memory via pointer dereference — no read() or fopen() calls to intercept
Resistant to GOT/PLT hijack
No library calls needed to read code bytes; detection logic uses only inline memory access
SVC bypass benefit
Not applicable — reading own process memory requires no syscall, just pointer dereference
Remaining bypass surface
Attacker can hook the detection function itself; mitigate with self-integrity checks and multiple redundant check points
Detection Strategy
Enumerate critical functions — Build a list of security-relevant functions to monitor (e.g., open, read, mmap, ptrace, getenv, ART internals).
Resolve function addresses — Read function addresses via dlsym() or ART method entry_point_ field.
Read prologue bytes — Read the first 4–20 bytes at each function address via direct pointer dereference.
Check for known trampoline patterns:
ARM64: B #imm26 as first instruction — (insn & 0xFC000000) == 0x14000000
ARM64: LDR Xn, #literal; BR Xn pair — (insn & 0xFF000000) == 0x58000000 followed by (insn & 0xFFFFFC1F) == 0xD61F0000
ARM64: ADRP; ADD; BR three-instruction sequence
ARM64: RET Xn where Xn ≠ X30 used as branch — (insn & 0xFFFFFC1F) == 0xD65F0000 and Rn ≠ 30
x86_64: FF 25 (JMP [RIP+disp32]) or E9 (JMP rel32)
Cross-reference with on-disk ELF — For deeper validation, compare in-memory bytes against the .text section of the original ELF file (see code-integrity-verification technique).
Validate branch targets — Check where the branch instruction jumps to; if the target falls outside any known .text segment in /proc/self/maps, flag as suspicious.
Scan for framework signatures — Look for STP X16, X17, [SP, #-0x10]! pattern at function entries (ShadowHook non-function hook signature).