Anti Debugging Techniques
More often than times your dumb executable will be flagged and obviously there would be times where you have to check the behavior of a weird executable. But there are enormous different ways a malware can identify its being debugged. I’ll discuss a few here, then I’ll just keep updating the links to different methods at the end.
1. Read BeingDebugged
BeingDebugged This is the simplest of all. There is a BeingDebugged structure in PEB which we can read to check if we are being debugged or not. Its set to 0x00 by default but if a program is being debugged, then it will be updated to 0x01.
Here’s a short program in rust to read what we want.
pub unsafe fn __readgsqword(diff: u32) -> i32 {
let out: i32;
asm!(
"mov {}, gs:[{:e}]",
lateout(reg) out,
in(reg) diff,
options(nostack, pure, readonly),
);
out
}
unsafe fn is_dbg(ppeb: usize) -> bool {
let peb = ppeb as *const u8;
*peb.offset(2) != 0
}
fn main() {
unsafe {
let peb = __readgsqword(OFFSET);
println!("PEB: {:#x}", peb);
let t = is_dbg(peb as usize);
println!("Debugging: {}", t);
}
}Below is the screenshot of me running the same program from VSCode & x64dbg attached as well, we can see that the debugging is true in the case of x64dbg.

2. Hardware Breakpoint
Whenever a hardware breakpoint is set, any of the DR[0-3] registers are updated. These are thread specific registers and we can read them using the GetThreadContext.
There are a total of 6 DR registers, the ones in 0-3 are responsible for storing the address of the breakpoint. So basically for a single thread, there can only be 4 h/w breakpoints. I’ll talk about them in some other posts but for now this should suffice
DR0-3 are responsible for storing the linear address of the breakpoint.
DR4-5 are Reserved and generally point to DR6-7 respectively unless the Debug Extension is enabled
DR6 stores the debug status. It contains bits to check if certain events were triggered
DR7 is the control register, responsible for enabling & disabling the breakpoints
3. TLS Callback
Checking for the presence of a debugger in the main function is not the best idea, as this is the first place a reverser will look when viewing a disassembler listing. Checks implemented in main can be erased by NOP instructions thus disarming the protection. If the CRT library is used, the main thread will already have a certain call stack before transfer of control to the main function. Thus a good place to perform a debugger presence check is in the TLS Callback. Callback function will be called before the executable module entry point call. Although it will not save you against seasoned reversers, but it will weed out many schoolchildren who will not understand what happened.
There is a long explanation of the “.CRT$XLY”, I’ll talk about it in another post, for now its enough to know that this will run before entering the actual main function.
4. NtGlobalFlag
The NtGlobalFlag inside PEB is 0 by default. Attaching a debugger doesn’t change its value but if the process was created by a debugger, the following flags will be set:
FLG_HEAP_ENABLE_TAIL_CHECK (0x10)
FLG_HEAP_ENABLE_FREE_CHECK (0x20)
FLG_HEAP_VALIDATE_PARAMETERS (0x40)
5. In-Circuit Exception (ICE / ICEBP)
Intel has an ice (0xF1) undocumented instruction which causes an EXCEPTION_SINGLE_STEP (0x80000004) when executed. The debugger considers this exception as the normal and generated by executing the instruction with the SingleStep bit set in the Flags registers.
6. Kernel Debugging
The SystemKernelDebuggerInformation (0x23) class returns the value of KdDebuggerEnabled in al which is 0 by default unless the user allows for kernel debugging (through bcdedit,etc), and KdDebuggerNotPresent in ah which is 1 by default unless a kernel debugger is present.
Another way is to directly check the KUSER_SHARED_DATA structure which has a constant address and doesn’t seem to change regardless of the different versions of windows.
7. Average Tick Counts
Tick count is simply the number of milliseconds that have passed since the system was started. If we calculate the difference b/w the tick count and the difference seem suspiciously high, then we know that there probably is a debugger present.
8. Heap Flags
The Heap contains two fields Flags & ForceFlags which are affected in the presence of a debugger and by default are set to HEAP_GROWABLE and 0. Also when a process is created by a debugger, then Debug heaps add extra protections like guard bytes, breakpoints on buffer overruns, etc., which slow down performance but help in debugging. We can query the HeapInfo to check its value is set to 2 (normal) or 0 (debug heap).
That’s all for now, these are just some of the few techniques possible. I’ll also add another section in future, just naming different possible anti debugging techniques. Next I’ll talk about self deletion & anti-vm techniques. As usual you can find the code uploaded on my github, I've included a few more techniques there as well so do check that out.
References
Last updated
Was this helpful?