Struct Hook
pub struct Hook { /* private fields */ }Expand description
A configurable hook for method interception.
Hooks combine matchers (to determine which methods to intercept) with handlers (to define what happens when intercepted). Use the builder pattern to configure matchers and handlers.
§Building Hooks
Hooks are constructed using a fluent builder pattern:
use dotscope::emulation::{Hook, PreHookResult, HookPriority};
let hook = Hook::new("my-hook")
.with_priority(HookPriority::HIGH)
.match_name("System", "String", "Concat")
.pre(|ctx, thread| {
println!("String.Concat called!");
PreHookResult::Continue
});§Matcher Evaluation
All matchers on a hook must match for the hook to be applied (AND semantics). A hook with no matchers never matches (safety default).
§Pre vs Post Hooks
-
Pre-hooks run before the original method. They can:
- Continue to let the original method run
- Bypass the original and return a value directly
- Report an error
-
Post-hooks run after the original method. They can:
- Keep the original return value
- Replace the return value
- Report an error
§Examples
§Logging Hook
use dotscope::emulation::{Hook, PreHookResult, PostHookResult};
let hook = Hook::new("log-calls")
.match_method_name("Decrypt")
.pre(|ctx, thread| {
println!("Decrypt called with {} args", ctx.args.len());
PreHookResult::Continue
})
.post(|ctx, thread, result| {
println!("Decrypt returned: {:?}", result);
PostHookResult::Keep
});§Bypass Hook
use dotscope::emulation::{Hook, PreHookResult, EmValue};
let hook = Hook::new("bypass-anti-debug")
.match_name("System.Diagnostics", "Debugger", "get_IsAttached")
.pre(|ctx, thread| {
// Always return false to bypass anti-debugging
PreHookResult::Bypass(Some(EmValue::Bool(false)))
});Implementations§
§impl Hook
impl Hook
pub fn priority(&self) -> HookPriority
pub fn priority(&self) -> HookPriority
Returns the hook’s priority.
pub fn with_priority(self, priority: HookPriority) -> Self
pub fn with_priority(self, priority: HookPriority) -> Self
Sets the hook’s priority.
Higher priority hooks are checked first. The default is
HookPriority::NORMAL.
§Arguments
priority- The priority level
pub fn add_matcher<M: HookMatcher + 'static>(self, matcher: M) -> Self
pub fn add_matcher<M: HookMatcher + 'static>(self, matcher: M) -> Self
Adds a custom matcher.
Custom matchers can implement any matching logic by implementing
the HookMatcher trait.
§Arguments
matcher- The matcher to add
pub fn match_name(
self,
namespace: impl Into<String>,
type_name: impl Into<String>,
method_name: impl Into<String>,
) -> Self
pub fn match_name( self, namespace: impl Into<String>, type_name: impl Into<String>, method_name: impl Into<String>, ) -> Self
Adds a name-based matcher for namespace, type, and method.
All three components must match exactly.
§Arguments
namespace- The namespace to matchtype_name- The type name to matchmethod_name- The method name to match
§Examples
use dotscope::emulation::Hook;
let hook = Hook::new("string-concat")
.match_name("System", "String", "Concat");pub fn match_method_name(self, method_name: impl Into<String>) -> Self
pub fn match_method_name(self, method_name: impl Into<String>) -> Self
Adds a matcher for method name only.
Matches any method with the given name, regardless of namespace or type.
§Arguments
method_name- The method name to match
pub fn match_type_name(self, type_name: impl Into<String>) -> Self
pub fn match_type_name(self, type_name: impl Into<String>) -> Self
Adds a matcher for type name only.
Matches any method on types with the given name, regardless of namespace.
§Arguments
type_name- The type name to match
pub fn match_internal_method(self) -> Self
pub fn match_internal_method(self) -> Self
Adds a matcher that only matches internal methods (MethodDef).
Internal methods are defined in the assembly being analyzed. This is useful for matching obfuscator-generated methods.
pub fn match_native(
self,
dll: impl Into<String>,
function: impl Into<String>,
) -> Self
pub fn match_native( self, dll: impl Into<String>, function: impl Into<String>, ) -> Self
Adds a matcher for P/Invoke (native) method calls.
This matches calls to unmanaged code through P/Invoke. Both DLL name and function name must be specified for an exact match.
§Arguments
dll- The DLL name (e.g., “kernel32” or “kernel32.dll”)function- The native function name (e.g., “VirtualProtect”)
§Examples
use dotscope::emulation::{Hook, PreHookResult, EmValue};
let hook = Hook::new("virtual-protect-hook")
.match_native("kernel32", "VirtualProtect")
.pre(|ctx, thread| {
// Handle VirtualProtect call
PreHookResult::Bypass(Some(EmValue::I32(1)))
});pub fn match_native_dll(self, dll: impl Into<String>) -> Self
pub fn match_native_dll(self, dll: impl Into<String>) -> Self
Adds a matcher for any P/Invoke call to a specific DLL.
This matches all P/Invoke calls to the specified DLL, regardless of the function name.
§Arguments
dll- The DLL name (e.g., “kernel32” or “kernel32.dll”)
pub fn match_signature(
self,
params: Vec<CilFlavor>,
return_type: Option<CilFlavor>,
) -> Self
pub fn match_signature( self, params: Vec<CilFlavor>, return_type: Option<CilFlavor>, ) -> Self
Adds a signature matcher for parameter and return types.
§Arguments
params- The expected parameter typesreturn_type- The expected return type (orNonefor void/any)
§Examples
use dotscope::emulation::Hook;
use dotscope::metadata::typesystem::CilFlavor;
// Match methods that take (int32, int32) and return int32
let hook = Hook::new("int-transformer")
.match_signature(vec![CilFlavor::I4, CilFlavor::I4], Some(CilFlavor::I4));pub fn match_runtime<F>(
self,
description: impl Into<String>,
predicate: F,
) -> Self
pub fn match_runtime<F>( self, description: impl Into<String>, predicate: F, ) -> Self
Adds a runtime matcher that inspects argument values.
Runtime matchers are evaluated during method call interception and can inspect actual argument values to make matching decisions.
§Arguments
description- Human-readable description for debuggingpredicate- Function that returnstrueif the hook should match
§Examples
use dotscope::emulation::{Hook, EmValue, HookContext, EmulationThread};
use dotscope::metadata::typesystem::PointerSize;
let hook = Hook::new("lzma-detector")
.match_runtime("lzma-header", |ctx: &HookContext<'_>, thread: &EmulationThread| {
// Check if first arg is a byte[] starting with LZMA magic
if let Some(EmValue::ObjectRef(r)) = ctx.args.first() {
if let Some(bytes) = thread.heap().get_array_as_bytes(*r, PointerSize::Bit64) {
return bytes.len() >= 5 && bytes[0] == 0x5D;
}
}
false
});pub fn pre<F>(self, handler: F) -> Self
pub fn pre<F>(self, handler: F) -> Self
Sets the pre-hook handler.
Pre-hooks run before the original method and can:
- Continue to let the original method run
- Bypass the original method and return a value directly
- Report an error
§Arguments
handler- The pre-hook handler function
§Examples
use dotscope::emulation::{Hook, PreHookResult};
let hook = Hook::new("log-and-continue")
.match_method_name("Decrypt")
.pre(|ctx, thread| {
println!("Decrypt called!");
PreHookResult::Continue
});pub fn post<F>(self, handler: F) -> Selfwhere
F: Fn(&HookContext<'_>, &mut EmulationThread, Option<&EmValue>) -> PostHookResult + Send + Sync + 'static,
pub fn post<F>(self, handler: F) -> Selfwhere
F: Fn(&HookContext<'_>, &mut EmulationThread, Option<&EmValue>) -> PostHookResult + Send + Sync + 'static,
Sets the post-hook handler.
Post-hooks run after the original method and can:
- Keep the original result unchanged
- Replace the result with a new value
- Report an error
§Arguments
handler- The post-hook handler function
pub fn matches(
&self,
context: &HookContext<'_>,
thread: &EmulationThread,
) -> bool
pub fn matches( &self, context: &HookContext<'_>, thread: &EmulationThread, ) -> bool
Checks if all matchers match the given context.
Returns false if the hook has no matchers (safety default).
pub fn execute_pre(
&self,
context: &HookContext<'_>,
thread: &mut EmulationThread,
) -> Option<PreHookResult>
pub fn execute_pre( &self, context: &HookContext<'_>, thread: &mut EmulationThread, ) -> Option<PreHookResult>
Executes the pre-hook if present.
§Returns
Some(result) if a pre-hook is registered, None otherwise.
pub fn execute_post(
&self,
context: &HookContext<'_>,
thread: &mut EmulationThread,
result: Option<&EmValue>,
) -> Option<PostHookResult>
pub fn execute_post( &self, context: &HookContext<'_>, thread: &mut EmulationThread, result: Option<&EmValue>, ) -> Option<PostHookResult>
Executes the post-hook if present.
§Returns
Some(result) if a post-hook is registered, None otherwise.
pub fn has_pre_hook(&self) -> bool
pub fn has_pre_hook(&self) -> bool
Returns true if this hook has a pre-hook handler.
pub fn has_post_hook(&self) -> bool
pub fn has_post_hook(&self) -> bool
Returns true if this hook has a post-hook handler.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for Hook
impl !RefUnwindSafe for Hook
impl Send for Hook
impl Sync for Hook
impl Unpin for Hook
impl !UnwindSafe for Hook
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more