├── .gitignore ├── README.md ├── anti_debug.cpp └── anti_debug.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # debug folder 2 | Debug/ 3 | Release/ 4 | x64/ 5 | .vs/ 6 | 7 | # project files 8 | *.sln 9 | *.vcxproj* 10 | 11 | # utils 12 | main.cpp 13 | utilities.cpp 14 | utilities.hpp 15 | xor_cc.hpp 16 | 17 | # prerequisites 18 | *.d 19 | 20 | # compiled object files 21 | *.slo 22 | *.lo 23 | *.o 24 | *.obj 25 | 26 | # precompiled headers 27 | *.gch 28 | *.pch 29 | 30 | # compiled dynamic libraries 31 | *.so 32 | *.dylib 33 | *.dll 34 | 35 | # fortran module files 36 | *.mod 37 | *.smod 38 | 39 | # compiled static libraries 40 | *.lai 41 | *.la 42 | *.a 43 | *.lib 44 | 45 | # executables 46 | *.exe 47 | *.out 48 | *.app -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cpp-anti-debug 2 | 3 | a c++ library that offers debugger detection. 4 | 5 | ## getting started 6 | 7 | these instructions will get you a copy of the project up and running on your local machine for development and testing purposes. 8 | 9 | literally download & include 10 | 11 | you can then use 12 | 13 | ``` 14 | #include anti_debug.hpp 15 | 16 | //will return security::internal::debug_results::none if no debuggers etc. were found, returns the specific code if something was found 17 | //see anti_debug.hpp for more information 18 | if(security::check_security() != security::internal::debug_results::none) { 19 | std::cout << "security check was not successful." << std::endl; 20 | } 21 | ``` 22 | 23 | ## features 24 | string encryption to prevent static anlysis, strings get returned from one central function (if possible) 25 | the library features the following functions (in-depth explanation for most of them is in the code): 26 | alternatively you can use the big function check_security() to run all checks. 27 | ``` 28 | namespace memory { 29 | int being_debugged_peb(); 30 | int remote_debugger_present(); 31 | int check_window_name(); 32 | int is_debugger_present(); 33 | int nt_global_flag_peb(); 34 | int nt_query_information_process(); 35 | int nt_set_information_thread(); 36 | int debug_active_process(const char*); 37 | int write_buffer(); 38 | } 39 | 40 | namespace exceptions { 41 | int close_handle_exception(); 42 | int single_step_exception(); 43 | int int_3(); 44 | int int_2d(); 45 | int prefix_hop(); 46 | int debug_string(); 47 | } 48 | 49 | namespace timing { 50 | int rdtsc(); 51 | int query_performance_counter(); 52 | int get_tick_count(); 53 | } 54 | 55 | namespace cpu { 56 | int hardware_debug_registers(); 57 | int mov_ss(); 58 | } 59 | ``` 60 | 61 | ## todo: 62 | add virtualization and sandboxing checks to prevent your files from being executed in virtualized environments or sandboxes 63 | 64 | add detections for dumping tools 65 | 66 | ## notes: 67 | it's recommended to run the check multiple times (once every X seconds etc.) 68 | 69 | you will need to come up with the "xor_cc.hpp" yourself, it's a generic precompiler instruction that replaces xor("string") with a call to a generic xor function and passes the result of the first call to it (strings get unencrypted at runtime). you should be able to figure this out yourself. 70 | 71 | ## contributing 72 | please create a pull request if you have made another check and want to include it or create an issue if you want me to add another check. 73 | 74 | ## versioning 75 | 76 | no versions but updates (sometimes) 77 | 78 | ## authors 79 | * **John 'cetfor' Toterhi** - *initial work on the stuffs* - [GitHub](https://github.com/cetfor) 80 | * **Robert 'BaumFX'** - *refactored, commented stuff and added stuff* - [website](https://baumfx.xyz) - [GitHub](https://github.com/BaumFX) 81 | 82 | ## license 83 | 84 | its like (insert current line amount) lines but dont say you made it, okay? 85 | -------------------------------------------------------------------------------- /anti_debug.cpp: -------------------------------------------------------------------------------- 1 | #define _CRT_SECURE_NO_WARNINGS 2 | 3 | #include 4 | #include "anti_debug.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | //precompiler instructions -> replace the xor(string) with a xor(xor'd_string) so that 14 | //the strings won't be caught by static analysis 15 | #include "xor_cc.hpp" 16 | 17 | //disable warnings because #cleancode 18 | #pragma warning(disable : 6387) 19 | #pragma warning(disable : 4244) 20 | #pragma warning(disable : 6262) 21 | #pragma warning(disable : 4733) 22 | #pragma warning(disable : 4731) 23 | 24 | bool found = true; 25 | 26 | int __cdecl security::internal::vm_handler(EXCEPTION_RECORD* p_rec, void* est, unsigned char* p_context, void* disp) 27 | { 28 | found = true; 29 | (*(unsigned long*)(p_context + 0xB8)) += 4; 30 | return ExceptionContinueExecution; 31 | } 32 | 33 | void security::internal::to_lower(unsigned char* input) 34 | { 35 | char* p = (char*)input; 36 | unsigned long length = strlen(p); 37 | for (unsigned long i = 0; i < length; i++) p[i] = tolower(p[i]); 38 | } 39 | 40 | //returns strings for the check_window_name() function 41 | //this combined with the xoring of strings is to prevent static analysis / make it harder 42 | const wchar_t* security::internal::get_string(int index) { 43 | std::string value = ""; 44 | 45 | switch (index) { 46 | case 0: value = xor ("Qt5QWindowIcon"); break; 47 | case 1: value = xor ("OLLYDBG"); break; 48 | case 2: value = xor ("SunAwtFrame"); break; 49 | case 3: value = xor ("ID"); break; 50 | case 4: value = xor ("ntdll.dll"); break; 51 | case 5: value = xor ("antidbg"); break; 52 | case 6: value = xor ("%random_environment_var_name_that_doesnt_exist?[]<>@\\;*!-{}#:/~%"); break; 53 | case 7: value = xor ("%random_file_name_that_doesnt_exist?[]<>@\\;*!-{}#:/~%"); break; 54 | } 55 | 56 | return std::wstring(value.begin(), value.end()).c_str(); 57 | } 58 | 59 | //checks the process environment block (peb) for a "beingdebugged" field (gets set if process is launched in a debugger) 60 | //possible bypass: once the peb byte is set, set the value to 0 before the application checks 61 | int security::internal::memory::being_debugged_peb() { 62 | BOOL found = FALSE; 63 | _asm 64 | { 65 | xor eax, eax; //clear the eax register 66 | mov eax, fs: [0x30] ; //reference start of the process environment block 67 | mov eax, [eax + 0x02]; //beingdebugged is stored in peb + 2 68 | and eax, 0x000000FF; //reference one byte 69 | mov found, eax; //copy value to found 70 | } 71 | 72 | return (found) ? security::internal::debug_results::being_debugged_peb : security::internal::debug_results::none; 73 | } 74 | 75 | //checks if a debugger is running (in another system/process) 76 | //possible bypass: set a breakpoint before this gets called, single step, set the return value to 0 77 | int security::internal::memory::remote_debugger_present() { 78 | //declare variables to hold the process handle & bool to check if it was found 79 | HANDLE h_process = INVALID_HANDLE_VALUE; 80 | BOOL found = FALSE; 81 | 82 | //set the process handle to the current process 83 | h_process = GetCurrentProcess(); 84 | //check if a remote debugger is present 85 | CheckRemoteDebuggerPresent(h_process, &found); 86 | 87 | //if found is true, we return the right code. 88 | return (found) ? security::internal::debug_results::remote_debugger_present : security::internal::debug_results::none; 89 | } 90 | 91 | //checks if certain windows are present (not the name that can be easily changed but the window_class_name) 92 | //possible bypass: set a breakpoint before this gets called, single step, set the return value to 0 93 | int security::internal::memory::check_window_name() { 94 | const wchar_t* names[4] = { get_string(0), get_string(1), get_string(2), get_string(3) }; 95 | 96 | for (const wchar_t* name : names) { 97 | if (FindWindow(name, 0)) { return security::internal::debug_results::find_window; } 98 | } 99 | 100 | return security::internal::debug_results::none; 101 | } 102 | 103 | //another check for the peb flag, this time by the function from winapi.h 104 | //possible bypass: set a breakpoint before this gets called, single step, set the return value to 0 105 | int security::internal::memory::is_debugger_present() { 106 | //if debugger is found, we return the right code. 107 | return (IsDebuggerPresent()) ? security::internal::debug_results::debugger_is_present : security::internal::debug_results::none; 108 | } 109 | 110 | //looks for process environment block references 111 | //they usually start with FS:[0x30h]. fs = frame segment, indicates reference to the programs internal header structures 112 | //0x68 offset from the peb is ntglobalflag, three flags get set if a process is being debugged 113 | //FLG_HEAP_ENABLE_TAIL_CHECK (0x10), FLG_HEAP_ENABLE_FREE_CHECK (0x20), FLG_HEAP_VALIDATE_PARAMETERS(0x40) 114 | int security::internal::memory::nt_global_flag_peb() { 115 | //bool to indicate find status 116 | BOOL found = FALSE; 117 | _asm 118 | { 119 | xor eax, eax; //clear the eax register 120 | mov eax, fs: [0x30] ; //reference start of the peb 121 | mov eax, [eax + 0x68]; //peb+0x68 points to NtGlobalFlags 122 | and eax, 0x00000070; //check three flags 123 | mov found, eax; //copy value to found 124 | } 125 | 126 | //if found is true, we return the right code. 127 | return (found) ? security::internal::debug_results::being_debugged_peb : security::internal::debug_results::none; 128 | } 129 | 130 | //two checks here, 1. xxx, 2. NoDebugInherit 131 | int security::internal::memory::nt_query_information_process() { 132 | HANDLE h_process = INVALID_HANDLE_VALUE; 133 | DWORD found = FALSE; 134 | DWORD process_debug_port = 0x07; //first method, check msdn for details 135 | DWORD process_debug_flags = 0x1F; //second method, check msdn for details 136 | 137 | //get a handle to ntdll.dll so we can use NtQueryInformationProcess 138 | HMODULE h_ntdll = LoadLibraryW(get_string(4)); 139 | 140 | //if we cant get the handle for some reason, we return none 141 | if (h_ntdll == INVALID_HANDLE_VALUE || h_ntdll == NULL) { return security::internal::debug_results::none; } 142 | 143 | //dynamically acquire the address of NtQueryInformationProcess 144 | _NtQueryInformationProcess NtQueryInformationProcess = NULL; 145 | NtQueryInformationProcess = (_NtQueryInformationProcess)GetProcAddress(h_ntdll, xor("NtQueryInformationProcess")); 146 | 147 | //if we cant get access for some reason, we return none 148 | if (NtQueryInformationProcess == NULL) { return security::internal::debug_results::none; } 149 | 150 | //method 1: query ProcessDebugPort 151 | h_process = GetCurrentProcess(); 152 | NTSTATUS status = NtQueryInformationProcess(h_process, ProcessDebugPort, &found, sizeof(DWORD), NULL); 153 | 154 | //found something 155 | if (!status && found) { return security::internal::debug_results::nt_query_information_process; } 156 | 157 | //method 2: query ProcessDebugFlags 158 | status = NtQueryInformationProcess(h_process, process_debug_flags, &found, sizeof(DWORD), NULL); 159 | 160 | //the ProcessDebugFlags set found to 1 if no debugger is found, so we check !found. 161 | if (!status && !found) { return security::internal::debug_results::nt_query_information_process; } 162 | 163 | return security::internal::debug_results::none; 164 | } 165 | 166 | //hides the thread from any debugger, any attempt to control the process after this call ends the debugging session 167 | int security::internal::memory::nt_set_information_thread() { 168 | DWORD thread_hide_from_debugger = 0x11; 169 | 170 | //get a handle to ntdll.dll so we can use NtQueryInformationProcess 171 | HMODULE h_ntdll = LoadLibraryW(get_string(4)); 172 | 173 | //if we cant get the handle for some reason, we return none 174 | if (h_ntdll == INVALID_HANDLE_VALUE || h_ntdll == NULL) { return security::internal::debug_results::none; } 175 | 176 | //dynamically acquire the address of NtQueryInformationProcess 177 | _NtQueryInformationProcess NtQueryInformationProcess = NULL; 178 | NtQueryInformationProcess = (_NtQueryInformationProcess)GetProcAddress(h_ntdll, xor ("NtQueryInformationProcess")); 179 | 180 | //if we cant get access for some reason, we return none 181 | if (NtQueryInformationProcess == NULL) { return security::internal::debug_results::none; } 182 | 183 | //make call to detach a debugger :moyai: 184 | (_NtSetInformationThread)(GetCurrentThread(), thread_hide_from_debugger, 0, 0, 0); 185 | 186 | return security::internal::debug_results::none; 187 | } 188 | 189 | int security::internal::memory::debug_active_process() { 190 | BOOL found = FALSE; 191 | STARTUPINFOA si = { 0 }; 192 | PROCESS_INFORMATION pi = { 0 }; 193 | si.cb = sizeof(si); 194 | TCHAR sz_path[MAX_PATH]; 195 | DWORD exit_code = 0; 196 | 197 | DWORD proc_id = GetCurrentProcessId(); 198 | std::stringstream stream; 199 | stream << proc_id; 200 | std::string args = stream.str(); 201 | 202 | const char* cp_id = args.c_str(); 203 | CreateMutex(NULL, FALSE, get_string(5)); 204 | if (GetLastError() != ERROR_SUCCESS) 205 | { 206 | //if we get here, we're in the child process 207 | if (DebugActiveProcess((DWORD)atoi(cp_id))) 208 | { 209 | //no debugger found 210 | return security::internal::debug_results::none; 211 | } 212 | else 213 | { 214 | //debugger found, exit child with unique code that we can check for 215 | exit(555); 216 | } 217 | } 218 | 219 | //parent process 220 | DWORD pid = GetCurrentProcessId(); 221 | GetModuleFileName(NULL, sz_path, MAX_PATH); 222 | 223 | char cmdline[MAX_PATH + 1 + sizeof(int)]; 224 | snprintf(cmdline, sizeof(cmdline), xor ("%ws %d"), sz_path, pid); 225 | 226 | //start child process 227 | BOOL success = CreateProcessA( 228 | NULL, //path (NULL means use cmdline instead) 229 | cmdline, //command line 230 | NULL, //process handle not inheritable 231 | NULL, //thread handle not inheritable 232 | FALSE, //set handle inheritance to FALSE 233 | 0, //no creation flags 234 | NULL, //use parent's environment block 235 | NULL, //use parent's starting directory 236 | &si, //pointer to STARTUPINFO structure 237 | &pi); //pointer to PROCESS_INFORMATION structure 238 | 239 | //wait until child process exits and get the code 240 | WaitForSingleObject(pi.hProcess, INFINITE); 241 | 242 | //check for our unique exit code 243 | if (GetExitCodeProcess(pi.hProcess, &exit_code) == 555) { found = TRUE; } 244 | 245 | // Close process and thread handles. 246 | CloseHandle(pi.hProcess); 247 | CloseHandle(pi.hThread); 248 | //if found is true, we return the right code. 249 | return (found) ? security::internal::debug_results::being_debugged_peb : security::internal::debug_results::none; 250 | } 251 | 252 | //uses MEM_WRITE_WATCH feature of VirtualAlloc to check whether a debugger etc. is writing to our memory 253 | //4 possible options: 254 | //allocate a buffer, write to it once, check if its accessed more than once 255 | //allocate a buffer and pass it to an API where the buffer isn't touched (but it's still being passed as an argument), then check if its accessed more than once 256 | //allocate a buffer and store something "important" (IsDebuggerPresent() return value etc.), check if the memory was used once or not 257 | //allocate an executable buffer, copy a debug check routine to it, run the check and check if any writes were performed after the initial write 258 | 259 | //thanks to LordNoteworthy/al-khaser for the idea 260 | int security::internal::memory::write_buffer() { 261 | //first option 262 | 263 | //vars to store the amount of accesses to the buffer and the granularity for GetWriteWatch() 264 | ULONG_PTR hits; 265 | DWORD granularity; 266 | 267 | PVOID* addresses = static_cast(VirtualAlloc(NULL, 4096 * sizeof(PVOID), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); 268 | if (addresses == NULL) { 269 | return security::internal::debug_results::write_buffer; } 270 | 271 | int* buffer = static_cast(VirtualAlloc(NULL, 4096 * 4096, MEM_RESERVE | MEM_COMMIT | MEM_WRITE_WATCH, PAGE_READWRITE)); 272 | if (buffer == NULL) { 273 | VirtualFree(addresses, 0, MEM_RELEASE); 274 | return security::internal::debug_results::write_buffer; 275 | } 276 | 277 | //read the buffer once 278 | buffer[0] = 1234; 279 | 280 | hits = 4096; 281 | if (GetWriteWatch(0, buffer, 4096, addresses, &hits, &granularity) != 0) { return security::internal::debug_results::write_buffer; } 282 | else 283 | { 284 | //free the memory again 285 | VirtualFree(addresses, 0, MEM_RELEASE); 286 | VirtualFree(buffer, 0, MEM_RELEASE); 287 | 288 | //we should have 1 hit if everything is fine 289 | return (hits == 1) ? security::internal::debug_results::none : security::internal::debug_results::write_buffer; 290 | } 291 | 292 | //second option 293 | 294 | BOOL result = FALSE, error = FALSE; 295 | 296 | addresses = static_cast(VirtualAlloc(NULL, 4096 * sizeof(PVOID), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); 297 | if (addresses == NULL) { return security::internal::debug_results::write_buffer; } 298 | 299 | buffer = static_cast(VirtualAlloc(NULL, 4096 * 4096, MEM_RESERVE | MEM_COMMIT | MEM_WRITE_WATCH, PAGE_READWRITE)); 300 | if (buffer == NULL) { 301 | VirtualFree(addresses, 0, MEM_RELEASE); 302 | return security::internal::debug_results::write_buffer; 303 | } 304 | 305 | //make some calls where a buffer *can* be written to, but isn't actually edited because we pass invalid parameters 306 | if (GlobalGetAtomName(INVALID_ATOM, (LPTSTR)buffer, 1) != FALSE || GetEnvironmentVariable(get_string(6), (LPWSTR)buffer, 4096 * 4096) != FALSE || GetBinaryType(get_string(7), (LPDWORD)buffer) != FALSE 307 | || HeapQueryInformation(0, (HEAP_INFORMATION_CLASS)69, buffer, 4096, NULL) != FALSE || ReadProcessMemory(INVALID_HANDLE_VALUE, (LPCVOID)0x69696969, buffer, 4096, NULL) != FALSE 308 | || GetThreadContext(INVALID_HANDLE_VALUE, (LPCONTEXT)buffer) != FALSE || GetWriteWatch(0, &security::internal::memory::write_buffer, 0, NULL, NULL, (PULONG)buffer) == 0) { 309 | result = false; 310 | error = true; 311 | } 312 | 313 | if (error == FALSE) 314 | { 315 | //all calls failed as they're supposed to 316 | hits = 4096; 317 | if (GetWriteWatch(0, buffer, 4096, addresses, &hits, &granularity) != 0) 318 | { 319 | result = FALSE; 320 | } 321 | else 322 | { 323 | //should have zero reads here because GlobalGetAtomName doesn't probe the buffer until other checks have succeeded 324 | //if there's an API hook or debugger in here it'll probably try to probe the buffer, which will be caught here 325 | result = hits != 0; 326 | } 327 | } 328 | 329 | VirtualFree(addresses, 0, MEM_RELEASE); 330 | VirtualFree(buffer, 0, MEM_RELEASE); 331 | 332 | return result; 333 | } 334 | 335 | //will throw an exception when trying to close an invalid handle (only when debugged) 336 | //so if we pass an invalid handle and get the exception, we know that we're being debugged 337 | //possible bypass: change the passed handle to an existing handle or adjust the extended instruction pointer register to skip over the invalid handle 338 | int security::internal::exceptions::close_handle_exception() { 339 | //invalid handle 340 | HANDLE h_invalid = (HANDLE)0xDEADBEEF; 341 | 342 | __try 343 | { 344 | CloseHandle(h_invalid); 345 | } 346 | __except (EXCEPTION_EXECUTE_HANDLER) 347 | { 348 | //if we get the exception, we return the right code. 349 | return security::internal::debug_results::close_handle_exception; 350 | } 351 | 352 | return security::internal::debug_results::none; 353 | } 354 | 355 | //we force an exception to occur, if it occurs outside of a debugger the __except() handler is called, if it's inside a debugger it will not be called 356 | int security::internal::exceptions::single_step_exception() { 357 | BOOL debugger_present = TRUE; 358 | __try 359 | { 360 | __asm 361 | { 362 | pushfd //save flag register 363 | or dword ptr[esp], 0x100 //set trap flag in EFlags 364 | popfd //restore flag register 365 | nop //does nothing 366 | } 367 | } 368 | __except (EXCEPTION_EXECUTE_HANDLER) { debugger_present = FALSE; } 369 | 370 | //if the exception was raised, return none 371 | //if a debugger handled the exception (no exception for us to handle), return detection 372 | return (debugger_present) ? security::internal::debug_results::single_step : security::internal::debug_results::none; 373 | } 374 | 375 | //i3 is a standard software breakcode (opcode 0xCC), when you set a breakpoint the debugger replaces the opcode under the breakpoint location with 376 | //0xCC (int 3), when the debugger hits this opcode, it breaks and restores the original opcode (after clicking go again) 377 | //we use an exception handler to switch found from true to false 378 | //without the debugger, something has to handle the breakpoint exception (our handler) 379 | //if it doesn't get hit, theres a debugger handling it instead -> we can detect that our handler was not run -> debugger found 380 | //possible bypass: most debuggers give an option (pass exception to the application or let the debugger handle it), if the debugger handles it, we can detect it. 381 | int security::internal::exceptions::int_3() { 382 | __try 383 | { 384 | _asm 385 | { 386 | int 3; //0xCC / standard software breakpoint 387 | } 388 | } 389 | //exception is handled by our app = debugger did not attempt to intervene 390 | __except (EXCEPTION_EXECUTE_HANDLER) { return security::internal::debug_results::none; } 391 | 392 | //if we don't get the exception, we return the right code. 393 | return security::internal::debug_results::int_3_cc; 394 | } 395 | 396 | //2d is a kernel interrupt (opcode 0x2D), when it gets executed, windows will use the extended instruction pointer register value as the exception address, 397 | //after then it increments the extended instruction pointer register value by 1. 398 | //windows also checks the eax register value to determine how to adjust the exception address 399 | //if the eax register is 1, 3, or 4 (on all windows version) or 5 on vista and later, it will increase the exception address by one 400 | //here we have 2 options, first we check if we handle the exception or the debugger (same as above) 401 | // 402 | //after increasing the exception address windows issues an EXCEPTION_BREAKPOINT (0x80000003) exception if a debugger is present. 403 | //some debuggers use the extended instruction pointer register to determine from where to resume execution 404 | //some other debuggers will use the exception address as the address from where to resume execution 405 | //this might result in a single-byte instruction being skipped (because windows increased the exception address by one) or in the 406 | //execution of a completely different instruction because the first instruction byte is missing. 407 | //this behaviour can be checked to see whether a debugger is present. 408 | int security::internal::exceptions::int_2d() { 409 | BOOL found = false; 410 | __try 411 | { 412 | _asm 413 | { 414 | int 0x2D; //kernel breakpoint 415 | } 416 | } 417 | 418 | __except (EXCEPTION_EXECUTE_HANDLER) { return security::internal::debug_results::none; } 419 | 420 | __try 421 | { 422 | __asm 423 | { 424 | xor eax, eax; //clear the eax register 425 | int 2dh; //try to get the debugger to bypass the instruction 426 | inc eax; //set the eax register to 1 427 | mov found, eax; 428 | } 429 | } 430 | 431 | __except (EXCEPTION_EXECUTE_HANDLER) { return security::internal::debug_results::none; } 432 | 433 | //if we don't get the exception, we return the right code. 434 | return security::internal::debug_results::int_2; 435 | } 436 | 437 | int security::internal::exceptions::prefix_hop() { 438 | __try 439 | { 440 | _asm 441 | { 442 | __emit 0xF3; //0xF3 0x64 is the prefix rep 443 | __emit 0x64; 444 | __emit 0xCC; //this gets skipped over if being debugged (read security::internal::exceptions::int_3()) 445 | } 446 | } 447 | 448 | __except (EXCEPTION_EXECUTE_HANDLER) { return security::internal::debug_results::none; } 449 | 450 | //if we don't get the exception, we return the right code. 451 | return security::internal::debug_results::prefix_hop; 452 | } 453 | 454 | //checks whether a debugger is present by attempting to output a string to the debugger (helper functions for debugging applications) 455 | //if no debugger is present an error occurs -> we can check if the last error is not 0 (an error) -> debugger not found 456 | int security::internal::exceptions::debug_string() { 457 | SetLastError(0); 458 | OutputDebugStringA(xor ("anti-debugging test.")); 459 | 460 | return (GetLastError() != 0) ? security::internal::debug_results::debug_string : security::internal::debug_results::none; 461 | } 462 | 463 | int security::internal::timing::rdtsc() { 464 | //integers for time values 465 | UINT64 time_a, time_b = 0; 466 | int time_upper_a, time_lower_a = 0; 467 | int time_upper_b, time_lower_b = 0; 468 | 469 | _asm 470 | { 471 | //rdtsc stores result across EDX:EAX 472 | rdtsc; 473 | mov time_upper_a, edx; 474 | mov time_lower_a, eax; 475 | 476 | //junk code -> skip through breakpoint 477 | xor eax, eax; 478 | mov eax, 5; 479 | shr eax, 2; 480 | sub eax, ebx; 481 | cmp eax, ecx 482 | 483 | rdtsc; 484 | mov time_upper_b, edx; 485 | mov time_lower_b, eax; 486 | } 487 | 488 | time_a = time_upper_a; 489 | time_a = (time_a << 32) | time_lower_a; 490 | 491 | time_b = time_upper_b; 492 | time_b = (time_b << 32) | time_lower_b; 493 | 494 | //0x10000 is purely empirical and is based on the computer's clock cycle, could be less if the cpu clocks really fast etc. 495 | //should change depending on the length and complexity of the code between each rdtsc operation (-> asm code inbetween needs longer to execute but takes A LOT longer if its being debugged / someone is stepping through it) 496 | return (time_b - time_a > 0x10000) ? security::internal::debug_results::rdtsc : security::internal::debug_results::none; 497 | } 498 | 499 | //checks how much time passes between the two query performance counters 500 | //if more than X (here 30ms) pass, a debugger is slowing execution down (manual breakpoints etc.) 501 | int security::internal::timing::query_performance_counter() { 502 | LARGE_INTEGER t1; 503 | LARGE_INTEGER t2; 504 | 505 | QueryPerformanceCounter(&t1); 506 | 507 | //junk code 508 | _asm 509 | { 510 | xor eax, eax; 511 | push eax; 512 | push ecx; 513 | pop eax; 514 | pop ecx; 515 | sub ecx, eax; 516 | shl ecx, 4; 517 | } 518 | 519 | QueryPerformanceCounter(&t2); 520 | 521 | //30 is a random value 522 | return ((t2.QuadPart - t1.QuadPart) > 30) ? security::internal::debug_results::query_performance_counter : security::internal::debug_results::none; 523 | } 524 | 525 | //same as above 526 | int security::internal::timing::get_tick_count() { 527 | DWORD t1; 528 | DWORD t2; 529 | 530 | t1 = GetTickCount64(); 531 | 532 | //junk code to keep the cpu busy for a few cycles so that time passes and the return value of GetTickCount() changes (so we can detect if it runs at "normal" speed or is being checked through by a human) 533 | _asm 534 | { 535 | xor eax, eax; 536 | push eax; 537 | push ecx; 538 | pop eax; 539 | pop ecx; 540 | sub ecx, eax; 541 | shl ecx, 4; 542 | } 543 | 544 | t2 = GetTickCount64(); 545 | 546 | //30 ms seems ok 547 | return ((t2 - t1) > 30) ? security::internal::debug_results::query_performance_counter : security::internal::debug_results::none; 548 | } 549 | 550 | int security::internal::cpu::hardware_debug_registers() { 551 | CONTEXT ctx = { 0 }; 552 | HANDLE h_thread = GetCurrentThread(); 553 | 554 | ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; 555 | if (GetThreadContext(h_thread, &ctx)) 556 | { 557 | return ((ctx.Dr0 != 0x00) || (ctx.Dr1 != 0x00) || (ctx.Dr2 != 0x00) || (ctx.Dr3 != 0x00) || (ctx.Dr6 != 0x00) || (ctx.Dr7 != 0x00)) ? security::internal::debug_results::hardware_debug_registers : security::internal::debug_results::none; 558 | } 559 | 560 | return security::internal::debug_results::none; 561 | } 562 | 563 | //single stepping check 564 | int security::internal::cpu::mov_ss() { 565 | BOOL found = FALSE; 566 | 567 | _asm 568 | { 569 | push ss; 570 | pop ss; 571 | pushfd; 572 | test byte ptr[esp + 1], 1; 573 | jne fnd; 574 | jmp end; 575 | fnd: 576 | mov found, 1; 577 | end: 578 | nop; 579 | } 580 | 581 | return (found) ? security::internal::debug_results::mov_ss : security::internal::debug_results::none; 582 | } 583 | 584 | int security::internal::virtualization::check_cpuid() { 585 | bool found = false; 586 | __asm { 587 | xor eax, eax 588 | mov eax, 0x40000000 589 | cpuid 590 | cmp ecx, 0x4D566572 591 | jne nop_instr 592 | cmp edx, 0x65726177 593 | jne nop_instr 594 | mov found, 0x1 595 | nop_instr: 596 | nop 597 | } 598 | 599 | return (found) ? security::internal::debug_results::check_cpuid : security::internal::debug_results::none; 600 | } 601 | 602 | int security::internal::virtualization::check_registry() { 603 | HKEY h_key = 0; 604 | if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, xor (L"HARDWARE\\ACPI\\DSDT\\VBOX__"), 0, KEY_READ, &h_key) == ERROR_SUCCESS) { return security::internal::debug_results::check_registry; } 605 | 606 | return security::internal::debug_results::none; 607 | } 608 | 609 | int security::internal::virtualization::vm() { 610 | if (CreateFile(xor (L"\\\\.\\VBoxMiniRdrDN"), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, 0, 0) != INVALID_HANDLE_VALUE) { return security::internal::debug_results::vm; } 611 | 612 | if (LoadLibrary(xor (L"VBoxHook.dll"))) { return security::internal::debug_results::vm; } 613 | 614 | HKEY h_key = 0; 615 | if ((ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, xor(L"SOFTWARE\\Oracle\\VirtualBox Guest Additions"), 0, KEY_READ, &h_key)) && h_key) { RegCloseKey(h_key); return security::internal::debug_results::vm; } 616 | 617 | h_key = 0; 618 | if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, xor(L"HARDWARE\\DESCRIPTION\\System"), 0, KEY_READ, &h_key) == ERROR_SUCCESS) 619 | { 620 | unsigned long type = 0; 621 | unsigned long size = 0x100; 622 | char* systembiosversion = (char*)LocalAlloc(LMEM_ZEROINIT, size + 10); 623 | if (ERROR_SUCCESS == RegQueryValueEx(h_key, xor(L"SystemBiosVersion"), 0, &type, (unsigned char*)systembiosversion, &size)) 624 | { 625 | to_lower((unsigned char*)systembiosversion); 626 | if (type == REG_SZ || type == REG_MULTI_SZ) 627 | { 628 | if (strstr(systembiosversion, xor("vbox"))) 629 | { 630 | return security::internal::debug_results::vm; 631 | } 632 | } 633 | } 634 | LocalFree(systembiosversion); 635 | 636 | type = 0; 637 | size = 0x200; 638 | char* videobiosversion = (char*)LocalAlloc(LMEM_ZEROINIT, size + 10); 639 | if (ERROR_SUCCESS == RegQueryValueEx(h_key, xor(L"VideoBiosVersion"), 0, &type, (unsigned char*)videobiosversion, &size)) 640 | { 641 | if (type == REG_MULTI_SZ) 642 | { 643 | char* video = videobiosversion; 644 | while (*(unsigned char*)video) 645 | { 646 | to_lower((unsigned char*)video); 647 | if (strstr(video, xor("oracle")) || strstr(video, xor("virtualbox"))) { return security::internal::debug_results::vm; } 648 | video = &video[strlen(video) + 1]; 649 | } 650 | } 651 | } 652 | LocalFree(videobiosversion); 653 | RegCloseKey(h_key); 654 | } 655 | 656 | HANDLE h = CreateFile(xor(L"\\\\.\\pipe\\VBoxTrayIPC"), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0); 657 | if (h != INVALID_HANDLE_VALUE) { CloseHandle(h); return security::internal::debug_results::vm; } 658 | 659 | unsigned long pnsize = 0x1000; 660 | char* s_provider = (char*)LocalAlloc(LMEM_ZEROINIT, pnsize); 661 | wchar_t w_provider[0x1000]; 662 | mbstowcs(w_provider, s_provider, strlen(s_provider) + 1); 663 | 664 | h_key = 0; 665 | const char* s_subkey = xor("SYSTEM\\CurrentControlSet\\Enum\\IDE"); 666 | wchar_t w_subkey[22]; 667 | mbstowcs(w_subkey, s_subkey, strlen(s_subkey) + 1); 668 | if ((ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE, w_subkey, 0, KEY_READ, &h_key)) && h_key) 669 | { 670 | unsigned long n_subkeys = 0; 671 | unsigned long max_subkey_length = 0; 672 | if (ERROR_SUCCESS == RegQueryInfoKey(h_key, 0, 0, 0, &n_subkeys, &max_subkey_length, 0, 0, 0, 0, 0, 0)) 673 | { 674 | //n_subkeys is usually 2 675 | if (n_subkeys) 676 | { 677 | char* s_new_key = (char*)LocalAlloc(LMEM_ZEROINIT, max_subkey_length + 1); 678 | for (unsigned long i = 0; i < n_subkeys; i++) 679 | { 680 | memset(s_new_key, 0, max_subkey_length + 1); 681 | HKEY h_new_key = 0; 682 | 683 | wchar_t w_key_new[2048]; 684 | mbstowcs(w_key_new, s_new_key, strlen(s_new_key) + 1); 685 | 686 | if (ERROR_SUCCESS == RegEnumKey(h_key, i, w_key_new, max_subkey_length + 1)) 687 | { 688 | if ((RegOpenKeyEx(h_key, w_key_new, 0, KEY_READ, &h_new_key) == ERROR_SUCCESS) && h_new_key) 689 | { 690 | unsigned long nn = 0; 691 | unsigned long maxlen = 0; 692 | RegQueryInfoKey(h_new_key, 0, 0, 0, &nn, &maxlen, 0, 0, 0, 0, 0, 0); 693 | char* s_newer_key = (char*)LocalAlloc(LMEM_ZEROINIT, maxlen + 1); 694 | wchar_t w_key_newer[2048]; 695 | mbstowcs(w_key_newer, s_newer_key, strlen(s_newer_key) + 1); 696 | if (RegEnumKey(h_new_key, 0, w_key_newer, maxlen + 1) == ERROR_SUCCESS) 697 | { 698 | HKEY HKKK = 0; 699 | if (RegOpenKeyEx(h_new_key, w_key_newer, 0, KEY_READ, &HKKK) == ERROR_SUCCESS) 700 | { 701 | unsigned long size = 0xFFF; 702 | unsigned char value_name[0x1000] = { 0 }; 703 | if (RegQueryValueEx(h_new_key, xor(L"FriendlyName"), 0, 0, value_name, &size) == ERROR_SUCCESS) { to_lower(value_name); if (strstr((char*)value_name, xor("vbox"))) { return security::internal::debug_results::vm; } } 704 | RegCloseKey(HKKK); 705 | } 706 | } 707 | LocalFree(w_key_newer); 708 | LocalFree(s_newer_key); 709 | RegCloseKey(h_new_key); 710 | } 711 | } 712 | } 713 | LocalFree(s_new_key); 714 | } 715 | } 716 | RegCloseKey(h_key); 717 | } 718 | 719 | __asm 720 | { 721 | push offset vm_handler 722 | push dword ptr fs : [0x0] 723 | mov dword ptr fs : [0x0] , esp 724 | __emit 0Fh 725 | __emit 3Fh 726 | __emit 07h 727 | __emit 0Bh 728 | } 729 | 730 | if (found == false) { return security::internal::debug_results::vm; } 731 | 732 | __asm 733 | { 734 | pop dword ptr fs : [0x0] 735 | pop eax 736 | } 737 | 738 | bool found = 0; 739 | __asm 740 | { 741 | pushad 742 | pushfd 743 | pop eax 744 | or eax, 0x00200000 745 | push eax 746 | popfd 747 | pushfd 748 | pop eax 749 | and eax, 0x00200000 750 | jz cpu_id_not_supported 751 | xor eax, eax 752 | xor edx, edx 753 | xor ecx, ecx 754 | xor ebx, ebx 755 | inc eax 756 | cpuid 757 | test ecx, 0x80000000 758 | jnz hypervisor 759 | mov found, 0 760 | jmp bye 761 | hypervisor : 762 | mov found, 1 763 | jmp bye 764 | cpu_id_not_supported : 765 | mov found, 2 766 | bye : 767 | popad 768 | } 769 | if (found == 1) { return security::internal::debug_results::vm; } 770 | 771 | return security::internal::debug_results::none; 772 | } 773 | 774 | security::internal::debug_results security::check_security() { 775 | //memory 776 | if (security::internal::memory::being_debugged_peb() != security::internal::debug_results::none) { 777 | return security::internal::debug_results::being_debugged_peb; 778 | } 779 | if (security::internal::memory::remote_debugger_present() != security::internal::debug_results::none) { 780 | return security::internal::debug_results::remote_debugger_present; 781 | } 782 | 783 | if (security::internal::memory::check_window_name() != security::internal::debug_results::none) { 784 | return security::internal::debug_results::find_window; 785 | } 786 | 787 | if (security::internal::memory::is_debugger_present() != security::internal::debug_results::none) { 788 | return security::internal::debug_results::debugger_is_present; 789 | } 790 | 791 | if (security::internal::memory::nt_global_flag_peb() != security::internal::debug_results::none) { 792 | return security::internal::debug_results::being_debugged_peb; 793 | } 794 | 795 | if (security::internal::memory::nt_query_information_process() != security::internal::debug_results::none) { 796 | return security::internal::debug_results::nt_query_information_process; 797 | } 798 | 799 | //if (security::internal::memory::debug_active_process() != security::internal::debug_results::none) { 800 | //return security::internal::debug_results::debug_active_process; 801 | //} 802 | 803 | if (security::internal::memory::write_buffer() != security::internal::debug_results::none) { 804 | return security::internal::debug_results::write_buffer; 805 | } 806 | 807 | //exceptions 808 | if (security::internal::exceptions::close_handle_exception() != security::internal::debug_results::none) { 809 | return security::internal::debug_results::close_handle_exception; 810 | } 811 | 812 | if (security::internal::exceptions::single_step_exception() != security::internal::debug_results::none) { 813 | return security::internal::debug_results::single_step; 814 | } 815 | 816 | if (security::internal::exceptions::int_3() != security::internal::debug_results::none) { 817 | return security::internal::debug_results::int_3_cc; 818 | } 819 | 820 | if (security::internal::exceptions::int_2d() != security::internal::debug_results::none) { 821 | return security::internal::debug_results::int_2; 822 | } 823 | 824 | if (security::internal::exceptions::prefix_hop() != security::internal::debug_results::none) { 825 | return security::internal::debug_results::prefix_hop; 826 | } 827 | 828 | if (security::internal::exceptions::debug_string() != security::internal::debug_results::none) { 829 | return security::internal::debug_results::debug_string; 830 | } 831 | 832 | //timing 833 | if (security::internal::timing::rdtsc() != security::internal::debug_results::none) { 834 | return security::internal::debug_results::rdtsc; 835 | } 836 | 837 | if (security::internal::timing::query_performance_counter() != security::internal::debug_results::none) { 838 | return security::internal::debug_results::query_performance_counter; 839 | } 840 | 841 | if (security::internal::timing::get_tick_count() != security::internal::debug_results::none) { 842 | return security::internal::debug_results::get_tick_count; 843 | } 844 | 845 | //cpu 846 | if (security::internal::cpu::hardware_debug_registers() != security::internal::debug_results::none) { 847 | return security::internal::debug_results::hardware_debug_registers; 848 | } 849 | 850 | if (security::internal::cpu::mov_ss() != security::internal::debug_results::none) { 851 | return security::internal::debug_results::mov_ss; 852 | } 853 | 854 | //virtualization 855 | if (security::internal::virtualization::check_cpuid() != security::internal::debug_results::none) { 856 | return security::internal::debug_results::check_cpuid; 857 | } 858 | 859 | if (security::internal::virtualization::check_registry() != security::internal::debug_results::none) { 860 | return security::internal::debug_results::check_registry; 861 | } 862 | 863 | if (security::internal::virtualization::vm() != security::internal::debug_results::none) { 864 | return security::internal::debug_results::vm; 865 | } 866 | 867 | return security::internal::debug_results::none; 868 | } 869 | -------------------------------------------------------------------------------- /anti_debug.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #pragma warning( disable : 4091) 8 | 9 | //main namespace for security 10 | namespace security { 11 | //internal (used by the security itself, no need to be used outside of namespace) 12 | namespace internal { 13 | int __cdecl vm_handler(EXCEPTION_RECORD* p_rec, void* est, unsigned char* p_context, void* disp); 14 | void to_lower(unsigned char* input); 15 | const wchar_t* get_string(int index); 16 | 17 | //dynamically resolved functions 18 | typedef NTSTATUS(__stdcall* _NtQueryInformationProcess)(_In_ HANDLE, _In_ unsigned int, _Out_ PVOID, _In_ ULONG, _Out_ PULONG); 19 | typedef NTSTATUS(__stdcall* _NtSetInformationThread)(_In_ HANDLE, _In_ THREAD_INFORMATION_CLASS, _In_ PVOID, _In_ ULONG); 20 | 21 | //enum for the results of the antidebugger 22 | extern enum debug_results 23 | { 24 | //nothing was caught, value = 0 25 | none = 0x0000, 26 | 27 | //something caught in memory (0x1000 - 0x1009) 28 | being_debugged_peb = 0x1000, 29 | remote_debugger_present = 0x1001, 30 | debugger_is_present = 0x1002, 31 | dbg_global_flag = 0x1003, 32 | nt_query_information_process = 0x0004, 33 | find_window = 0x1005, 34 | output_debug_string = 0x1006, 35 | nt_set_information_thread = 0x1007, 36 | debug_active_process = 0x1008, 37 | write_buffer = 0x1009, 38 | 39 | //something caught in exceptions (0x2000 - 0x2005) 40 | close_handle_exception = 0x2000, 41 | single_step = 0x2001, 42 | int_3_cc = 0x2002, 43 | int_2 = 0x2003, 44 | prefix_hop = 0x2004, 45 | debug_string = 0x2005, 46 | 47 | //something caught with timings (0x3000 - 0x3002) 48 | rdtsc = 0x3000, 49 | query_performance_counter = 0x3001, 50 | get_tick_count = 0x3002, 51 | 52 | //something caught in cpu (0x4000 - 0x4001) 53 | hardware_debug_registers = 0x4000, 54 | mov_ss = 0x4001, 55 | 56 | //virtualization (0x5000 - 0x5003) 57 | check_cpuid = 0x5000, 58 | check_registry = 0x5001, 59 | vm = 0x5002, 60 | }; 61 | 62 | namespace memory { 63 | int being_debugged_peb(); 64 | int remote_debugger_present(); 65 | int check_window_name(); 66 | int is_debugger_present(); 67 | int nt_global_flag_peb(); 68 | int nt_query_information_process(); 69 | int nt_set_information_thread(); 70 | int debug_active_process(); 71 | int write_buffer(); 72 | } 73 | 74 | namespace exceptions { 75 | int close_handle_exception(); 76 | int single_step_exception(); 77 | int int_3(); 78 | int int_2d(); 79 | int prefix_hop(); 80 | int debug_string(); 81 | } 82 | 83 | namespace timing { 84 | int rdtsc(); 85 | int query_performance_counter(); 86 | int get_tick_count(); 87 | } 88 | 89 | namespace cpu { 90 | int hardware_debug_registers(); 91 | int mov_ss(); 92 | } 93 | 94 | namespace virtualization { 95 | int check_cpuid(); 96 | int check_registry(); 97 | int vm(); 98 | } 99 | } 100 | 101 | internal::debug_results check_security(); 102 | } --------------------------------------------------------------------------------