Latest commit

History

27 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Wisp

wisp

Overview

Wisp provides runtime function hooking capabilities for ARM64 platforms, primarily designed for Android (aarch64-linux-android). It allows you to replace or intercept function calls at runtime by dynamically modifying executable code.

Warning: This library is still under development and cannot handle cases where instructions at the beginning of the target function contain PC-relative addressing instructions like adrp. Do not use in production environments.

Features

  • Function Replacement: Replace target functions entirely with proxy implementations
  • Function Hooking: Intercept function calls while preserving access to original implementation
  • Dynamic Original Function: Retrieve original function pointer dynamically via orig_fn!() macro
    • Function Interception: Modify function arguments at runtime via a callback before the original function executes
  • Instruction Cache Synchronization: Ensures cache coherency after code modifications

Installation

Add this to your Cargo.toml:

[dependencies]
wisp = { git = "https://github.com/Mufanc/wisp" }

Usage

Function Replacement

Replace a target function entirely with a proxy function:

use wisp::Wisp;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{
a * b
}unsafe{let stub = Wisp::replace_fn(target_fn as_, proxy_fn as_).expect("failed to replace function");// target_fn now executes proxy_fn's codeassert_eq!(target_fn(2,3),6);// 2 * 3// Save the stub and explicitly unhook when restoration is needed.
stub.unhook().expect("failed to unhook function");assert_eq!(target_fn(2,3),5);}

Dropping a stub leaves the hook and its executable buffers installed until process exit. Call Stub::unhook explicitly before dropping it to restore the original function.

Function Hooking

Hook a function while maintaining access to the original implementation:

use wisp::Wisp;use std::ffi::c_void;staticmutORIG_FN:*constc_void = std::ptr::null();extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{// Call original functionlet result = unsafe{
std::mem::transmute::<*constc_void,fn(i32,i32) -> i32>(ORIG_FN)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,Some(&mutORIG_FN)).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Dynamic Original Function

Use orig_fn!() macro to dynamically retrieve the original function without static variables:

use wisp::{Wisp, orig_fn};use std::ffi::c_void;use std::mem;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{let orig_fn = orig_fn!();// Call original functionlet result = unsafe{
mem::transmute::<*constc_void,fn(i32,i32) -> i32>(orig_fn)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,None).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Function Interception

Intercept a function to modify its arguments at runtime via a callback before the original function executes:

