anti_debug/
lib.rs

1#![doc = include_str!("../README.md")]
2#![warn(missing_docs)]
3
4/// Checks if a debugger is currently attached to the process.
5///
6/// This function performs platform-specific checks to detect
7/// whether a debugger is actively attached to the current process.
8///
9/// # Platform-specific Behavior
10///
11/// - **Windows**: Uses `IsDebuggerPresent`.
12///   When the `deep-detect` feature is enabled, additionally checks
13///   `CheckRemoteDebuggerPresent` and `NtQueryInformationProcess`.
14/// - **Linux/Android**: Checks the `TracerPid` field in `/proc/self/status`.
15/// - **macOS**: Uses `proc_pidinfo` to retrieve `proc_bsdinfo` and checks the `pbi_flags` field.
16/// - **Other platforms**: Compilation error.
17///
18/// # Return Value
19///
20/// Returns `Ok(true)` if a debugger is detected, `Ok(false)` if no debugger is present,
21/// or `Err(std::io::Error)` if the check could not be performed due to a system error.
22///
23/// # Examples
24///
25/// ```rust
26/// # fn main() {
27/// match anti_debug::is_debugger_present() {
28///     Ok(true) => println!("Debugger detected!"),
29///     Ok(false) => println!("No debugger present"),
30///     Err(e) => println!("Error checking for debugger: {}", e),
31/// }
32/// # }
33/// ```
34///
35/// # Notes
36///
37/// - This detection can be bypassed by skilled attackers using advanced anti-anti-debugging techniques
38/// - Some debuggers may not be detected depending on their attachment method
39/// - The check is performed at the moment the function is called and may not reflect
40///   subsequent attachment/detachment of debuggers
41pub fn is_debugger_present() -> Result<bool, std::io::Error> {
42    #[cfg(target_os = "windows")] {
43        // Check with `IsDebuggerPresent`.
44        unsafe {
45            let result = windows_sys::Win32::System::Diagnostics::Debug::IsDebuggerPresent();
46            if result != windows_sys::Win32::Foundation::FALSE {
47                return Ok(true);
48            }
49        }
50        // Check with `CheckRemoteDebuggerPresent`.
51        #[cfg(feature = "deep-detect")]
52        unsafe {
53            let mut p_debugger_present = windows_sys::Win32::Foundation::FALSE;
54            let result = windows_sys::Win32::System::Diagnostics::Debug::CheckRemoteDebuggerPresent(
55                windows_sys::Win32::System::Threading::GetCurrentProcess(),
56                &mut p_debugger_present,
57            );
58            if result == windows_sys::Win32::Foundation::FALSE {
59                return Err(std::io::Error::last_os_error());
60            }
61            if p_debugger_present != windows_sys::Win32::Foundation::FALSE {
62                return Ok(true);
63            }
64        }
65        // Check with `NtQueryInformationProcess`.
66        #[cfg(feature = "deep-detect")]
67        unsafe {
68            let mut p_debug_port = 0i32;
69            let result = windows_sys::Wdk::System::Threading::NtQueryInformationProcess(
70                windows_sys::Win32::System::Threading::GetCurrentProcess(),
71                windows_sys::Wdk::System::Threading::ProcessDebugPort,
72                &mut p_debug_port as *mut _ as _,
73                size_of::<i32>() as _,
74                &mut 0,
75            );
76            let result = windows_sys::Win32::Foundation::RtlNtStatusToDosError(result);
77            if result != 0 {
78                return Err(std::io::Error::from_raw_os_error(result as _));
79            }
80            if p_debug_port != 0 {
81                return Ok(true);
82            }
83        }
84        Ok(false)
85    }
86    #[cfg(any(target_os = "linux", target_os = "android"))] {
87        // Check with `/proc/self/status`.
88        {
89            let proc = std::fs::read_to_string("/proc/self/status")?;
90            let pid = proc
91                .lines()
92                .filter_map(|line| line.strip_prefix("TracerPid:"))
93                .filter_map(|pid| pid.trim().parse::<i32>().ok())
94                .next()
95                .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "invalid pid format"))?;
96            if pid != 0 {
97                return Ok(true);
98            }
99        }
100        Ok(false)
101    }
102    #[cfg(target_os = "macos")] {
103        // Check with `proc_pidinfo`.
104        {
105            let pid = std::process::id() as i32;
106            let result = libproc::proc_pid::pidinfo::<libproc::bsd_info::BSDInfo>(pid, 0);
107            let proc_bsdinfo = match result {
108                Ok(proc_bsdinfo) => proc_bsdinfo,
109                Err(_message) => return Err(std::io::Error::last_os_error()),
110            };
111            const PROC_FLAG_TRACED: u32 = 2; // use libproc::osx_libproc_bindings::PROC_FLAG_TRACED;
112            if proc_bsdinfo.pbi_flags & PROC_FLAG_TRACED != 0 { return Ok(true); }
113        }
114        Ok(false)
115    }
116    #[cfg(not(any(
117        target_os = "windows",
118        target_os = "linux",
119        target_os = "android",
120        target_os = "macos",
121    )))]
122    compile_error!("Anti-Debug doesn't support current platform.")
123}
124
125/// Attempts to prevent debuggers from attaching to the current process.
126///
127/// This function performs platform-specific operations to prevent debuggers
128/// from attaching to the current process.
129///
130/// # Platform-specific Behavior
131///
132/// - **Windows/Linux/Android**: There is no way to prevent the debugger from attaching in the future.
133///   Checks if a debugger is currently attached using [`is_debugger_present`].
134///   If a debugger is detected, returns an error.
135/// - **macOS**: Uses `ptrace` with the `PT_DENY_ATTACH` flag.
136/// - **Other platforms**: Compilation error.
137///
138/// # Return Value
139///
140/// - Returns `Ok(())` if:
141///   - On Windows/Linux/Android: No debugger is currently attached.
142///   - On macOS: The `ptrace(PT_DENY_ATTACH)` call succeeded.
143/// - Returns `Err(std::io::Error)` if:
144///   - On Windows/Linux/Android: A debugger is currently attached.
145///   - On macOS: The `ptrace` system call failed.
146///   - Any platform-specific system call fails.
147///
148/// # Examples
149///
150/// ```rust
151/// # fn main() {
152/// if let Err(e) = anti_debug::deny_attach() {
153///     println!("Debugger protection failed: {}", e);
154/// }
155/// # }
156/// ```
157///
158/// # Notes
159///
160/// - This detection can be bypassed by skilled attackers using advanced anti-anti-debugging techniques
161/// - Some debuggers may not be detected depending on their attachment method
162/// - On Windows/Linux/Android, this is a detection-based approach. i.e. passive detection
163pub fn deny_attach() -> Result<(), std::io::Error> {
164    #[cfg(any(target_os = "windows", target_os = "linux", target_os = "android"))] {
165        if is_debugger_present()? {
166            return Err(std::io::Error::new(std::io::ErrorKind::AlreadyExists, "Debugger present."));
167        }
168        Ok(())
169    }
170    #[cfg(target_os = "macos")] {
171        // Deny with `ptrace`.
172        unsafe {
173            let result = libc::ptrace(libc::PT_DENY_ATTACH, 0, std::ptr::null_mut(), 0);
174            if result == -1 { return Err(std::io::Error::last_os_error()); }
175        }
176        Ok(())
177    }
178    #[cfg(not(any(
179        target_os = "windows",
180        target_os = "linux",
181        target_os = "android",
182        target_os = "macos",
183    )))]
184    compile_error!("Anti-Debug doesn't support current platform.")
185}
186
187#[cfg(test)]
188mod tests {
189    #[test]
190    fn test_is_debugger_present() {
191        assert!(!super::is_debugger_present().unwrap_or(false));
192        assert!(!super::is_debugger_present().unwrap_or(false));
193        assert!(!super::is_debugger_present().unwrap_or(false));
194    }
195
196    #[test]
197    fn test_deny_attach() {
198        super::deny_attach().unwrap();
199        super::deny_attach().unwrap();
200        super::deny_attach().unwrap();
201    }
202}