Continued series from the Malware Development for Ethical Hackers Book.
GitHub repo: EricTurner3 – Malware_Development.
Detecting Debugger
IsDebuggerPresent()
/*
Anti-Debugging - Check for Debugger
28 Jan 2025
Eric
To build: x86_64-w64-mingw32-gcc 05_debugger_present.c -o DebugCheck.exe -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main() {
// Check if a debugger is present
if (IsDebuggerPresent()) {
MessageBox(NULL, "New Message", "Nothing to see here", MB_OK);
return 1; // exit if a debugger is present
}
MessageBox(NULL, "Hack", "Hacking mainframe...", MB_OK);
return 0;
}

Sample code using the IsDebuggerPresent()
check. I simplified my version over what was in the book. It is also important to note that I ahve plugins in my x64dbg, including ScyllaHide. ScyllaHide has a bunch of options to fake the flags for debug checks to prevent the application from truly telling if it is being debugged. I had to disable all options for it to properly work:

CheckRemoteDebuggerPresent()
A slight modification to the debug check script to useCheckRemoteDebuggerPresent
API call instead for detecting a debugger:
/*
Anti-Debugging - Check for RemoteDebugger
28 Jan 2025
Eric
To build: x86_64-w64-mingw32-gcc 05_debugger_present_remote.c -o RemoteDebugCheck.exe -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main() {
BOOL HasDebugPort = FALSE;
// Check if a debugger is present
// https://unprotect.it/technique/checkremotedebuggerpresent/
if (CheckRemoteDebuggerPresent(GetCurrentProcess(), &HasDebugPort)) {
MessageBox(NULL, "New Message", "Nothing to see here", MB_OK);
return 1; // exit if a debugger is present
}
MessageBox(NULL, "Hack", "Hacking mainframe...", MB_OK);
return 0;
}
Breakpoint Checksum
This use case did not make a lot of sense to me purely going off of the information provided in the book. It turns out that this code appears to be copied from elsewhere. Here is the same example on Unprotect.it, which links back to the original article from Apriorit.
Essentially, set up our important function that should have integrity and use a stub function to determine the end of that function. We then compile and execute the program to determine the original CRC of our function. Hardcode this value into the source code so that if the code is modified via debugging or a breakpoint, it will fail the checksum and can terminate or escape the main logic.
/*
Anti-Debugging - Breakpoint Detection / Checksum
28 Jan 2025
Eric
To build: x86_64-w64-mingw32-g++ -O2 05_func_checksum.c -o Breakpoint.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive -lpsapi
*/
#include <windows.h>
#include <stdio.h>
DWORD CalcFuncCrc(PUCHAR funcBegin, PUCHAR funcEnd) {
DWORD crc = 0;
for (; funcBegin < funcEnd; ++funcBegin) {
crc += *funcBegin;
}
return crc;
}
// prevent compiler from making functions embedded
#pragma auto_inline(off)
VOID DebuggeeFunction() {
printf("Hello World");
}
VOID DebuggeeFunctionEnd() {}; // stub function trick to detect end of our func we are calculating crc of
#pragma auto_inline(on)
// to calculate this value, the program needs compiled and executed
// monitor the output of the crc in the console, and update the value here
// thus, if the code is modified in anyway
// then the crc no longer will match and it will flag
// one example is a breakpoint, which injects an int 3h / 0xCC opcode into the function
// this would destroy the integrity of the checksum
DWORD g_origCrc = 0x4db;
int main() {
DWORD crc = CalcFuncCrc((PUCHAR)DebuggeeFunction, (PUCHAR)DebuggeeFunctionEnd);
printf("crc: 0x%x (%ld)", crc, crc);
if (g_origCrc != crc) {
MessageBox(NULL, "Breakpoint detected", "Debug Check", MB_OK);
return -1;
}
MessageBox(NULL, "Running as usual", "Debug Check", MB_OK);
return 0;
}
I added my own printf statement into this to illustrate where the CRC comes from, as my CRC is different than the one from the book and the original author. Once this value is displayed, I can add it back to the code to re-compile and now my check shows running as usual.
In order to trigger the breakpoint check, we need to find the actual DebuggeeFunction and place a breakpoint exactly inside of this. I found the original code to be too tricky to find the breakpoint, so I modified it to print a string, and was able to easily find the string reference to place a breakpoint. Now we can see the comparison of running it directly, vs the debugger with the breakpoint:

Flags & Artifacts
NTGlobalFlag
NTGlobalFlag is part of the Process Environment Block. If a debugger is the parent process, additional flags are set, vs if the debugger is attached later on.
/*
Anti-Debugging - Check NTGlobalFlag
28 Jan 2025
Eric
To build: x86_64-w64-mingw32-gcc 05_flag_ntglobal.c -o ntglobal_flag.exe -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc
*/
#include <winternl.h>
#include <intrin.h>
#include <windows.h>
#include <stdio.h>
#define FLG_HEAP_ENABLE_TAIL_CHECK 0x10
#define FLG_HEAP_ENABLE_FREE_CHECK 0x20
#define FLG_HEAP_VALIDATE_PARAMETERS 0x40
#define NT_GLOBAL_FLAG_DEBUGGED (FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS)
DWORD checkNtGlobalFlag() {
PPEB ppeb = (PPEB)__readgsqword(0x60);
DWORD myNtGlobalFlag = *(PDWORD)((PBYTE)ppeb + 0xBC);
MessageBox(NULL, myNtGlobalFlag & NT_GLOBAL_FLAG_DEBUGGED ? "Debugger Active" : "Debugger Inactive", "Debug Check", MB_OK);
return 0;
}
int main(int argc, char* argv[]) {
DWORD check = checkNtGlobalFlag();
return 0;
}
Compiling the code and executing provides the following two scenarios:

Process Debug Flags
There is an undocumented class named ProcessDebugFlags
that when passed to the NtQueryInformationProcess function, returns information on if a debugger is present. See another example here.
/*
Anti-Debugging - Check NTQuery ProcessDebugFlag
28 Jan 2025
Eric
To build: x86_64-w64-mingw32-gcc 05_flag_ntquery.c -o ntquery_flag.exe -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <stdbool.h>
typedef NTSTATUS(NTAPI *fNtQueryInformationProcess)(
IN HANDLE ProcessHandle,
IN DWORD ProcessInformationClass,
OUT PVOID ProcessInformation,
IN ULONG ProcessInformationLength,
OUT PULONG ReturnLength
);
// Function to check if a debugger is present
bool DebuggerCheck() {
BOOL result;
DWORD rProcDebugFlags;
DWORD returned;
const DWORD ProcessDebugFlags = 0x1f; // not documented in below link
HMODULE nt = LoadLibraryA("ntdll.dll");
// https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess
fNtQueryInformationProcess myNtQueryInformationProcess = (fNtQueryInformationProcess)
GetProcAddress(nt, "NtQueryInformationProcess");
myNtQueryInformationProcess(GetCurrentProcess(), ProcessDebugFlags,
&rProcDebugFlags, sizeof(DWORD), &returned);
if (0 == rProcDebugFlags){
MessageBox(NULL, "Debugger Detected", "Program", MB_OK);
return 1; // exit if a debugger is present
}
return result;
}
// Function that simulates the main functionality
void hack() {
MessageBox(NULL, "Malicious Code", "Program", MB_OK);
}
int main() {
// Check if a debugger is present
DebuggerCheck();
// Main functionality
hack();
return 0;
}
I modified the code a bit to be more inline with the other example from Checkpoint. It presents like the prior example, as follows:

Summary
A very interesting chapter into several anti-debugging techniques. I had to do some searching on external resources as some of the book content did not feel very well explained to me. However, it still included some very interesting techniques. From the profile information in ScyllaHide in my x64dbg, it appears all of these techniques can easily be masked in order to allow the application to continue without needing to manually step through the code and disable any debugger checks.