use wisp::Wisp;use libc::c_long;extern"C"fntarget_fn(a:u32,b:u32) -> u32{
a + b
}extern"C"fncallback_fn(args:*mutc_long){unsafe{// args points to saved registers x0-x7 on the stack// Modify x0 (first argument)*args = 100;// Modify x1 (second argument)*args.add(1) = 200;}}unsafe{let _stub = Wisp::intercept_fn(target_fn as_, callback_fn).expect("failed to intercept function");// target_fn is called with modified arguments (100, 200) regardless of inputassert_eq!(target_fn(1,2),300);}

The callback receives a pointer to the saved argument registers (x0-x7) on the stack, allowing you to read or modify any of the first 8 arguments before the original function executes.

Custom Unhook Behavior

Implement custom unhooking logic with the Unhooker trait:

use wisp::{CustomWisp,UnhookContext,Unhooker,WispResult};structMadviseUnhooker;unsafeimplUnhookerforMadviseUnhooker{fnunhook(context:UnhookContext<'_>) -> WispResult<()>{// Pseudocode:// let pages = page_range(context.target(), context.backup_insn().len());// madvise(pages, MADV_DONTNEED);// touch_pages(pages);todo!()}}typeMadviseWisp = CustomWisp<MadviseUnhooker>;

API

Core Types

  • Wisp: Main type alias for CustomWisp<SimpleUnhooker>
  • CustomWisp<U>: Generic hooking interface with custom unhooker
  • Stub<U>: Handle used to explicitly unhook a function
  • UnhookContext: Read-only target information passed to an unhooker
  • Unhooker: Unsafe extension point for custom unhook behavior
  • SimpleUnhooker: Default unhooker implementation

Methods

Wisp::replace_fn

pubunsafefnreplace_fn(target_fn:*constc_void,proxy_fn:*constc_void,) -> WispResult<Stub<U>>

Replaces the target function with a proxy function.

Wisp::hook_fn

pubunsafefnhook_fn(target_fn:*constc_void,proxy_fn:*constc_void,backup_orig:Option<&mut*constc_void>,) -> WispResult<Stub<U>>

Hooks the target function while preserving access to the original implementation. Pass Some(&mut ptr) to store the original function pointer, or None to skip storing.

Wisp::intercept_fn

pubunsafefnintercept_fn(target_fn:*constc_void,callback_fn:extern"C"fn(*mutc_long),) -> WispResult<Stub<U>>

Intercepts the target function, invoking the callback with a mutable pointer to the saved arguments on the stack before executing the original function. The callback can read or modify the arguments.

Stub::unhook

pubunsafefnunhook(self) -> WispResult<()>

Restores the target function and releases its executable buffers. Dropping the stub without calling this method leaves the hook installed.

orig_fn!()

Macro to dynamically retrieve the original function pointer within a proxy function. Must be called at the beginning of the proxy function.

Limitations

  • Recursive functions: Hooking functions that recursively call themselves is not supported
  • Multiple hooks: Attaching multiple hooks to a single function is not supported
  • Minimum instruction length: Target functions must have at least 4 ARM64 instructions (16 bytes)
  • Concurrent operations: Simultaneous hook/unhook operations on the same function from multiple threads result in undefined behavior
  • Internal library calls: Behavior is undefined when hooking functions that internally use library functions like open, mmap, etc.

Safety

All hooking operations are inherently unsafe and require careful consideration:

  • Target and proxy functions must be valid pointers to executable code
  • Target functions must not be executed by other threads during patching to avoid race conditions
  • Proper synchronization is the caller's responsibility
  • Hooking functions whose first 4 instructions contain PC-relative addressing instructions (e.g., adrp) is not yet supported

Testing

Run tests on Android ARM64 target:

just

This requires:

  • Android NDK installed
  • ANDROID_NDK environment variable set
  • cargo-nextest installed

Platform Support

Currently supports:

  • Architecture: ARM64/AArch64
  • Target: aarch64-linux-android

About

A lightweight Rust library for inline hooking on Android

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

27 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Wisp

wisp

Overview

Wisp provides runtime function hooking capabilities for ARM64 platforms, primarily designed for Android (aarch64-linux-android). It allows you to replace or intercept function calls at runtime by dynamically modifying executable code.

Warning: This library is still under development and cannot handle cases where instructions at the beginning of the target function contain PC-relative addressing instructions like adrp. Do not use in production environments.

Features

  • Function Replacement: Replace target functions entirely with proxy implementations
  • Function Hooking: Intercept function calls while preserving access to original implementation
  • Dynamic Original Function: Retrieve original function pointer dynamically via orig_fn!() macro
    • Function Interception: Modify function arguments at runtime via a callback before the original function executes
  • Instruction Cache Synchronization: Ensures cache coherency after code modifications

Installation

Add this to your Cargo.toml:

[dependencies]
wisp = { git = "https://github.com/Mufanc/wisp" }

Usage

Function Replacement

Replace a target function entirely with a proxy function:

use wisp::Wisp;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{
a * b
}unsafe{let stub = Wisp::replace_fn(target_fn as_, proxy_fn as_).expect("failed to replace function");// target_fn now executes proxy_fn's codeassert_eq!(target_fn(2,3),6);// 2 * 3// Save the stub and explicitly unhook when restoration is needed.
stub.unhook().expect("failed to unhook function");assert_eq!(target_fn(2,3),5);}

Dropping a stub leaves the hook and its executable buffers installed until process exit. Call Stub::unhook explicitly before dropping it to restore the original function.

Function Hooking

Hook a function while maintaining access to the original implementation:

use wisp::Wisp;use std::ffi::c_void;staticmutORIG_FN:*constc_void = std::ptr::null();extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{// Call original functionlet result = unsafe{
std::mem::transmute::<*constc_void,fn(i32,i32) -> i32>(ORIG_FN)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,Some(&mutORIG_FN)).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Dynamic Original Function

Use orig_fn!() macro to dynamically retrieve the original function without static variables:

use wisp::{Wisp, orig_fn};use std::ffi::c_void;use std::mem;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{let orig_fn = orig_fn!();// Call original functionlet result = unsafe{
mem::transmute::<*constc_void,fn(i32,i32) -> i32>(orig_fn)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,None).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Function Interception

Intercept a function to modify its arguments at runtime via a callback before the original function executes:

use wisp::Wisp;use libc::c_long;extern"C"fntarget_fn(a:u32,b:u32) -> u32{
a + b
}extern"C"fncallback_fn(args:*mutc_long){unsafe{// args points to saved registers x0-x7 on the stack// Modify x0 (first argument)*args = 100;// Modify x1 (second argument)*args.add(1) = 200;}}unsafe{let _stub = Wisp::intercept_fn(target_fn as_, callback_fn).expect("failed to intercept function");// target_fn is called with modified arguments (100, 200) regardless of inputassert_eq!(target_fn(1,2),300);}

The callback receives a pointer to the saved argument registers (x0-x7) on the stack, allowing you to read or modify any of the first 8 arguments before the original function executes.

Custom Unhook Behavior

Implement custom unhooking logic with the Unhooker trait:

use wisp::{CustomWisp,UnhookContext,Unhooker,WispResult};structMadviseUnhooker;unsafeimplUnhookerforMadviseUnhooker{fnunhook(context:UnhookContext<'_>) -> WispResult<()>{// Pseudocode:// let pages = page_range(context.target(), context.backup_insn().len());// madvise(pages, MADV_DONTNEED);// touch_pages(pages);todo!()}}typeMadviseWisp = CustomWisp<MadviseUnhooker>;

API

Core Types

  • Wisp: Main type alias for CustomWisp<SimpleUnhooker>
  • CustomWisp<U>: Generic hooking interface with custom unhooker
  • Stub<U>: Handle used to explicitly unhook a function
  • UnhookContext: Read-only target information passed to an unhooker
  • Unhooker: Unsafe extension point for custom unhook behavior
  • SimpleUnhooker: Default unhooker implementation

Methods

Wisp::replace_fn

pubunsafefnreplace_fn(target_fn:*constc_void,proxy_fn:*constc_void,) -> WispResult<Stub<U>>

Replaces the target function with a proxy function.

Wisp::hook_fn

pubunsafefnhook_fn(target_fn:*constc_void,proxy_fn:*constc_void,backup_orig:Option<&mut*constc_void>,) -> WispResult<Stub<U>>

Hooks the target function while preserving access to the original implementation. Pass Some(&mut ptr) to store the original function pointer, or None to skip storing.

Wisp::intercept_fn

pubunsafefnintercept_fn(target_fn:*constc_void,callback_fn:extern"C"fn(*mutc_long),) -> WispResult<Stub<U>>

Intercepts the target function, invoking the callback with a mutable pointer to the saved arguments on the stack before executing the original function. The callback can read or modify the arguments.

Stub::unhook

pubunsafefnunhook(self) -> WispResult<()>

Restores the target function and releases its executable buffers. Dropping the stub without calling this method leaves the hook installed.

orig_fn!()

Macro to dynamically retrieve the original function pointer within a proxy function. Must be called at the beginning of the proxy function.

Limitations

  • Recursive functions: Hooking functions that recursively call themselves is not supported
  • Multiple hooks: Attaching multiple hooks to a single function is not supported
  • Minimum instruction length: Target functions must have at least 4 ARM64 instructions (16 bytes)
  • Concurrent operations: Simultaneous hook/unhook operations on the same function from multiple threads result in undefined behavior
  • Internal library calls: Behavior is undefined when hooking functions that internally use library functions like open, mmap, etc.

Safety

All hooking operations are inherently unsafe and require careful consideration:

  • Target and proxy functions must be valid pointers to executable code
  • Target functions must not be executed by other threads during patching to avoid race conditions
  • Proper synchronization is the caller's responsibility
  • Hooking functions whose first 4 instructions contain PC-relative addressing instructions (e.g., adrp) is not yet supported

Testing

Run tests on Android ARM64 target:

just

This requires:

  • Android NDK installed
  • ANDROID_NDK environment variable set
  • cargo-nextest installed

Platform Support

Currently supports:

  • Architecture: ARM64/AArch64
  • Target: aarch64-linux-android

About

A lightweight Rust library for inline hooking on Android

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

27 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Wisp

wisp

Overview

Wisp provides runtime function hooking capabilities for ARM64 platforms, primarily designed for Android (aarch64-linux-android). It allows you to replace or intercept function calls at runtime by dynamically modifying executable code.

Warning: This library is still under development and cannot handle cases where instructions at the beginning of the target function contain PC-relative addressing instructions like adrp. Do not use in production environments.

Features

  • Function Replacement: Replace target functions entirely with proxy implementations
  • Function Hooking: Intercept function calls while preserving access to original implementation
  • Dynamic Original Function: Retrieve original function pointer dynamically via orig_fn!() macro
    • Function Interception: Modify function arguments at runtime via a callback before the original function executes
  • Instruction Cache Synchronization: Ensures cache coherency after code modifications

Installation

Add this to your Cargo.toml:

[dependencies]
wisp = { git = "https://github.com/Mufanc/wisp" }

Usage

Function Replacement

Replace a target function entirely with a proxy function:

use wisp::Wisp;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{
a * b
}unsafe{let stub = Wisp::replace_fn(target_fn as_, proxy_fn as_).expect("failed to replace function");// target_fn now executes proxy_fn's codeassert_eq!(target_fn(2,3),6);// 2 * 3// Save the stub and explicitly unhook when restoration is needed.
stub.unhook().expect("failed to unhook function");assert_eq!(target_fn(2,3),5);}

Dropping a stub leaves the hook and its executable buffers installed until process exit. Call Stub::unhook explicitly before dropping it to restore the original function.

Function Hooking

Hook a function while maintaining access to the original implementation:

use wisp::Wisp;use std::ffi::c_void;staticmutORIG_FN:*constc_void = std::ptr::null();extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{// Call original functionlet result = unsafe{
std::mem::transmute::<*constc_void,fn(i32,i32) -> i32>(ORIG_FN)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,Some(&mutORIG_FN)).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Dynamic Original Function

Use orig_fn!() macro to dynamically retrieve the original function without static variables:

use wisp::{Wisp, orig_fn};use std::ffi::c_void;use std::mem;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{let orig_fn = orig_fn!();// Call original functionlet result = unsafe{
mem::transmute::<*constc_void,fn(i32,i32) -> i32>(orig_fn)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,None).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Function Interception

Intercept a function to modify its arguments at runtime via a callback before the original function executes:

use wisp::Wisp;use libc::c_long;extern"C"fntarget_fn(a:u32,b:u32) -> u32{
a + b
}extern"C"fncallback_fn(args:*mutc_long){unsafe{// args points to saved registers x0-x7 on the stack// Modify x0 (first argument)*args = 100;// Modify x1 (second argument)*args.add(1) = 200;}}unsafe{let _stub = Wisp::intercept_fn(target_fn as_, callback_fn).expect("failed to intercept function");// target_fn is called with modified arguments (100, 200) regardless of inputassert_eq!(target_fn(1,2),300);}

The callback receives a pointer to the saved argument registers (x0-x7) on the stack, allowing you to read or modify any of the first 8 arguments before the original function executes.

Custom Unhook Behavior

Implement custom unhooking logic with the Unhooker trait:

use wisp::{CustomWisp,UnhookContext,Unhooker,WispResult};structMadviseUnhooker;unsafeimplUnhookerforMadviseUnhooker{fnunhook(context:UnhookContext<'_>) -> WispResult<()>{// Pseudocode:// let pages = page_range(context.target(), context.backup_insn().len());// madvise(pages, MADV_DONTNEED);// touch_pages(pages);todo!()}}typeMadviseWisp = CustomWisp<MadviseUnhooker>;

API

Core Types

  • Wisp: Main type alias for CustomWisp<SimpleUnhooker>
  • CustomWisp<U>: Generic hooking interface with custom unhooker
  • Stub<U>: Handle used to explicitly unhook a function
  • UnhookContext: Read-only target information passed to an unhooker
  • Unhooker: Unsafe extension point for custom unhook behavior
  • SimpleUnhooker: Default unhooker implementation

Methods

Wisp::replace_fn

pubunsafefnreplace_fn(target_fn:*constc_void,proxy_fn:*constc_void,) -> WispResult<Stub<U>>

Replaces the target function with a proxy function.

Wisp::hook_fn

pubunsafefnhook_fn(target_fn:*constc_void,proxy_fn:*constc_void,backup_orig:Option<&mut*constc_void>,) -> WispResult<Stub<U>>

Hooks the target function while preserving access to the original implementation. Pass Some(&mut ptr) to store the original function pointer, or None to skip storing.

Wisp::intercept_fn

pubunsafefnintercept_fn(target_fn:*constc_void,callback_fn:extern"C"fn(*mutc_long),) -> WispResult<Stub<U>>

Intercepts the target function, invoking the callback with a mutable pointer to the saved arguments on the stack before executing the original function. The callback can read or modify the arguments.

Stub::unhook

pubunsafefnunhook(self) -> WispResult<()>

Restores the target function and releases its executable buffers. Dropping the stub without calling this method leaves the hook installed.

orig_fn!()

Macro to dynamically retrieve the original function pointer within a proxy function. Must be called at the beginning of the proxy function.

Limitations

  • Recursive functions: Hooking functions that recursively call themselves is not supported
  • Multiple hooks: Attaching multiple hooks to a single function is not supported
  • Minimum instruction length: Target functions must have at least 4 ARM64 instructions (16 bytes)
  • Concurrent operations: Simultaneous hook/unhook operations on the same function from multiple threads result in undefined behavior
  • Internal library calls: Behavior is undefined when hooking functions that internally use library functions like open, mmap, etc.

Safety

All hooking operations are inherently unsafe and require careful consideration:

  • Target and proxy functions must be valid pointers to executable code
  • Target functions must not be executed by other threads during patching to avoid race conditions
  • Proper synchronization is the caller's responsibility
  • Hooking functions whose first 4 instructions contain PC-relative addressing instructions (e.g., adrp) is not yet supported

Testing

Run tests on Android ARM64 target:

just

This requires:

  • Android NDK installed
  • ANDROID_NDK environment variable set
  • cargo-nextest installed

Platform Support

Currently supports:

  • Architecture: ARM64/AArch64
  • Target: aarch64-linux-android

About

A lightweight Rust library for inline hooking on Android

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

27 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Wisp

wisp

Overview

Wisp provides runtime function hooking capabilities for ARM64 platforms, primarily designed for Android (aarch64-linux-android). It allows you to replace or intercept function calls at runtime by dynamically modifying executable code.

Warning: This library is still under development and cannot handle cases where instructions at the beginning of the target function contain PC-relative addressing instructions like adrp. Do not use in production environments.

Features

  • Function Replacement: Replace target functions entirely with proxy implementations
  • Function Hooking: Intercept function calls while preserving access to original implementation
  • Dynamic Original Function: Retrieve original function pointer dynamically via orig_fn!() macro
    • Function Interception: Modify function arguments at runtime via a callback before the original function executes
  • Instruction Cache Synchronization: Ensures cache coherency after code modifications

Installation

Add this to your Cargo.toml:

[dependencies]
wisp = { git = "https://github.com/Mufanc/wisp" }

Usage

Function Replacement

Replace a target function entirely with a proxy function:

use wisp::Wisp;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{
a * b
}unsafe{let stub = Wisp::replace_fn(target_fn as_, proxy_fn as_).expect("failed to replace function");// target_fn now executes proxy_fn's codeassert_eq!(target_fn(2,3),6);// 2 * 3// Save the stub and explicitly unhook when restoration is needed.
stub.unhook().expect("failed to unhook function");assert_eq!(target_fn(2,3),5);}

Dropping a stub leaves the hook and its executable buffers installed until process exit. Call Stub::unhook explicitly before dropping it to restore the original function.

Function Hooking

Hook a function while maintaining access to the original implementation:

use wisp::Wisp;use std::ffi::c_void;staticmutORIG_FN:*constc_void = std::ptr::null();extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{// Call original functionlet result = unsafe{
std::mem::transmute::<*constc_void,fn(i32,i32) -> i32>(ORIG_FN)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,Some(&mutORIG_FN)).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Dynamic Original Function

Use orig_fn!() macro to dynamically retrieve the original function without static variables:

use wisp::{Wisp, orig_fn};use std::ffi::c_void;use std::mem;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{let orig_fn = orig_fn!();// Call original functionlet result = unsafe{
mem::transmute::<*constc_void,fn(i32,i32) -> i32>(orig_fn)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,None).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Function Interception

Intercept a function to modify its arguments at runtime via a callback before the original function executes:

use wisp::Wisp;use libc::c_long;extern"C"fntarget_fn(a:u32,b:u32) -> u32{
a + b
}extern"C"fncallback_fn(args:*mutc_long){unsafe{// args points to saved registers x0-x7 on the stack// Modify x0 (first argument)*args = 100;// Modify x1 (second argument)*args.add(1) = 200;}}unsafe{let _stub = Wisp::intercept_fn(target_fn as_, callback_fn).expect("failed to intercept function");// target_fn is called with modified arguments (100, 200) regardless of inputassert_eq!(target_fn(1,2),300);}

The callback receives a pointer to the saved argument registers (x0-x7) on the stack, allowing you to read or modify any of the first 8 arguments before the original function executes.

Custom Unhook Behavior

Implement custom unhooking logic with the Unhooker trait:

use wisp::{CustomWisp,UnhookContext,Unhooker,WispResult};structMadviseUnhooker;unsafeimplUnhookerforMadviseUnhooker{fnunhook(context:UnhookContext<'_>) -> WispResult<()>{// Pseudocode:// let pages = page_range(context.target(), context.backup_insn().len());// madvise(pages, MADV_DONTNEED);// touch_pages(pages);todo!()}}typeMadviseWisp = CustomWisp<MadviseUnhooker>;

API

Core Types

  • Wisp: Main type alias for CustomWisp<SimpleUnhooker>
  • CustomWisp<U>: Generic hooking interface with custom unhooker
  • Stub<U>: Handle used to explicitly unhook a function
  • UnhookContext: Read-only target information passed to an unhooker
  • Unhooker: Unsafe extension point for custom unhook behavior
  • SimpleUnhooker: Default unhooker implementation

Methods

Wisp::replace_fn

pubunsafefnreplace_fn(target_fn:*constc_void,proxy_fn:*constc_void,) -> WispResult<Stub<U>>

Replaces the target function with a proxy function.

Wisp::hook_fn

pubunsafefnhook_fn(target_fn:*constc_void,proxy_fn:*constc_void,backup_orig:Option<&mut*constc_void>,) -> WispResult<Stub<U>>

Hooks the target function while preserving access to the original implementation. Pass Some(&mut ptr) to store the original function pointer, or None to skip storing.

Wisp::intercept_fn

pubunsafefnintercept_fn(target_fn:*constc_void,callback_fn:extern"C"fn(*mutc_long),) -> WispResult<Stub<U>>

Intercepts the target function, invoking the callback with a mutable pointer to the saved arguments on the stack before executing the original function. The callback can read or modify the arguments.

Stub::unhook

pubunsafefnunhook(self) -> WispResult<()>

Restores the target function and releases its executable buffers. Dropping the stub without calling this method leaves the hook installed.

orig_fn!()

Macro to dynamically retrieve the original function pointer within a proxy function. Must be called at the beginning of the proxy function.

Limitations

  • Recursive functions: Hooking functions that recursively call themselves is not supported
  • Multiple hooks: Attaching multiple hooks to a single function is not supported
  • Minimum instruction length: Target functions must have at least 4 ARM64 instructions (16 bytes)
  • Concurrent operations: Simultaneous hook/unhook operations on the same function from multiple threads result in undefined behavior
  • Internal library calls: Behavior is undefined when hooking functions that internally use library functions like open, mmap, etc.

Safety

All hooking operations are inherently unsafe and require careful consideration:

  • Target and proxy functions must be valid pointers to executable code
  • Target functions must not be executed by other threads during patching to avoid race conditions
  • Proper synchronization is the caller's responsibility
  • Hooking functions whose first 4 instructions contain PC-relative addressing instructions (e.g., adrp) is not yet supported

Testing

Run tests on Android ARM64 target:

just

This requires:

  • Android NDK installed
  • ANDROID_NDK environment variable set
  • cargo-nextest installed

Platform Support

Currently supports:

  • Architecture: ARM64/AArch64
  • Target: aarch64-linux-android

About

A lightweight Rust library for inline hooking on Android

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

27 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Wisp

wisp

Overview

Wisp provides runtime function hooking capabilities for ARM64 platforms, primarily designed for Android (aarch64-linux-android). It allows you to replace or intercept function calls at runtime by dynamically modifying executable code.

Warning: This library is still under development and cannot handle cases where instructions at the beginning of the target function contain PC-relative addressing instructions like adrp. Do not use in production environments.

Features

  • Function Replacement: Replace target functions entirely with proxy implementations
  • Function Hooking: Intercept function calls while preserving access to original implementation
  • Dynamic Original Function: Retrieve original function pointer dynamically via orig_fn!() macro
    • Function Interception: Modify function arguments at runtime via a callback before the original function executes
  • Instruction Cache Synchronization: Ensures cache coherency after code modifications

Installation

Add this to your Cargo.toml:

[dependencies]
wisp = { git = "https://github.com/Mufanc/wisp" }

Usage

Function Replacement

Replace a target function entirely with a proxy function:

use wisp::Wisp;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{
a * b
}unsafe{let stub = Wisp::replace_fn(target_fn as_, proxy_fn as_).expect("failed to replace function");// target_fn now executes proxy_fn's codeassert_eq!(target_fn(2,3),6);// 2 * 3// Save the stub and explicitly unhook when restoration is needed.
stub.unhook().expect("failed to unhook function");assert_eq!(target_fn(2,3),5);}

Dropping a stub leaves the hook and its executable buffers installed until process exit. Call Stub::unhook explicitly before dropping it to restore the original function.

Function Hooking

Hook a function while maintaining access to the original implementation:

use wisp::Wisp;use std::ffi::c_void;staticmutORIG_FN:*constc_void = std::ptr::null();extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{// Call original functionlet result = unsafe{
std::mem::transmute::<*constc_void,fn(i32,i32) -> i32>(ORIG_FN)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,Some(&mutORIG_FN)).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Dynamic Original Function

Use orig_fn!() macro to dynamically retrieve the original function without static variables:

use wisp::{Wisp, orig_fn};use std::ffi::c_void;use std::mem;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{let orig_fn = orig_fn!();// Call original functionlet result = unsafe{
mem::transmute::<*constc_void,fn(i32,i32) -> i32>(orig_fn)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,None).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Function Interception

Intercept a function to modify its arguments at runtime via a callback before the original function executes:

use wisp::Wisp;use libc::c_long;extern"C"fntarget_fn(a:u32,b:u32) -> u32{
a + b
}extern"C"fncallback_fn(args:*mutc_long){unsafe{// args points to saved registers x0-x7 on the stack// Modify x0 (first argument)*args = 100;// Modify x1 (second argument)*args.add(1) = 200;}}unsafe{let _stub = Wisp::intercept_fn(target_fn as_, callback_fn).expect("failed to intercept function");// target_fn is called with modified arguments (100, 200) regardless of inputassert_eq!(target_fn(1,2),300);}

The callback receives a pointer to the saved argument registers (x0-x7) on the stack, allowing you to read or modify any of the first 8 arguments before the original function executes.

Custom Unhook Behavior

Implement custom unhooking logic with the Unhooker trait:

use wisp::{CustomWisp,UnhookContext,Unhooker,WispResult};structMadviseUnhooker;unsafeimplUnhookerforMadviseUnhooker{fnunhook(context:UnhookContext<'_>) -> WispResult<()>{// Pseudocode:// let pages = page_range(context.target(), context.backup_insn().len());// madvise(pages, MADV_DONTNEED);// touch_pages(pages);todo!()}}typeMadviseWisp = CustomWisp<MadviseUnhooker>;

API

Core Types

  • Wisp: Main type alias for CustomWisp<SimpleUnhooker>
  • CustomWisp<U>: Generic hooking interface with custom unhooker
  • Stub<U>: Handle used to explicitly unhook a function
  • UnhookContext: Read-only target information passed to an unhooker
  • Unhooker: Unsafe extension point for custom unhook behavior
  • SimpleUnhooker: Default unhooker implementation

Methods

Wisp::replace_fn

pubunsafefnreplace_fn(target_fn:*constc_void,proxy_fn:*constc_void,) -> WispResult<Stub<U>>

Replaces the target function with a proxy function.

Wisp::hook_fn

pubunsafefnhook_fn(target_fn:*constc_void,proxy_fn:*constc_void,backup_orig:Option<&mut*constc_void>,) -> WispResult<Stub<U>>

Hooks the target function while preserving access to the original implementation. Pass Some(&mut ptr) to store the original function pointer, or None to skip storing.

Wisp::intercept_fn

pubunsafefnintercept_fn(target_fn:*constc_void,callback_fn:extern"C"fn(*mutc_long),) -> WispResult<Stub<U>>

Intercepts the target function, invoking the callback with a mutable pointer to the saved arguments on the stack before executing the original function. The callback can read or modify the arguments.

Stub::unhook

pubunsafefnunhook(self) -> WispResult<()>

Restores the target function and releases its executable buffers. Dropping the stub without calling this method leaves the hook installed.

orig_fn!()

Macro to dynamically retrieve the original function pointer within a proxy function. Must be called at the beginning of the proxy function.

Limitations

  • Recursive functions: Hooking functions that recursively call themselves is not supported
  • Multiple hooks: Attaching multiple hooks to a single function is not supported
  • Minimum instruction length: Target functions must have at least 4 ARM64 instructions (16 bytes)
  • Concurrent operations: Simultaneous hook/unhook operations on the same function from multiple threads result in undefined behavior
  • Internal library calls: Behavior is undefined when hooking functions that internally use library functions like open, mmap, etc.

Safety

All hooking operations are inherently unsafe and require careful consideration:

  • Target and proxy functions must be valid pointers to executable code
  • Target functions must not be executed by other threads during patching to avoid race conditions
  • Proper synchronization is the caller's responsibility
  • Hooking functions whose first 4 instructions contain PC-relative addressing instructions (e.g., adrp) is not yet supported

Testing

Run tests on Android ARM64 target:

just

This requires:

  • Android NDK installed
  • ANDROID_NDK environment variable set
  • cargo-nextest installed

Platform Support

Currently supports:

  • Architecture: ARM64/AArch64
  • Target: aarch64-linux-android

About

A lightweight Rust library for inline hooking on Android

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

27 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Wisp

wisp

Overview

Wisp provides runtime function hooking capabilities for ARM64 platforms, primarily designed for Android (aarch64-linux-android). It allows you to replace or intercept function calls at runtime by dynamically modifying executable code.

Warning: This library is still under development and cannot handle cases where instructions at the beginning of the target function contain PC-relative addressing instructions like adrp. Do not use in production environments.

Features

  • Function Replacement: Replace target functions entirely with proxy implementations
  • Function Hooking: Intercept function calls while preserving access to original implementation
  • Dynamic Original Function: Retrieve original function pointer dynamically via orig_fn!() macro
    • Function Interception: Modify function arguments at runtime via a callback before the original function executes
  • Instruction Cache Synchronization: Ensures cache coherency after code modifications

Installation

Add this to your Cargo.toml:

[dependencies]
wisp = { git = "https://github.com/Mufanc/wisp" }

Usage

Function Replacement

Replace a target function entirely with a proxy function:

use wisp::Wisp;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{
a * b
}unsafe{let stub = Wisp::replace_fn(target_fn as_, proxy_fn as_).expect("failed to replace function");// target_fn now executes proxy_fn's codeassert_eq!(target_fn(2,3),6);// 2 * 3// Save the stub and explicitly unhook when restoration is needed.
stub.unhook().expect("failed to unhook function");assert_eq!(target_fn(2,3),5);}

Dropping a stub leaves the hook and its executable buffers installed until process exit. Call Stub::unhook explicitly before dropping it to restore the original function.

Function Hooking

Hook a function while maintaining access to the original implementation:

use wisp::Wisp;use std::ffi::c_void;staticmutORIG_FN:*constc_void = std::ptr::null();extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{// Call original functionlet result = unsafe{
std::mem::transmute::<*constc_void,fn(i32,i32) -> i32>(ORIG_FN)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,Some(&mutORIG_FN)).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Dynamic Original Function

Use orig_fn!() macro to dynamically retrieve the original function without static variables:

use wisp::{Wisp, orig_fn};use std::ffi::c_void;use std::mem;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{let orig_fn = orig_fn!();// Call original functionlet result = unsafe{
mem::transmute::<*constc_void,fn(i32,i32) -> i32>(orig_fn)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,None).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Function Interception

Intercept a function to modify its arguments at runtime via a callback before the original function executes:

use wisp::Wisp;use libc::c_long;extern"C"fntarget_fn(a:u32,b:u32) -> u32{
a + b
}extern"C"fncallback_fn(args:*mutc_long){unsafe{// args points to saved registers x0-x7 on the stack// Modify x0 (first argument)*args = 100;// Modify x1 (second argument)*args.add(1) = 200;}}unsafe{let _stub = Wisp::intercept_fn(target_fn as_, callback_fn).expect("failed to intercept function");// target_fn is called with modified arguments (100, 200) regardless of inputassert_eq!(target_fn(1,2),300);}

The callback receives a pointer to the saved argument registers (x0-x7) on the stack, allowing you to read or modify any of the first 8 arguments before the original function executes.

Custom Unhook Behavior

Implement custom unhooking logic with the Unhooker trait:

use wisp::{CustomWisp,UnhookContext,Unhooker,WispResult};structMadviseUnhooker;unsafeimplUnhookerforMadviseUnhooker{fnunhook(context:UnhookContext<'_>) -> WispResult<()>{// Pseudocode:// let pages = page_range(context.target(), context.backup_insn().len());// madvise(pages, MADV_DONTNEED);// touch_pages(pages);todo!()}}typeMadviseWisp = CustomWisp<MadviseUnhooker>;

API

Core Types

  • Wisp: Main type alias for CustomWisp<SimpleUnhooker>
  • CustomWisp<U>: Generic hooking interface with custom unhooker
  • Stub<U>: Handle used to explicitly unhook a function
  • UnhookContext: Read-only target information passed to an unhooker
  • Unhooker: Unsafe extension point for custom unhook behavior
  • SimpleUnhooker: Default unhooker implementation

Methods

Wisp::replace_fn

pubunsafefnreplace_fn(target_fn:*constc_void,proxy_fn:*constc_void,) -> WispResult<Stub<U>>

Replaces the target function with a proxy function.

Wisp::hook_fn

pubunsafefnhook_fn(target_fn:*constc_void,proxy_fn:*constc_void,backup_orig:Option<&mut*constc_void>,) -> WispResult<Stub<U>>

Hooks the target function while preserving access to the original implementation. Pass Some(&mut ptr) to store the original function pointer, or None to skip storing.

Wisp::intercept_fn

pubunsafefnintercept_fn(target_fn:*constc_void,callback_fn:extern"C"fn(*mutc_long),) -> WispResult<Stub<U>>

Intercepts the target function, invoking the callback with a mutable pointer to the saved arguments on the stack before executing the original function. The callback can read or modify the arguments.

Stub::unhook

pubunsafefnunhook(self) -> WispResult<()>

Restores the target function and releases its executable buffers. Dropping the stub without calling this method leaves the hook installed.

orig_fn!()

Macro to dynamically retrieve the original function pointer within a proxy function. Must be called at the beginning of the proxy function.

Limitations

  • Recursive functions: Hooking functions that recursively call themselves is not supported
  • Multiple hooks: Attaching multiple hooks to a single function is not supported
  • Minimum instruction length: Target functions must have at least 4 ARM64 instructions (16 bytes)
  • Concurrent operations: Simultaneous hook/unhook operations on the same function from multiple threads result in undefined behavior
  • Internal library calls: Behavior is undefined when hooking functions that internally use library functions like open, mmap, etc.

Safety

All hooking operations are inherently unsafe and require careful consideration:

  • Target and proxy functions must be valid pointers to executable code
  • Target functions must not be executed by other threads during patching to avoid race conditions
  • Proper synchronization is the caller's responsibility
  • Hooking functions whose first 4 instructions contain PC-relative addressing instructions (e.g., adrp) is not yet supported

Testing

Run tests on Android ARM64 target:

just

This requires:

  • Android NDK installed
  • ANDROID_NDK environment variable set
  • cargo-nextest installed

Platform Support

Currently supports:

  • Architecture: ARM64/AArch64
  • Target: aarch64-linux-android

About

A lightweight Rust library for inline hooking on Android

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

27 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Wisp

wisp

Overview

Wisp provides runtime function hooking capabilities for ARM64 platforms, primarily designed for Android (aarch64-linux-android). It allows you to replace or intercept function calls at runtime by dynamically modifying executable code.

Warning: This library is still under development and cannot handle cases where instructions at the beginning of the target function contain PC-relative addressing instructions like adrp. Do not use in production environments.

Features

  • Function Replacement: Replace target functions entirely with proxy implementations
  • Function Hooking: Intercept function calls while preserving access to original implementation
  • Dynamic Original Function: Retrieve original function pointer dynamically via orig_fn!() macro
    • Function Interception: Modify function arguments at runtime via a callback before the original function executes
  • Instruction Cache Synchronization: Ensures cache coherency after code modifications

Installation

Add this to your Cargo.toml:

[dependencies]
wisp = { git = "https://github.com/Mufanc/wisp" }

Usage

Function Replacement

Replace a target function entirely with a proxy function:

use wisp::Wisp;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{
a * b
}unsafe{let stub = Wisp::replace_fn(target_fn as_, proxy_fn as_).expect("failed to replace function");// target_fn now executes proxy_fn's codeassert_eq!(target_fn(2,3),6);// 2 * 3// Save the stub and explicitly unhook when restoration is needed.
stub.unhook().expect("failed to unhook function");assert_eq!(target_fn(2,3),5);}

Dropping a stub leaves the hook and its executable buffers installed until process exit. Call Stub::unhook explicitly before dropping it to restore the original function.

Function Hooking

Hook a function while maintaining access to the original implementation:

use wisp::Wisp;use std::ffi::c_void;staticmutORIG_FN:*constc_void = std::ptr::null();extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{// Call original functionlet result = unsafe{
std::mem::transmute::<*constc_void,fn(i32,i32) -> i32>(ORIG_FN)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,Some(&mutORIG_FN)).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Dynamic Original Function

Use orig_fn!() macro to dynamically retrieve the original function without static variables:

use wisp::{Wisp, orig_fn};use std::ffi::c_void;use std::mem;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{let orig_fn = orig_fn!();// Call original functionlet result = unsafe{
mem::transmute::<*constc_void,fn(i32,i32) -> i32>(orig_fn)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,None).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Function Interception

Intercept a function to modify its arguments at runtime via a callback before the original function executes:

use wisp::Wisp;use libc::c_long;extern"C"fntarget_fn(a:u32,b:u32) -> u32{
a + b
}extern"C"fncallback_fn(args:*mutc_long){unsafe{// args points to saved registers x0-x7 on the stack// Modify x0 (first argument)*args = 100;// Modify x1 (second argument)*args.add(1) = 200;}}unsafe{let _stub = Wisp::intercept_fn(target_fn as_, callback_fn).expect("failed to intercept function");// target_fn is called with modified arguments (100, 200) regardless of inputassert_eq!(target_fn(1,2),300);}

The callback receives a pointer to the saved argument registers (x0-x7) on the stack, allowing you to read or modify any of the first 8 arguments before the original function executes.

Custom Unhook Behavior

Implement custom unhooking logic with the Unhooker trait:

use wisp::{CustomWisp,UnhookContext,Unhooker,WispResult};structMadviseUnhooker;unsafeimplUnhookerforMadviseUnhooker{fnunhook(context:UnhookContext<'_>) -> WispResult<()>{// Pseudocode:// let pages = page_range(context.target(), context.backup_insn().len());// madvise(pages, MADV_DONTNEED);// touch_pages(pages);todo!()}}typeMadviseWisp = CustomWisp<MadviseUnhooker>;

API

Core Types

  • Wisp: Main type alias for CustomWisp<SimpleUnhooker>
  • CustomWisp<U>: Generic hooking interface with custom unhooker
  • Stub<U>: Handle used to explicitly unhook a function
  • UnhookContext: Read-only target information passed to an unhooker
  • Unhooker: Unsafe extension point for custom unhook behavior
  • SimpleUnhooker: Default unhooker implementation

Methods

Wisp::replace_fn

pubunsafefnreplace_fn(target_fn:*constc_void,proxy_fn:*constc_void,) -> WispResult<Stub<U>>

Replaces the target function with a proxy function.

Wisp::hook_fn

pubunsafefnhook_fn(target_fn:*constc_void,proxy_fn:*constc_void,backup_orig:Option<&mut*constc_void>,) -> WispResult<Stub<U>>

Hooks the target function while preserving access to the original implementation. Pass Some(&mut ptr) to store the original function pointer, or None to skip storing.

Wisp::intercept_fn

pubunsafefnintercept_fn(target_fn:*constc_void,callback_fn:extern"C"fn(*mutc_long),) -> WispResult<Stub<U>>

Intercepts the target function, invoking the callback with a mutable pointer to the saved arguments on the stack before executing the original function. The callback can read or modify the arguments.

Stub::unhook

pubunsafefnunhook(self) -> WispResult<()>

Restores the target function and releases its executable buffers. Dropping the stub without calling this method leaves the hook installed.

orig_fn!()

Macro to dynamically retrieve the original function pointer within a proxy function. Must be called at the beginning of the proxy function.

Limitations

  • Recursive functions: Hooking functions that recursively call themselves is not supported
  • Multiple hooks: Attaching multiple hooks to a single function is not supported
  • Minimum instruction length: Target functions must have at least 4 ARM64 instructions (16 bytes)
  • Concurrent operations: Simultaneous hook/unhook operations on the same function from multiple threads result in undefined behavior
  • Internal library calls: Behavior is undefined when hooking functions that internally use library functions like open, mmap, etc.

Safety

All hooking operations are inherently unsafe and require careful consideration:

  • Target and proxy functions must be valid pointers to executable code
  • Target functions must not be executed by other threads during patching to avoid race conditions
  • Proper synchronization is the caller's responsibility
  • Hooking functions whose first 4 instructions contain PC-relative addressing instructions (e.g., adrp) is not yet supported

Testing

Run tests on Android ARM64 target:

just

This requires:

  • Android NDK installed
  • ANDROID_NDK environment variable set
  • cargo-nextest installed

Platform Support

Currently supports:

  • Architecture: ARM64/AArch64
  • Target: aarch64-linux-android

About

A lightweight Rust library for inline hooking on Android

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

27 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Wisp

wisp

Overview

Wisp provides runtime function hooking capabilities for ARM64 platforms, primarily designed for Android (aarch64-linux-android). It allows you to replace or intercept function calls at runtime by dynamically modifying executable code.

Warning: This library is still under development and cannot handle cases where instructions at the beginning of the target function contain PC-relative addressing instructions like adrp. Do not use in production environments.

Features

  • Function Replacement: Replace target functions entirely with proxy implementations
  • Function Hooking: Intercept function calls while preserving access to original implementation
  • Dynamic Original Function: Retrieve original function pointer dynamically via orig_fn!() macro
    • Function Interception: Modify function arguments at runtime via a callback before the original function executes
  • Instruction Cache Synchronization: Ensures cache coherency after code modifications

Installation

Add this to your Cargo.toml:

[dependencies]
wisp = { git = "https://github.com/Mufanc/wisp" }

Usage

Function Replacement

Replace a target function entirely with a proxy function:

use wisp::Wisp;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{
a * b
}unsafe{let stub = Wisp::replace_fn(target_fn as_, proxy_fn as_).expect("failed to replace function");// target_fn now executes proxy_fn's codeassert_eq!(target_fn(2,3),6);// 2 * 3// Save the stub and explicitly unhook when restoration is needed.
stub.unhook().expect("failed to unhook function");assert_eq!(target_fn(2,3),5);}

Dropping a stub leaves the hook and its executable buffers installed until process exit. Call Stub::unhook explicitly before dropping it to restore the original function.

Function Hooking

Hook a function while maintaining access to the original implementation:

use wisp::Wisp;use std::ffi::c_void;staticmutORIG_FN:*constc_void = std::ptr::null();extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{// Call original functionlet result = unsafe{
std::mem::transmute::<*constc_void,fn(i32,i32) -> i32>(ORIG_FN)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,Some(&mutORIG_FN)).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Dynamic Original Function

Use orig_fn!() macro to dynamically retrieve the original function without static variables:

use wisp::{Wisp, orig_fn};use std::ffi::c_void;use std::mem;extern"C"fntarget_fn(a:i32,b:i32) -> i32{
a + b
}extern"C"fnproxy_fn(a:i32,b:i32) -> i32{let orig_fn = orig_fn!();// Call original functionlet result = unsafe{
mem::transmute::<*constc_void,fn(i32,i32) -> i32>(orig_fn)(a, b)};// Modify behavior
result *2}unsafe{let _stub = Wisp::hook_fn(target_fn as_, proxy_fn as_,None).expect("failed to hook function");assert_eq!(target_fn(2,3),10);// (2 + 3) * 2}

Function Interception

Intercept a function to modify its arguments at runtime via a callback before the original function executes:

use wisp::Wisp;use libc::c_long;extern"C"fntarget_fn(a:u32,b:u32) -> u32{
a + b
}extern"C"fncallback_fn(args:*mutc_long){unsafe{// args points to saved registers x0-x7 on the stack// Modify x0 (first argument)*args = 100;// Modify x1 (second argument)*args.add(1) = 200;}}unsafe{let _stub = Wisp::intercept_fn(target_fn as_, callback_fn).expect("failed to intercept function");// target_fn is called with modified arguments (100, 200) regardless of inputassert_eq!(target_fn(1,2),300);}

The callback receives a pointer to the saved argument registers (x0-x7) on the stack, allowing you to read or modify any of the first 8 arguments before the original function executes.

Custom Unhook Behavior

Implement custom unhooking logic with the Unhooker trait:

use wisp::{CustomWisp,UnhookContext,Unhooker,WispResult};structMadviseUnhooker;unsafeimplUnhookerforMadviseUnhooker{fnunhook(context:UnhookContext<'_>) -> WispResult<()>{// Pseudocode:// let pages = page_range(context.target(), context.backup_insn().len());// madvise(pages, MADV_DONTNEED);// touch_pages(pages);todo!()}}typeMadviseWisp = CustomWisp<MadviseUnhooker>;

API

Core Types

  • Wisp: Main type alias for CustomWisp<SimpleUnhooker>
  • CustomWisp<U>: Generic hooking interface with custom unhooker
  • Stub<U>: Handle used to explicitly unhook a function
  • UnhookContext: Read-only target information passed to an unhooker
  • Unhooker: Unsafe extension point for custom unhook behavior
  • SimpleUnhooker: Default unhooker implementation

Methods

Wisp::replace_fn

pubunsafefnreplace_fn(target_fn:*constc_void,proxy_fn:*constc_void,) -> WispResult<Stub<U>>

Replaces the target function with a proxy function.

Wisp::hook_fn

pubunsafefnhook_fn(target_fn:*constc_void,proxy_fn:*constc_void,backup_orig:Option<&mut*constc_void>,) -> WispResult<Stub<U>>

Hooks the target function while preserving access to the original implementation. Pass Some(&mut ptr) to store the original function pointer, or None to skip storing.

Wisp::intercept_fn

pubunsafefnintercept_fn(target_fn:*constc_void,callback_fn:extern"C"fn(*mutc_long),) -> WispResult<Stub<U>>

Intercepts the target function, invoking the callback with a mutable pointer to the saved arguments on the stack before executing the original function. The callback can read or modify the arguments.

Stub::unhook

pubunsafefnunhook(self) -> WispResult<()>

Restores the target function and releases its executable buffers. Dropping the stub without calling this method leaves the hook installed.

orig_fn!()

Macro to dynamically retrieve the original function pointer within a proxy function. Must be called at the beginning of the proxy function.

Limitations

  • Recursive functions: Hooking functions that recursively call themselves is not supported
  • Multiple hooks: Attaching multiple hooks to a single function is not supported
  • Minimum instruction length: Target functions must have at least 4 ARM64 instructions (16 bytes)
  • Concurrent operations: Simultaneous hook/unhook operations on the same function from multiple threads result in undefined behavior
  • Internal library calls: Behavior is undefined when hooking functions that internally use library functions like open, mmap, etc.

Safety

All hooking operations are inherently unsafe and require careful consideration:

  • Target and proxy functions must be valid pointers to executable code
  • Target functions must not be executed by other threads during patching to avoid race conditions
  • Proper synchronization is the caller's responsibility
  • Hooking functions whose first 4 instructions contain PC-relative addressing instructions (e.g., adrp) is not yet supported

Testing

Run tests on Android ARM64 target:

just

This requires:

  • Android NDK installed
  • ANDROID_NDK environment variable set
  • cargo-nextest installed

Platform Support

Currently supports:

  • Architecture: ARM64/AArch64
  • Target: aarch64-linux-android

About

A lightweight Rust library for inline hooking on Android

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages