simple, but effective system independent anti-debug trick based on well-documented APIs and does not involve assembly inline (means: it could be implemented in pure C). also it works as anti-dump sensor.
caution: I would recommend do _not_ use this thick in production code, because it’s based on the bug (two bugs actually: one in Windows, another in OllyDbg), which could be fixed at any moment. however, noting terrible happens if the bug would be fixed - the application just could not detect debugger/dumper.
in passing: I found this bug working on the project for a spectrography cherry group, well, not a cherry actually, but I prefer to keep the real name if it under the mat, anyway it’s all about Ciscar Fon - my first love, a gothic type, very kinky and yet creative.
in a nutshell: the whole idea based on PAGE_GUARD attribute. SDK says: “any attempt to access a guard page causes the system to raise a STATUS_GUARD_PAGE (80000001h) exception and turn off the guard page status… if a guard page exception occurs during a system service, the service typically returns a failure status indicator“. wow! how I like these words: “typically”, “usually”, “normally”… they say nothing, but at the same time they say everything!!! just read between the lines…
ReadProcessMemory: normally, /* I mean _normally_ */ ReadProcessMemory() returns error if it meet a page with PAGE_GUARD attribute. does it make sense? of course! but, _normally_ does not mean “every time”. Windows has a bug (I tested W2K SP4, XP SP3, Vista SP0 and Vista 64bit SP1 - they are all affected).
the bug: if PAGE_GUARD page is created by VirtualAlloc() call, ReadProcessMemory() turns off the guard page status without any exception and returns a failure status indicator. however, the second ReadProcessMemory() call returns a positive status (because PAGE_GUARD was turned off), so when the application will try to access to that page - there will be no exception (as it’s supposed to be), because there is no guard anymore.
the sensor: it’s easy to create a sensor to detect dumpers. allocate a page with PAGE_GUARD attribute and check it from time to time: just install SEH handler and read the content. no exception means we’re fucked, oh, sorry, dumped. I tested PE-TOOLS and other popular dumpers and they all were detected.
demo: to demonstrate the bug, I wrote two simple applications. one - “protected-like” application, another - dumper-like application. please download the sources and binaries.
“protected” application (PAGE_GUARD_BUG.c) is very simple, basically it just calls VirtualAlloc(,,,PAGE_READWRITE | PAGE_GUARD), displays the address/PID, waits for pressing ENTER and attempts to read the content of the allocated block. there is no SEH handler, so if an exception happens you will see the standard Windows message box.
p = VirtualAlloc(0, 0×1000, MEM_COMMIT, PAGE_READWRITE | PAGE_GUARD);
printf(”run turnoff.exe %d %d twice and press enter”, GetCurrentProcessId(), p);
gets(buf); printf(”result: %x\n”, *p);
and the “dumper” (turnoff.c ) just calls ReadProcessMemory() and displays the result:
h = OpenProcess(PROCESS_ALL_ACCESS, 0, atol(arg_id));
x = ReadProcessMemory(h, (void*)atol(arg_addr), &buf, 0×1, &n);
oh, here we go. follow me, please!
1) run the protected app (”$start PAGE_GUARD_BUG.exe“);
2) it displays ID/addr, like: id:1216 addr:4325376;
4) ops! exception! this means: PAGE_GUARD works!!!
5) run the protected app again (”$start PAGE_GUARD_BUG.exe“);
6) it displays ID/addr, like: id:1212 addr:4325376;
7) run the dumper, passing ID and addr (”$turnoff.exe 1212 4325376“);
8) it says: “satus:0, bytes read: 0″ (means: ReadProcMem failed);
9) but! if you switch to PAGE_GUARD_BUG.exe and press ENTER you will see no exception (means: PAGE_GUARD was turned off);
10) if you run the dumper twice (of course without pressing ENTER) it will displays: “satus:1, bytes read: 1″ (means: there is no PAGE_GUARD anymore);
nice trick, it’s it? but actually it was just a little warming-up. the real tricks are coming.
NOTE: if PAGE_GUARD attribute is assigned by VirtualProtect(), Windows respects the attribute and ReadProcessMemory() fails, leaving PAGE_GUARD turned on.
debuggers: what happens if a debugger meet PAGE_GUARD page? the answer is: there will be no exception, the debugger just turns PAGE_GUARD off, processes the content and forgets to return PAGE_GUARD back.
demonstration: to demonstrate this nasty behavior I wrote a simple program PAGE_GUARD_DBG.c, download it, please. and follow me. the source code is easy to understand:
push 0×102 ; PAGE_READONLY | PAGE_GUARD
mov eax, [eax]
execute it step-by-step, make step over the VirtualAlloc() call and display the content of the allocated memory block (for example, in IDA-Pro press
OllyDbg is even worse. it automatically resolves memory references displaying the content in the right column, so we don’t need to go to the dump window nor pressing CTRL-G… just trace it and the debugger will be detected, since there will be no exception!!!
IDA-Pro: well, what about if we just run the program under debugger? just run, no trace! IDA-Pro triggers an exception: “401035: A page of memory that marks the end of a data structure such as a stack or an array has been accessed (exc. code 80000001, TID 1312)” and offers to pass the exception to the application. in this case the debugger will be _not_ detected.
OllyDbg: the standard de-facto debugger stops when the application accesses PAGE_GUARD, giving the message “Break-on-access when reading” in the status bar, but Olly does not offer us to pass the exception to the application. even we go to options->exceptions and add 0×80000001 (STATUS_GUARD_PAGE) exception to the list, Olly will ignore it! guess, PAGE_GUARD is just a part of “memory-breakpoint” engine, so no way to pass PAGE_GUARD exception to the application, so it’s easy to detect the debugger. (I tested OllyDbg 1.10).
Soft-Ice: it does not display the content of PAGE_GUARDED pages, so it could not be detected by this way. in other hand, keeping the impotent content under PAGE_GUARD makes debugging much harder. we can’t perform full memory search, we can’t find cross references… we’re blind.