{{ page.title }}
8 |19 |
23 | {% include share.html %} 24 | 25 | 26 | -------------------------------------------------------------------------------- /_techniques/assembly.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Anti-Debug: Assembly instructions" 4 | title-image: "/assets/icons/assembly.svg" 5 | categories: anti-debug 6 | tags: assembly 7 | --- 8 | 9 |
Contents
10 | 11 | [Assembly instructions](#assembly) 12 | 13 | * [1. INT 3](#int3) 14 | * [2. INT 2D](#int2d) 15 | * [3. DebugBreak](#debugbreak) 16 | * [4. ICE](#ice) 17 | * [5. Stack Segment Register](#ss_register) 18 | * [6. Instruction Counting](#instruction-counting) 19 | * [7. POPF and Trap Flag](#popf_and_trap_flag) 20 | * [8. Instruction Prefixes](#instruction_prefixes) 21 | * [Mitigations](#mitigations) 22 |23 | 24 |
25 | 26 |
Assembly instructions
27 | The following techniques are intended to detect a debugger presence based on how debuggers behave when the CPU executes a certain instruction. 28 | 29 |30 |
1. INT 3
31 | Instruction INT3 is an interruption which is used as a software breakpoint. Without a debugger present, after getting to the INT3 instruction, the exception EXCEPTION_BREAKPOINT (0x80000003) is generated and an exception handler will be called. If the debugger is present, the control won't be given to the exception handler. 32 | 33 |34 | 35 | C/C++ Code 36 | 37 | 38 | {% highlight c %} 39 | 40 | bool IsDebugged() 41 | { 42 | __try 43 | { 44 | __asm int 3; 45 | return true; 46 | } 47 | __except(EXCEPTION_EXECUTE_HANDLER) 48 | { 49 | return false; 50 | } 51 | } 52 | 53 | {% endhighlight %} 54 | 55 |
Besides the short form of INT3 instruction (0xCC opcode), there is also a long form of this instruction: CD 03 opcode. 56 | 57 | When the exception EXCEPTION_BREAKPOINT occurs, the Windows decrements EIP register to the assumed location of the 0xCC opcode and pass the control to the exception handler. In the case of the long form of the INT3 instruction, EIP will point to the middle of the instruction (i.e. to 0x03 byte). Therefore, EIP should be edited in the exception handler if we want to continue execution after the INT3 instruction (otherwise we'll most likely get an EXCEPTION_ACCESS_VIOLATION exception). If not, we can neglect the instruction pointer modification. 58 | 59 |
60 | 61 | C/C++ Code 62 | 63 | 64 | {% highlight c %} 65 | 66 | bool g_bDebugged = false; 67 | 68 | int filter(unsigned int code, struct _EXCEPTION_POINTERS *ep) 69 | { 70 | g_bDebugged = code != EXCEPTION_BREAKPOINT; 71 | return EXCEPTION_EXECUTE_HANDLER; 72 | } 73 | 74 | bool IsDebugged() 75 | { 76 | __try 77 | { 78 | __asm __emit(0xCD); 79 | __asm __emit(0x03); 80 | } 81 | __except (filter(GetExceptionCode(), GetExceptionInformation())) 82 | { 83 | return g_bDebugged; 84 | } 85 | } 86 | 87 | {% endhighlight %} 88 | 89 | 90 |
91 | 92 |
93 |
2. INT 2D
94 | Just like in the case of INT3 instruction when the instruction INT2D is executed, the exception EXCEPTION_BREAKPOINT is raised as well. But with INT2D, Windows uses the EIP register as an exception address and then increments the EIP register value. Windows also examines the value of the EAX register while INT2D is executed. If it's 1, 3 or 4 on all versions of Windows, or 5 on Vista+, the exception address will be increased by one. 95 | 96 | This instruction can cause problems for some debuggers because after the EIP incrimination, the byte which follows the INT2D instruction will be skipped and the execution might continue from the damaged instruction. 97 | 98 | In the example, we put one-byte NOP instruction after INT2D to skip it in any case. If the program is executed without a debugger, the control will be passed to the exception handler. 99 | 100 |101 | 102 | C/C++ Code 103 | 104 | 105 | {% highlight c %} 106 | 107 | bool IsDebugged() 108 | { 109 | __try 110 | { 111 | __asm xor eax, eax; 112 | __asm int 0x2d; 113 | __asm nop; 114 | return true; 115 | } 116 | __except(EXCEPTION_EXECUTE_HANDLER) 117 | { 118 | return false; 119 | } 120 | } 121 | 122 | {% endhighlight %} 123 | 124 |
125 | 126 |
127 |
3. DebugBreak
128 | As written in DebugBreak documentation, "DebugBreak causes a breakpoint exception to occur in the current process. This allows the calling thread to signal the debugger to handle the exception". 129 | 130 | If the program is executed without a debugger, the control will be passed to the exception handler. Otherwise, the execution will be intercepted by the debugger. 131 | 132 |133 | 134 | C/C++ Code 135 | 136 | 137 | {% highlight c %} 138 | 139 | bool IsDebugged() 140 | { 141 | __try 142 | { 143 | DebugBreak(); 144 | } 145 | __except(EXCEPTION_BREAKPOINT) 146 | { 147 | return false; 148 | } 149 | 150 | return true; 151 | } 152 | 153 | {% endhighlight %} 154 | 155 |
156 | 157 |
158 |
4. ICE
159 | "ICE" is one of Intel's undocumented instructions. Its opcode is 0xF1. It can be used to detect if the program is traced. 160 | 161 | If ICE instruction is executed, the EXCEPTION_SINGLE_STEP (0x80000004) exception will be raised. 162 | 163 | However, if the program has been already traced, the debugger will consider this exception as the normal exception generated by executing the instruction with the SingleStep bit set in the Flags registers. Therefore, under a debugger, the exception handler won't be called and execution will continue after the ICE instruction. 164 | 165 |166 | 167 | C/C++ Code 168 | 169 | 170 | {% highlight c %} 171 | 172 | bool IsDebugged() 173 | { 174 | __try 175 | { 176 | __asm __emit 0xF1; 177 | return true; 178 | } 179 | __except(EXCEPTION_EXECUTE_HANDLER) 180 | { 181 | return false; 182 | } 183 | } 184 | 185 | {% endhighlight %} 186 | 187 |
188 | 189 |
190 |
5. Stack Segment Register
191 | This is a trick that can be used to detect if the program is being traced. 192 | The trick consists of tracing over the following sequence of assembly instructions: 193 | {% highlight asm %} 194 | push ss 195 | pop ss 196 | pushf 197 | {% endhighlight %} 198 | 199 |After single-stepping in a debugger through this code, the Trap Flag will be set. Usually it's not visible as debuggers clear the Trap Flag after each debugger event is delivered. However, if we previously save EFLAGS to the stack, we'll be able to check whether the Trap Flag is set. 200 | 201 |
202 | 203 | C/C++ Code 204 | 205 | 206 | {% highlight c %} 207 | 208 | bool IsDebugged() 209 | { 210 | bool bTraced = false; 211 | 212 | __asm 213 | { 214 | push ss 215 | pop ss 216 | pushf 217 | test byte ptr [esp+1], 1 218 | jz movss_not_being_debugged 219 | } 220 | 221 | bTraced = true; 222 | 223 | movss_not_being_debugged: 224 | // restore stack 225 | __asm popf; 226 | 227 | return bTraced; 228 | } 229 | 230 | {% endhighlight %} 231 | 232 |
233 | 234 |
235 |
6. Instruction Counting
236 | This technique abuses how some debuggers handle EXCEPTION_SINGLE_STEP exceptions. 237 | 238 | The idea of this trick is to set hardware breakpoints to each instruction in some predefined sequence (e.g. sequence of NOPs). Execution of the instruction with a hardware breakpoint on it raises the EXCEPTION_SINGLE_STEP exception which can be caught by a vectored exception handler. In the exception handler, we increment a register which plays the role of instruction counter (EAX in our case) and the instruction pointer EIP to pass the control to the next instruction in the sequence. Therefore, each time the control is passed to the next instruction in our sequence, the exception is raised and the counter is incremented. After the sequence is finished, we check the counter and if it is not equal to the length of our sequence, we consider it as if the program is being debugged. 239 | 240 |241 | 242 | C/C++ Code 243 | 244 | 245 | {% highlight c %} 246 | 247 | #include "hwbrk.h" 248 | 249 | static LONG WINAPI InstructionCountingExeptionHandler(PEXCEPTION_POINTERS pExceptionInfo) 250 | { 251 | if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) 252 | { 253 | pExceptionInfo->ContextRecord->Eax += 1; 254 | pExceptionInfo->ContextRecord->Eip += 1; 255 | return EXCEPTION_CONTINUE_EXECUTION; 256 | } 257 | return EXCEPTION_CONTINUE_SEARCH; 258 | } 259 | 260 | __declspec(naked) DWORD WINAPI InstructionCountingFunc(LPVOID lpThreadParameter) 261 | { 262 | __asm 263 | { 264 | xor eax, eax 265 | nop 266 | nop 267 | nop 268 | nop 269 | cmp al, 4 270 | jne being_debugged 271 | } 272 | 273 | ExitThread(FALSE); 274 | 275 | being_debugged: 276 | ExitThread(TRUE); 277 | } 278 | 279 | bool IsDebugged() 280 | { 281 | PVOID hVeh = nullptr; 282 | HANDLE hThread = nullptr; 283 | bool bDebugged = false; 284 | 285 | __try 286 | { 287 | hVeh = AddVectoredExceptionHandler(TRUE, InstructionCountingExeptionHandler); 288 | if (!hVeh) 289 | __leave; 290 | 291 | hThread = CreateThread(0, 0, InstructionCountingFunc, NULL, CREATE_SUSPENDED, 0); 292 | if (!hThread) 293 | __leave; 294 | 295 | PVOID pThreadAddr = &InstructionCountingFunc; 296 | // Fix thread entry address if it is a JMP stub (E9 XX XX XX XX) 297 | if (*(PBYTE)pThreadAddr == 0xE9) 298 | pThreadAddr = (PVOID)((DWORD)pThreadAddr + 5 + *(PDWORD)((PBYTE)pThreadAddr + 1)); 299 | 300 | for (auto i = 0; i < m_nInstructionCount; i++) 301 | m_hHwBps[i] = SetHardwareBreakpoint( 302 | hThread, HWBRK_TYPE_CODE, HWBRK_SIZE_1, (PVOID)((DWORD)pThreadAddr + 2 + i)); 303 | 304 | ResumeThread(hThread); 305 | WaitForSingleObject(hThread, INFINITE); 306 | 307 | DWORD dwThreadExitCode; 308 | if (TRUE == GetExitCodeThread(hThread, &dwThreadExitCode)) 309 | bDebugged = (TRUE == dwThreadExitCode); 310 | } 311 | __finally 312 | { 313 | if (hThread) 314 | CloseHandle(hThread); 315 | 316 | for (int i = 0; i < 4; i++) 317 | { 318 | if (m_hHwBps[i]) 319 | RemoveHardwareBreakpoint(m_hHwBps[i]); 320 | } 321 | 322 | if (hVeh) 323 | RemoveVectoredExceptionHandler(hVeh); 324 | } 325 | 326 | return bDebugged; 327 | } 328 | 329 | {% endhighlight %} 330 | 331 |
332 | 333 |
334 |
7. POPF and Trap Flag
335 | This is another trick that can indicate whether a program is being traced. 336 | 337 | There is a Trap Flag in the Flags register. When the Trap Flag is set, the exception SINGLE_STEP is raised. However, if we traced the code, the Trap Flag will be cleared by a debugger so we won't see the exception. 338 | 339 |340 | 341 | C/C++ Code 342 | 343 | 344 | {% highlight c %} 345 | 346 | bool IsDebugged() 347 | { 348 | __try 349 | { 350 | __asm 351 | { 352 | pushfd 353 | mov dword ptr [esp], 0x100 354 | popfd 355 | nop 356 | } 357 | return true; 358 | } 359 | __except(GetExceptionCode() == EXCEPTION_SINGLE_STEP 360 | ? EXCEPTION_EXECUTE_HANDLER 361 | : EXCEPTION_CONTINUE_EXECUTION) 362 | { 363 | return false; 364 | } 365 | } 366 | 367 | {% endhighlight %} 368 | 369 |
370 | 371 |
372 |
8. Instruction Prefixes
373 | This trick works only in some debuggers. It abuses the way how these debuggers handle instruction prefixes. 374 | 375 | If we execute the following code in OllyDbg, after stepping to the first byte F3, we'll immediately get to the end of try block. The debugger just skips the prefix and gives the control to the INT1 instruction. 376 | 377 | If we run the same code without a debugger, an exception will be raised and we'll get to except block. 378 | 379 |380 | 381 | C/C++ Code 382 | 383 | 384 | {% highlight c %} 385 | 386 | bool IsDebugged() 387 | { 388 | __try 389 | { 390 | // 0xF3 0x64 disassembles as PREFIX REP: 391 | __asm __emit 0xF3 392 | __asm __emit 0x64 393 | // One byte INT 1 394 | __asm __emit 0xF1 395 | return true; 396 | } 397 | __except(EXCEPTION_EXECUTE_HANDLER) 398 | { 399 | return false; 400 | } 401 | } 402 | 403 | {% endhighlight %} 404 | 405 |
406 | 407 |
408 |
Mitigations
409 | * During debugging: 410 | * The best way to mitigate all the following checks is to patch them with NOP instructions. 411 | * Regarding anti-tracing techniques: instead of patching the code, we can simply set a breakpoint in the code which follows the check and run the program till this breakpoint. 412 | * For anti-anti-debug tool development: No mitigation. 413 | -------------------------------------------------------------------------------- /_techniques/exceptions.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Anti-Debug: Exceptions" 4 | title-image: "/assets/icons/exceptions.svg" 5 | categories: anti-debug 6 | tags: exceptions 7 | --- 8 | 9 |Contents
10 | 11 | [Exceptions](#exceptions) 12 | 13 | * [1. UnhandledExceptionFilter()](#unhandledexceptionfilter) 14 | * [2. RaiseException()](#raiseexception) 15 | * [3. Hiding Control Flow with Exception Handlers](#hiding-cf-with-eh) 16 | * [Mitigations](#mitigations) 17 |18 | 19 |
20 | 21 |
Exceptions
22 | The following methods deliberately cause exceptions to verify if the further behavior is not typical for a process running without a debugger. 23 | 24 |25 |
1. UnhandledExceptionFilter()
26 | If an exception occurs and no exception handler is registered (or it is registered but doesn't handle such an exception), the kernel32!UnhandledExceptionFilter() function will be called. It is possible to register a custom unhandled exception filter using the kernel32!SetUnhandledExceptionFilter(). But if the program is running under a debugger, the custom filter won't be called and the exception will be passed to the debugger. Therefore, if the unhandled exception filter is registered and the control is passed to it, then the process is not running with a debugger. 27 | 28 |29 | 30 | x86 Assembly (FASM) 31 | 32 | 33 | {% highlight asm %} 34 | include 'win32ax.inc' 35 | 36 | .code 37 | 38 | start: 39 | jmp begin 40 | 41 | not_debugged: 42 | invoke MessageBox,HWND_DESKTOP,"Not Debugged","",MB_OK 43 | invoke ExitProcess,0 44 | 45 | begin: 46 | invoke SetUnhandledExceptionFilter, not_debugged 47 | int 3 48 | jmp being_debugged 49 | 50 | being_debugged: 51 | invoke MessageBox,HWND_DESKTOP,"Debugged","",MB_OK 52 | invoke ExitProcess,0 53 | 54 | .end start 55 | {% endhighlight %} 56 | 57 |
58 | 59 | C/C++ Code 60 | 61 | 62 | {% highlight c %} 63 | 64 | LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo) 65 | { 66 | PCONTEXT ctx = pExceptionInfo->ContextRecord; 67 | ctx->Eip += 3; // Skip \xCC\xEB\x?? 68 | return EXCEPTION_CONTINUE_EXECUTION; 69 | } 70 | 71 | bool Check() 72 | { 73 | bool bDebugged = true; 74 | SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)UnhandledExceptionFilter); 75 | __asm 76 | { 77 | int 3 // CC 78 | jmp near being_debugged // EB ?? 79 | } 80 | bDebugged = false; 81 | 82 | being_debugged: 83 | return bDebugged; 84 | } 85 | 86 | {% endhighlight %} 87 | 88 |
89 | 90 |
91 |
2. RaiseException()
92 | Exceptions such as DBC_CONTROL_C or DBG_RIPEVENT are not passed to exception handlers of the current process and are consumed by a debugger. This lets us register an exception handler, raise these exceptions using the kernel32!RaiseException() function, and check whether the control is passed to our handler. If the exception handler is not called, the process is likely under debugging. 93 | 94 |95 | 96 | C/C++ Code 97 | 98 | 99 | {% highlight c %} 100 | 101 | bool Check() 102 | { 103 | __try 104 | { 105 | RaiseException(DBG_CONTROL_C, 0, 0, NULL); 106 | return true; 107 | } 108 | __except(DBG_CONTROL_C == GetExceptionCode() 109 | ? EXCEPTION_EXECUTE_HANDLER 110 | : EXCEPTION_CONTINUE_SEARCH) 111 | { 112 | return false; 113 | } 114 | } 115 | 116 | {% endhighlight %} 117 | 118 |
119 | 120 |
121 |
3. Hiding Control Flow with Exception Handlers
122 | This approach does not check whether a debugger is present, but it helps to hide the control flow of the program in the sequence of exception handlers. 123 | 124 | We can register an exception handler (structured or vectored) which raises another exception which is passed to the next handler which raises the next exception, and so on. Finally, the sequence of handlers should lead to the procedure that we wanted to hide. 125 | 126 |Using Structured Exception Handlers: 127 | 128 |
129 | 130 | C/C++ Code 131 | 132 | 133 | {% highlight c %} 134 | 135 | #include
Using Vectored Exception Handlers: 183 | 184 |
185 | 186 | C/C++ Code 187 | 188 | 189 | {% highlight c %} 190 | 191 | #include
231 | 232 |
233 |
Mitigations
234 | * During debugging: 235 | * For debugger detection checks: Just fill the corresponding check with NOPs. 236 | * For Control Flow hiding: You have to manually trace the program till the payload. 237 | * For anti-anti-debug tool development: The issue with these type of techniques is that different debuggers consume different exceptions and do not return them to the debugger. This means that you have to implement a plugin for a specific debugger and change the behavior of the event handlers which are triggered after the corresponding exceptions. 238 | -------------------------------------------------------------------------------- /_techniques/interactive.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Anti-Debug: Direct debugger interaction" 4 | title-image: "/assets/icons/interactive.svg" 5 | categories: anti-debug 6 | tags: interactive 7 | --- 8 | 9 |Contents
10 | 11 | [Direct debugger interaction](#interactive) 12 | 13 | * [1. Self-Debugging](#self-debugging) 14 | * [2. GenerateConsoleCtrlEvent()](#generateconsolectrlevent) 15 | * [3. BlockInput()](#blockinput) 16 | * [4. NtSetInformationThread()](#ntsetinformationthread) 17 | * [5. EnumWindows() and SuspendThread()](#suspendthread) 18 | * [6. SwitchDesktop()](#switchdesktop) 19 | * [7. OutputDebugString()](#outputdebugstring) 20 | * [Mitigations](#mitigations) 21 |22 | 23 |
24 | 25 |
Direct debugger interaction
26 | The following techniques let the running process manage a user interface or engage with its parent process to discover inconsistencies that are inherent for a debugged process. 27 | 28 |29 |
1. Self-Debugging
30 | There are at least three functions that can be used to attach as a debugger to a running process: 31 | 32 | * kernel32!DebugActiveProcess() 33 | * ntdll!DbgUiDebugActiveProcess() 34 | * ntdll!NtDebugActiveProcess() 35 | 36 | As only one debugger can be attached to a process at a time, a failure to attach to the process might indicate the presence of another debugger. 37 | 38 | In the example below, we run the second instance of our process which tries to attach a debugger to its parent (the first instance of the process). If kenel32!DebugActiveProcess() finishes unsuccessfully, we set the named event which was created by the first instance. If the event is set, the first instance understands that a debugger is present. 39 | 40 |41 | 42 | C/C++ Code 43 | 44 | 45 | {% highlight c %} 46 | 47 | #define EVENT_SELFDBG_EVENT_NAME L"SelfDebugging" 48 | 49 | bool IsDebugged() 50 | { 51 | WCHAR wszFilePath[MAX_PATH], wszCmdLine[MAX_PATH]; 52 | STARTUPINFO si = { sizeof(si) }; 53 | PROCESS_INFORMATION pi; 54 | HANDLE hDbgEvent; 55 | 56 | hDbgEvent = CreateEventW(NULL, FALSE, FALSE, EVENT_SELFDBG_EVENT_NAME); 57 | if (!hDbgEvent) 58 | return false; 59 | 60 | if (!GetModuleFileNameW(NULL, wszFilePath, _countof(wszFilePath))) 61 | return false; 62 | 63 | swprintf_s(wszCmdLine, L"%s %d", wszFilePath, GetCurrentProcessId()); 64 | if (CreateProcessW(NULL, wszCmdLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) 65 | { 66 | WaitForSingleObject(pi.hProcess, INFINITE); 67 | CloseHandle(pi.hProcess); 68 | CloseHandle(pi.hThread); 69 | 70 | return WAIT_OBJECT_0 == WaitForSingleObject(hDbgEvent, 0); 71 | } 72 | 73 | return false; 74 | } 75 | 76 | bool EnableDebugPrivilege() 77 | { 78 | bool bResult = false; 79 | HANDLE hToken = NULL; 80 | DWORD ec = 0; 81 | 82 | do 83 | { 84 | if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) 85 | break; 86 | 87 | TOKEN_PRIVILEGES tp; 88 | tp.PrivilegeCount = 1; 89 | if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid)) 90 | break; 91 | 92 | tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 93 | if( !AdjustTokenPrivileges( hToken, FALSE, &tp, sizeof(tp), NULL, NULL)) 94 | break; 95 | 96 | bResult = true; 97 | } 98 | while (0); 99 | 100 | if (hToken) 101 | CloseHandle(hToken); 102 | 103 | return bResult; 104 | } 105 | 106 | int main(int argc, char **argv) 107 | { 108 | if (argc < 2) 109 | { 110 | if (IsDebugged()) 111 | ExitProcess(0); 112 | } 113 | else 114 | { 115 | DWORD dwParentPid = atoi(argv[1]); 116 | HANDLE hEvent = OpenEventW(EVENT_MODIFY_STATE, FALSE, EVENT_SELFDBG_EVENT_NAME); 117 | if (hEvent && EnableDebugPrivilege()) 118 | { 119 | if (FALSE == DebugActiveProcess(dwParentPid)) 120 | SetEvent(hEvent); 121 | else 122 | DebugActiveProcessStop(dwParentPid); 123 | } 124 | ExitProcess(0); 125 | } 126 | 127 | // ... 128 | 129 | return 0; 130 | } 131 | 132 | {% endhighlight %} 133 | 134 |
135 | 136 |
137 |
2. GenerateConsoleCtrlEvent()
138 | When a user presses Ctrl+C or Ctrl+Break and a console window is in the focus, Windows checks if there is a handler for this event. All console processes have a default handler function that calls the kernel32!ExitProcess() function. However, we can register a custom handler for these events which neglects the Ctrl+C or Ctrl+Break signals. 139 | 140 | However, if a console process is being debugged and CTRL+C signals have not been disabled, the system generates a DBG_CONTROL_C exception. Usually this exception is intercepted by a debugger, but if we register an exception handler, we will be able to check whether DBG_CONTROL_C is raised. If we intercepted the DBG_CONTROL_C exception in our own exception handler, it may indicate that the process is being debugged. 141 | 142 |143 | 144 | C/C++ Code 145 | 146 | 147 | {% highlight c %} 148 | 149 | bool g_bDebugged{ false }; 150 | std::atomic
208 | 209 |
210 |
3. BlockInput()
211 | The function user32!BlockInput() can block all mouse and keyboard events, which is quite an effective way to disable a debugger. On Windows Vista and higher versions, this call requires administrator privileges. 212 | 213 | We can also detect whether a tool that hooks the user32!BlockInput() and other anti-debug calls is present. The function allows the input to be blocked only once. The second call will return FALSE. If the function returns TRUE regardless of the input, it may indicate that some hooking solution is present. 214 | 215 |216 | 217 | C/C++ Code 218 | 219 | 220 | {% highlight c %} 221 | 222 | bool IsHooked () 223 | { 224 | BOOL bFirstResult = FALSE, bSecondResult = FALSE; 225 | __try 226 | { 227 | bFirstResult = BlockInput(TRUE); 228 | bSecondResult = BlockInput(TRUE); 229 | } 230 | __finally 231 | { 232 | BlockInput(FALSE); 233 | } 234 | return bFirstResult && bSecondResult; 235 | } 236 | 237 | {% endhighlight %} 238 | 239 |
240 | 241 |
242 |
4. NtSetInformationThread()
243 | The function ntdll!NtSetInformationThread() can be used to hide a thread from a debugger. It is possible with a help of the undocumented value THREAD_INFORMATION_CLASS::ThreadHideFromDebugger (0x11). This is intended to be used by an external process, but any thread can use it on itself. 244 | 245 | After the thread is hidden from the debugger, it will continue running but the debugger won’t receive events related to this thread. This thread can perform anti-debugging checks such as code checksum, debug flags verification, etc. 246 | 247 | However, if there is a breakpoint in the hidden thread or if we hide the main thread from the debugger, the process will crash and the debugger will be stuck. 248 | 249 | In the example, we hide the current thread from the debugger. This means that if we trace this code in the debugger or put a breakpoint to any instruction of this thread, the debugging will be stuck once ntdll!NtSetInformationThread() is called. 250 | 251 |252 | 253 | C/C++ Code 254 | 255 | 256 | {% highlight c %} 257 | 258 | #define NtCurrentThread ((HANDLE)-2) 259 | 260 | bool AntiDebug() 261 | { 262 | NTSTATUS status = ntdll::NtSetInformationThread( 263 | NtCurrentThread, 264 | ntdll::THREAD_INFORMATION_CLASS::ThreadHideFromDebugger, 265 | NULL, 266 | 0); 267 | return status >= 0; 268 | } 269 | 270 | {% endhighlight %} 271 | 272 |
273 | 274 |
275 |
5. EnumWindows() and SuspendThread()
276 | The idea of this technique is to suspend the owning thread of the parent process. 277 | 278 | First, we need to verify whether the parent process is a debugger. This can be achieved by enumerating all top-level windows on the screen (using user32!EnumWindows() or user32!EnumThreadWindows()), searching the window for which process ID is the ID of the parent process (using user32!GetWindowThreadProcessId()), and checking the title of this window (by user32!GetWindowTextW()). If the window title of the parent process looks like a debugger title, we can suspend the owning thread using kernel32!SuspendThread() or ntdll!NtSuspendThread(). 279 | 280 |281 | 282 | C/C++ Code 283 | 284 | 285 | {% highlight c %} 286 | 287 | DWORD g_dwDebuggerProcessId = -1; 288 | 289 | BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam) 290 | { 291 | DWORD dwProcessId = *(PDWORD)lParam; 292 | 293 | DWORD dwWindowProcessId; 294 | GetWindowThreadProcessId(hwnd, &dwWindowProcessId); 295 | 296 | if (dwProcessId == dwWindowProcessId) 297 | { 298 | std::wstring wsWindowTitle{ string_heper::ToLower(std::wstring(GetWindowTextLengthW(hwnd) + 1, L'\0')) }; 299 | GetWindowTextW(hwnd, &wsWindowTitle[0], wsWindowTitle.size()); 300 | 301 | if (string_heper::FindSubstringW(wsWindowTitle, L"dbg") || 302 | string_heper::FindSubstringW(wsWindowTitle, L"debugger")) 303 | { 304 | g_dwDebuggerProcessId = dwProcessId; 305 | return FALSE; 306 | } 307 | return FALSE; 308 | } 309 | 310 | return TRUE; 311 | } 312 | 313 | bool IsDebuggerProcess(DWORD dwProcessId) const 314 | { 315 | EnumWindows(EnumWindowsProc, reinterpret_cast
352 | 353 |
354 |
6. SwitchDesktop()
355 | Windows supports multiple desktops per session. It is possible to select a different active desktop, which has the effect of hiding the windows of the previously active desktop, and with no obvious way to switch back to the old desktop. 356 | 357 | Further, the mouse and keyboard events from the debugged process desktop will no longer be delivered to the debugger, because their source is no longer shared. This obviously makes debugging impossible. 358 | 359 |360 | 361 | C/C++ Code 362 | 363 | 364 | {% highlight c %} 365 | 366 | BOOL Switch() 367 | { 368 | HDESK hNewDesktop = CreateDesktopA( 369 | m_pcszNewDesktopName, 370 | NULL, 371 | NULL, 372 | 0, 373 | DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP, 374 | NULL); 375 | if (!hNewDesktop) 376 | return FALSE; 377 | 378 | return SwitchDesktop(hNewDesktop); 379 | } 380 | 381 | {% endhighlight %} 382 | 383 |
384 | 385 |
386 |
7. OutputDebugString()
387 | This technique is deprecated as it works only for Windows versions earlier than Vista. However, this technique is too well known to not mention here. 388 | 389 | The idea is simple. If a debugger is not present and kernel32!OutputDebugString is called, then an error will occur. 390 | 391 |392 | 393 | C/C++ Code (variant1) 394 | 395 | 396 | {% highlight c %} 397 | 398 | bool IsDebugged() 399 | { 400 | if (IsWindowsVistaOrGreater()) 401 | return false; 402 | 403 | DWORD dwLastError = GetLastError(); 404 | OutputDebugString(L"AntiDebug_OutputDebugString_v1"); 405 | return GetLastError() != dwLastError; 406 | } 407 | 408 | {% endhighlight %} 409 | 410 |
411 | 412 | C/C++ Code (variant2) 413 | 414 | 415 | {% highlight c %} 416 | 417 | bool IsDebugged() 418 | { 419 | if (IsWindowsVistaOrGreater()) 420 | return false; 421 | 422 | DWORD dwErrVal = 0x666; 423 | SetLastError(dwErrVal); 424 | OutputDebugString(L"AntiDebug_OutputDebugString_v2"); 425 | return GetLastError() != dwErrVal; 426 | } 427 | 428 | {% endhighlight %} 429 | 430 |
431 | 432 |
433 |
Mitigations
434 | During debugging, it is better to skip suspicious function calls (e.g. fill them with NOPs). 435 | 436 | If you write an anti-anti-debug solution, all the following functions can be hooked: 437 | 438 | * kernel32!DebugActiveProcess 439 | * ntdll!DbgUiDebugActiveProcess 440 | * ntdll!NtDebugActiveProcess 441 | * kernel32!GenerateConsoleCtrlEvent() 442 | * user32!NtUserBlockInput 443 | * ntdll!NtSetInformationThread 444 | * user32!NtUserBuildHwndList (for filtering EnumWindows output) 445 | * kernel32!SuspendThread 446 | * user32!SwitchDesktop 447 | * kernel32!OutputDebugStringW 448 | 449 | Hooked functions can check input arguments and modify the original function behavior. -------------------------------------------------------------------------------- /_techniques/misc.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Anti-Debug: Misc" 4 | title-image: "/assets/icons/misc.svg" 5 | categories: anti-debug 6 | tags: misc 7 | --- 8 | 9 |Contents
10 | 11 | [Misc](#misc) 12 | 13 | * [1. FindWindow()](#findwindow) 14 | * [2. Parent Process Check](#parent-process-check) 15 | * [2.1. NtQueryInformationProcess()](#parent-process-check-ntqueryinformationprocess) 16 | * [2.2. CreateToolhelp32Snapshot()](#parent-process-check-createtoolhelp32snapshot) 17 | * [3. Selectors](#selectors) 18 | * [4. DbgPrint()](#dbgprint) 19 | * [5. DbgSetDebugFilterState()](#dbgsetdebugfilterstate) 20 | * [6. NtYieldExecution() / SwitchToThread()](#switchtothread) 21 | * [7. VirtualAlloc() / GetWriteWatch()](#getwritewatch) 22 | * [Mitigations](#mitigations) 23 |24 | 25 |
26 | 27 |
Misc
28 | 29 |30 |
1. FindWindow()
31 | This technique includes the simple enumeration of window classes in the system and comparing them with known windows classes of debuggers. 32 | 33 | The following functions can be used: 34 | 35 | * user32!FindWindowW() 36 | * user32!FindWindowA() 37 | * user32!FindWindowExW() 38 | * user32!FindWindowExA() 39 | 40 |41 | 42 | C/C++ Code 43 | 44 | 45 | {% highlight c %} 46 | 47 | const std::vector
73 | 74 |
75 |
2. Parent Process Check
76 | Normally, a user-mode process is executed by double-clicking on a file icon. If the process is executed this way, its parent process will be the shell process ("explorer.exe"). 77 | 78 | The main idea of the two following methods is to compare the PID of the parent process with the PID of "explorer.exe". 79 | 80 |81 |
2.1. NtQueryInformationProcess()
82 | This method includes obtaining the shell process window handle using user32!GetShellWindow() and obtaining its process ID by calling user32!GetWindowThreadProcessId(). 83 | 84 | Then, the parent process ID can be obtained from the PROCESS_BASIC_INFORMATION structure by calling ntdll!NtQueryInformationProcess() with the ProcessBasicInformation class. 85 | 86 |87 | 88 | C/C++ Code 89 | 90 | 91 | {% highlight c %} 92 | 93 | bool IsDebugged() 94 | { 95 | HWND hExplorerWnd = GetShellWindow(); 96 | if (!hExplorerWnd) 97 | return false; 98 | 99 | DWORD dwExplorerProcessId; 100 | GetWindowThreadProcessId(hExplorerWnd, &dwExplorerProcessId); 101 | 102 | ntdll::PROCESS_BASIC_INFORMATION ProcessInfo; 103 | NTSTATUS status = ntdll::NtQueryInformationProcess( 104 | GetCurrentProcess(), 105 | ntdll::PROCESS_INFORMATION_CLASS::ProcessBasicInformation, 106 | &ProcessInfo, 107 | sizeof(ProcessInfo), 108 | NULL); 109 | if (!NT_SUCCESS(status)) 110 | return false; 111 | 112 | return (DWORD)ProcessInfo.InheritedFromUniqueProcessId != dwExplorerProcessId; 113 | } 114 | 115 | {% endhighlight %} 116 | 117 |
118 |
2.2. CreateToolhelp32Snapshot()
119 | The parent process ID and the parent process name can be obtained using the kernel32!CreateToolhelp32Snapshot() and kernel32!Process32Next() functions. 120 | 121 |122 | 123 | C/C++ Code 124 | 125 | 126 | {% highlight c %} 127 | 128 | DWORD GetParentProcessId(DWORD dwCurrentProcessId) 129 | { 130 | DWORD dwParentProcessId = -1; 131 | PROCESSENTRY32W ProcessEntry = { 0 }; 132 | ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); 133 | 134 | HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 135 | if(Process32FirstW(hSnapshot, &ProcessEntry)) 136 | { 137 | do 138 | { 139 | if (ProcessEntry.th32ProcessID == dwCurrentProcessId) 140 | { 141 | dwParentProcessId = ProcessEntry.th32ParentProcessID; 142 | break; 143 | } 144 | } while(Process32NextW(hSnapshot, &ProcessEntry)); 145 | } 146 | 147 | CloseHandle(hSnapshot); 148 | return dwParentProcessId; 149 | } 150 | 151 | bool IsDebugged() 152 | { 153 | bool bDebugged = false; 154 | DWORD dwParentProcessId = GetParentProcessId(GetCurrentProcessId()); 155 | 156 | PROCESSENTRY32 ProcessEntry = { 0 }; 157 | ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); 158 | 159 | HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 160 | if(Process32First(hSnapshot, &ProcessEntry)) 161 | { 162 | do 163 | { 164 | if ((ProcessEntry.th32ProcessID == dwParentProcessId) && 165 | (strcmp(ProcessEntry.szExeFile, "explorer.exe"))) 166 | { 167 | bDebugged = true; 168 | break; 169 | } 170 | } while(Process32Next(hSnapshot, &ProcessEntry)); 171 | } 172 | 173 | CloseHandle(hSnapshot); 174 | return bDebugged; 175 | } 176 | 177 | {% endhighlight %} 178 | 179 |
180 | 181 |
182 |
3. Selectors
183 | Selector values might appear to be stable, but they are actually volatile in certain circumstances, and also depending on the version of Windows. For example, a selector value can be set within a thread, but it might not hold that value for very long. Certain events might cause the selector value to be changed back to its default value. One such event is an exception. In the context of a debugger, the single-step exception is still an 184 | exception, which can cause some unexpected behavior. 185 | 186 | x86 Assembly 187 | 188 | 189 | {% highlight nasm %} 190 | xor eax, eax 191 | push fs 192 | pop ds 193 | l1: xchg [eax], cl 194 | xchg [eax], cl 195 | {% endhighlight %} 196 | 197 |198 | 199 | On the 64-bit versions of Windows, single-stepping through this code will cause an access violation exception at l1 because the DS selector will be restored to its default value even before l1 is reached. On the 32-bit versions of Windows, the DS selector will not have its value restored, unless a non-debugging exception occurs. The version-specific difference in behaviors expands even further if the SS selector is used. On the 64-bit versions of Windows, the SS selector will be restored to its default value, as in the DS selector case. However, on the 32-bit versions of Windows, the SS selector value will not be restored, even if an exception occurs. 200 | 201 | x86-64 Assembly 202 | 203 | 204 | {% highlight nasm %} 205 | xor eax, eax 206 | push offset l2 207 | push d fs:[eax] 208 | mov fs:[eax], esp 209 | push fs 210 | pop ss 211 | xchg [eax], cl 212 | xchg [eax], cl 213 | l1: int 3 ;force exception to occur 214 | l2: ;looks like it would be reached 215 | ;if an exception occurs 216 | ... 217 | {% endhighlight %} 218 | 219 |
220 | 221 | then when the "int 3" instruction is reached at l1 and the breakpoint exception occurs, the exception handler at l2 is not called as expected. Instead, the process is simply terminated. 222 | 223 | A variation of this technique detects the single-step event by simply checking if the assignment was successful. 224 | 225 | {% highlight nasm %} 226 | push 3 227 | pop gs 228 | mov ax, gs 229 | cmp al, 3 230 | jne being_debugged 231 | {% endhighlight %} 232 | 233 |
234 | 235 | The FS and GS selectors are special cases. For certain values, they will be affected by the single-step event, even on the 32-bit versions of Windows. However, in the case of the FS selector (and, technically, the GS selector), it will be not restored to its default value on the 32-bit versions of Windows, if it was set to a value from zero to three. Instead, it will be set to zero (the GS selector is affected in the same way, but the default value for the GS selector is zero). On the 64-bit versions of Windows, it (they) will be restored to its (their) default value. 236 | 237 | This code is also vulnerable to a race condition caused by a thread-switch event. When a thread-switch event occurs, it behaves like an exception, and will cause the selector values to be altered, which, in the case of the FS selector, means that it will be set to zero. 238 | 239 | A variation of this technique solves that problem by waiting intentionally for a thread-switch event to occur. 240 | 241 | {% highlight nasm %} 242 | push 3 243 | pop gs 244 | l1: mov ax, gs 245 | cmp al, 3 246 | je l1 247 | {% endhighlight %} 248 | 249 |
250 | 251 | However, this code is vulnerable to the problem that it was trying to detect in the first place, because it does not check if the original assignment was successful. Of course, the two code snippets can be combined to produce the desired effect, by waiting until the thread-switch event occurs, and then performing the assignment within the window of time that should exist until the next one occurs. [Ferrie] 252 | 253 |
254 | 255 | C/C++ Code 256 | 257 | 258 | {% highlight c %} 259 | 260 | bool IsTraced() 261 | { 262 | __asm 263 | { 264 | push 3 265 | pop gs 266 | 267 | __asm SeclectorsLbl: 268 | mov ax, gs 269 | cmp al, 3 270 | je SeclectorsLbl 271 | 272 | push 3 273 | pop gs 274 | mov ax, gs 275 | cmp al, 3 276 | jne Selectors_Debugged 277 | } 278 | 279 | return false; 280 | 281 | Selectors_Debugged: 282 | return true; 283 | } 284 | 285 | {% endhighlight %} 286 | 287 |
288 | 289 |
290 |
4. DbgPrint()
291 | The debug functions such as ntdll!DbgPrint() and kernel32!OutputDebugStringW() cause the exception DBG_PRINTEXCEPTION_C (0x40010006). If a program is executed with an attached debugger, then the debugger will handle this exception. But if no debugger is present, and an exception handler is registered, this exception will be caught by the exception handler. 292 | 293 |294 | 295 | C/C++ Code 296 | 297 | 298 | {% highlight c %} 299 | 300 | bool IsDebugged() 301 | { 302 | __try 303 | { 304 | RaiseException(DBG_PRINTEXCEPTION_C, 0, 0, 0); 305 | } 306 | __except(GetExceptionCode() == DBG_PRINTEXCEPTION_C) 307 | { 308 | return false; 309 | } 310 | 311 | return true; 312 | } 313 | 314 | {% endhighlight %} 315 | 316 |
317 | 318 |
319 |
5. DbgSetDebugFilterState()
320 | The functions ntdll!DbgSetDebugFilterState() and ntdll!NtSetDebugFilterState() only set a flag which will be checked be a kernel-mode debugger if it is present. Therefore, if a kernel debugger is attached to the system, these functions will succeed. However, the functions can also succeed because of side-effects caused by some user-mode debuggers. These functions require administrator privileges. 321 | 322 |323 | 324 | C/C++ Code 325 | 326 | 327 | {% highlight c %} 328 | 329 | bool IsDebugged() 330 | { 331 | return NT_SUCCESS(ntdll::NtSetDebugFilterState(0, 0, TRUE)); 332 | } 333 | 334 | {% endhighlight %} 335 | 336 |
337 | 338 |
339 |
6. NtYieldExecution() / SwitchToThread()
340 | This method is not really reliable because it only shows if there a high priority thread in the current process. However, it could be used as an anti-tracing technique. 341 | 342 | When an application is traced in a debugger and a single-step is executed, the context can't be switched to other thread. This means that ntdll!NtYieldExecution() returns STATUS_NO_YIELD_PERFORMED (0x40000024), which leads to kernel32!SwitchToThread() returning zero. 343 | 344 | The strategy of using this technique is that there is a loop which modifies some counter if kernel32!SwitchToThread() returns zero, or ntdll!NtYieldExecution() returns STATUS_NO_YIELD_PERFORMED. This can be a loop which decrypts strings or some other loop which is supposed to be analyzed manually in a debugger. If the counter has the expected value (expected i.e. the value if all kernel32!SwitchToThread() returned zero) after leaving the loop, we consider that the debugger is present. 345 | 346 | In the example below, we define a one-byte counter (initialized with 0) which shifts one bit to the left if kernel32!SwitchToThread returns zero. If it shifts 8 times, then the value of the counter will become 0 and the debugger is considered to be present. 347 | 348 |349 | 350 | C/C++ Code 351 | 352 | 353 | {% highlight c %} 354 | 355 | bool IsDebugged() 356 | { 357 | BYTE ucCounter = 1; 358 | for (int i = 0; i < 8; i++) 359 | { 360 | Sleep(0x0F); 361 | ucCounter <<= (1 - SwitchToThread()); 362 | } 363 | 364 | return ucCounter == 0; 365 | } 366 | 367 | {% endhighlight %} 368 | 369 |
370 | 371 |
372 |
7. VirtualAlloc() / GetWriteWatch()
373 | This technique was described as a suggestion for a famous al-khaser solution, a tool for testing VMs, debuggers, sandboxes, AV, etc. against many malware-like defences. 374 | 375 | The idea is drawn from the documentation for GetWriteWatch function where the following is stated in a "Remarks" section: 376 | 377 | "When you call the VirtualAlloc function to reserve or commit memory, you can specify MEM_WRITE_WATCH. This value causes the system to keep track of the pages that are written to in the committed memory region. You can call the GetWriteWatch function to retrieve the addresses of the pages that have been written to since the region has been allocated or the write-tracking state has been reset". 378 | 379 | This feature can be used to track debuggers that may modify memory pages outside the expected pattern. 380 | 381 |382 | 383 | C/C++ Code (variant 1) 384 | 385 | 386 | {% highlight c %} 387 | 388 | bool Generic::CheckWrittenPages1() const { 389 | const int SIZE_TO_CHECK = 4096; 390 | 391 | PVOID* addresses = static_cast
425 | 426 | C/C++ Code (variant 2) 427 | 428 | 429 | {% highlight c %} 430 | 431 | bool Generic::CheckWrittenPages2() const { 432 | BOOL result = FALSE, error = FALSE; 433 | 434 | const int SIZE_TO_CHECK = 4096; 435 | 436 | PVOID* addresses = static_cast
488 | 489 |
490 |
Mitigations
491 | During debugging: Fill anti-debug pr anti-traced checks with NOPs. 492 | 493 | For anti-anti-debug tool development: 494 | 495 | 1. For FindWindow(): Hook user32!NtUserFindWindowEx(). In the hook, call the original user32!NtUserFindWindowEx() function. If it is called from the debugged process and the parent process looks suspicious, then return unsuccessfully from the hook. 496 | 497 | 2. For Parent Process Checks: Hook ntdll!NtQuerySystemInformation(). If SystemInformationClass is one of the following values: 498 | * SystemProcessInformation 499 | * SystemSessionProcessInformation 500 | * SystemExtendedProcessInformation 501 | 502 | and the process name looks suspicious, then the hook must modify the process name. 503 | 504 | 3. For Selectors: No mitigations. 505 | 506 | 4. For DbgPrint: you have to implement a plugin for a specific debugger and change the behavior of event handler which is triggered after the DBG_PRINTEXCEPTION_C exception has arrived. 507 | 508 | 5. For DbgSetDebugFilterState(): Hook ntdll!NtSetDebugFilterState(). If the process is running with debug privileges, return unsuccessfully from the hook. 509 | 510 | 6. For SwitchToThread: Hook ntdll!NtYieldExecution() and return an unsuccessful status from the hook. 511 | 512 | 7. For GetWriteWatch: Hook VirtualAlloc() and GetWriteWatch() to track if VirtualAlloc() is called with MEM_WRITE_WATCH flag. If it is the case, check what is the region to track and return the expected value in GetWriteWatch(). 513 | -------------------------------------------------------------------------------- /_techniques/object-handles.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Anti-Debug: Object Handles" 4 | title-image: "/assets/icons/object-handles.svg" 5 | categories: anti-debug 6 | tags: object-handles 7 | --- 8 | 9 |Contents
10 | 11 | [Object Handles](#object-handles) 12 | 13 | * [1. OpenProcess()](#openprocess) 14 | * [2. CreateFile()](#createfile) 15 | * [3. CloseHandle()](#closehandle) 16 | * [4. LoadLibrary()](#loadlibrary) 17 | * [5. NtQueryObject()](#ntqueryobject) 18 | * [Mitigations](#mitigations) 19 |20 | 21 |
22 | 23 |
Object Handles
24 | The following set of techniques represents the checks which use kernel objects handles to detect a debugger presence. Some WinAPI functions that accept kernel object handles as their parameters can behave differently under debugging or cause side-effects that emerge because of debuggers' implementation. Moreover, there are specific kernel objecst that are created by the operation system when debugging begins. 25 | 26 |27 |
1. OpenProcess()
28 | Some debuggers can be detected by using the kernel32!OpenProcess() function on the csrss.exe process. The call will succeed only if the user for the process is a member of the administrators group and has debug privileges. 29 | 30 |31 | 32 | C/C++ Code 33 | 34 | 35 | {% highlight c %} 36 | 37 | typedef DWORD (WINAPI *TCsrGetProcessId)(VOID); 38 | 39 | bool Check() 40 | { 41 | HMODULE hNtdll = LoadLibraryA("ntdll.dll"); 42 | if (!hNtdll) 43 | return false; 44 | 45 | TCsrGetProcessId pfnCsrGetProcessId = (TCsrGetProcessId)GetProcAddress(hNtdll, "CsrGetProcessId"); 46 | if (!pfnCsrGetProcessId) 47 | return false; 48 | 49 | HANDLE hCsr = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pfnCsrGetProcessId()); 50 | if (hCsr != NULL) 51 | { 52 | CloseHandle(hCsr); 53 | return true; 54 | } 55 | else 56 | return false; 57 | } 58 | 59 | {% endhighlight %} 60 | 61 |
62 | 63 |
64 |
2. CreateFile()
65 | When the CREATE_PROCESS_DEBUG_EVENT event occurs, the handle of the debugged file is stored in the CREATE_PROCESS_DEBUG_INFO structure. Therefore, debuggers can read the debug information from this file. If this handle is not closed by the debugger, the file won't be opened with exclusive access. Some debuggers can forget to close the handle. 66 | 67 | This trick uses kernel32!CreateFileW() (or kernel32!CreateFileA()) to exclusively open the file of the current process. If the call fails, we can consider that the current process is being run in the presence of a debugger. 68 | 69 |70 | 71 | C/C++ Code 72 | 73 | 74 | {% highlight c %} 75 | 76 | bool Check() 77 | { 78 | CHAR szFileName[MAX_PATH]; 79 | if (0 == GetModuleFileNameA(NULL, szFileName, sizeof(szFileName))) 80 | return false; 81 | 82 | return INVALID_HANDLE_VALUE == CreateFileA(szFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0); 83 | } 84 | 85 | {% endhighlight %} 86 | 87 |
88 | 89 |
3. CloseHandle()
90 | If a process is running under a debugger and an invalid handle is passed to the ntdll!NtClose() or kernel32!CloseHandle() function, then the EXCEPTION_INVALID_HANDLE (0xC0000008) exception will be raised. The exception can be cached by an exception handler. If the control is passed to the exception handler, it indicates that a debugger is present. 91 | 92 |93 | 94 | C/C++ Code 95 | 96 | 97 | {% highlight c %} 98 | 99 | bool Check() 100 | { 101 | __try 102 | { 103 | CloseHandle((HANDLE)0xDEADBEEF); 104 | return false; 105 | } 106 | __except (EXCEPTION_INVALID_HANDLE == GetExceptionCode() 107 | ? EXCEPTION_EXECUTE_HANDLER 108 | : EXCEPTION_CONTINUE_SEARCH) 109 | { 110 | return true; 111 | } 112 | } 113 | 114 | {% endhighlight %} 115 | 116 |
117 | 118 |
4. LoadLibrary()
119 | When a file is loaded to process memory using the kernel32!LoadLibraryW() (or kernel32!LoadLibraryA()) function, the LOAD_DLL_DEBUG_EVENT event occurs. The handle of the loaded file will be stored in the LOAD_DLL_DEBUG_INFO structure. Therefore, debuggers can read the debug information from this file. If this handle is not closed by the debugger, the file won't be opened with exclusive access. Some debuggers can forget to close the handle. 120 | 121 | To check for the debugger presence, we can load any file using kernel32!LoadLibraryA() and try to exclusively open it using kernel32!CreateFileA(). If the kernel32!CreateFileA() call fails, it indicates that the debugger is present. 122 | 123 |124 | 125 | C/C++ Code 126 | 127 | 128 | {% highlight c %} 129 | 130 | bool Check() 131 | { 132 | CHAR szBuffer[] = { "C:\\Windows\\System32\\calc.exe" }; 133 | LoadLibraryA(szBuffer); 134 | return INVALID_HANDLE_VALUE == CreateFileA(szBuffer, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); 135 | } 136 | 137 | {% endhighlight %} 138 | 139 |
140 | 141 |
5. NtQueryObject()
142 | When a debugging session begins, a kernel object called "debug object" is created, and a handle is associated with it. Using the ntdll!NtQueryObject() function, it is possible to query for the list of existing objects, and check the number of handles associated with any debug object that exists. 143 | 144 | However this technique can't say for sure if the current process is being debugged right now. It only shows if the debugger is running on the system at all since the system's boot. 145 | 146 |147 | 148 | C/C++ Code 149 | 150 | 151 | {% highlight c %} 152 | 153 | typedef struct _OBJECT_TYPE_INFORMATION 154 | { 155 | UNICODE_STRING TypeName; 156 | ULONG TotalNumberOfHandles; 157 | ULONG TotalNumberOfObjects; 158 | } OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION; 159 | 160 | typedef struct _OBJECT_ALL_INFORMATION 161 | { 162 | ULONG NumberOfObjects; 163 | OBJECT_TYPE_INFORMATION ObjectTypeInformation[1]; 164 | } OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION; 165 | 166 | typedef NTSTATUS (WINAPI *TNtQueryObject)( 167 | HANDLE Handle, 168 | OBJECT_INFORMATION_CLASS ObjectInformationClass, 169 | PVOID ObjectInformation, 170 | ULONG ObjectInformationLength, 171 | PULONG ReturnLength 172 | ); 173 | 174 | enum { ObjectAllTypesInformation = 3 }; 175 | 176 | #define STATUS_INFO_LENGTH_MISMATCH 0xC0000004 177 | 178 | bool Check() 179 | { 180 | bool bDebugged = false; 181 | NTSTATUS status; 182 | LPVOID pMem = nullptr; 183 | ULONG dwMemSize; 184 | POBJECT_ALL_INFORMATION pObjectAllInfo; 185 | PBYTE pObjInfoLocation; 186 | HMODULE hNtdll; 187 | TNtQueryObject pfnNtQueryObject; 188 | 189 | hNtdll = LoadLibraryA("ntdll.dll"); 190 | if (!hNtdll) 191 | return false; 192 | 193 | pfnNtQueryObject = (TNtQueryObject)GetProcAddress(hNtdll, "NtQueryObject"); 194 | if (!pfnNtQueryObject) 195 | return false; 196 | 197 | status = pfnNtQueryObject( 198 | NULL, 199 | (OBJECT_INFORMATION_CLASS)ObjectAllTypesInformation, 200 | &dwMemSize, sizeof(dwMemSize), &dwMemSize); 201 | if (STATUS_INFO_LENGTH_MISMATCH != status) 202 | goto NtQueryObject_Cleanup; 203 | 204 | pMem = VirtualAlloc(NULL, dwMemSize, MEM_COMMIT, PAGE_READWRITE); 205 | if (!pMem) 206 | goto NtQueryObject_Cleanup; 207 | 208 | status = pfnNtQueryObject( 209 | (HANDLE)-1, 210 | (OBJECT_INFORMATION_CLASS)ObjectAllTypesInformation, 211 | pMem, dwMemSize, &dwMemSize); 212 | if (!SUCCEEDED(status)) 213 | goto NtQueryObject_Cleanup; 214 | 215 | pObjectAllInfo = (POBJECT_ALL_INFORMATION)pMem; 216 | pObjInfoLocation = (PBYTE)pObjectAllInfo->ObjectTypeInformation; 217 | for(UINT i = 0; i < pObjectAllInfo->NumberOfObjects; i++) 218 | { 219 | 220 | POBJECT_TYPE_INFORMATION pObjectTypeInfo = 221 | (POBJECT_TYPE_INFORMATION)pObjInfoLocation; 222 | 223 | if (wcscmp(L"DebugObject", pObjectTypeInfo->TypeName.Buffer) == 0) 224 | { 225 | if (pObjectTypeInfo->TotalNumberOfObjects > 0) 226 | bDebugged = true; 227 | break; 228 | } 229 | 230 | // Get the address of the current entries 231 | // string so we can find the end 232 | pObjInfoLocation = (PBYTE)pObjectTypeInfo->TypeName.Buffer; 233 | 234 | // Add the size 235 | pObjInfoLocation += pObjectTypeInfo->TypeName.Length; 236 | 237 | // Skip the trailing null and alignment bytes 238 | ULONG tmp = ((ULONG)pObjInfoLocation) & -4; 239 | 240 | // Not pretty but it works 241 | pObjInfoLocation = ((PBYTE)tmp) + sizeof(DWORD); 242 | } 243 | 244 | NtQueryObject_Cleanup: 245 | if (pMem) 246 | VirtualFree(pMem, 0, MEM_RELEASE); 247 | 248 | return bDebugged; 249 | } 250 | 251 | {% endhighlight %} 252 | 253 |
254 | 255 |
256 |
Mitigations
257 | The simplest way to mitigate these checks is to just manually trace the program till a check and then skip it (e.g. patch with NOPs or change the instruction pointer or change the Zero Flag after the check). 258 | 259 | If you write an anti-anti-debug solution, you need to hook the listed functions and change return values after analyzing their input:260 | 261 | * ntdll!OpenProcess: Return NULL if the third argument is the handle of csrss.exe. 262 | * ntdll!NtClose: You can check if it is possible to retrieve any information about the input handle using ntdll!NtQueryObject() and not throw an exception if the handle is invalid. 263 | * ntdll!NtQueryObject: Filter debug objects from the results if the ObjectAllTypesInformation class is queried. 264 | 265 | The following techniques should be handled without hooks: 266 | 267 | * ntdll!NtCreateFile: Too generic to mitigate. However, if you write a plugin for a specific debugger, you can ensure that the handle of the debugged file is closed. 268 | * kernel32!LoadLibraryW/A: No mitigation. 269 | -------------------------------------------------------------------------------- /_techniques/process-memory.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Anti-Debug: Process Memory" 4 | title-image: "/assets/icons/memory.svg" 5 | categories: anti-debug 6 | tags: process-memory 7 | --- 8 | 9 |
Contents
10 | 11 | [Process Memory](#process-memory) 12 | 13 | * [1. Breakpoints](#breakpoints) 14 | * [1.1. Software Breakpoints (INT3)](#software-breakpoints) 15 | * [1.2. Anti-Step-Over](#anti-step-over) 16 | * [1.2.1. Direct Memory Modification](#direct-memory-modification) 17 | * [1.2.2. ReadFile()](#readfile) 18 | * [1.2.3. WriteProcessMemory()](#writeprocessmemory) 19 | * [1.2.4. Toolhelp32ReadProcessMemory()](#toolhelp32readprocessmemory) 20 | * [1.3. Memory Breakpoints](#memory-breakpoints) 21 | * [1.4. Hardware Breakpoints](#hardware-breakpoints) 22 | * [2. Other memory checks](#other-memory-checks) 23 | * [2.1. NtQueryVirtualMemory()](#ntqueryvirtualmemory) 24 | * [2.2. Detecting a function patch](#detecting-a-function-patch) 25 | * [2.3. Patch ntdll!DbgBreakPoint()](#patch_ntdll_dbgbreakpoint) 26 | * [2.4. Patch ntdll!DbgUiRemoteBreakin()](#patch_ntdll_dbguiremotebreakin) 27 | * [2.5 Performing Code Checksums](#code-checksums) 28 | * [Mitigations](#mitigations) 29 |30 | 31 |
32 | 33 |
Process Memory
34 | A process can examine its own memory to either detect the debugger presence or interfere with the debugger. 35 | 36 | This section includes the process memory and examining the thread contexts, searching for breakpoints, and function patching as anti-attaching methods. 37 | 38 |39 |
1. Breakpoints
40 | It is always possible to examine the process memory and search for software breakpoints in the code, or check the CPU debug registers to determine if hardware breakpoints are set. 41 | 42 |43 |
1.1. Software Breakpoints (INT3)
44 | The idea is to identify the machine code of some functions for 0xCC byte which stands for INT 3 assembly instruction. 45 | 46 | This method can generate many false-positive cases and should therefore be used with caution. 47 | 48 |49 | 50 | C/C++ Code 51 | 52 | 53 | {% highlight c %} 54 | 55 | bool CheckForSpecificByte(BYTE cByte, PVOID pMemory, SIZE_T nMemorySize = 0) 56 | { 57 | PBYTE pBytes = (PBYTE)pMemory; 58 | for (SIZE_T i = 0; ; i++) 59 | { 60 | // Break on RET (0xC3) if we don't know the function's size 61 | if (((nMemorySize > 0) && (i >= nMemorySize)) || 62 | ((nMemorySize == 0) && (pBytes[i] == 0xC3))) 63 | break; 64 | 65 | if (pBytes[i] == cByte) 66 | return true; 67 | } 68 | return false; 69 | } 70 | 71 | bool IsDebugged() 72 | { 73 | PVOID functionsToCheck[] = { 74 | &Function1, 75 | &Function2, 76 | &Function3, 77 | }; 78 | for (auto funcAddr : functionsToCheck) 79 | { 80 | if (CheckForSpecificByte(0xCC, funcAddr)) 81 | return true; 82 | } 83 | return false; 84 | } 85 | 86 | {% endhighlight %} 87 | 88 |
89 |
1.2. Anti-Step-Over
90 | Debuggers allow you to step over the function call. In such a case, the debugger implicitly sets a software breakpoint on the instruction which follows the call (i.e. the return address of the called function). 91 | 92 | To detect if there was an attempt to step over the function, we can examine the first byte of memory at the return address. If a software breakpoint (0xCC) is located at the return address, we can patch it with some other instruction (e.g. NOP). It will most likely break the code and crash the process. On the other hand, we can patch the return address with some meaningful code instead of NOP and change the control flow of the program. 93 | 94 |95 | 96 |
1.2.1. Direct Memory Modification
97 | It is possible to check from inside a function if there is a software breakpoint after the call of this function. We can read one byte at the return address and if the byte is equal to 0xCC (INT 3), it can be rewritten by 0x90 (NOP). The process will probably crash because we damage the instruction at the return address. However, if you know which instruction follows the function call, you can rewrite the breakpoint with the first byte of this instruction. 98 | 99 |100 | 101 | C/C++ Code 102 | 103 | 104 | {% highlight c %} 105 | 106 | #include
130 | 131 |
1.2.2. ReadFile()
132 | The method uses the kernel32!ReadFile() function to patch the code at the return address. 133 | 134 | The idea is to read the executable file of the current process and pass the return address as the output buffer to kernel32!ReadFile(). The byte at the return address will be patched with 'M' character (the first byte of PE image) and the process will probably crash. 135 | 136 |137 | 138 | C/C++ Code 139 | 140 | 141 | {% highlight c %} 142 | 143 | #include
175 | 176 |
1.2.3. WriteProcessMemory()
177 | This method uses the kernel32!WriteProcessMemory() function for patching the code at the return address. 178 | 179 |180 | 181 | C/C++ Code 182 | 183 | 184 | {% highlight c %} 185 | 186 | #include
211 | 212 |
1.2.4. Toolhelp32ReadProcessMemory()
213 | The function kernel32!Toolhelp32ReadProcessMemory() allows you to read the memory of other processes. However, it can be used for checking an anti-step-over condition. 214 | 215 |216 | 217 | C/C++ Code 218 | 219 | 220 | {% highlight c %} 221 | 222 | #include
242 |
1.3. Memory Breakpoints
243 | Memory breakpoints are implemented by using guard pages (at least, in OllyDbg and ImmunityDebugger). A guard page provides a one-shot alarm for memory page access. When a guard page is executed, the exception STATUS_GUARD_PAGE_VIOLATION is raised. 244 | 245 | A guard page can be created by setting the PAGE_GUARD page protection modifier in the kernel32!VirtualAlloc(), kernel32!VirtualAllocEx(), kernel32!VirtualProtect(), and kernel32!VirtualProtectEx() functions. 246 | 247 | However, we can abuse the way the debuggers implement memory breakpoints to check whether the program is executed under a debugger. We can allocate an executable buffer which contains only one byte 0xC3 which stands for RET instruction. We then mark this buffer as a guard page, push the address where the case if a debugger is present is handled to the stack, and jump to the allocated buffer. The instruction RET will be executed and if the debugger (OllyDbg or ImmunityDebugger) is present, we'll get to the address we had pushed to the stack. If the program is executed without the debugger, we'll get to an exception handler. 248 | 249 |250 | 251 | C/C++ Code 252 | 253 | 254 | {% highlight c %} 255 | 256 | bool IsDebugged() 257 | { 258 | DWORD dwOldProtect = 0; 259 | SYSTEM_INFO SysInfo = { 0 }; 260 | 261 | GetSystemInfo(&SysInfo); 262 | PVOID pPage = VirtualAlloc(NULL, SysInfo.dwPageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 263 | if (NULL == pPage) 264 | return false; 265 | 266 | PBYTE pMem = (PBYTE)pPage; 267 | *pMem = 0xC3; 268 | 269 | // Make the page a guard page 270 | if (!VirtualProtect(pPage, SysInfo.dwPageSize, PAGE_EXECUTE_READWRITE | PAGE_GUARD, &dwOldProtect)) 271 | return false; 272 | 273 | __try 274 | { 275 | __asm 276 | { 277 | mov eax, pPage 278 | push mem_bp_being_debugged 279 | jmp eax 280 | } 281 | } 282 | __except(EXCEPTION_EXECUTE_HANDLER) 283 | { 284 | VirtualFree(pPage, NULL, MEM_RELEASE); 285 | return false; 286 | } 287 | 288 | mem_bp_being_debugged: 289 | VirtualFree(pPage, NULL, MEM_RELEASE); 290 | return true; 291 | } 292 | 293 | {% endhighlight %} 294 | 295 |
296 |
1.4. Hardware Breakpoints
297 | Debug registers DR0, DR1, DR2 and DR3 can be retrieved from the thread context. If they contain non-zero values, it may mean that the process is executed under a debugger and a hardware breakpoint was set. 298 | 299 |300 | 301 | C/C++ Code 302 | 303 | 304 | {% highlight c %} 305 | 306 | bool IsDebugged() 307 | { 308 | CONTEXT ctx; 309 | ZeroMemory(&ctx, sizeof(CONTEXT)); 310 | ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; 311 | 312 | if(!GetThreadContext(GetCurrentThread(), &ctx)) 313 | return false; 314 | 315 | return ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3; 316 | } 317 | 318 | {% endhighlight %} 319 | 320 |
321 |
2. Other memory checks
322 | This section contains techniques which directly examine or manipulate the virtual memory of running processes to detect or prevent the debugging. 323 | 324 |325 |
2.1. NtQueryVirtualMemory()
326 | Memory pages of the process where code is located are shared between all processes until a page is written. Afterward, the OS makes a copy of this page and map it to the process virtual memory so this page is no longer "shared". 327 | 328 | Therefore, we can query the Working Set of the current process and check the Shared and ShareCount fields of the Working Set Block for the page with code. If there were software breakpoints in the code, these fields must not be set. 329 | 330 |331 | 332 | NTDLL declarations 333 | 334 | 335 | {% highlight c %} 336 | 337 | namespace ntdll 338 | { 339 | //... 340 | 341 | #define STATUS_INFO_LENGTH_MISMATCH 0xC0000004 342 | 343 | // ... 344 | 345 | typedef enum _MEMORY_INFORMATION_CLASS { 346 | MemoryBasicInformation, 347 | MemoryWorkingSetList, 348 | } MEMORY_INFORMATION_CLASS; 349 | 350 | // ... 351 | 352 | typedef union _PSAPI_WORKING_SET_BLOCK { 353 | ULONG Flags; 354 | struct { 355 | ULONG Protection :5; 356 | ULONG ShareCount :3; 357 | ULONG Shared :1; 358 | ULONG Reserved :3; 359 | ULONG VirtualPage:20; 360 | }; 361 | } PSAPI_WORKING_SET_BLOCK, *PPSAPI_WORKING_SET_BLOCK; 362 | 363 | typedef struct _MEMORY_WORKING_SET_LIST 364 | { 365 | ULONG NumberOfPages; 366 | PSAPI_WORKING_SET_BLOCK WorkingSetList[1]; 367 | } MEMORY_WORKING_SET_LIST, *PMEMORY_WORKING_SET_LIST; 368 | 369 | // ... 370 | } 371 | 372 | {% endhighlight %} 373 | 374 |
375 | 376 | C/C++ Code 377 | 378 | 379 | {% highlight c %} 380 | 381 | bool IsDebugged() 382 | { 383 | #ifndef _WIN64 384 | NTSTATUS status; 385 | PBYTE pMem = nullptr; 386 | DWORD dwMemSize = 0; 387 | 388 | do 389 | { 390 | dwMemSize += 0x1000; 391 | pMem = (PBYTE)_malloca(dwMemSize); 392 | if (!pMem) 393 | return false; 394 | 395 | memset(pMem, 0, dwMemSize); 396 | status = ntdll::NtQueryVirtualMemory( 397 | GetCurrentProcess(), 398 | NULL, 399 | ntdll::MemoryWorkingSetList, 400 | pMem, 401 | dwMemSize, 402 | NULL); 403 | } while (status == STATUS_INFO_LENGTH_MISMATCH); 404 | 405 | ntdll::PMEMORY_WORKING_SET_LIST pWorkingSet = (ntdll::PMEMORY_WORKING_SET_LIST)pMem; 406 | for (ULONG i = 0; i < pWorkingSet->NumberOfPages; i++) 407 | { 408 | DWORD dwAddr = pWorkingSet->WorkingSetList[i].VirtualPage << 0x0C; 409 | DWORD dwEIP = 0; 410 | __asm 411 | { 412 | push eax 413 | call $+5 414 | pop eax 415 | mov dwEIP, eax 416 | pop eax 417 | } 418 | 419 | if (dwAddr == (dwEIP & 0xFFFFF000)) 420 | return (pWorkingSet->WorkingSetList[i].Shared == 0) || (pWorkingSet->WorkingSetList[i].ShareCount == 0); 421 | } 422 | #endif // _WIN64 423 | return false; 424 | } 425 | 426 | {% endhighlight %} 427 | 428 | Credits for this technique: Virus Bulletin 429 | 430 |
431 |
2.2. Detecting a function patch
432 | A popular way to detect a debugger is to call kernel32!IsDebuggerPresent(). It's simple to mitigate this check e.g. to change the result in the EAX register or to patch the kernel32!IsDebuggerPresent() function's code. 433 | 434 | Therefore, instead of examining the process memory for breakpoints, we can verify if kernel32!IsDebuggerPresent() was modified. We can read the first bytes of this function and compare them to these bytes of the same function from other processes. Even with enabled ASLR, Windows libraries are loaded to the same base addresses in all the processes. The base addresses are changed only after a reboot, but for all the processes they will stay the same during the session. 435 | 436 |437 | 438 | C/C++ Code 439 | 440 | 441 | {% highlight c %} 442 | 443 | bool IsDebuggerPresent() 444 | { 445 | HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); 446 | if (!hKernel32) 447 | return false; 448 | 449 | FARPROC pIsDebuggerPresent = GetProcAddress(hKernel32, "IsDebuggerPresent"); 450 | if (!pIsDebuggerPresent) 451 | return false; 452 | 453 | HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 454 | if (INVALID_HANDLE_VALUE == hSnapshot) 455 | return false; 456 | 457 | PROCESSENTRY32W ProcessEntry; 458 | ProcessEntry.dwSize = sizeof(PROCESSENTRY32W); 459 | 460 | if (!Process32FirstW(hSnapshot, &ProcessEntry)) 461 | return false; 462 | 463 | bool bDebuggerPresent = false; 464 | HANDLE hProcess = NULL; 465 | DWORD dwFuncBytes = 0; 466 | const DWORD dwCurrentPID = GetCurrentProcessId(); 467 | do 468 | { 469 | __try 470 | { 471 | if (dwCurrentPID == ProcessEntry.th32ProcessID) 472 | continue; 473 | 474 | hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessEntry.th32ProcessID); 475 | if (NULL == hProcess) 476 | continue; 477 | 478 | if (!ReadProcessMemory(hProcess, pIsDebuggerPresent, &dwFuncBytes, sizeof(DWORD), NULL)) 479 | continue; 480 | 481 | if (dwFuncBytes != *(PDWORD)pIsDebuggerPresent) 482 | { 483 | bDebuggerPresent = true; 484 | break; 485 | } 486 | } 487 | __finally 488 | { 489 | if (hProcess) 490 | CloseHandle(hProcess); 491 | } 492 | } while (Process32NextW(hSnapshot, &ProcessEntry)); 493 | 494 | if (hSnapshot) 495 | CloseHandle(hSnapshot); 496 | return bDebuggerPresent; 497 | } 498 | 499 | {% endhighlight %} 500 | 501 | Credits for this technique: Rouse_ 502 | 503 |
504 |
2.3. Patch ntdll!DbgBreakPoint()
505 | The function ntdll!DbgBreakPoint() has the following implementation: 506 | 507 |
It is called when a debugger attaches to a running process. It allows the debugger to gain control because an exception is raised which it can intercept. If we erase the breakpoint inside ntdll!DbgBreakPoint(), the debugger won't break in and the thread will exit. 512 | 513 |
514 | 515 | C/C++ Code 516 | 517 | 518 | {% highlight c %} 519 | 520 | void Patch_DbgBreakPoint() 521 | { 522 | HMODULE hNtdll = GetModuleHandleA("ntdll.dll"); 523 | if (!hNtdll) 524 | return; 525 | 526 | FARPROC pDbgBreakPoint = GetProcAddress(hNtdll, "DbgBreakPoint"); 527 | if (!pDbgBreakPoint) 528 | return; 529 | 530 | DWORD dwOldProtect; 531 | if (!VirtualProtect(pDbgBreakPoint, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect)) 532 | return; 533 | 534 | *(PBYTE)pDbgBreakPoint = (BYTE)0xC3; // ret 535 | } 536 | 537 | {% endhighlight %} 538 | 539 |
540 |
2.4. Patch ntdll!DbgUiRemoteBreakin()
541 | When a debugger calls the kernel32!DebugActiveProcess(), a debugger calls ntdll!DbgUiRemoteBreakin() correspondingly. To prevent the debugger from attaching to the process, we can patch ntdll!DbgUiRemoteBreakin() code to invoke the kernel32!TerminateProcess(). 542 | 543 | In the example below we patch ntdll!DbgUiRemoteBreakin() with the following code: 544 | {% highlight asm %} 545 | 6A 00 push 0 546 | 68 FF FF FF FF push -1 ; GetCurrentProcess() result 547 | B8 XX XX XX XX mov eax, kernel32!TreminateProcess 548 | FF D0 call eax 549 | {% endhighlight %} 550 | 551 |As the result, the application will terminate itself once we try to attach the debugger to it. 552 | 553 |
554 | 555 | C/C++ Code 556 | 557 | 558 | {% highlight c %} 559 | 560 | #pragma pack(push, 1) 561 | struct DbgUiRemoteBreakinPatch 562 | { 563 | WORD push_0; 564 | BYTE push; 565 | DWORD CurrentPorcessHandle; 566 | BYTE mov_eax; 567 | DWORD TerminateProcess; 568 | WORD call_eax; 569 | }; 570 | #pragma pack(pop) 571 | 572 | void Patch_DbgUiRemoteBreakin() 573 | { 574 | HMODULE hNtdll = GetModuleHandleA("ntdll.dll"); 575 | if (!hNtdll) 576 | return; 577 | 578 | FARPROC pDbgUiRemoteBreakin = GetProcAddress(hNtdll, "DbgUiRemoteBreakin"); 579 | if (!pDbgUiRemoteBreakin) 580 | return; 581 | 582 | HMODULE hKernel32 = GetModuleHandleA("kernel32.dll"); 583 | if (!hKernel32) 584 | return; 585 | 586 | FARPROC pTerminateProcess = GetProcAddress(hKernel32, "TerminateProcess"); 587 | if (!pTerminateProcess) 588 | return; 589 | 590 | DbgUiRemoteBreakinPatch patch = { 0 }; 591 | patch.push_0 = '\x6A\x00'; 592 | patch.push = '\x68'; 593 | patch.CurrentPorcessHandle = 0xFFFFFFFF; 594 | patch.mov_eax = '\xB8'; 595 | patch.TerminateProcess = (DWORD)pTerminateProcess; 596 | patch.call_eax = '\xFF\xD0'; 597 | 598 | DWORD dwOldProtect; 599 | if (!VirtualProtect(pDbgUiRemoteBreakin, sizeof(DbgUiRemoteBreakinPatch), PAGE_READWRITE, &dwOldProtect)) 600 | return; 601 | 602 | ::memcpy_s(pDbgUiRemoteBreakin, sizeof(DbgUiRemoteBreakinPatch), 603 | &patch, sizeof(DbgUiRemoteBreakinPatch)); 604 | VirtualProtect(pDbgUiRemoteBreakin, sizeof(DbgUiRemoteBreakinPatch), dwOldProtect, &dwOldProtect); 605 | } 606 | 607 | {% endhighlight %} 608 | 609 | Credits for this technique: Rouse_ 610 | 611 |
612 |
2.5 Performing Code Checksums
613 | Verifying code checksum is a reliable way to detect software breakpoints, debugger's step-overs, functions' inline hooks, or data modification. 614 | 615 | The example below shows how it is possible to verify the checksum of a function. 616 | 617 | C/C++ Code 618 | 619 | 620 | {% highlight c %} 621 | 622 | PVOID g_pFuncAddr; 623 | DWORD g_dwFuncSize; 624 | DWORD g_dwOriginalChecksum; 625 | 626 | static void VeryImportantFunction() 627 | { 628 | // ... 629 | } 630 | 631 | static DWORD WINAPI ThreadFuncCRC32(LPVOID lpThreadParameter) 632 | { 633 | while (true) 634 | { 635 | if (CRC32((PBYTE)g_pFuncAddr, g_dwFuncSize) != g_dwOriginalChecksum) 636 | ExitProcess(0); 637 | Sleep(10000); 638 | } 639 | return 0; 640 | } 641 | 642 | size_t DetectFunctionSize(PVOID pFunc) 643 | { 644 | PBYTE pMem = (PBYTE)pFunc; 645 | size_t nFuncSize = 0; 646 | do 647 | { 648 | ++nFuncSize; 649 | } while (*(pMem++) != 0xC3); 650 | return nFuncSize; 651 | } 652 | 653 | int main() 654 | { 655 | g_pFuncAddr = (PVOID)&VeryImportantFunction; 656 | g_dwFuncSize = DetectFunctionSize(g_pFuncAddr); 657 | g_dwOriginalChecksum = CRC32((PBYTE)g_pFuncAddr, g_dwFuncSize); 658 | 659 | HANDLE hChecksumThread = CreateThread(NULL, NULL, ThreadFuncCRC32, NULL, NULL, NULL); 660 | 661 | // ... 662 | 663 | return 0; 664 | } 665 | 666 | {% endhighlight %} 667 | 668 |669 | 670 |
671 |
Mitigations
672 | * During debugging: 673 | * For Anti-Step-Over tricks: Step in the function which performs the Step-Over check and execute it till the end (Ctrl+F9 in OllyDbg/x32/x64dbg). 674 | * The best way to mitigate all the "memory" tricks (including Anti-Step-Over) is to find the exact check and patch it with NOPs, or set the return value which allows the application to execute further. 675 | * For anti-anti-debug tool development: 676 | * Breakpoints scan: 677 | * Software Breakpoint & Anti-Step-Over: There is no possibility of interfering with these checks as they don't need to use API and they access memory directly. 678 | * Memory Breakpoints: In general, it is possible to track the sequence of function that are called to apply this check. 679 | * Hardware Breakpoints: Hook kernel32!GetThreadContext() and modify debug registers. 680 | * Other checks: No mitigations. 681 | -------------------------------------------------------------------------------- /_techniques/timing.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Anti-Debug: Timing" 4 | title-image: "/assets/icons/timing.svg" 5 | categories: anti-debug 6 | tags: timing 7 | --- 8 | 9 |Contents
10 | 11 | [Timing](#timing) 12 | 13 | * [1. RDPMC/RDTSC](#rdpmc_rdtsc) 14 | * [2. GetLocalTime()](#getlocaltime) 15 | * [3. GetSystemTime()](#getsystemtime) 16 | * [4. GetTickCount()](#gettickcount) 17 | * [5. ZwGetTickCount() / KiGetTickCount()](#kernel-timing) 18 | * [6. QueryPerformanceCounter()](#queryperformancecounter) 19 | * [7. timeGetTime()](#timegettime) 20 | * [Mitigations](#mitigations) 21 |22 | 23 |
24 | 25 |
Timing
26 | When a process is traced in a debugger, there is a huge delay between instructions and execution. The "native" delay between some parts of code can be measured and compared with the actual delay using several approaches. 27 | 28 |29 |
1. RDPMC/RDTSC
30 | These instructions require the flag PCE to be set in CR4 register. 31 | 32 |33 | 34 | RDPMC instruction can be used only in Kernel Mode. 35 | 36 | C/C++ Code 37 | 38 | 39 | {% highlight c %} 40 | 41 | bool IsDebugged(DWORD64 qwNativeElapsed) 42 | { 43 | ULARGE_INTEGER Start, End; 44 | __asm 45 | { 46 | xor ecx, ecx 47 | rdpmc 48 | mov Start.LowPart, eax 49 | mov Start.HighPart, edx 50 | } 51 | // ... some work 52 | __asm 53 | { 54 | xor ecx, ecx 55 | rdpmc 56 | mov End.LowPart, eax 57 | mov End.HighPart, edx 58 | } 59 | return (End.QuadPart - Start.QuadPart) > qwNativeElapsed; 60 | } 61 | 62 | {% endhighlight %} 63 | 64 |
65 | 66 | RDTSC is a User Mode instruction. 67 | 68 | C/C++ Code 69 | 70 | 71 | {% highlight c %} 72 | 73 | bool IsDebugged(DWORD64 qwNativeElapsed) 74 | { 75 | ULARGE_INTEGER Start, End; 76 | __asm 77 | { 78 | xor ecx, ecx 79 | rdtsc 80 | mov Start.LowPart, eax 81 | mov Start.HighPart, edx 82 | } 83 | // ... some work 84 | __asm 85 | { 86 | xor ecx, ecx 87 | rdtsc 88 | mov End.LowPart, eax 89 | mov End.HighPart, edx 90 | } 91 | return (End.QuadPart - Start.QuadPart) > qwNativeElapsed; 92 | } 93 | 94 | {% endhighlight %} 95 | 96 |
97 | 98 |
99 |
2. GetLocalTime()
100 | 101 | C/C++ Code 102 | 103 | 104 | {% highlight c %} 105 | bool IsDebugged(DWORD64 qwNativeElapsed) 106 | { 107 | SYSTEMTIME stStart, stEnd; 108 | FILETIME ftStart, ftEnd; 109 | ULARGE_INTEGER uiStart, uiEnd; 110 | 111 | GetLocalTime(&stStart); 112 | // ... some work 113 | GetLocalTime(&stEnd); 114 | 115 | if (!SystemTimeToFileTime(&stStart, &ftStart)) 116 | return false; 117 | if (!SystemTimeToFileTime(&stEnd, &ftEnd)) 118 | return false; 119 | 120 | uiStart.LowPart = ftStart.dwLowDateTime; 121 | uiStart.HighPart = ftStart.dwHighDateTime; 122 | uiEnd.LowPart = ftEnd.dwLowDateTime; 123 | uiEnd.HighPart = ftEnd.dwHighDateTime; 124 | return (uiEnd.QuadPart - uiStart.QuadPart) > qwNativeElapsed; 125 | } 126 | 127 | {% endhighlight %} 128 | 129 |130 | 131 |
132 |
3. GetSystemTime()
133 | 134 | C/C++ Code 135 | 136 | 137 | {% highlight c %} 138 | 139 | bool IsDebugged(DWORD64 qwNativeElapsed) 140 | { 141 | SYSTEMTIME stStart, stEnd; 142 | FILETIME ftStart, ftEnd; 143 | ULARGE_INTEGER uiStart, uiEnd; 144 | 145 | GetSystemTime(&stStart); 146 | // ... some work 147 | GetSystemTime(&stEnd); 148 | 149 | if (!SystemTimeToFileTime(&stStart, &ftStart)) 150 | return false; 151 | if (!SystemTimeToFileTime(&stEnd, &ftEnd)) 152 | return false; 153 | 154 | uiStart.LowPart = ftStart.dwLowDateTime; 155 | uiStart.HighPart = ftStart.dwHighDateTime; 156 | uiEnd.LowPart = ftEnd.dwLowDateTime; 157 | uiEnd.HighPart = ftEnd.dwHighDateTime; 158 | return (uiEnd.QuadPart - uiStart.QuadPart) > qwNativeElapsed; 159 | } 160 | 161 | {% endhighlight %} 162 | 163 |164 | 165 |
166 |
4. GetTickCount()
167 | 168 | C/C++ Code 169 | 170 | 171 | {% highlight c %} 172 | 173 | bool IsDebugged(DWORD dwNativeElapsed) 174 | { 175 | DWORD dwStart = GetTickCount(); 176 | // ... some work 177 | return (GetTickCount() - dwStart) > dwNativeElapsed; 178 | } 179 | 180 | {% endhighlight %} 181 | 182 |183 | 184 |
185 |
5. ZwGetTickCount() / KiGetTickCount()
186 | Both functions are used only from Kernel Mode. 187 | 188 | Just like User Mode GetTickCount() or GetSystemTime(), Kernel Mode ZwGetTickCount() reads from the KUSER_SHARED_DATA page. This page is mapped read-only into the user mode range of the virtual address and read-write in the kernel range. The system clock tick updates the system time, which is stored directly in this page. 189 | 190 | ZwGetTickCount() is used the same way as GetTickCount(). Using KiGetTickCount() is faster than calling ZwGetTickCount(), but slightly slower than reading from the KUSER_SHARED_DATA page directly. 191 | 192 | C/C++ Code 193 | 194 | 195 | {% highlight c %} 196 | 197 | bool IsDebugged(DWORD64 qwNativeElapsed) 198 | { 199 | ULARGE_INTEGER Start, End; 200 | __asm 201 | { 202 | int 2ah 203 | mov Start.LowPart, eax 204 | mov Start.HighPart, edx 205 | } 206 | // ... some work 207 | __asm 208 | { 209 | int 2ah 210 | mov End.LowPart, eax 211 | mov End.HighPart, edx 212 | } 213 | return (End.QuadPart - Start.QuadPart) > qwNativeElapsed; 214 | } 215 | 216 | {% endhighlight %} 217 | 218 |219 | 220 |
221 |
6. QueryPerformanceCounter()
222 | 223 | C/C++ Code 224 | 225 | 226 | {% highlight c %} 227 | 228 | bool IsDebugged(DWORD64 qwNativeElapsed) 229 | { 230 | LARGE_INTEGER liStart, liEnd; 231 | QueryPerformanceCounter(&liStart); 232 | // ... some work 233 | QueryPerformanceCounter(&liEnd); 234 | return (liEnd.QuadPart - liStart.QuadPart) > qwNativeElapsed; 235 | } 236 | 237 | {% endhighlight %} 238 | 239 |240 | 241 |
242 |
7. timeGetTime()
243 | 244 | C/C++ Code 245 | 246 | 247 | {% highlight c %} 248 | 249 | bool IsDebugged(DWORD dwNativeElapsed) 250 | { 251 | DWORD dwStart = timeGetTime(); 252 | // ... some work 253 | return (timeGetTime() - dwStart) > dwNativeElapsed; 254 | } 255 | 256 | {% endhighlight %} 257 | 258 |259 | 260 |
261 |
Mitigations
262 | * During debugging: Just fill timing checks with NOPs and set the result of these checks to the appropriate value. 263 | * For anti-anti-debug solution development: There is no great need to do anything with it, as all timing checks are not very reliable. You can still hook timing functions and accelerate the time between calls. 264 | 265 | -------------------------------------------------------------------------------- /about.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: "About anti-debug tricks" 4 | permalink: /about/ 5 | --- 6 | 7 | Debugging is the essential part of malware analysis. Every time we need to drill down into malware behavior, restore encryption methods or examine communication protocols – generally, whenever we need to examine memory at a certain moment of time – we use debuggers. 8 | 9 | Debuggers interfere with the debugged process in a way that usually produces side-effects. These side-effects are often used by malicious programs to verify if they are executed under debugging. In turn knowledge of anti-debug techniques helps us detect when the malware tries to prevent us from debugging it and mitigate the interference. 10 | 11 | This encyclopedia contains the description of anti-debug tricks which work on the latest Windows releases with the most popular debuggers (such as OllyDbg, WinDbg, x64dbg). Deprecated techniques (e.g. for SoftICE, etc.) are not included (despite all the love to SoftICE). 12 | 13 | Anti-Debug tricks are grouped by the way in which they trigger side-effects (“meh, yet another classification”, you might think). Each group includes the description of corresponding tricks, their implementation in C/C++ or x86/x86-64 Assembly language, and recommendations of how to mitigate the trick for developers who want to create their own anti-anti-debug solution. In general, for bypassing anti-debug techniques we recommend using the ScyllaHide plugin which supports OllyDbg, x64dbg and IDA Pro. 14 | 15 | All the techniques which are described in this encyclopedia are implemented in our ShowStopper open-source project. The encyclopedia can help you to better understand how these techniques work or to assess debuggers and anti-anti-debug plugins. 16 | 17 |
18 | Yaraslau Harakhavik ( @slevin_by),
19 | Reverse Engineer at Check Point Research
20 |
22 | 23 |
References
24 |-
25 |
- P. Ferrie. The “Ultimate”Anti-Debugging Reference 26 |
- N. Falliere. Windows Anti-Debug Reference 27 |
- J. Jackson. An Anti-Reverse Engineering Guide 28 |
- Anti Debugging Protection Techniques with Examples 29 |
- simpliFiRE.AntiRE 30 |
33 |
34 | -------------------------------------------------------------------------------- /assets/css/main.scss: -------------------------------------------------------------------------------- 1 | --- 2 | # Only the main Sass file needs front matter (the dashes are enough) 3 | --- 4 | @charset "utf-8"; 5 | 6 | // Our variables 7 | $base-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 8 | $base-font-size: 16px; 9 | $base-font-weight: 400; 10 | $small-font-size: $base-font-size * 0.875; 11 | $base-line-height: 1.5; 12 | 13 | $spacing-unit: 30px; 14 | 15 | $text-color: #111; 16 | $background-color: #fdfdfd; 17 | $brand-color: #2a7ae2; 18 | 19 | $grey-color: #828282; 20 | $grey-color-light: lighten($grey-color, 40%); 21 | $grey-color-dark: darken($grey-color, 25%); 22 | 23 | // Width of the content area 24 | $content-width: 800px; 25 | 26 | $on-palm: 600px; 27 | $on-laptop: 800px; 28 | 29 | 30 | .navbar-default{ 31 | background: #161616!important; 32 | border: none !important; 33 | 34 | } 35 | 36 | .navbar-default .navbar-nav > li > a{ 37 | color:white !important; 38 | } 39 | .navbar-default .navbar-nav > li > a:hover{ 40 | font-weight: bold; 41 | } 42 | .navbar-default .navbar-nav > li > a:focus{ 43 | font-weight: bold; 44 | } 45 | .navbar-default .navbar-brand{ 46 | color:white !important; 47 | } 48 | 49 | .navbar-fixed-bottom { 50 | background-color: #161616 !important; 51 | } 52 | .navbar-fixed-top { 53 | background-color: #e45785 !important; 54 | color: white !important; 55 | } 56 | .navbar-fixed-top a { 57 | color: white !important; 58 | 59 | } 60 | .footer-content{ 61 | text-align: center; 62 | padding: 0.5em; 63 | background-color: #161616; 64 | width:100%; 65 | } 66 | 67 | .footer-content a { 68 | color: #ff4883; 69 | } 70 | 71 | .footer-content a:hover { 72 | color: #ffebf2 !important; 73 | } 74 | 75 | .logo { 76 | margin-top: 20px; 77 | font-size: 25pt; 78 | } 79 | 80 | .title { 81 | white-space: nowrap; 82 | } 83 | 84 | body{ 85 | background-color: #333333 !important; 86 | font-family: "Fira Mono" !important; 87 | color: white !important; 88 | } 89 | .page-stuff{ 90 | a { 91 | color: #51bffb !important; 92 | text-decoration: none; 93 | &:hover{ 94 | color:#ca0098 !important; 95 | text-decoration: none !important; 96 | border-bottom: 1px dotted #ff34d3; 97 | padding-bottom:0.5em; 98 | } 99 | } 100 | } 101 | #page-content{ 102 | padding-bottom: 4em; 103 | } 104 | 105 | .page-stuff{ 106 | padding-top: 2em; 107 | padding-bottom: 5em; 108 | } 109 | .jumbotron { 110 | background: #161616!important; 111 | } 112 | a { 113 | color:#51bffb !important; 114 | text-decoration: none; 115 | } 116 | a:hover{ 117 | color:#ca0098 !important; 118 | text-decoration: none !important; 119 | border-bottom: 1px dotted #ca0098; 120 | 121 | } 122 | 123 | .post-list{ 124 | 125 | text-align: left; 126 | 127 | } 128 | .post-box{ 129 | padding-top: 2em; 130 | padding-bottom: 2em; 131 | } 132 | 133 | .post-title{ 134 | color: #ff20bc !important; 135 | } 136 | 137 | .post-excerpt{ 138 | color: white !important; 139 | padding-top: 0.5em; 140 | padding-bottom: 0.3em; 141 | } 142 | .post-title-main{ 143 | text-align: left; 144 | color: white !important; 145 | } 146 | .post-content{ 147 | font-size: 19px; 148 | text-align: justify; 149 | padding-bottom: 5em; 150 | } 151 | pre{ 152 | background-color: black !important; 153 | border:none !important; 154 | border-radius: 0px !important; 155 | font-size: 20px !important; 156 | padding: 1.5em !important; 157 | color:white !important; 158 | } 159 | .post-meta{ 160 | color: #e45785 !important; 161 | } 162 | 163 | code{ 164 | background-color: black !important; 165 | color: white !important; 166 | border: none !important; 167 | border-radius: 0px !important; 168 | } 169 | 170 | .page-tagline{ 171 | font-size: 20px; 172 | text-align: center; 173 | } 174 | 175 | .download{ 176 | padding:2em; 177 | font-size: 19px; 178 | } 179 | .intro{ 180 | font-size: 19px; 181 | 182 | } 183 | .manual-title{ 184 | font-size: 21px; 185 | padding: 1em; 186 | /*text-transform: uppercase;*/ 187 | 188 | } 189 | .manual-content{ 190 | text-align: justify; 191 | font-size: 19px; 192 | } 193 | 194 | .man-title{ 195 | font-size: 22px; 196 | text-align: left; 197 | padding: 1em; 198 | letter-spacing: 2px; 199 | 200 | } 201 | 202 | .jumbotron{ 203 | box-shadow: 0px 1px 10px 0px rgba(0, 0, 0, 0.15); 204 | } 205 | .navbar{ 206 | box-shadow: 0px 1px 10px 0px rgba(0, 0, 0, 0.15); 207 | } 208 | // Our variables 209 | $base-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 210 | $base-font-size: 16px; 211 | $base-font-weight: 400; 212 | $small-font-size: $base-font-size * 0.875; 213 | $base-line-height: 1.5; 214 | 215 | $spacing-unit: 30px; 216 | 217 | $text-color: #111; 218 | $background-color: #fdfdfd; 219 | $brand-color: #2a7ae2; 220 | 221 | $grey-color: #828282; 222 | $grey-color-light: lighten($grey-color, 40%); 223 | $grey-color-dark: darken($grey-color, 25%); 224 | 225 | // Width of the content area 226 | $content-width: 800px; 227 | 228 | $on-palm: 600px; 229 | $on-laptop: 800px; 230 | 231 | 232 | .navbar-fixed-top { 233 | background-color: #e45785 !important; 234 | color: white !important; 235 | } 236 | .navbar-fixed-top a { 237 | color: white !important; 238 | 239 | } 240 | // Use media queries like this: 241 | // @include media-query($on-palm) { 242 | // .wrapper { 243 | // padding-right: $spacing-unit / 2; 244 | // padding-left: $spacing-unit / 2; 245 | // } 246 | // } 247 | @mixin media-query($device) { 248 | @media screen and (max-width: $device) { 249 | @content; 250 | } 251 | } 252 | 253 | 254 | .wrap-collabsible { 255 | margin-bottom: 1.2rem 0; 256 | } 257 | .wrap-collabsible-title { 258 | margin-bottom: 1.2rem 0; 259 | } 260 | 261 | input[type='checkbox'] { 262 | display: none; 263 | } 264 | 265 | .lbl-toggle { 266 | display: block; 267 | 268 | font-weight: bold; 269 | font-family: monospace; 270 | /*text-transform: uppercase;*/ 271 | text-align: left; 272 | 273 | padding: 1rem; 274 | 275 | color: rgb(201, 38, 166); 276 | cursor: pointer; 277 | 278 | transition: all 0.25s ease-out; 279 | } 280 | .lbl-toggle-title { 281 | display: block; 282 | background: #fdeefd; 283 | font-weight: bold; 284 | font-family: monospace; 285 | /*text-transform: uppercase;*/ 286 | text-align: left; 287 | padding: 1rem; 288 | color: rgb(201, 38, 166); 289 | } 290 | 291 | .odd { 292 | background: #fefefe; 293 | } 294 | 295 | .even { 296 | background: #f4f4f4; 297 | } 298 | 299 | .lbl-toggle:hover { 300 | color: rgb(138, 0, 131); 301 | } 302 | 303 | .lbl-toggle .first::before { 304 | content: ' '; 305 | display: inline-block; 306 | 307 | border-top: 5px solid transparent; 308 | border-bottom: 5px solid transparent; 309 | border-left: 5px solid currentColor; 310 | vertical-align: middle; 311 | margin-right: .7rem; 312 | transform: translateY(-2px); 313 | 314 | transition: transform .2s ease-out; 315 | } 316 | 317 | .toggle:checked + .lbl-toggle .first::before { 318 | transform: rotate(90deg) translateX(-3px); 319 | } 320 | 321 | .collapsible-content { 322 | max-height: 0px; 323 | overflow: hidden; 324 | transition: max-height .25s ease-in-out; 325 | } 326 | 327 | .toggle:checked + .lbl-toggle + .collapsible-content { 328 | max-height: 650px; 329 | margin-bottom: 10px; 330 | 331 | } 332 | 333 | .toggle:checked + .lbl-toggle { 334 | border-bottom-right-radius: 0; 335 | border-bottom-left-radius: 0; 336 | } 337 | 338 | .collapsible-content .content-inner { 339 | color: black; 340 | background: rgb(251, 245, 252); 341 | border-bottom: 1px solid rgb(51, 40, 50); 342 | border-bottom-left-radius: 7px; 343 | border-bottom-right-radius: 7px; 344 | padding: .5rem 1rem; 345 | overflow-y: auto; 346 | position: relative; 347 | max-height: 650px; 348 | padding-left: 25px; 349 | } 350 | 351 | .wrap-collabsible label { 352 | font-weight: normal; 353 | margin: 0px; 354 | } 355 | 356 | .wrap-collabsible-title label { 357 | font-weight: normal; 358 | margin: 0px; 359 | } 360 | 361 | .wrap-collabsible a { 362 | color: #ff20bc !important; 363 | } 364 | 365 | .wrap-collabsible-title a { 366 | color: #ff20bc !important; 367 | } 368 | 369 | 370 | .active-pink { 371 | border: none !important; 372 | border-radius: 0 !important; 373 | margin-top: 5px !important; 374 | color: white !important; 375 | border-bottom: 1px solid #ffffff !important; 376 | box-shadow: 0 1px 0 0 #f48fb1 !important; 377 | background-color: transparent !important; 378 | } 379 | 380 | 381 | .active-pink::placeholder { 382 | color: #ffd7e5 !important; 383 | } 384 | 385 | .active-pink:hover { 386 | border-bottom: 1px solid #ffbdd3 !important; 387 | } 388 | 389 | .active-pink:focus::-webkit-input-placeholder { 390 | color:white; 391 | } 392 | 393 | 394 | .navbar-default .navbar-toggle .icon-bar { 395 | background-color: white !important; 396 | } 397 | 398 | 399 | .btn-sort { 400 | background-color: #e45785 !important; 401 | color: white !important; 402 | border: 0px; 403 | 404 | } 405 | .btn-sort:hover { 406 | color: white !important; 407 | background-color: #c94370 !important; 408 | 409 | } 410 | 411 | .btn-sort:focus { 412 | outline: #ca0098 !important; 413 | } 414 | 415 | .btn-bleft { 416 | margin: 0 5px; 417 | } 418 | 419 | .btn-expand { 420 | background-color: #fd83ac !important; 421 | color: white !important; 422 | border: 0px; 423 | margin-left: 25px; 424 | } 425 | 426 | 427 | .btn-expand:hover { 428 | color: white !important; 429 | background-color: #f16d99 !important; 430 | } 431 | 432 | 433 | .btn-expand:focus { 434 | outline: #ca0098 !important; 435 | } 436 | 437 | 438 | .content-search { 439 | margin-bottom: 20px; 440 | width: 50%; 441 | } 442 | 443 | 444 | /* MAIN PARTS OF STYLE */ 445 | 446 | .info { 447 | background-color: #e7f3fe !important; 448 | border-left: 6px solid #2196F3 !important; 449 | margin-bottom: 15px !important; 450 | padding: 4px 12px !important; 451 | } 452 | 453 | p { 454 | font-size: 17px; 455 | } 456 | 457 | a { 458 | color: #51bffb; 459 | text-decoration: none; 460 | } 461 | a:hover { 462 | border: 0px solid; 463 | } 464 | a:active, a:focus { 465 | outline: 0; 466 | border: none; 467 | text-decoration: none; 468 | -moz-outline-style: none; 469 | } 470 | 471 | .a-active { 472 | display: block; 473 | } 474 | 475 | .a-pink { 476 | color:#ff4883 !important; 477 | text-decoration: none; 478 | } 479 | 480 | .a-dummy { 481 | pointer-events: none; 482 | text-decoration: none; 483 | } 484 | .a-dummy:active, .a-dummy:hover, .a-dummy:focus { 485 | text-decoration: none; 486 | color: #51bffb !important; 487 | } 488 | 489 | table, th, td { 490 | border: 1px solid gray; 491 | border-collapse: collapse; 492 | font-family: Courier New, Verdana, Tahoma, Fixedsys; 493 | font-size: 15px; 494 | } 495 | th, td { 496 | padding: 3px; 497 | text-align: left; 498 | } 499 | 500 | pre { 501 | border: 1px solid gray !important; 502 | } 503 | 504 | pre, code { 505 | background-color: #343434 !important; 506 | font-size: 17px !important; 507 | } 508 | 509 | hr.space { 510 | height: 3px; 511 | visibility: hidden; 512 | margin-bottom: -1px; 513 | } 514 | 515 | h2 { 516 | font-size: 27px; 517 | } 518 | 519 | tt { 520 | font-size: 20px; 521 | } 522 | 523 | 524 | /* CODE HIGHLIGHTING */ 525 | 526 | .highlight pre { background-color: #343434 } 527 | .highlight .hll { background-color: #343434 } 528 | .highlight .c { color: #008800; font-style: italic; background-color: #0f140f } /* Comment */ 529 | .highlight .err { color: #ffffff } /* Error */ 530 | .highlight .g { color: #ffffff } /* Generic */ 531 | .highlight .k { color: #cc8153; font-weight: bold } /* Keyword */ 532 | .highlight .l { color: #ffffff } /* Literal */ 533 | .highlight .n { color: #ffffff } /* Name */ 534 | .highlight .o { color: #ffffff } /* Operator */ 535 | .highlight .x { color: #ffffff } /* Other */ 536 | .highlight .p { color: #ffffff } /* Punctuation */ 537 | .highlight .cm { color: #afceaf; font-style: italic; } /* Comment.Multiline */ 538 | .highlight .cp { color: #afceaf; font-weight: bold; font-style: italic; } /* Comment.Preproc */ 539 | .highlight .c1 { color: #afceaf; font-style: italic; } /* Comment.Single */ 540 | .highlight .cs { color: #afceaf; font-style: italic; } /* Comment.Special */ 541 | .highlight .gd { color: #ffffff } /* Generic.Deleted */ 542 | .highlight .ge { color: #ffffff } /* Generic.Emph */ 543 | .highlight .gr { color: #ffffff } /* Generic.Error */ 544 | .highlight .gh { color: #ffffff; font-weight: bold } /* Generic.Heading */ 545 | .highlight .gi { color: #ffffff } /* Generic.Inserted */ 546 | .highlight .go { color: #444444; background-color: #222222 } /* Generic.Output */ 547 | .highlight .gp { color: #ffffff } /* Generic.Prompt */ 548 | .highlight .gs { color: #ffffff } /* Generic.Strong */ 549 | .highlight .gu { color: #ffffff; font-weight: bold } /* Generic.Subheading */ 550 | .highlight .gt { color: #ffffff } /* Generic.Traceback */ 551 | .highlight .kc { color: #cc8153; font-weight: bold } /* Keyword.Constant */ 552 | .highlight .kd { color: #cc8153; font-weight: bold } /* Keyword.Declaration */ 553 | .highlight .kn { color: #cc8153; font-weight: bold } /* Keyword.Namespace */ 554 | .highlight .kp { color: #cc8153 } /* Keyword.Pseudo */ 555 | .highlight .kr { color: #cc8153; font-weight: bold } /* Keyword.Reserved */ 556 | .highlight .kt { color: #cdcaa9; font-weight: bold } /* Keyword.Type */ 557 | .highlight .ld { color: #ffffff } /* Literal.Date */ 558 | .highlight .m { color: #56a8d7; font-weight: bold } /* Literal.Number */ 559 | .highlight .s { color: #56a8d7 } /* Literal.String */ 560 | .highlight .na { color: #e562a7; font-weight: bold } /* Name.Attribute */ 561 | .highlight .nb { color: #ffffff } /* Name.Builtin */ 562 | .highlight .nc { color: #ffffff } /* Name.Class */ 563 | .highlight .no { color: #56a8d7 } /* Name.Constant */ 564 | .highlight .nd { color: #ffffff } /* Name.Decorator */ 565 | .highlight .ni { color: #ffffff } /* Name.Entity */ 566 | .highlight .ne { color: #ffffff } /* Name.Exception */ 567 | .highlight .nf { color: #e562a7; font-weight: bold } /* Name.Function */ 568 | .highlight .nl { color: #ffffff } /* Name.Label */ 569 | .highlight .nn { color: #ffffff } /* Name.Namespace */ 570 | .highlight .nx { color: #ffffff } /* Name.Other */ 571 | .highlight .py { color: #ffffff } /* Name.Property */ 572 | .highlight .nt { color: #cc8153; font-weight: bold } /* Name.Tag */ 573 | .highlight .nv { color: #cc8153 } /* Name.Variable */ 574 | .highlight .ow { color: #ffffff } /* Operator.Word */ 575 | .highlight .w { color: #888888 } /* Text.Whitespace */ 576 | .highlight .mf { color: #56a8d7; font-weight: bold } /* Literal.Number.Float */ 577 | .highlight .mh { color: #56a8d7; font-weight: bold } /* Literal.Number.Hex */ 578 | .highlight .mi { color: #56a8d7; font-weight: bold } /* Literal.Number.Integer */ 579 | .highlight .mo { color: #56a8d7; font-weight: bold } /* Literal.Number.Oct */ 580 | .highlight .sb { color: #56a8d7 } /* Literal.String.Backtick */ 581 | .highlight .sc { color: #56a8d7 } /* Literal.String.Char */ 582 | .highlight .sd { color: #56a8d7 } /* Literal.String.Doc */ 583 | .highlight .s2 { color: #56a8d7 } /* Literal.String.Double */ 584 | .highlight .se { color: #56a8d7 } /* Literal.String.Escape */ 585 | .highlight .sh { color: #56a8d7 } /* Literal.String.Heredoc */ 586 | .highlight .si { color: #56a8d7 } /* Literal.String.Interpol */ 587 | .highlight .sx { color: #56a8d7 } /* Literal.String.Other */ 588 | .highlight .sr { color: #56a8d7 } /* Literal.String.Regex */ 589 | .highlight .s1 { color: #56a8d7 } /* Literal.String.Single */ 590 | .highlight .ss { color: #56a8d7 } /* Literal.String.Symbol */ 591 | .highlight .bp { color: #ffffff } /* Name.Builtin.Pseudo */ 592 | .highlight .vc { color: #cc8153 } /* Name.Variable.Class */ 593 | .highlight .vg { color: #cc8153 } /* Name.Variable.Global */ 594 | .highlight .vi { color: #cc8153 } /* Name.Variable.Instance */ 595 | .highlight .il { color: #56a8d7; font-weight: bold } /* Literal.Number.Integer.Long */ 596 | 597 | 598 | /* SHARE BUTTONS */ 599 | 600 | 601 | /* Share buttons */ 602 | .sharebuttons { 603 | margin: 0 auto 0 auto; 604 | } 605 | 606 | .sharebuttons ul { 607 | margin: 20px 0 0 0; 608 | text-align: center; 609 | } 610 | 611 | .sharebuttons ul li { 612 | display: inline; 613 | } 614 | 615 | .sharebuttons ul li a { 616 | text-decoration: none; 617 | } 618 | 619 | .sharebuttons ul li svg { 620 | width: 20px; 621 | height: 20px; 622 | } 623 | 624 | .sharebuttons .reddit svg { 625 | fill: #FF4500; 626 | } 627 | 628 | .sharebuttons .hn svg { 629 | fill: #F0652F; 630 | } 631 | 632 | .sharebuttons .twitter svg { 633 | fill: #1DA1F2; 634 | } 635 | 636 | .sharebuttons .linkedin svg { 637 | fill: #0077B5; 638 | } -------------------------------------------------------------------------------- /assets/icons/assembly.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/debug_flags.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/exceptions.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/interactive.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/memory.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/misc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/object-handles.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/icons/timing.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/CPR_Logo_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheckPointSW/Anti-Debug-DB/289d3cd9b0c37450f90d14891ba1625f4b0d97ff/assets/images/CPR_Logo_transparent.png -------------------------------------------------------------------------------- /assets/images/cpr_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheckPointSW/Anti-Debug-DB/289d3cd9b0c37450f90d14891ba1625f4b0d97ff/assets/images/cpr_logo.png -------------------------------------------------------------------------------- /assets/images/cpr_logo_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheckPointSW/Anti-Debug-DB/289d3cd9b0c37450f90d14891ba1625f4b0d97ff/assets/images/cpr_logo_big.png -------------------------------------------------------------------------------- /assets/images/cpr_logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheckPointSW/Anti-Debug-DB/289d3cd9b0c37450f90d14891ba1625f4b0d97ff/assets/images/cpr_logo_small.png -------------------------------------------------------------------------------- /assets/images/dbgbreakpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheckPointSW/Anti-Debug-DB/289d3cd9b0c37450f90d14891ba1625f4b0d97ff/assets/images/dbgbreakpoint.png -------------------------------------------------------------------------------- /assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CheckPointSW/Anti-Debug-DB/289d3cd9b0c37450f90d14891ba1625f4b0d97ff/assets/images/favicon.ico -------------------------------------------------------------------------------- /assets/js/search-script.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Simple-Jekyll-Search v1.7.2 (https://github.com/christian-fei/Simple-Jekyll-Search) 3 | * Copyright 2015-2018, Christian Fei 4 | * Licensed under the MIT License. 5 | */ 6 | 7 | (function(){ 8 | /* globals ActiveXObject:false */ 9 | 10 | 'use strict' 11 | 12 | var _$JSONLoader_2 = { 13 | load: load 14 | } 15 | 16 | function load (location, callback) { 17 | var xhr = getXHR() 18 | xhr.open('GET', location, true) 19 | xhr.onreadystatechange = createStateChangeListener(xhr, callback) 20 | xhr.send() 21 | } 22 | 23 | function createStateChangeListener (xhr, callback) { 24 | return function () { 25 | if (xhr.readyState === 4 && xhr.status === 200) { 26 | try { 27 | callback(null, JSON.parse(xhr.responseText)) 28 | } catch (err) { 29 | callback(err, null) 30 | } 31 | } 32 | } 33 | } 34 | 35 | function getXHR () { 36 | return window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP') 37 | } 38 | 39 | 'use strict' 40 | 41 | var _$OptionsValidator_3 = function OptionsValidator (params) { 42 | if (!validateParams(params)) { 43 | throw new Error('-- OptionsValidator: required options missing') 44 | } 45 | 46 | if (!(this instanceof OptionsValidator)) { 47 | return new OptionsValidator(params) 48 | } 49 | 50 | var requiredOptions = params.required 51 | 52 | this.getRequiredOptions = function () { 53 | return requiredOptions 54 | } 55 | 56 | this.validate = function (parameters) { 57 | var errors = [] 58 | requiredOptions.forEach(function (requiredOptionName) { 59 | if (typeof parameters[requiredOptionName] === 'undefined') { 60 | errors.push(requiredOptionName) 61 | } 62 | }) 63 | return errors 64 | } 65 | 66 | function validateParams (params) { 67 | if (!params) { 68 | return false 69 | } 70 | return typeof params.required !== 'undefined' && params.required instanceof Array 71 | } 72 | } 73 | 74 | 'use strict'; 75 | 76 | function fuzzysearch (needle, haystack) { 77 | var tlen = haystack.length; 78 | var qlen = needle.length; 79 | if (qlen > tlen) { 80 | return false; 81 | } 82 | if (qlen === tlen) { 83 | return needle === haystack; 84 | } 85 | outer: for (var i = 0, j = 0; i < qlen; i++) { 86 | var nch = needle.charCodeAt(i); 87 | while (j < tlen) { 88 | if (haystack.charCodeAt(j++) === nch) { 89 | continue outer; 90 | } 91 | } 92 | return false; 93 | } 94 | return true; 95 | } 96 | 97 | var _$fuzzysearch_1 = fuzzysearch; 98 | 99 | 'use strict' 100 | 101 | /* removed: var _$fuzzysearch_1 = require('fuzzysearch') */; 102 | 103 | var _$FuzzySearchStrategy_5 = new FuzzySearchStrategy() 104 | 105 | function FuzzySearchStrategy () { 106 | this.matches = function (string, crit) { 107 | return _$fuzzysearch_1(crit.toLowerCase(), string.toLowerCase()) 108 | } 109 | } 110 | 111 | 'use strict' 112 | 113 | var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy() 114 | 115 | function LiteralSearchStrategy () { 116 | this.matches = function (str, crit) { 117 | if (!str) return false 118 | 119 | str = str.trim().toLowerCase() 120 | crit = crit.trim().toLowerCase() 121 | 122 | return crit.split(' ').filter(function (word) { 123 | return str.indexOf(word) >= 0 124 | }).length === crit.split(' ').length 125 | } 126 | } 127 | 128 | 'use strict' 129 | 130 | var _$Repository_4 = { 131 | put: put, 132 | clear: clear, 133 | search: search, 134 | setOptions: setOptions 135 | } 136 | 137 | /* removed: var _$FuzzySearchStrategy_5 = require('./SearchStrategies/FuzzySearchStrategy') */; 138 | /* removed: var _$LiteralSearchStrategy_6 = require('./SearchStrategies/LiteralSearchStrategy') */; 139 | 140 | function NoSort () { 141 | return 0 142 | } 143 | 144 | var data = [] 145 | var opt = {} 146 | 147 | opt.fuzzy = false 148 | opt.limit = 10 149 | opt.searchStrategy = opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6 150 | opt.sort = NoSort 151 | 152 | function put (data) { 153 | if (isObject(data)) { 154 | return addObject(data) 155 | } 156 | if (isArray(data)) { 157 | return addArray(data) 158 | } 159 | return undefined 160 | } 161 | function clear () { 162 | data.length = 0 163 | return data 164 | } 165 | 166 | function isObject (obj) { 167 | return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Object]' 168 | } 169 | 170 | function isArray (obj) { 171 | return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Array]' 172 | } 173 | 174 | function addObject (_data) { 175 | data.push(_data) 176 | return data 177 | } 178 | 179 | function addArray (_data) { 180 | var added = [] 181 | clear() 182 | for (var i = 0, len = _data.length; i < len; i++) { 183 | if (isObject(_data[i])) { 184 | added.push(addObject(_data[i])) 185 | } 186 | } 187 | return added 188 | } 189 | 190 | function search (crit) { 191 | if (!crit) { 192 | return [] 193 | } 194 | return findMatches(data, crit, opt.searchStrategy, opt).sort(opt.sort) 195 | } 196 | 197 | function setOptions (_opt) { 198 | opt = _opt || {} 199 | 200 | opt.fuzzy = _opt.fuzzy || false 201 | opt.limit = _opt.limit || 10 202 | opt.searchStrategy = _opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6 203 | opt.sort = _opt.sort || NoSort 204 | } 205 | 206 | function findMatches (data, crit, strategy, opt) { 207 | var matches = [] 208 | for (var i = 0; i < data.length && matches.length < opt.limit; i++) { 209 | var match = findMatchesInObject(data[i], crit, strategy, opt) 210 | if (match) { 211 | matches.push(match) 212 | } 213 | } 214 | return matches 215 | } 216 | 217 | function findMatchesInObject (obj, crit, strategy, opt) { 218 | for (var key in obj) { 219 | if (!isExcluded(obj[key], opt.exclude) && strategy.matches(obj[key], crit)) { 220 | return obj 221 | } 222 | } 223 | } 224 | 225 | function isExcluded (term, excludedTerms) { 226 | var excluded = false 227 | excludedTerms = excludedTerms || [] 228 | for (var i = 0, len = excludedTerms.length; i < len; i++) { 229 | var excludedTerm = excludedTerms[i] 230 | if (!excluded && new RegExp(term).test(excludedTerm)) { 231 | excluded = true 232 | } 233 | } 234 | return excluded 235 | } 236 | 237 | 'use strict' 238 | 239 | var _$Templater_7 = { 240 | compile: compile, 241 | setOptions: __setOptions_7 242 | } 243 | 244 | var options = {} 245 | options.pattern = /\{(.*?)\}/g 246 | options.template = '' 247 | options.middleware = function () {} 248 | 249 | function __setOptions_7 (_options) { 250 | options.pattern = _options.pattern || options.pattern 251 | options.template = _options.template || options.template 252 | if (typeof _options.middleware === 'function') { 253 | options.middleware = _options.middleware 254 | } 255 | } 256 | 257 | function htmlDecode(input){ 258 | var e = document.createElement('div'); 259 | e.innerHTML = input; 260 | return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue; 261 | } 262 | 263 | 264 | function compile (data) { 265 | return options.template.replace(options.pattern, function (match, prop) { 266 | var value = options.middleware(prop, data[prop], options.template) 267 | if (typeof value !== 'undefined') { 268 | return value 269 | } 270 | if (prop == 'content') { 271 | return(htmlDecode(data[prop])) || match 272 | } 273 | return data[prop] || match 274 | }) 275 | } 276 | 277 | 'use strict' 278 | 279 | var _$utils_9 = { 280 | merge: merge, 281 | isJSON: isJSON 282 | } 283 | 284 | function merge (defaultParams, mergeParams) { 285 | var mergedOptions = {} 286 | for (var option in defaultParams) { 287 | mergedOptions[option] = defaultParams[option] 288 | if (typeof mergeParams[option] !== 'undefined') { 289 | mergedOptions[option] = mergeParams[option] 290 | } 291 | } 292 | return mergedOptions 293 | } 294 | 295 | function isJSON (json) { 296 | try { 297 | if (json instanceof Object && JSON.parse(JSON.stringify(json))) { 298 | return true 299 | } 300 | return false 301 | } catch (err) { 302 | return false 303 | } 304 | } 305 | 306 | var _$src_8 = {}; 307 | (function (window) { 308 | 'use strict' 309 | 310 | var options = { 311 | searchInput: null, 312 | resultsContainer: null, 313 | json: [], 314 | success: Function.prototype, 315 | searchResultTemplate:'

109 |
110 | 111 |
207 | 208 | 209 | --------------------------------------------------------------------------------