{
	"id": "3310c126-6c85-4618-b140-8a31a1193fcd",
	"created_at": "2026-04-06T01:29:03.9637Z",
	"updated_at": "2026-04-10T13:12:40.567927Z",
	"deleted_at": null,
	"sha1_hash": "cb1823334e0acd6976acb385ee415949f9240e62",
	"title": "Anti Debugging Protection Techniques with Examples",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 339979,
	"plain_text": "Anti Debugging Protection Techniques with Examples\r\nBy alexey.erko@apriorit.com\r\nPublished: 2019-05-23 · Archived: 2026-04-06 00:18:36 UTC\r\nKey takeaways:\r\nAnti-debugging techniques increase the effort and cost of debugging for attackers, protecting\r\nyour software and intellectual property.\r\nCombining multiple anti-debugging techniques makes your software stronger and more resilient.\r\nProcess-level hardening and obfuscation add extra layers of protection on top of standard code-level anti-debugging techniques.\r\nWith expert guidance, anti-debugging measures can be applied effectively  without affecting\r\nperformance.\r\nWhile debuggers are legitimate development tools, hackers can use them to research an application’s logic,\r\nuncover vulnerabilities, and access proprietary algorithms.\r\nThis is where anti-debugging techniques step in. These techniques detect and obstruct unwanted debugging\r\nattempts so that hackers need much more time and expertise  — and many more resources — to analyze or tamper\r\nwith your software.\r\nIn this article, we explore practical anti-debugging methods, from basic checks to advanced protections, and\r\ndemonstrate how experienced reverse engineers attempt to bypass them. You’ll gain insights into how these\r\ntechniques work, when they should be applied, and how they can help you protect your software from\r\nunauthorized analysis and tampering.\r\nThis article will be useful for project and development leaders who want to fortify their software.\r\nBest anti-debugging techniques from Apriorit experts\r\nSoftware developers use anti-debugging techniques to prevent malicious actors from analyzing or modifying a\r\nprogram’s code or data. The main goal of these techniques is to complicate the process as much as possible by\r\ndetecting if a program is being run in a debugger ー a specialized software tool for analyzing and troubleshooting\r\ncode execution. Generally, software engineers use debuggers during their coding workflow to search for errors\r\nand make sure their applications work as intended. However, hackers can also use debuggers to get into your code,\r\nsee how it works, and steal or harm it. Therefore, your team must be ready for such threats and apply anti-debugging measures to protect your software.\r\nIt’s important to understand that anti-debugging techniques are not foolproof. Determined attackers with advanced\r\nskills may still be able to bypass these protections. However, a competent team can use anti-debugging techniques\r\nto significantly increase the time and effort required to reverse engineer or crack software, making your programs\r\nsafe from casual attacks.\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 1 of 35\n\nAt Apriorit, we use a range of proven anti-debugging techniques to make software more resilient against\r\nunauthorized access and tampering. Even though determined attackers may persist, our goal is to make it harder\r\nfor them to compromise software integrity.\r\nWhen choosing which techniques to use, we analyze the project’s specifics and the client’s requirements. You\r\ncould choose one or several techniques, depending on the task you want to solve. Let’s explore the most\r\ncommonly used techniques in detail, showing practical examples for each.\r\n1. Calling the IsDebuggerPresent function\r\nPerhaps the simplest anti-debugging method is calling the IsDebuggerPresent function. This function detects if the\r\ncalling process is being debugged by a user-mode debugger. The code below shows an example of elementary\r\nprotection:\r\nC++\r\nint main()\r\n{\r\n if (IsDebuggerPresent())\r\n {\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n }\r\n return 0;\r\n}\r\nIf we take a look inside the IsDebuggerPresent function, we’ll find the following code:\r\nShellScript\r\n0:000\u003c u kernelbase!IsDebuggerPresent L3\r\nKERNELBASE!IsDebuggerPresent:\r\n751ca8d0 64a130000000 mov eax,dword ptr fs:[00000030h]\r\n751ca8d6 0fb64002 movzx eax,byte ptr [eax+2]\r\n751ca8da c3 ret\r\nFor x64 process:\r\nShellScript\r\n0:000\u003c u kernelbase!IsDebuggerPresent L3\r\nKERNELBASE!IsDebuggerPresent:\r\n00007ffc`ab6c1aa0 65488b042560000000 mov rax,qword ptr gs:[60h]\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 2 of 35\n\n00007ffc`ab6c1aa9 0fb64002 movzx eax,byte ptr [rax+2]\r\n00007ffc`ab6c1aad c3 ret\r\nWe see the PEB (Process Environment Block) structure by the 30h offset relative to the fs segment (the 60h offset\r\nrelative to the gs segment for x64 systems). If we look by the 2 offset in the PEB, we’ll find the BeingDebugged\r\nfield:\r\nShellScript\r\n0:000\u003c dt _PEB\r\nntdll!_PEB\r\n +0x000 InheritedAddressSpace : UChar\r\n +0x001 ReadImageFileExecOptions : UChar\r\n +0x002 BeingDebugged : UChar\r\nIn other words, the IsDebuggerPresent function reads the value of the BeingDebugged field. If the process is\r\nbeing debugged, the value is 1 if not, it’s 0.\r\nHow to bypass the IsDebuggerPresent check\r\nTo bypass the IsDebuggerPresent check, set BeingDebugged to 0 before the checking code is executed. DLL\r\ninjection can be used to do this:\r\nShellScript\r\nmov eax, dword ptr fs:[0x30]\r\nmov byte ptr ds:[eax+2], 0\r\nFor x64 process:\r\nShellScript\r\nDWORD64 dwpeb = __readgsqword(0x60);\r\n*((PBYTE)(dwpeb + 2)) = 0;\r\n2. Inspecting the Process Environment Block (PEB) for debugger flags\r\nPEB is a closed structure used inside the Windows operating system. Depending on the environment, you need to\r\nget the PEB structure pointer in different ways. Below, you can find an example of how to obtain the PEB pointer\r\nfor x32 and x64 systems:\r\nC++\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 3 of 35\n\n// Current PEB for 64bit and 32bit processes accordingly\r\nPVOID GetPEB()\r\n{\r\n#ifdef _WIN64\r\n return (PVOID)__readgsqword(0x0C * sizeof(PVOID));\r\n#else\r\n return (PVOID)__readfsdword(0x0C * sizeof(PVOID));\r\n#endif\r\n}\r\nThe WOW64 mechanism is used for an x32 process started on an x64 system, and another PEB structure is\r\ncreated. Here’s an example of how to obtain the PEB structure pointer in a WOW64 environment:\r\nC++\r\n// Get PEB for WOW64 Process\r\nPVOID GetPEB64()\r\n{\r\n PVOID pPeb = 0;\r\n#ifndef _WIN64\r\n // 1. There are two copies of PEB - PEB64 and PEB32 in WOW64 process\r\n // 2. PEB64 follows after PEB32\r\n // 3. This is true for versions lower than Windows 8, else __readfsdword returns address of real PEB64\r\n if (IsWin8OrHigher())\r\n {\r\n BOOL isWow64 = FALSE;\r\n typedef BOOL(WINAPI *pfnIsWow64Process)(HANDLE hProcess, PBOOL isWow64);\r\n pfnIsWow64Process fnIsWow64Process = (pfnIsWow64Process)\r\n GetProcAddress(GetModuleHandleA(\"Kernel32.dll\"), \"IsWow64Process\");\r\n if (fnIsWow64Process(GetCurrentProcess(), \u0026isWow64))\r\n {\r\n if (isWow64)\r\n {\r\n pPeb = (PVOID)__readfsdword(0x0C * sizeof(PVOID));\r\n pPeb = (PVOID)((PBYTE)pPeb + 0x1000);\r\n }\r\n }\r\n }\r\n#endif\r\n return pPeb;\r\n}\r\nThe code for the function to check the operating system version is below:\r\nC++\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 4 of 35\n\nWORD GetVersionWord()\r\n{\r\n OSVERSIONINFO verInfo = { sizeof(OSVERSIONINFO) };\r\n GetVersionEx(\u0026verInfo);\r\n return MAKEWORD(verInfo.dwMinorVersion, verInfo.dwMajorVersion);\r\n}\r\nBOOL IsWin8OrHigher() { return GetVersionWord() \u003e= _WIN32_WINNT_WIN8; }\r\nBOOL IsVistaOrHigher() { return GetVersionWord() \u003e= _WIN32_WINNT_VISTA; }\r\n3. Using TLS callbacks to detect debugger activity\r\nChecking for the presence of a debugger in the main function is not the best idea, as this is the first place a\r\nreverser will look when viewing a disassembler listing. Checks implemented in main can be erased by NOP\r\ninstructions, thus disarming the protection. If the CRT library is used, the main thread will already have a certain\r\ncall stack before transfer of control to the main function. Thus a good place to perform a debugger presence\r\ncheck is in the TLS Callback. Callback function will be called before the executable module entry point call.\r\nC++\r\n#pragma section(\".CRT$XLY\", long, read)\r\n__declspec(thread) int var = 0xDEADBEEF;\r\nVOID NTAnopPI TlsCallback(PVOID DllHandle, DWORD Reason, VOID Reserved)\r\n{\r\n var = 0xB15BADB0; // Required for TLS Callback call\r\n if (IsDebuggerPresent())\r\n {\r\n MessageBoxA(NULL, \"Stop debugging program!\", \"Error\", MB_OK | MB_ICONERROR);\r\n TerminateProcess(GetCurrentProcess(), 0xBABEFACE);\r\n }\r\n}\r\n__declspec(allocate(\".CRT$XLY\"))PIMAGE_TLS_CALLBACK g_tlsCallback = TlsCallback;\r\n4. Checking the NtGlobalFlag for debugging indicators\r\nIn Windows NT, there’s a set of flags that are stored in the global variable NtGlobalFlag , which is common for\r\nthe whole system. At boot, the NtGlobalFlag global system variable is initialized with the value from the system\r\nregistry key:\r\n[HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession ManagerGlobalFlag]\r\nThis variable value is used for system tracing, debugging, and control. The variable flags are undocumented, but\r\nthe SDK includes the gflags utility, which allows you to edit a global flag value. The PEB structure also includes\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 5 of 35\n\nthe NtGlobalFlag field, and its bit structure does not correspond to the NtGlobalFlag global system variable.\r\nDuring debugging, such flags are set in the NtGlobalFlag field:\r\nC++\r\nFLG_HEAP_ENABLE_TAIL_CHECK (0x10)\r\nFLG_HEAP_ENABLE_FREE_CHECK (0x20)\r\nFLG_HEAP_VALIDATE_PARAMETERS (0x40)\r\nTo check if a process has been started with a debugger, check the value of the NtGlobalFlag field in the PEB\r\nstructure. This field is located by the 0x068 and 0x0bc offset for the x32 and x64 systems respectively relative to\r\nthe beginning of the PEB structure.\r\nC++\r\n0:000\u003e dt _PEB NtGlobalFlag @$peb\r\nntdll!_PEB\r\n +0x068 NtGlobalFlag : 0x70\r\nFor x64 process:\r\nC++\r\n0:000\u003e dt _PEB NtGlobalFlag @$peb\r\nntdll!_PEB\r\n +0x0bc NtGlobalFlag : 0x70\r\nThe following piece of code is an example of anti debugging protection based on the NtGlobalFlag flags check:\r\nC++\r\n#define FLG_HEAP_ENABLE_TAIL_CHECK 0x10\r\n#define FLG_HEAP_ENABLE_FREE_CHECK 0x20\r\n#define FLG_HEAP_VALIDATE_PARAMETERS 0x40\r\n#define NT_GLOBAL_FLAG_DEBUGGED (FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PAR\r\nvoid CheckNtGlobalFlag()\r\n{\r\n PVOID pPeb = GetPEB();\r\n PVOID pPeb64 = GetPEB64();\r\n DWORD offsetNtGlobalFlag = 0;\r\n#ifdef _WIN64\r\n offsetNtGlobalFlag = 0xBC;\r\n#else\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 6 of 35\n\noffsetNtGlobalFlag = 0x68;\r\n#endif\r\n DWORD NtGlobalFlag = *(PDWORD)((PBYTE)pPeb + offsetNtGlobalFlag);\r\n if (NtGlobalFlag \u0026 NT_GLOBAL_FLAG_DEBUGGED)\r\n {\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n }\r\n if (pPeb64)\r\n {\r\n DWORD NtGlobalFlagWow64 = *(PDWORD)((PBYTE)pPeb64 + 0xBC);\r\n if (NtGlobalFlagWow64 \u0026 NT_GLOBAL_FLAG_DEBUGGED)\r\n {\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n }\r\n }\r\n}\r\nHow to bypass the NtGlobalFlag check\r\nTo bypass the NtGlobalFlag check, just performing reverse the actions that we took before the check; in other\r\nwords, set the the NtGlobalFlag field of the PEB structure of the debugged process to 0 before this value is\r\nchecked by the anti debugging protection.\r\n5. Combining NtGlobalFlag checks with IMAGE_LOAD_CONFIG_DIRECTORY\r\nThe executable can include the IMAGE_LOAD_CONFIG_DIRECTORY structure, which contains additional configuration\r\nparameters for the system loader. This structure is not built into an executable by default, but it can be added using\r\na patch. This structure has the GlobalFlagsClear field, which indicates which flags of the NtGlobalFlag field\r\nof the PEB structure should be reset. If an executable was initially created without the mentioned structure or with\r\nGlobalFlagsClear = 0 , while on the disk or in the memory, the field will have a non-zero value indicating that\r\nthere’s a hidden debugger working. The code example below checks the GlobalFlagsClear field in the memory\r\nof the running process and on the disk thus illustrating one of the popular anti debugging techniques:\r\nC++\r\nPIMAGE_NT_HEADERS GetImageNtHeaders(PBYTE pImageBase)\r\n{\r\n PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)pImageBase;\r\n return (PIMAGE_NT_HEADERS)(pImageBase + pImageDosHeader-\u003ee_lfanew);\r\n}\r\nPIMAGE_SECTION_HEADER FindRDataSection(PBYTE pImageBase)\r\n{\r\n static const std::string rdata = \".rdata\";\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 7 of 35\n\nPIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders(pImageBase);\r\n PIMAGE_SECTION_HEADER pImageSectionHeader = IMAGE_FIRST_SECTION(pImageNtHeaders);\r\n int n = 0;\r\n for (; n \u003c pImageNtHeaders-\u003eFileHeader.NumberOfSections; ++n)\r\n {\r\n if (rdata == (char*)pImageSectionHeader[n].Name)\r\n {\r\n break;\r\n }\r\n }\r\n return \u0026pImageSectionHeader[n];\r\n}\r\nvoid CheckGlobalFlagsClearInProcess()\r\n{\r\n PBYTE pImageBase = (PBYTE)GetModuleHandle(NULL);\r\n PIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders(pImageBase);\r\n PIMAGE_LOAD_CONFIG_DIRECTORY pImageLoadConfigDirectory = (PIMAGE_LOAD_CONFIG_DIRECTORY)(pImageBase\r\n + pImageNtHeaders-\u003eOptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddress);\r\n if (pImageLoadConfigDirectory-\u003eGlobalFlagsClear != 0)\r\n {\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n }\r\n}\r\nvoid CheckGlobalFlagsClearInFile()\r\n{\r\n HANDLE hExecutable = INVALID_HANDLE_VALUE;\r\n HANDLE hExecutableMapping = NULL;\r\n PBYTE pMappedImageBase = NULL;\r\n __try\r\n {\r\n PBYTE pImageBase = (PBYTE)GetModuleHandle(NULL);\r\n PIMAGE_SECTION_HEADER pImageSectionHeader = FindRDataSection(pImageBase);\r\n TCHAR pszExecutablePath[MAX_PATH];\r\n DWORD dwPathLength = GetModuleFileName(NULL, pszExecutablePath, MAX_PATH);\r\n if (0 == dwPathLength) __leave;\r\n hExecutable = CreateFile(pszExecutablePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL)\r\n if (INVALID_HANDLE_VALUE == hExecutable) __leave;\r\n hExecutableMapping = CreateFileMapping(hExecutable, NULL, PAGE_READONLY, 0, 0, NULL);\r\n if (NULL == hExecutableMapping) __leave;\r\n pMappedImageBase = (PBYTE)MapViewOfFile(hExecutableMapping, FILE_MAP_READ, 0, 0,\r\n pImageSectionHeader-\u003ePointerToRawData + pImageSectionHeader-\u003eSizeOfRawData);\r\n if (NULL == pMappedImageBase) __leave;\r\n PIMAGE_NT_HEADERS pImageNtHeaders = GetImageNtHeaders(pMappedImageBase);\r\n PIMAGE_LOAD_CONFIG_DIRECTORY pImageLoadConfigDirectory = (PIMAGE_LOAD_CONFIG_DIRECTORY)(pMappedImageBase\r\n + (pImageSectionHeader-\u003ePointerToRawData\r\n + (pImageNtHeaders-\u003eOptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG].VirtualAddre\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 8 of 35\n\nif (pImageLoadConfigDirectory-\u003eGlobalFlagsClear != 0)\r\n {\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n }\r\n }\r\n __finally\r\n {\r\n if (NULL != pMappedImageBase)\r\n UnmapViewOfFile(pMappedImageBase);\r\n if (NULL != hExecutableMapping)\r\n CloseHandle(hExecutableMapping);\r\n if (INVALID_HANDLE_VALUE != hExecutable)\r\n CloseHandle(hExecutable);\r\n }\r\n}\r\nIn this code sample, the CheckGlobalFlagsClearInProcess function finds the PIMAGE_LOAD_CONFIG_DIRECTORY\r\nstructure by the loading address of the currently running process and checks the value of the  GlobalFlagsClear\r\nfield. If this value is not 0, then the process is likely being debugged. The CheckGlobalFlagsClearInFile\r\nfunction performs the same check but for the executable on the disk.\r\n6. Verifying Heap Flags and ForceFlags for debugger detection\r\nThe PEB structure contains a pointer to the process heap (the _HEAP structure):\r\nC++\r\n0:000\u003e dt _PEB ProcessHeap @$peb\r\nntdll!_PEB\r\n +0x018 ProcessHeap : 0x00440000 Void\r\n0:000\u003e dt _HEAP Flags ForceFlags 00440000\r\nntdll!_HEAP\r\n +0x040 Flags : 0x40000062\r\n +0x044 ForceFlags : 0x40000060\r\nFor x64:\r\nC++\r\n0:000\u003e dt _PEB ProcessHeap @$peb\r\nntdll!_PEB\r\n +0x030 ProcessHeap : 0x0000009d`94b60000 Void\r\n0:000\u003e dt _HEAP Flags ForceFlags 0000009d`94b60000\r\nntdll!_HEAP\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 9 of 35\n\n+0x070 Flags : 0x40000062\r\n +0x074 ForceFlags : 0x40000060\r\nIf the process is being debugged, both the  Flags and ForceFlags  fields will have specific debug values:\r\n1. If the Flags field does not have the  HEAP_GROWABLE (0x00000002) flag set, then the process is being\r\ndebugged.\r\n2. If the value of ForceFlags is not 0 , then the process is being debugged.\r\nIt’s worth mentioning that the _HEAP structure is undocumented and that the values of offsets of the Flags and\r\nForceFlags fields can differ depending on the operating system version. The following code shows an anti-debugging protection example based on the heap flag check:\r\nC++\r\nint GetHeapFlagsOffset(bool x64)\r\n{\r\n return x64 ?\r\n IsVistaOrHigher() ? 0x70 : 0x14: //x64 offsets\r\n IsVistaOrHigher() ? 0x40 : 0x0C; //x86 offsets\r\n}\r\nint GetHeapForceFlagsOffset(bool x64)\r\n{\r\n return x64 ?\r\n IsVistaOrHigher() ? 0x74 : 0x18: //x64 offsets\r\n IsVistaOrHigher() ? 0x44 : 0x10; //x86 offsets\r\n}\r\nvoid CheckHeap()\r\n{\r\n PVOID pPeb = GetPEB();\r\n PVOID pPeb64 = GetPEB64();\r\n PVOID heap = 0;\r\n DWORD offsetProcessHeap = 0;\r\n PDWORD heapFlagsPtr = 0, heapForceFlagsPtr = 0;\r\n BOOL x64 = FALSE;\r\n#ifdef _WIN64\r\n x64 = TRUE;\r\n offsetProcessHeap = 0x30;\r\n#else\r\n offsetProcessHeap = 0x18;\r\n#endif\r\n heap = (PVOID)*(PDWORD_PTR)((PBYTE)pPeb + offsetProcessHeap);\r\n heapFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapFlagsOffset(x64));\r\n heapForceFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapForceFlagsOffset(x64));\r\n if (*heapFlagsPtr \u0026 ~HEAP_GROWABLE || *heapForceFlagsPtr != 0)\r\n {\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 10 of 35\n\nstd::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n }\r\n if (pPeb64)\r\n {\r\n heap = (PVOID)*(PDWORD_PTR)((PBYTE)pPeb64 + 0x30);\r\n heapFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapFlagsOffset(true));\r\n heapForceFlagsPtr = (PDWORD)((PBYTE)heap + GetHeapForceFlagsOffset(true));\r\n if (*heapFlagsPtr \u0026 ~HEAP_GROWABLE || *heapForceFlagsPtr != 0)\r\n {\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n }\r\n }\r\n}\r\nHow to bypass the Heap Flags and ForceFlags checks\r\nTo bypass anti-debugging protection based on the heap flag check, set the HEAP_GROWABLE flag for the Flags\r\nfield as well as the value of the ForceFlags field to 0. Obviously, these field values should be redefined before\r\nthe heap flag check.\r\n7. Performing a Trap Flag check to catch single-step debugging\r\nThe Trap Flag (TF) is inside the EFLAGS register. If TF is set to 1, the CPU will generate INT 01h, or the “Single\r\nStep” exception after each instruction execution. The following anti-debugging example is based on the TF setting\r\nand exception call check:\r\nC++\r\nBOOL isDebugged = TRUE;\r\n__try\r\n{\r\n __asm\r\n {\r\n pushfd\r\n or dword ptr[esp], 0x100 // set the Trap Flag\r\n popfd // Load the value into EFLAGS register\r\n nop\r\n }\r\n}\r\n__except (EXCEPTION_EXECUTE_HANDLER)\r\n{\r\n // If an exception has been raised – debugger is not present\r\n isDebugged = FALSE;\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 11 of 35\n\n}\r\nif (isDebugged)\r\n{\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n}\r\nHere, TF is intentionally set to generate an exception. If the process is being debugged, the exception will be\r\ncaught by the debugger.\r\nHow to bypass the TF check\r\nTo neutralize the TF flag check during debugging, pass the pushfd instruction not by single-stepping but by\r\njumping over it, putting the breakpoint after it, and continuing the program execution. After the breakpoint,\r\ntracing can be continued.\r\n8. Calling CheckRemoteDebuggerPresent and NtQueryInformationProcess\r\nUnlike the IsDebuggerPresent function, CheckRemoteDebuggerPresent checks if a process is being debugged\r\nby another parallel process. Here’s an example of anti-debugging technique based\r\non  CheckRemoteDebuggerPresent :\r\nC++\r\nint main(int argc, char *argv[])\r\n{\r\n BOOL isDebuggerPresent = FALSE;\r\n if (CheckRemoteDebuggerPresent(GetCurrentProcess(), \u0026isDebuggerPresent ))\r\n {\r\n if (isDebuggerPresent )\r\n {\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n }\r\n }\r\n return 0;\r\n}\r\nInside CheckRemoteDebuggerPresent , the NtQueryInformationProcess function is called:\r\nShellScript\r\n0:000\u003e uf kernelbase!CheckRemotedebuggerPresent\r\nKERNELBASE!CheckRemoteDebuggerPresent:\r\n...\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 12 of 35\n\n75207a24 6a00 push 0\r\n75207a26 6a04 push 4\r\n75207a28 8d45fc lea eax,[ebp-4]\r\n75207a2b 50 push eax\r\n75207a2c 6a07 push 7\r\n75207a2e ff7508 push dword ptr [ebp+8]\r\n75207a31 ff151c602775 call dword ptr [KERNELBASE!_imp__NtQueryInformationProcess (7527601c)]\r\n75207a37 85c0 test eax,eax\r\n75207a39 0f88607e0100 js KERNELBASE!CheckRemoteDebuggerPresent+0x2b (7521f89f)\r\n...\r\nIf we take a look at the NtQueryInformationProcess documentation, this Assembler listing will show us that the\r\nCheckRemoteDebuggerPresent function is assigned the DebugPort value, as the ProcessInformationClass\r\nparameter value (the second one) is 7. The following anti-debugging code example is based on calling\r\nthe  NtQueryInformationProcess :\r\nC++\r\ntypedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)(\r\n _In_ HANDLE ProcessHandle,\r\n _In_ UINT ProcessInformationClass,\r\n _Out_ PVOID ProcessInformation,\r\n _In_ ULONG ProcessInformationLength,\r\n _Out_opt_ PULONG ReturnLength\r\n );\r\nconst UINT ProcessDebugPort = 7;\r\nint main(int argc, char *argv[])\r\n{\r\n pfnNtQueryInformationProcess NtQueryInformationProcess = NULL;\r\n NTSTATUS status;\r\n DWORD isDebuggerPresent = 0;\r\n HMODULE hNtDll = LoadLibrary(TEXT(\"ntdll.dll\"));\r\n \r\n if (NULL != hNtDll)\r\n {\r\n NtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, \"NtQueryInformationProc\r\n if (NULL != NtQueryInformationProcess)\r\n {\r\n status = NtQueryInformationProcess(\r\n GetCurrentProcess(),\r\n ProcessDebugPort,\r\n \u0026isDebuggerPresent,\r\n sizeof(DWORD),\r\n NULL);\r\n if (status == 0x00000000 \u0026\u0026 isDebuggerPresent != 0)\r\n {\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 13 of 35\n\nstd::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n }\r\n }\r\n }\r\n return 0;\r\n}\r\nHow to bypass CheckRemoteDebuggerPresent and NtQueryInformationProcess\r\nTo bypass CheckRemoteDebuggerPresent and NTQueryInformationProcess, substitute the value returned by the\r\nNtQueryInformationProcess function. You can use mhook to do this. To set up a hook, inject DLL into the\r\ndebugged process and set up a hook in DLLMain using mhook. Here’s an example of mhook in use:\r\nC++\r\n#include \u003cWindows.h\u003e\r\n#include \"mhook.h\"\r\ntypedef NTSTATUS(NTAPI *pfnNtQueryInformationProcess)(\r\n _In_ HANDLE ProcessHandle,\r\n _In_ UINT ProcessInformationClass,\r\n _Out_ PVOID ProcessInformation,\r\n _In_ ULONG ProcessInformationLength,\r\n _Out_opt_ PULONG ReturnLength\r\n );\r\nconst UINT ProcessDebugPort = 7;\r\npfnNtQueryInformationProcess g_origNtQueryInformationProcess = NULL;\r\nNTSTATUS NTAPI HookNtQueryInformationProcess(\r\n _In_ HANDLE ProcessHandle,\r\n _In_ UINT ProcessInformationClass,\r\n _Out_ PVOID ProcessInformation,\r\n _In_ ULONG ProcessInformationLength,\r\n _Out_opt_ PULONG ReturnLength\r\n )\r\n{\r\n NTSTATUS status = g_origNtQueryInformationProcess(\r\n ProcessHandle,\r\n ProcessInformationClass,\r\n ProcessInformation,\r\n ProcessInformationLength,\r\n ReturnLength);\r\n if (status == 0x00000000 \u0026\u0026 ProcessInformationClass == ProcessDebugPort)\r\n {\r\n *((PDWORD_PTR)ProcessInformation) = 0;\r\n }\r\n return status;\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 14 of 35\n\n}\r\nDWORD SetupHook(PVOID pvContext)\r\n{\r\n HMODULE hNtDll = LoadLibrary(TEXT(\"ntdll.dll\"));\r\n if (NULL != hNtDll)\r\n {\r\n g_origNtQueryInformationProcess = (pfnNtQueryInformationProcess)GetProcAddress(hNtDll, \"NtQueryInformati\r\n if (NULL != g_origNtQueryInformationProcess)\r\n {\r\n Mhook_SetHook((PVOID*)\u0026g_origNtQueryInformationProcess, HookNtQueryInformationProcess);\r\n }\r\n }\r\n return 0;\r\n}\r\nBOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD fdwReason, LPVOID lpvReserved)\r\n{\r\n switch (fdwReason)\r\n {\r\n case DLL_PROCESS_ATTACH:\r\n DisableThreadLibraryCalls(hInstDLL);\r\n CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)SetupHook, NULL, NULL, NULL);\r\n Sleep(20);\r\n case DLL_PROCESS_DETACH:\r\n if (NULL != g_origNtQueryInformationProcess)\r\n {\r\n Mhook_Unhook((PVOID*)\u0026g_origNtQueryInformationProcess);\r\n }\r\n break;\r\n }\r\n return TRUE;\r\n}\r\n9. Other anti-debugging techniques based on NtQueryInformationProcess\r\nThere are several techniques for debugger detection using information provided by the\r\nNtQueryInformationProcess function:\r\n1. ProcessDebugPort 0x07 – discussed above\r\n2. ProcessDebugObjectHandle 0x1E\r\n3. ProcessDebugFlags 0x1F\r\n4. ProcessBasicInformation 0x00\r\nLet’s consider numbers two through four in detail.\r\nProcessDebugObjectHandle\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 15 of 35\n\nStarting with Windows XP, a “debug object” is created for a debugged process. Here’s an example of checking for\r\na “debug object” in the current process:\r\nC++\r\nstatus = NtQueryInformationProcess(\r\n GetCurrentProcess(),\r\n ProcessDebugObjectHandle,\r\n \u0026hProcessDebugObject,\r\n sizeof(HANDLE),\r\n NULL);\r\nif (0x00000000 == status \u0026\u0026 NULL != hProcessDebugObject)\r\n{\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n}\r\nIf a debug object exists, then the process is being debugged.\r\nProcessDebugFlags\r\nWhen checking this flag, it returns the inverse value of the NoDebugInherit bit of the EPROCESS kernel\r\nstructure. If the returned value of the NtQueryInformationProcess function is 0, then the process is being\r\ndebugged. Here’s an example of such an anti-debugging check:\r\nC++\r\nstatus = NtQueryInformationProcess(\r\n GetCurrentProcess(),\r\n ProcessDebugObjectHandle,\r\n \u0026debugFlags,\r\n sizeof(ULONG),\r\n NULL);\r\nif (0x00000000 == status \u0026\u0026 NULL != debugFlags)\r\n{\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n}\r\nProcessBasicInformation\r\nWhen calling the NtQueryInformationProcess function with the ProcessBasicInformation flag, the\r\nPROCESS_BASIC_INFORMATION structure is returned:\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 16 of 35\n\nC++\r\ntypedef struct _PROCESS_BASIC_INFORMATION {\r\n NTSTATUS ExitStatus;\r\n PVOID PebBaseAddress;\r\n ULONG_PTR AffinityMask;\r\n KPRIORITY BasePriority;\r\n HANDLE UniqueProcessId;\r\n HANDLE InheritedFromUniqueProcessId;\r\n} PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION;\r\nThe most interesting thing in this structure is the InheritedFromUniqueProcessId field. Here, we need to get the\r\nname of the parent process and compare it to the names of popular debuggers. Let’s see how to create an anti-debugger:\r\nC++\r\nstd::wstring GetProcessNameById(DWORD pid)\r\n{\r\n HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);\r\n if (hProcessSnap == INVALID_HANDLE_VALUE)\r\n {\r\n return 0;\r\n }\r\n PROCESSENTRY32 pe32;\r\n pe32.dwSize = sizeof(PROCESSENTRY32);\r\n std::wstring processName = L\"\";\r\n if (!Process32First(hProcessSnap, \u0026pe32))\r\n {\r\n CloseHandle(hProcessSnap);\r\n return processName;\r\n }\r\n do\r\n {\r\n if (pe32.th32ProcessID == pid)\r\n {\r\n processName = pe32.szExeFile;\r\n break;\r\n }\r\n } while (Process32Next(hProcessSnap, \u0026pe32));\r\n \r\n CloseHandle(hProcessSnap);\r\n return processName;\r\n}\r\nstatus = NtQueryInformationProcess(\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 17 of 35\n\nGetCurrentProcess(),\r\n ProcessBasicInformation,\r\n \u0026processBasicInformation,\r\n sizeof(PROCESS_BASIC_INFORMATION),\r\n NULL);\r\nstd::wstring parentProcessName = GetProcessNameById((DWORD)processBasicInformation.InheritedFromUniqueProcessId)\r\nif (L\"devenv.exe\" == parentProcessName)\r\n{\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n}\r\nHow to bypass the NtQueryInformationProcess checks\r\nBypassing the NtQueryInformation Process checks is simple. Just change the values returned by the\r\nNtQueryInformationProcess function to values that don’t indicate the presence of a debugger:\r\n1. Set ProcessDebugObjectHandle to 0\r\n2. Set ProcessDebugFlags to 1\r\n3. For  ProcessBasicInformation , change the InheritedFromUniqueProcessId value to the ID of another\r\nprocess, e.g. explorer.exe\r\n10. Detecting software and hardware breakpoints\r\nBreakpoints are the main tool provided by debuggers. Breakpoints allow you to interrupt program execution at a\r\nspecified place. There are two types of breakpoints:\r\n1. Software breakpoints\r\n2. Hardware breakpoints\r\nIt’s very hard to reverse engineer software without breakpoints. Popular anti-reverse engineering tactics are based\r\non detecting breakpoints, providing a series of corresponding anti-debugging methods.\r\nSoftware Breakpoints\r\nIn the IA-32 architecture, there’s a specific instruction – int 3h with the 0xCC opcode – that is used to call the\r\ndebug handle. When the CPU executes this instruction, an interruption is generated and control is transferred to\r\nthe debugger. To get control, the debugger has to inject the int 3h instruction into the code. To detect a breakpoint,\r\nwe can calculate the checksum of the function. Here’s an example:\r\nC++\r\nDWORD CalcFuncCrc(PUCHAR funcBegin, PUCHAR funcEnd)\r\n{\r\n DWORD crc = 0;\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 18 of 35\n\nfor (; funcBegin \u003c funcEnd; ++funcBegin)\r\n {\r\n crc += *funcBegin;\r\n }\r\n return crc;\r\n}\r\n#pragma auto_inline(off)\r\nVOID DebuggeeFunction()\r\n{\r\n int calc = 0;\r\n calc += 2;\r\n calc \u003c\u003c= 8;\r\n calc -= 3;\r\n}\r\nVOID DebuggeeFunctionEnd()\r\n{\r\n};\r\n#pragma auto_inline(on)\r\nDWORD g_origCrc = 0x2bd0;\r\nint main()\r\n{\r\n DWORD crc = CalcFuncCrc((PUCHAR)DebuggeeFunction, (PUCHAR)DebuggeeFunctionEnd);\r\n if (g_origCrc != crc)\r\n {\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n }\r\n return 0;\r\n}\r\nIt is worth mentioning that this will only work if the /INCREMENTAL:NO linker option is set, otherwise, when\r\ngetting the function address to calculate the checksum, we’ll get the relative jump address:\r\nShellScript\r\nDebuggeeFunction:\r\n013C16DB jmp DebuggeeFunction (013C4950h)\r\nThe g_origCrc global variable contains crc already calculated by the CalcFuncCrc function. To detect the end\r\nof the function, we use the stub function trick. As the function code is located sequentially, the end of the\r\nDebuggeeFunction  is the beginning of the DebuggeeFunctionEnd function. We also used the #pragma\r\nauto_inline(off) directive to prevent the compiler from making functions embedded.\r\nHow to bypass a software breakpoint check\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 19 of 35\n\nThere’s no universal approach for bypassing a software breakpoint check. To bypass this protection, you should\r\nfind the code calculating the checksum and substitute the returned value with a constant, as well as the values of\r\nall variables storing function checksums.\r\nHardware Breakpoints\r\nIn the x86 architecture, there’s a set of debug registers used by developers when checking and debugging code.\r\nThese registers allow you to interrupt program execution and transfer control to a debugger when accessing\r\nmemory to read or write. Debug registers are a privileged resource and can be used by a program only in real\r\nmode or safe mode with privilege level CPL=0. There are eight debug registers DR0-DR7:\r\n1. DR0-DR3 – breakpoint registers\r\n2. DR4 \u0026 DR5 – reserved\r\n3. DR6 – debug status\r\n4. DR7 – debug control\r\nDR0-DR3 contain linear addresses of breakpoints. Comparison of these addresses is performed before physical\r\naddress translation. Each of these breakpoints is separately described in the DR7 register. The DR6 register\r\nindicates which breakpoint is activated. DR7 defines the breakpoint activation mode by the access mode: read,\r\nwrite, or execute. Here’s an example of a hardware breakpoint check:\r\nC++\r\nCONTEXT ctx = {};\r\nctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;\r\nif (GetThreadContext(GetCurrentThread(), \u0026ctx))\r\n{\r\n if (ctx.Dr0 != 0 || ctx.Dr1 != 0 || ctx.Dr2 != 0 || ctx.Dr3 != 0)\r\n {\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n }\r\n}\r\nIt’s also possible to reset hardware breakpoints by means of the SetThreadContext function. Here’s an example\r\nof a hardware breakpoint reset:\r\nC++\r\nCONTEXT ctx = {};\r\nctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;\r\nSetThreadContext(GetCurrentThread(), \u0026ctx);\r\nAs we can see, all DRx registers are set to 0.\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 20 of 35\n\nHow to bypass a hardware breakpoint check and reset\r\nIf we look inside the GetThreadContext function, we’ll see that it calls the NtGetContextThread function:\r\nShellScript\r\n0:000\u003e u KERNELBASE!GetThreadContext L6\r\nKERNELBASE!GetThreadContext:\r\n7538d580 8bff mov edi,edi\r\n7538d582 55 push ebp\r\n7538d583 8bec mov ebp,esp\r\n7538d585 ff750c push dword ptr [ebp+0Ch]\r\n7538d588 ff7508 push dword ptr [ebp+8]\r\n7538d58b ff1504683975 call dword ptr [KERNELBASE!_imp__NtGetContextThread (75396804)]\r\nTo make the protection receive zero values in Dr0-Dr7, reset the CONTEXT_DEBUG_REGISTERS flag in the\r\nContextFlags field of the CONTEXT structure and then restore its value after the original NtGetContextThread\r\nfunction call. As for the GetThreadContext function, it calls NtSetContextThread . The following example\r\nshows how to bypass a hardware breakpoint check and reset:\r\nC++\r\ntypedef NTSTATUS(NTAPI *pfnNtGetContextThread)(\r\n _In_ HANDLE ThreadHandle,\r\n _Out_ PCONTEXT pContext\r\n );\r\ntypedef NTSTATUS(NTAPI *pfnNtSetContextThread)(\r\n _In_ HANDLE ThreadHandle,\r\n _In_ PCONTEXT pContext\r\n );\r\npfnNtGetContextThread g_origNtGetContextThread = NULL;\r\npfnNtSetContextThread g_origNtSetContextThread = NULL;\r\nNTSTATUS NTAPI HookNtGetContextThread(\r\n _In_ HANDLE ThreadHandle,\r\n _Out_ PCONTEXT pContext)\r\n{\r\n DWORD backupContextFlags = pContext-\u003eContextFlags;\r\n pContext-\u003eContextFlags \u0026= ~CONTEXT_DEBUG_REGISTERS;\r\n NTSTATUS status = g_origNtGetContextThread(ThreadHandle, pContext);\r\n pContext-\u003eContextFlags = backupContextFlags;\r\n return status;\r\n}\r\nNTSTATUS NTAPI HookNtSetContextThread(\r\n _In_ HANDLE ThreadHandle,\r\n _In_ PCONTEXT pContext)\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 21 of 35\n\n{\r\n DWORD backupContextFlags = pContext-\u003eContextFlags;\r\n pContext-\u003eContextFlags \u0026= ~CONTEXT_DEBUG_REGISTERS;\r\n NTSTATUS status = g_origNtSetContextThread(ThreadHandle, pContext);\r\n pContext-\u003eContextFlags = backupContextFlags;\r\n return status;\r\n}\r\nvoid HookThreadContext()\r\n{\r\nHMODULE hNtDll = LoadLibrary(TEXT(\"ntdll.dll\"));\r\ng_origNtGetContextThread = (pfnNtGetContextThread)GetProcAddress(hNtDll, \"NtGetContextThread\");\r\ng_origNtSetContextThread = (pfnNtSetContextThread)GetProcAddress(hNtDll, \"NtSetContextThread\");\r\nMhook_SetHook((PVOID*)\u0026g_origNtGetContextThread, HookNtGetContextThread);\r\nMhook_SetHook((PVOID*)\u0026g_origNtSetContextThread, HookNtSetContextThread);\r\n}\r\n11. Using Structured Exception Handling (SEH) for debugger detection\r\nStructured exception handling is a mechanism provided by the operating system to an application allowing it to\r\nreceive notifications about exceptional situations like division by zero, reference to a nonexistent pointer, or\r\nexecution of a restricted instruction. This mechanism allows you to handle exceptions inside an application,\r\nwithout operating system involvement. If an exception is not handled, it will result in abnormal program\r\ntermination. Developers usually locate pointers to SEH in the stack, which are called SEH frames. The current\r\nSEH frame address is located by the 0 offset relative to the FS selector (or GS selector for the x64 systems). This\r\naddress points to the ntdll! _EXCEPTION_REGISTRATION_RECORD structure:\r\nShellScript\r\n0:000\u003e dt ntdll!_EXCEPTION_REGISTRATION_RECORD\r\n +0x000 Next : Ptr32 _EXCEPTION_REGISTRATION_RECORD\r\n +0x004 Handler : Ptr32 _EXCEPTION_DISPOSITION\r\nWhen an exception is initiated, control is transferred to the current SEH handler. Depending on the situation, this\r\nSEH handler should return one of the _EXCEPTION_DISPOSITION members:\r\nC++\r\ntypedef enum _EXCEPTION_DISPOSITION {\r\n ExceptionContinueExecution,\r\n ExceptionContinueSearch,\r\n ExceptionNestedException,\r\n ExceptionCollidedUnwind\r\n} EXCEPTION_DISPOSITION;\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 22 of 35\n\nIf the handler returns ExceptionContinueSearch , the system continues execution from the instruction, that\r\ntriggered the exception. If the handler doesn’t know what to do with an exception, it\r\nreturns  ExceptionContinueSearch and then the system moves to the next handler in the chain. You can browse\r\nthe current exception chain using the !exchain command in the WinDbg debugger:\r\nShellScript\r\n0:000\u003e !exchain\r\n00a5f3bc: AntiDebug!_except_handler4+0 (008b7530)\r\n CRT scope 0, filter: AntiDebug!SehInternals+67 (00883d67)\r\n func: AntiDebug!SehInternals+6d (00883d6d)\r\n00a5f814: AntiDebug!__scrt_stub_for_is_c_termination_complete+164b (008bc16b)\r\n00a5f87c: AntiDebug!_except_handler4+0 (008b7530)\r\n CRT scope 0, filter: AntiDebug!__scrt_common_main_seh+1b0 (008b7c60)\r\n func: AntiDebug!__scrt_common_main_seh+1cb (008b7c7b)\r\n00a5f8e8: ntdll!_except_handler4+0 (775674a0)\r\n CRT scope 0, filter: ntdll!__RtlUserThreadStart+54386 (7757f076)\r\n func: ntdll!__RtlUserThreadStart+543cd (7757f0bd)\r\n00a5f900: ntdll!FinalExceptionHandlerPad4+0 (77510213)\r\nThe last in the chain is the default handler assigned by the system. If none of the previous handlers managed to\r\nhandle the exception, then the system handler goes to the registry to get the key\r\nHKEY_LOCAL_MACHINESoftwareMicrosoftWindows NTCurrentVersionAeDebug\r\nDepending on the AeDebug key value, either the application is terminated or control is transferred to the\r\ndebugger. The debugger path should be indicated in Debugger REG_SZ .\r\nWhen creating a new process, the system adds the primary SEH frame to it. The handler for the primary SEH\r\nframe is also defined by the system. The primary SEH frame itself is located almost at the very beginning of the\r\nmemory stack allocated for the process. The SEH handler function signature looks as follows:\r\nC++\r\ntypedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) (\r\n __in struct _EXCEPTION_RECORD *ExceptionRecord,\r\n __in PVOID EstablisherFrame,\r\n __inout struct _CONTEXT *ContextRecord,\r\n __inout PVOID DispatcherContext\r\n );\r\nIf an application is being debugged, after the int 3h interruption generation, control will be intercepted by the\r\ndebugger. Otherwise, control will be transferred to the SEH handler. The following code example shows SEH\r\nframe-based anti-debugging protection:\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 23 of 35\n\nC++\r\nBOOL g_isDebuggerPresent = TRUE;\r\nEXCEPTION_DISPOSITION ExceptionRoutine(\r\n PEXCEPTION_RECORD ExceptionRecord,\r\n PVOID EstablisherFrame,\r\n PCONTEXT ContextRecord,\r\n PVOID DispatcherContext)\r\n{\r\n g_isDebuggerPresent = FALSE;\r\n ContextRecord-\u003eEip += 1;\r\n return ExceptionContinueExecution;\r\n}\r\nint main()\r\n{\r\n __asm\r\n {\r\n // set SEH handler\r\n push ExceptionRoutine\r\n push dword ptr fs:[0]\r\n mov dword ptr fs:[0], esp\r\n // generate interrupt\r\n int 3h\r\n // return original SEH handler\r\n mov eax, [esp]\r\n mov dword ptr fs:[0], eax\r\n add esp, 8\r\n }\r\n if (g_isDebuggerPresent)\r\n {\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n }\r\n return 0\r\n}\r\nIn this example, an SEH handler is set. The pointer to this handler is put at the beginning of the handler chain.\r\nThen the int 3h interruption is generated. If the application is not being debugged, control will be transferred to the\r\nSEH handler and the value of  g_isDebuggerPresent  will be set to FALSE . The ContextRecord-\u003eEip += 1 line\r\nalso changes the address of the next instruction in the execution flow, which will result in the execution of the\r\ninstruction after int 3h. Then the code returns the original SEH handler, clears the stack, and checks if a debugger\r\nis present.\r\nHow to bypass SEH checks\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 24 of 35\n\nThere’s no universal approach to bypassing SEH checks, but still there are some techniques to make a reverser’s\r\nlife easier. Let’s take a look at the call stack that led to the SEH handler call:\r\nShellScript\r\n0:000\u003e kn\r\n # ChildEBP RetAddr\r\n00 0059f06c 775100b1 AntiDebug!ExceptionRoutine\r\n01 0059f090 77510083 ntdll!ExecuteHandler2+0x26\r\n02 0059f158 775107ff ntdll!ExecuteHandler+0x24\r\n03 0059f158 003b11a5 ntdll!KiUserExceptionDispatcher+0xf\r\n04 0059fa90 003d7f4e AntiDebug!main+0xb5\r\n05 0059faa4 003d7d9a AntiDebug!invoke_main+0x1e\r\n06 0059fafc 003d7c2d AntiDebug!__scrt_common_main_seh+0x15a\r\n07 0059fb04 003d7f68 AntiDebug!__scrt_common_main+0xd\r\n08 0059fb0c 753e7c04 AntiDebug!mainCRTStartup+0x8\r\n09 0059fb20 7752ad1f KERNEL32!BaseThreadInitThunk+0x24\r\n0a 0059fb68 7752acea ntdll!__RtlUserThreadStart+0x2f\r\n0b 0059fb78 00000000 ntdll!_RtlUserThreadStart+0x1b\r\nWe can see that this call came from ntdll!ExecuteHandler2 . This function is the starting point for calling any\r\nSEH handler. A breakpoint can be set at the call instruction:\r\nShellScript\r\n0:000\u003e u ntdll!ExecuteHandler2+24 L3\r\nntdll!ExecuteHandler2+0x24:\r\n775100af ffd1 call ecx\r\n775100b1 648b2500000000 mov esp,dword ptr fs:[0]\r\n775100b8 648f0500000000 pop dword ptr fs:[0]\r\n0:000\u003e bp 775100af\r\nAfter setting the breakpoint, you should analyze the code of each called SEH handler. If protection is based on\r\nmultiple calls to SEH handlers, a reverser will really sweat over bypassing it.\r\n12. Using Vectored Exception Handlers (VEH) to detect debuggers\r\nVEH was introduced in Windows XP and is a variation of SEH. VEH and SEH do not depend on each other and\r\nwork simultaneously. When a new VEH handler is added, the SEH chain is not affected as the list of VEH\r\nhandlers is stored in the ntdll!LdrpVectorHandlerList non-exported variable. The VEH and SEH mechanisms\r\nare pretty similar, the only difference being that documented functions are used to set up and remove a VEH\r\nhandler. The signatures of the functions for adding and removing a VEH handler as well as the VEH handler\r\nfunction itself are as follows:\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 25 of 35\n\nC++\r\nPVOID WINAPI AddVectoredExceptionHandler(\r\n ULONG FirstHandler,\r\n PVECTORED_EXCEPTION_HANDLER VectoredHandler\r\n);\r\nULONG WINAPI RemoveVectoredExceptionHandler(\r\n PVOID Handler\r\n);\r\nLONG CALLBACK VectoredHandler(\r\n PEXCEPTION_POINTERS ExceptionInfo\r\n);\r\nThe _EXCEPTION_POINTERS structure looks like this:\r\ntypedef struct _EXCEPTION_POINTERS {\r\n PEXCEPTION_RECORD ExceptionRecord;\r\n PCONTEXT ContextRecord;\r\n} EXCEPTION_POINTERS, *PEXCEPTION_POINTERS;\r\nAfter receiving control in the handler, the system collects the current process context and passes it via the\r\nContextRecord parameter. Here’s a sample of anti-debugging protection code using vector exception handling:\r\nC++\r\nLONG CALLBACK ExceptionHandler(PEXCEPTION_POINTERS ExceptionInfo)\r\n{\r\n PCONTEXT ctx = ExceptionInfo-\u003eContextRecord;\r\n if (ctx-\u003eDr0 != 0 || ctx-\u003eDr1 != 0 || ctx-\u003eDr2 != 0 || ctx-\u003eDr3 != 0)\r\n {\r\n std::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n }\r\n ctx-\u003eEip += 2;\r\n return EXCEPTION_CONTINUE_EXECUTION;\r\n}\r\nint main()\r\n{\r\n AddVectoredExceptionHandler(0, ExceptionHandler);\r\n __asm int 1h;\r\n return 0;\r\n}\r\nHere we set up a VEH handler and generated an interruption (int 1h is not necessary). When the interruption is\r\ngenerated, an exception appears and control is transferred to the VEH handler. If a hardware breakpoint is set,\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 26 of 35\n\nprogram execution is stopped. If there are no hardware breakpoints, the EIP register value is increased by 2 to\r\ncontinue execution after the int 1h generation instruction.\r\nHow to bypass a hardware breakpoint check and VEH\r\nLet’s take a look at the call stack that led to the VEH handler:\r\nShellScript\r\n0:000\u003e kn\r\n # ChildEBP RetAddr\r\n00 001cf21c 774d6822 AntiDebug!ExceptionHandler\r\n01 001cf26c 7753d151 ntdll!RtlpCallVectoredHandlers+0xba\r\n02 001cf304 775107ff ntdll!RtlDispatchException+0x72\r\n03 001cf304 00bf4a69 ntdll!KiUserExceptionDispatcher+0xf\r\n04 001cfc1c 00c2680e AntiDebug!main+0x59\r\n05 001cfc30 00c2665a AntiDebug!invoke_main+0x1e\r\n06 001cfc88 00c264ed AntiDebug!__scrt_common_main_seh+0x15a\r\n07 001cfc90 00c26828 AntiDebug!__scrt_common_main+0xd\r\n08 001cfc98 753e7c04 AntiDebug!mainCRTStartup+0x8\r\n09 001cfcac 7752ad1f KERNEL32!BaseThreadInitThunk+0x24\r\n0a 001cfcf4 7752acea ntdll!__RtlUserThreadStart+0x2f\r\n0b 001cfd04 00000000 ntdll!_RtlUserThreadStart+0x1b\r\nAs we can see, control was transferred from main+0x59 to ntdll!KiUserExceptionDispatcher . Let’s see what\r\ninstruction in main+0x59 resulted in this call:\r\nShellScript\r\n0:000\u003e u main+59 L1\r\nAntiDebug!main+0x59\r\n00bf4a69 cd02 int 1\r\nHere’s the instruction that generated the interruption. The KiUserExceptionDispatcher function is one of the\r\ncallbacks that the system calls from kernel mode to user mode. This is its signature:\r\nC++\r\nVOID NTAPI KiUserExceptionDispatcher(\r\n PEXCEPTION_RECORD pExcptRec,\r\n PCONTEXT ContextFrame\r\n);\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 27 of 35\n\nThe next code sample Shows how to bypass the hardware breakpoint check by applying the\r\nKiUserExceptionDispatcher function hook:\r\nC++\r\ntypedef VOID (NTAPI *pfnKiUserExceptionDispatcher)(\r\n PEXCEPTION_RECORD pExcptRec,\r\n PCONTEXT ContextFrame\r\n );\r\npfnKiUserExceptionDispatcher g_origKiUserExceptionDispatcher = NULL;\r\nVOID NTAPI HandleKiUserExceptionDispatcher(PEXCEPTION_RECORD pExcptRec, PCONTEXT ContextFrame)\r\n{\r\n if (ContextFrame \u0026\u0026 (CONTEXT_DEBUG_REGISTERS \u0026 ContextFrame-\u003eContextFlags))\r\n {\r\n ContextFrame-\u003eDr0 = 0;\r\n ContextFrame-\u003eDr1 = 0;\r\n ContextFrame-\u003eDr2 = 0;\r\n ContextFrame-\u003eDr3 = 0;\r\n ContextFrame-\u003eDr6 = 0;\r\n ContextFrame-\u003eDr7 = 0;\r\n ContextFrame-\u003eContextFlags \u0026= ~CONTEXT_DEBUG_REGISTERS;\r\n }\r\n}\r\n__declspec(naked) VOID NTAPI HookKiUserExceptionDispatcher()\r\n// Params: PEXCEPTION_RECORD pExcptRec, PCONTEXT ContextFrame\r\n{\r\n __asm\r\n {\r\n mov eax, [esp + 4]\r\n mov ecx, [esp]\r\n push eax\r\n push ecx\r\n call HandleKiUserExceptionDispatcher\r\n jmp g_origKiUserExceptionDispatcher\r\n }\r\n}\r\nint main()\r\n{\r\n HMODULE hNtDll = LoadLibrary(TEXT(\"ntdll.dll\"));\r\n g_origKiUserExceptionDispatcher = (pfnKiUserExceptionDispatcher)GetProcAddress(hNtDll, \"KiUserExceptionDispa\r\n Mhook_SetHook((PVOID*)\u0026g_origKiUserExceptionDispatcher, HookKiUserExceptionDispatcher);\r\n return 0;\r\n}\r\nIt this example, the values of the DRx registers are reset in the HookKiUserExceptionDispatcher function, in\r\nother words before the VEH handler call.\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 28 of 35\n\n13. Hiding threads from a debugger with NtSetInformationThread\r\nIn Windows 2000, a new class of thread information transferred to the   NtSetInformationThread function\r\nappeared – ThreadHideFromDebugger . This was one of the first anti-debugging techniques provided by Windows\r\nin Microsoft’s search for how to prevent reverse engineering, and it’s very powerful. If this flag is set for a thread,\r\nthen that thread stops sending notifications about debug events. These events include breakpoints and notifications\r\nabout program completion. The value of this flag is stored in the HideFromDebugger field of the _ETHREAD\r\nstructure:\r\nShellScript\r\n1: kd\u003e dt _ETHREAD HideFromDebugger 86bfada8\r\nntdll!_ETHREAD\r\n +0x248 HideFromDebugger : 0y1\r\nHere’s an example of how to set ThreadHideFromDebugger :\r\nC++\r\ntypedef NTSTATUS (NTAPI *pfnNtSetInformationThread)(\r\n _In_ HANDLE ThreadHandle,\r\n _In_ ULONG ThreadInformationClass,\r\n _In_ PVOID ThreadInformation,\r\n _In_ ULONG ThreadInformationLength\r\n );\r\nconst ULONG ThreadHideFromDebugger = 0x11;\r\nvoid HideFromDebugger()\r\n{\r\n HMODULE hNtDll = LoadLibrary(TEXT(\"ntdll.dll\"));\r\n pfnNtSetInformationThread NtSetInformationThread = (pfnNtSetInformationThread)\r\n GetProcAddress(hNtDll, \"NtSetInformationThread\");\r\n NTSTATUS status = NtSetInformationThread(GetCurrentThread(),\r\n ThreadHideFromDebugger, NULL, 0);\r\n}\r\nHow to bypass thread hiding from debugger\r\nTo prevent an application from hiding the thread from a debugger, you need to hook the\r\nNtSetInformationThread function call. Here’s a hook code example:\r\nC++\r\npfnNtSetInformationThread g_origNtSetInformationThread = NULL;\r\nNTSTATUS NTAPI HookNtSetInformationThread(\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 29 of 35\n\n_In_ HANDLE ThreadHandle,\r\n _In_ ULONG ThreadInformationClass,\r\n _In_ PVOID ThreadInformation,\r\n _In_ ULONG ThreadInformationLength\r\n )\r\n{\r\n if (ThreadInformationClass == ThreadHideFromDebugger \u0026\u0026\r\n ThreadInformation == 0 \u0026\u0026 ThreadInformationLength == 0)\r\n {\r\n return STATUS_SUCCESS;\r\n }\r\n return g_origNtSetInformationThread(ThreadHandle,\r\n ThreadInformationClass, ThreadInformation, ThreadInformationLength\r\n}\r\n \r\nvoid SetHook()\r\n{\r\n HMODULE hNtDll = LoadLibrary(TEXT(\"ntdll.dll\"));\r\n if (NULL != hNtDll)\r\n {\r\n g_origNtSetInformationThread = (pfnNtSetInformationThread)GetProcAddress(hNtDll, \"NtSetInformationThread\r\n if (NULL != g_origNtSetInformationThread)\r\n {\r\n Mhook_SetHook((PVOID*)\u0026g_origNtSetInformationThread, HookNtSetInformationThread);\r\n }\r\n }\r\n}\r\nIn the hooked function, when calling it correctly, STATUS_SUCCESS will be returned without transferring control to\r\nthe original NtSetInformationThread function.\r\n14. Using NtCreateThreadEx to evade debugging\r\nWindows Vista introduced the NtCreateThreadEx function, whose signature is as follows:\r\nC++\r\nNTSTATUS NTAPI NtCreateThreadEx (\r\n _Out_ PHANDLE ThreadHandle,\r\n _In_ ACCESS_MASK DesiredAccess,\r\n _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,\r\n _In_ HANDLE ProcessHandle,\r\n _In_ PVOID StartRoutine,\r\n _In_opt_ PVOID Argument,\r\n _In_ ULONG CreateFlags,\r\n _In_opt_ ULONG_PTR ZeroBits,\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 30 of 35\n\n_In_opt_ SIZE_T StackSize,\r\n _In_opt_ SIZE_T MaximumStackSize,\r\n _In_opt_ PVOID AttributeList\r\n);\r\nThe most interesting parameter is CreateFlags . This parameter gets flags such as:\r\nC++\r\n#define THREAD_CREATE_FLAGS_CREATE_SUSPENDED 0x00000001\r\n#define THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH 0x00000002\r\n#define THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER 0x00000004\r\n#define THREAD_CREATE_FLAGS_HAS_SECURITY_DESCRIPTOR 0x00000010\r\n#define THREAD_CREATE_FLAGS_ACCESS_CHECK_IN_TARGET 0x00000020\r\n#define THREAD_CREATE_FLAGS_INITIAL_THREAD 0x00000080\r\nIf a new thread gets the THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER flag, it will be hidden from the debugger\r\nwhen it’s created. This is the same ThreadHideFromDebugger , that’s set up by the NtSetInformationThread\r\nfunction. The code that’s responsible for security tasks, can be executed in the thread with the\r\nTHREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER flag set.\r\nHow to bypass NtCreateThreadEx\r\nThis technique can be bypassed by hooking the NtCreateThreadEx function, in which\r\nTHREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER will be reset.\r\n15. Tracing handles to spot debugger interference\r\nStarting with Windows XP, Windows systems have had a mechanism for kernel object handle tracing. When the\r\ntracing mode is on, all operations with handlers are saved to the circular buffer. Also, when trying to use a\r\nnonexistent handler, for instance, to close it using the CloseHandle function, the EXCEPTION_INVALID_HANDLE\r\nexception will be generated. If a process is started not from the debugger, the CloseHandle function will return\r\nFALSE . The following example shows anti-debugging protection based on CloseHandle :\r\nC++\r\nEXCEPTION_DISPOSITION ExceptionRoutine(\r\n PEXCEPTION_RECORD ExceptionRecord,\r\n PVOID EstablisherFrame,\r\n PCONTEXT ContextRecord,\r\n PVOID DispatcherContext)\r\n{\r\n if (EXCEPTION_INVALID_HANDLE == ExceptionRecord-\u003eExceptionCode)\r\n {\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 31 of 35\n\nstd::cout \u003c\u003c \"Stop debugging program!\" \u003c\u003c std::endl;\r\n exit(-1);\r\n }\r\n return ExceptionContinueExecution;\r\n}\r\nint main()\r\n{\r\n __asm\r\n {\r\n // set SEH handler\r\n push ExceptionRoutine\r\n push dword ptr fs : [0]\r\n mov dword ptr fs : [0], esp\r\n }\r\n CloseHandle((HANDLE)0xBAAD);\r\n __asm\r\n {\r\n // return original SEH handler\r\n mov eax, [esp]\r\n mov dword ptr fs : [0], eax\r\n add esp, 8\r\n }\r\n return 0\r\n}\r\n16. Manipulating the stack segment to confuse debuggers\r\nWhen manipulating the ss stack segment register, the debugger skips the instruction tracing. In the next example,\r\nthe debugger will immediately move to the xor edx, edx instruction, while the previous instruction will be\r\nexecuted:\r\nC++\r\n__asm\r\n{\r\n push ss\r\n pop ss\r\n mov eax, 0xC000C1EE // This line will be traced over by debugger\r\n xor edx, edx // Debugger will step to this line\r\n}\r\nSince Windows 10, the implementation of the OutputDebugString function has changed to a simple\r\nRaiseException call with the specific parameters. So, debug output exception must be now handled by the\r\ndebugger.\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 32 of 35\n\nThere are two exception types: DBG_PRINTEXCEPTION_C (0x40010006) and\r\nDBG_PRINTEXCEPTION_W(0x4001000A), which can be used to detect the debugger presence.\r\nC++\r\n#define DBG_PRINTEXCEPTION_WIDE_C 0x4001000A\r\nWCHAR * outputString = L\"Any text\";\r\nULONG_PTR args[4] = {0};\r\nargs[0] = (ULONG_PTR)wcslen(outputString) + 1;\r\nargs[1] = (ULONG_PTR)outputString;\r\n__try\r\n{\r\n RaiseException(DBG_PRINTEXCEPTION_WIDE_C, 0, 4, args);\r\n printf(\"Debugger detected\");\r\n}\r\n__except (EXCEPTION_EXECUTE_HANDLER)\r\n{\r\n printf(\"Debugger NOT detected\");\r\n}\r\nSo, in case the exception is unhandled, it means there’s no debugger attached.\r\nDBG_PRINTEXCEPTION_W is used for wide-char output, and DBG_PRINTEXCEPTION_C is used for ansi. In\r\nmeans that in case of DBG_PRINTEXCEPTION_C args[0] will hold strlen() result, and args[1] – the pointer to\r\nansi string (char *).\r\n18. Applying Protected Process Light (PPL) as an additional protection layer\r\nWhile it’s not strictly an anti-debugging tool, Protected Process Light (PPL) is a Windows security mechanism\r\ndesigned to prevent unauthorized interference with protected processes. PPL restricts what an operation’s external\r\nprocesses can do, including opening handles, injecting code, or modifying memory. PPL was originally intended\r\nto protect antivirus solutions and other security-critical components. \r\nThere are some limitations that make PPL unsuitable as a universal protection method:\r\n1. It requires Microsoft signing. To run a process in PPL mode, it must be signed with a special Microsoft\r\ncertificate. This is typically accessible only through official partnerships, which makes PPL unrealistic for\r\nmost independent developers.\r\n2. It is not effective against kernel-level debugging. PPL operates entirely in user mode. A kernel debugger\r\nor driver with sufficient privileges can still access and manipulate a PPL-protected process.\r\n3. It is not designed specifically for anti-debugging. PPL restricts some forms of interference, but it doesn’t\r\nprovide the detection or evasion mechanisms commonly expected from dedicated anti-debugging\r\ntechniques.\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 33 of 35\n\nAs you can see, PPL offers an additional hardening layer for products already operating within the Windows\r\nsecurity ecosystem. On the other hand, in the case of typical commercial software, it is difficult to adopt and\r\nshould not be considered standalone protection against debugging or reverse engineering.\r\nProtect your software from unauthorized reverse engineering with Apriorit\r\nDon’t think of modern anti-reverse engineering techniques as a set of isolated tricks. Anti-reverse engineering is a\r\nstrategic layer that helps safeguard your intellectual property, protect sensitive logic, and maintain product\r\nintegrity. At Apriorit, we bring together more than two decades of experience in cybersecurity, low-level\r\ndevelopment, and ethical reverse engineering to help companies build resilient products that can withstand\r\nincreasingly sophisticated analysis attempts.\r\nWhen you partner with Apriorit, you gain a team that is experienced in:\r\nImplementing various anti-debugging techniques, from classic debugger detection to advanced multi-layered protection\r\nImproving existing software with additional security layers, including code obfuscation, integrity\r\nchecks, anti-tampering mechanisms, and environment detection\r\nAuditing and strengthening current protections, identifying weak points in your defensive logic, and\r\nrecommending improvements tailored to your architecture\r\nIntegrating protection into the development lifecycle, making sure that security features work reliably\r\nacross platforms and don’t disrupt performance or maintainability\r\nApriorit specialists continually track evolving reverse-engineering and debugging techniques, allowing us to\r\ndesign solutions that resist both common tools and expert-level analysis. We’ll help you secure a new product or\r\nreinforce an existing one by building a robust protection strategy.\r\nConclusion\r\nSafeguarding your software from malicious reverse engineering activities is complex and tricky. To mitigate such\r\nthreats, your team must acquire strong cybersecurity knowledge, apply the most suitable tools, and use proven\r\ntechniques. One valuable technique against malicious activity is anti-debugging. It helps your team make\r\nreversers’ lives harder and complicate their work as much as possible.\r\nTo get the most out of anti-debugging techniques, involve professional developers with relevant skills. At Apriorit,\r\nwe have expert reverse engineering teams with extensive knowledge and experience in successfully applying anti-debugging protection methods. We are ready to help you safeguard your software, enhance project security, and\r\nsolve non-trivial tasks.\r\nFAQ\r\nAnti-debugging techniques are protective mechanisms built into software to spot and interfere with debugging\r\ntools. Depending on the approach, they might:\r\nVerify process states\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 34 of 35\n\nInspect system structures\r\nTrigger unusual execution flows\r\nExploit quirks of debugging environments to reveal or disrupt a debugger’s presence\r\nThese methods help prevent analysts or attackers from stepping through code or observing internal behavior.\r\nAnti-debugging and anti-tampering have different goals, but they are usually combined to create a more resilient\r\nlayer of protection against illegal reverse engineering. Anti-debugging focuses on detecting or obstructing tools\r\nthat inspect a program at runtime. Anti-tampering techniques, meanwhile, are designed to expose or block\r\nattempts to modify the application’s code or memory.\r\nA debugger attaches to a running process and communicates with the operating system to control it. With the help\r\nof this connection, the debugger can:\r\nPause process execution\r\nInspect variables and memory\r\nSet breakpoints\r\nStep through instructions\r\nDevelopers use this visibility to troubleshoot issues, but attackers can use the same capabilities to study and\r\nmanipulate an application.\r\nThere are many popular approaches, including: \r\nAPI checks\r\nPEB flag inspection\r\nException-based tricks\r\nTiming checks to spot delays caused by breakpoints\r\nHardware breakpoint detection\r\nMany applications combine several of these techniques for stronger protection.\r\nYour team can examine OS-level flags and behaviors. For example, software may query functions like\r\nIsDebuggerPresent, inspect process fields such as BeingDebugged in the PEB, monitor timing anomalies, or use\r\nexception handlers to catch unusual debugger-related behavior. You may also look for debugger-specific windows,\r\nhandles, or loaded modules.\r\nThe appropriate response will depend on the sensitivity of your application. Some solutions immediately stop\r\nexecution to block further inspection. Others deliberately alter behavior, restrict access to protected functionality,\r\nor log the incident for later analysis. More advanced systems may trigger alerts or activate additional verification\r\nsteps to protect critical code paths.\r\nSource: https://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nhttps://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software\r\nPage 35 of 35",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"MITRE"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software"
	],
	"report_names": [
		"367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software"
	],
	"threat_actors": [
		{
			"id": "aa73cd6a-868c-4ae4-a5b2-7cb2c5ad1e9d",
			"created_at": "2022-10-25T16:07:24.139848Z",
			"updated_at": "2026-04-10T02:00:04.878798Z",
			"deleted_at": null,
			"main_name": "Safe",
			"aliases": [],
			"source_name": "ETDA:Safe",
			"tools": [
				"DebugView",
				"LZ77",
				"OpenDoc",
				"SafeDisk",
				"TypeConfig",
				"UPXShell",
				"UsbDoc",
				"UsbExe"
			],
			"source_id": "ETDA",
			"reports": null
		}
	],
	"ts_created_at": 1775438943,
	"ts_updated_at": 1775826760,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/cb1823334e0acd6976acb385ee415949f9240e62.pdf",
		"text": "https://archive.orkl.eu/cb1823334e0acd6976acb385ee415949f9240e62.txt",
		"img": "https://archive.orkl.eu/cb1823334e0acd6976acb385ee415949f9240e62.jpg"
	}
}