616 lines
22 KiB
Rust
616 lines
22 KiB
Rust
//! Hook detection for identifying SetWindowsHookEx and inline hook-based injection.
|
|
//!
|
|
//! This module detects Windows message hooks and inline API hooks that are commonly
|
|
//! used for process injection (T1055.003, T1055.012).
|
|
//! On Linux, it detects LD_PRELOAD and LD_LIBRARY_PATH based injection.
|
|
|
|
use crate::{GhostError, Result};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashMap;
|
|
|
|
/// Type of hook detected.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum HookType {
|
|
/// SetWindowsHookEx hook (message hook).
|
|
WindowsHook(u32),
|
|
/// Inline/detour hook (JMP patch).
|
|
InlineHook,
|
|
/// Import Address Table (IAT) hook.
|
|
IATHook,
|
|
/// Export Address Table (EAT) hook.
|
|
EATHook,
|
|
/// LD_PRELOAD based library injection (Linux).
|
|
LdPreload,
|
|
/// LD_LIBRARY_PATH manipulation (Linux).
|
|
LdLibraryPath,
|
|
/// Ptrace-based injection (Linux).
|
|
PtraceInjection,
|
|
}
|
|
|
|
impl std::fmt::Display for HookType {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::WindowsHook(id) => write!(f, "WindowsHook({})", id),
|
|
Self::InlineHook => write!(f, "InlineHook"),
|
|
Self::IATHook => write!(f, "IATHook"),
|
|
Self::EATHook => write!(f, "EATHook"),
|
|
Self::LdPreload => write!(f, "LD_PRELOAD"),
|
|
Self::LdLibraryPath => write!(f, "LD_LIBRARY_PATH"),
|
|
Self::PtraceInjection => write!(f, "PtraceInjection"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Information about a detected hook.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct HookInfo {
|
|
/// Type of hook.
|
|
pub hook_type: HookType,
|
|
/// Thread ID (for message hooks) or 0 for system-wide.
|
|
pub thread_id: u32,
|
|
/// Address of the hook procedure.
|
|
pub hook_proc: usize,
|
|
/// Original address (for inline/IAT hooks).
|
|
pub original_address: usize,
|
|
/// Module containing the hook procedure.
|
|
pub module_name: String,
|
|
/// Function being hooked (for inline/IAT hooks).
|
|
pub hooked_function: String,
|
|
}
|
|
|
|
/// Result of hook detection analysis.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct HookDetectionResult {
|
|
/// List of detected hooks.
|
|
pub hooks: Vec<HookInfo>,
|
|
/// Number of suspicious hooks.
|
|
pub suspicious_count: usize,
|
|
/// Number of global/system-wide hooks.
|
|
pub global_hooks: usize,
|
|
/// Number of inline API hooks detected.
|
|
pub inline_hooks: usize,
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
mod platform {
|
|
use super::{HookDetectionResult, HookInfo, HookType};
|
|
use crate::{GhostError, Result};
|
|
use std::collections::HashMap;
|
|
use windows::Win32::Foundation::CloseHandle;
|
|
use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory;
|
|
use windows::Win32::System::LibraryLoader::{
|
|
GetModuleHandleW, GetProcAddress, LoadLibraryW,
|
|
};
|
|
use windows::Win32::System::ProcessStatus::{
|
|
EnumProcessModulesEx, GetModuleBaseNameW, GetModuleInformation, LIST_MODULES_ALL,
|
|
MODULEINFO,
|
|
};
|
|
use windows::Win32::System::Threading::{OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ};
|
|
use windows::Win32::UI::WindowsAndMessaging::{
|
|
WH_CALLWNDPROC, WH_CALLWNDPROCRET, WH_CBT, WH_DEBUG, WH_FOREGROUNDIDLE, WH_GETMESSAGE,
|
|
WH_JOURNALPLAYBACK, WH_JOURNALRECORD, WH_KEYBOARD, WH_KEYBOARD_LL, WH_MOUSE,
|
|
WH_MOUSE_LL, WH_MSGFILTER, WH_SHELL, WH_SYSMSGFILTER,
|
|
};
|
|
|
|
/// Critical APIs commonly hooked for injection.
|
|
const CRITICAL_APIS: &[(&str, &str)] = &[
|
|
("ntdll.dll", "NtCreateThread"),
|
|
("ntdll.dll", "NtCreateThreadEx"),
|
|
("ntdll.dll", "NtAllocateVirtualMemory"),
|
|
("ntdll.dll", "NtWriteVirtualMemory"),
|
|
("ntdll.dll", "NtProtectVirtualMemory"),
|
|
("ntdll.dll", "NtQueueApcThread"),
|
|
("kernel32.dll", "VirtualAllocEx"),
|
|
("kernel32.dll", "WriteProcessMemory"),
|
|
("kernel32.dll", "CreateRemoteThread"),
|
|
("kernel32.dll", "LoadLibraryA"),
|
|
("kernel32.dll", "LoadLibraryW"),
|
|
("user32.dll", "SetWindowsHookExA"),
|
|
("user32.dll", "SetWindowsHookExW"),
|
|
];
|
|
|
|
/// Detect Windows hook-based injection techniques.
|
|
pub fn detect_hook_injection(target_pid: u32) -> Result<HookDetectionResult> {
|
|
let mut hooks = Vec::new();
|
|
let mut suspicious_count = 0;
|
|
let mut global_hooks = 0;
|
|
let mut inline_hooks = 0;
|
|
|
|
// Detect inline hooks in critical APIs
|
|
match detect_inline_hooks(target_pid) {
|
|
Ok(inline) => {
|
|
inline_hooks = inline.len();
|
|
for hook in inline {
|
|
if is_suspicious_inline_hook(&hook) {
|
|
suspicious_count += 1;
|
|
}
|
|
hooks.push(hook);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
log::debug!("Failed to detect inline hooks: {}", e);
|
|
}
|
|
}
|
|
|
|
// Estimate global hooks based on system state
|
|
global_hooks = estimate_global_hooks();
|
|
if global_hooks > 10 {
|
|
suspicious_count += 1;
|
|
}
|
|
|
|
Ok(HookDetectionResult {
|
|
hooks,
|
|
suspicious_count,
|
|
global_hooks,
|
|
inline_hooks,
|
|
})
|
|
}
|
|
|
|
/// Detect inline (detour) hooks by checking for JMP instructions at API entry points.
|
|
fn detect_inline_hooks(target_pid: u32) -> Result<Vec<HookInfo>> {
|
|
let mut hooks = Vec::new();
|
|
|
|
unsafe {
|
|
let handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, target_pid)
|
|
.map_err(|e| GhostError::Process {
|
|
message: format!("Failed to open process: {}", e),
|
|
})?;
|
|
|
|
// Get loaded modules in target process
|
|
let mut modules = [windows::Win32::Foundation::HMODULE::default(); 1024];
|
|
let mut cb_needed = 0u32;
|
|
|
|
let result = EnumProcessModulesEx(
|
|
handle,
|
|
modules.as_mut_ptr(),
|
|
(modules.len() * std::mem::size_of::<windows::Win32::Foundation::HMODULE>()) as u32,
|
|
&mut cb_needed,
|
|
LIST_MODULES_ALL,
|
|
);
|
|
|
|
if result.is_err() {
|
|
let _ = CloseHandle(handle);
|
|
return Err(GhostError::Process {
|
|
message: "Failed to enumerate process modules".to_string(),
|
|
});
|
|
}
|
|
|
|
let module_count =
|
|
(cb_needed as usize) / std::mem::size_of::<windows::Win32::Foundation::HMODULE>();
|
|
|
|
// Check each critical API for hooks
|
|
for (module_name, func_name) in CRITICAL_APIS {
|
|
// Find the module in target process
|
|
for i in 0..module_count {
|
|
let mut name_buffer = [0u16; 256];
|
|
if GetModuleBaseNameW(handle, modules[i], &mut name_buffer) == 0 {
|
|
continue;
|
|
}
|
|
|
|
let mod_name = String::from_utf16_lossy(
|
|
&name_buffer[..name_buffer
|
|
.iter()
|
|
.position(|&c| c == 0)
|
|
.unwrap_or(name_buffer.len())],
|
|
)
|
|
.to_lowercase();
|
|
|
|
if !mod_name.contains(&module_name.to_lowercase().replace(".dll", "")) {
|
|
continue;
|
|
}
|
|
|
|
// Get module info
|
|
let mut mod_info = MODULEINFO::default();
|
|
if GetModuleInformation(
|
|
handle,
|
|
modules[i],
|
|
&mut mod_info,
|
|
std::mem::size_of::<MODULEINFO>() as u32,
|
|
)
|
|
.is_err()
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Get function address from our process (assume same base address)
|
|
let local_module = match GetModuleHandleW(windows::core::PCWSTR::from_raw(
|
|
module_name
|
|
.encode_utf16()
|
|
.chain(std::iter::once(0))
|
|
.collect::<Vec<_>>()
|
|
.as_ptr(),
|
|
)) {
|
|
Ok(h) => h,
|
|
Err(_) => continue,
|
|
};
|
|
|
|
let func_addr = match GetProcAddress(
|
|
local_module,
|
|
windows::core::PCSTR::from_raw(
|
|
std::ffi::CString::new(*func_name)
|
|
.unwrap()
|
|
.as_bytes_with_nul()
|
|
.as_ptr(),
|
|
),
|
|
) {
|
|
Some(addr) => addr as usize,
|
|
None => continue,
|
|
};
|
|
|
|
// Calculate offset from module base
|
|
let offset = func_addr - local_module.0 as usize;
|
|
let target_func_addr = mod_info.lpBaseOfDll as usize + offset;
|
|
|
|
// Read first bytes of function in target process
|
|
let mut buffer = [0u8; 16];
|
|
let mut bytes_read = 0usize;
|
|
|
|
if ReadProcessMemory(
|
|
handle,
|
|
target_func_addr as *const _,
|
|
buffer.as_mut_ptr() as *mut _,
|
|
buffer.len(),
|
|
Some(&mut bytes_read),
|
|
)
|
|
.is_ok()
|
|
&& bytes_read >= 5
|
|
{
|
|
// Check for common hook patterns
|
|
if let Some(hook) = detect_hook_pattern(&buffer, target_func_addr) {
|
|
hooks.push(HookInfo {
|
|
hook_type: HookType::InlineHook,
|
|
thread_id: 0,
|
|
hook_proc: hook,
|
|
original_address: target_func_addr,
|
|
module_name: module_name.to_string(),
|
|
hooked_function: func_name.to_string(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
let _ = CloseHandle(handle);
|
|
}
|
|
|
|
Ok(hooks)
|
|
}
|
|
|
|
/// Detect common hook patterns in function prologue.
|
|
fn detect_hook_pattern(bytes: &[u8], base_addr: usize) -> Option<usize> {
|
|
if bytes.len() < 5 {
|
|
return None;
|
|
}
|
|
|
|
// JMP rel32 (E9 xx xx xx xx)
|
|
if bytes[0] == 0xE9 {
|
|
let offset = i32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]);
|
|
let target = (base_addr as i64 + 5 + offset as i64) as usize;
|
|
return Some(target);
|
|
}
|
|
|
|
// JMP [rip+disp32] (FF 25 xx xx xx xx) - 64-bit
|
|
if bytes.len() >= 6 && bytes[0] == 0xFF && bytes[1] == 0x25 {
|
|
// This is an indirect jump, would need to read the target address
|
|
return Some(0xFFFFFFFF); // Indicate hook detected but target unknown
|
|
}
|
|
|
|
// MOV RAX, imm64; JMP RAX (48 B8 ... FF E0)
|
|
if bytes.len() >= 12
|
|
&& bytes[0] == 0x48
|
|
&& bytes[1] == 0xB8
|
|
&& bytes[10] == 0xFF
|
|
&& bytes[11] == 0xE0
|
|
{
|
|
let target = u64::from_le_bytes([
|
|
bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9],
|
|
]) as usize;
|
|
return Some(target);
|
|
}
|
|
|
|
// PUSH imm32; RET (68 xx xx xx xx C3) - 32-bit style
|
|
if bytes.len() >= 6 && bytes[0] == 0x68 && bytes[5] == 0xC3 {
|
|
let target = u32::from_le_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]) as usize;
|
|
return Some(target);
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn is_suspicious_inline_hook(hook: &HookInfo) -> bool {
|
|
// All inline hooks are suspicious in security context
|
|
matches!(hook.hook_type, HookType::InlineHook | HookType::IATHook)
|
|
}
|
|
|
|
fn estimate_global_hooks() -> usize {
|
|
// In a full implementation, this would enumerate the global hook chain
|
|
// by parsing USER32.dll's internal structures.
|
|
// Return typical value for now.
|
|
3
|
|
}
|
|
|
|
/// Get hook type name for display.
|
|
pub fn get_hook_type_name(hook_type: u32) -> &'static str {
|
|
match hook_type {
|
|
t if t == WH_CALLWNDPROC.0 => "WH_CALLWNDPROC",
|
|
t if t == WH_CALLWNDPROCRET.0 => "WH_CALLWNDPROCRET",
|
|
t if t == WH_CBT.0 => "WH_CBT",
|
|
t if t == WH_DEBUG.0 => "WH_DEBUG",
|
|
t if t == WH_FOREGROUNDIDLE.0 => "WH_FOREGROUNDIDLE",
|
|
t if t == WH_GETMESSAGE.0 => "WH_GETMESSAGE",
|
|
t if t == WH_JOURNALPLAYBACK.0 => "WH_JOURNALPLAYBACK",
|
|
t if t == WH_JOURNALRECORD.0 => "WH_JOURNALRECORD",
|
|
t if t == WH_KEYBOARD.0 => "WH_KEYBOARD",
|
|
t if t == WH_KEYBOARD_LL.0 => "WH_KEYBOARD_LL",
|
|
t if t == WH_MOUSE.0 => "WH_MOUSE",
|
|
t if t == WH_MOUSE_LL.0 => "WH_MOUSE_LL",
|
|
t if t == WH_MSGFILTER.0 => "WH_MSGFILTER",
|
|
t if t == WH_SHELL.0 => "WH_SHELL",
|
|
t if t == WH_SYSMSGFILTER.0 => "WH_SYSMSGFILTER",
|
|
_ => "UNKNOWN",
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
mod platform {
|
|
use super::{HookDetectionResult, HookInfo, HookType};
|
|
use crate::{GhostError, Result};
|
|
use std::fs;
|
|
use std::path::Path;
|
|
|
|
/// Detect hook injection on Linux (LD_PRELOAD, LD_LIBRARY_PATH, ptrace).
|
|
pub fn detect_hook_injection(target_pid: u32) -> Result<HookDetectionResult> {
|
|
let mut hooks = Vec::new();
|
|
let mut suspicious_count = 0;
|
|
|
|
// Check for LD_PRELOAD in process environment
|
|
if let Ok(ld_preload_hooks) = detect_ld_preload(target_pid) {
|
|
suspicious_count += ld_preload_hooks.len();
|
|
hooks.extend(ld_preload_hooks);
|
|
}
|
|
|
|
// Check for LD_LIBRARY_PATH manipulation
|
|
if let Ok(ld_library_path_hooks) = detect_ld_library_path(target_pid) {
|
|
suspicious_count += ld_library_path_hooks.len();
|
|
hooks.extend(ld_library_path_hooks);
|
|
}
|
|
|
|
// Check for ptrace attachment
|
|
if let Ok(ptrace_detected) = detect_ptrace_attachment(target_pid) {
|
|
if ptrace_detected {
|
|
suspicious_count += 1;
|
|
hooks.push(HookInfo {
|
|
hook_type: HookType::PtraceInjection,
|
|
thread_id: 0,
|
|
hook_proc: 0,
|
|
original_address: 0,
|
|
module_name: "ptrace".to_string(),
|
|
hooked_function: "process_vm_writev/ptrace".to_string(),
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check loaded libraries for suspicious patterns
|
|
if let Ok(suspicious_libs) = detect_suspicious_libraries(target_pid) {
|
|
hooks.extend(suspicious_libs);
|
|
}
|
|
|
|
Ok(HookDetectionResult {
|
|
hooks,
|
|
suspicious_count,
|
|
global_hooks: 0,
|
|
inline_hooks: 0,
|
|
})
|
|
}
|
|
|
|
/// Detect LD_PRELOAD environment variable in process.
|
|
fn detect_ld_preload(pid: u32) -> Result<Vec<HookInfo>> {
|
|
let environ_path = format!("/proc/{}/environ", pid);
|
|
let environ_content = fs::read_to_string(&environ_path).map_err(|e| {
|
|
GhostError::Process {
|
|
message: format!("Failed to read process environment: {}", e),
|
|
}
|
|
})?;
|
|
|
|
let mut hooks = Vec::new();
|
|
|
|
// Environment variables are null-separated
|
|
for env_var in environ_content.split('\0') {
|
|
if env_var.starts_with("LD_PRELOAD=") {
|
|
let libraries = env_var.strip_prefix("LD_PRELOAD=").unwrap_or("");
|
|
|
|
// Multiple libraries can be separated by spaces or colons
|
|
for lib in libraries.split(&[' ', ':'][..]) {
|
|
if !lib.is_empty() {
|
|
hooks.push(HookInfo {
|
|
hook_type: HookType::LdPreload,
|
|
thread_id: 0,
|
|
hook_proc: 0,
|
|
original_address: 0,
|
|
module_name: lib.to_string(),
|
|
hooked_function: "LD_PRELOAD".to_string(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(hooks)
|
|
}
|
|
|
|
/// Detect LD_LIBRARY_PATH environment variable manipulation.
|
|
fn detect_ld_library_path(pid: u32) -> Result<Vec<HookInfo>> {
|
|
let environ_path = format!("/proc/{}/environ", pid);
|
|
let environ_content = fs::read_to_string(&environ_path).map_err(|e| {
|
|
GhostError::Process {
|
|
message: format!("Failed to read process environment: {}", e),
|
|
}
|
|
})?;
|
|
|
|
let mut hooks = Vec::new();
|
|
|
|
for env_var in environ_content.split('\0') {
|
|
if env_var.starts_with("LD_LIBRARY_PATH=") {
|
|
let paths = env_var.strip_prefix("LD_LIBRARY_PATH=").unwrap_or("");
|
|
|
|
// Check for suspicious paths
|
|
for path in paths.split(':') {
|
|
if is_suspicious_library_path(path) {
|
|
hooks.push(HookInfo {
|
|
hook_type: HookType::LdLibraryPath,
|
|
thread_id: 0,
|
|
hook_proc: 0,
|
|
original_address: 0,
|
|
module_name: path.to_string(),
|
|
hooked_function: "LD_LIBRARY_PATH".to_string(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(hooks)
|
|
}
|
|
|
|
/// Check if a library path is suspicious.
|
|
fn is_suspicious_library_path(path: &str) -> bool {
|
|
// Suspicious patterns
|
|
let suspicious_patterns = [
|
|
"/tmp/",
|
|
"/dev/shm/",
|
|
"/var/tmp/",
|
|
".",
|
|
"..",
|
|
"/home/",
|
|
];
|
|
|
|
suspicious_patterns.iter().any(|&pattern| path.contains(pattern))
|
|
}
|
|
|
|
/// Detect ptrace attachment (debugging/injection).
|
|
fn detect_ptrace_attachment(pid: u32) -> Result<bool> {
|
|
let status_path = format!("/proc/{}/status", pid);
|
|
let status_content = fs::read_to_string(&status_path).map_err(|e| {
|
|
GhostError::Process {
|
|
message: format!("Failed to read process status: {}", e),
|
|
}
|
|
})?;
|
|
|
|
// Look for TracerPid field
|
|
for line in status_content.lines() {
|
|
if line.starts_with("TracerPid:") {
|
|
let tracer_pid = line
|
|
.split_whitespace()
|
|
.nth(1)
|
|
.and_then(|s| s.parse::<u32>().ok())
|
|
.unwrap_or(0);
|
|
|
|
// Non-zero TracerPid means the process is being traced
|
|
if tracer_pid != 0 {
|
|
return Ok(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(false)
|
|
}
|
|
|
|
/// Detect suspicious loaded libraries.
|
|
fn detect_suspicious_libraries(pid: u32) -> Result<Vec<HookInfo>> {
|
|
let maps_path = format!("/proc/{}/maps", pid);
|
|
let maps_content = fs::read_to_string(&maps_path).map_err(|e| {
|
|
GhostError::Process {
|
|
message: format!("Failed to read process maps: {}", e),
|
|
}
|
|
})?;
|
|
|
|
let mut hooks = Vec::new();
|
|
let mut seen_libraries = std::collections::HashSet::new();
|
|
|
|
for line in maps_content.lines() {
|
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
|
if parts.len() < 6 {
|
|
continue;
|
|
}
|
|
|
|
let pathname = parts[5..].join(" ");
|
|
|
|
// Check if it's a shared library
|
|
if pathname.ends_with(".so") || pathname.contains(".so.") {
|
|
// Skip if already seen
|
|
if !seen_libraries.insert(pathname.clone()) {
|
|
continue;
|
|
}
|
|
|
|
// Check for suspicious library locations
|
|
if is_suspicious_library(&pathname) {
|
|
hooks.push(HookInfo {
|
|
hook_type: HookType::InlineHook, // Generic classification
|
|
thread_id: 0,
|
|
hook_proc: 0,
|
|
original_address: 0,
|
|
module_name: pathname.clone(),
|
|
hooked_function: "suspicious_library".to_string(),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(hooks)
|
|
}
|
|
|
|
/// Check if a library path is suspicious.
|
|
fn is_suspicious_library(path: &str) -> bool {
|
|
// Libraries in these locations are often used for injection
|
|
let suspicious_locations = [
|
|
"/tmp/",
|
|
"/dev/shm/",
|
|
"/var/tmp/",
|
|
"/home/",
|
|
];
|
|
|
|
// Check if library is in a suspicious location
|
|
if suspicious_locations.iter().any(|&loc| path.starts_with(loc)) {
|
|
return true;
|
|
}
|
|
|
|
// Check for libraries with suspicious names
|
|
let suspicious_names = [
|
|
"inject",
|
|
"hook",
|
|
"cheat",
|
|
"hack",
|
|
"rootkit",
|
|
];
|
|
|
|
let path_lower = path.to_lowercase();
|
|
suspicious_names.iter().any(|&name| path_lower.contains(name))
|
|
}
|
|
|
|
pub fn get_hook_type_name(_hook_type: u32) -> &'static str {
|
|
"LINUX_HOOK"
|
|
}
|
|
}
|
|
|
|
#[cfg(not(any(windows, target_os = "linux")))]
|
|
mod platform {
|
|
use super::HookDetectionResult;
|
|
use crate::{GhostError, Result};
|
|
|
|
pub fn detect_hook_injection(_target_pid: u32) -> Result<HookDetectionResult> {
|
|
// Hook detection is not implemented for this platform
|
|
Ok(HookDetectionResult {
|
|
hooks: Vec::new(),
|
|
suspicious_count: 0,
|
|
global_hooks: 0,
|
|
inline_hooks: 0,
|
|
})
|
|
}
|
|
|
|
pub fn get_hook_type_name(_hook_type: u32) -> &'static str {
|
|
"UNSUPPORTED"
|
|
}
|
|
}
|
|
|
|
pub use platform::{detect_hook_injection, get_hook_type_name}; |