Skip to main content

Hook

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

pub fn new(name: impl Into<String>) -> Self

Creates a new hook with the given name.

The name is used for debugging and logging. It should be descriptive of what the hook does.

§Arguments
  • name - A descriptive name for the hook
§Examples
use dotscope::emulation::Hook;

let hook = Hook::new("string-concat-interceptor");

pub fn name(&self) -> &str

Returns the hook’s name.

pub fn priority(&self) -> HookPriority

Returns the hook’s priority.

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

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

Adds a name-based matcher for namespace, type, and method.

All three components must match exactly.

§Arguments
  • namespace - The namespace to match
  • type_name - The type name to match
  • method_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

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

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

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

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

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

Adds a signature matcher for parameter and return types.

§Arguments
  • params - The expected parameter types
  • return_type - The expected return type (or None for 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
where F: Fn(&HookContext<'_>, &EmulationThread) -> bool + Send + Sync + 'static,

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 debugging
  • predicate - Function that returns true if 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
where F: Fn(&HookContext<'_>, &mut EmulationThread) -> PreHookResult + Send + Sync + 'static,

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) -> Self
where 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

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>

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>

Executes the post-hook if present.

§Returns

Some(result) if a post-hook is registered, None otherwise.

pub fn has_pre_hook(&self) -> bool

Returns true if this hook has a pre-hook handler.

pub fn has_post_hook(&self) -> bool

Returns true if this hook has a post-hook handler.

Trait Implementations§

§

impl Debug for Hook

§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

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> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, A> IntoAst<A> for T
where T: Into<A>, A: Ast,

Source§

fn into_ast(self, _a: &A) -> A

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.