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}