Friday, September 28, 2012

PAGE_EXECUTE_WRITECOPY As Anti-Debug Trick

In this post, i will share with you a poorly discussed anti-debug trick that i may be the first one to discover or disclose.

Now let's start with a quick introduction. If a memory page with the "PAGE_EXECUTE_READWRITE" access protection attributes is requested from the OS, then a page with the "PAGE_EXECUTE_WRITECOPY" attributes, not the "PAGE_EXECUTE_READWRITE" attributes is given.   

The reason for that behavior is so simple, that is, the OS memory manager wants to physically share the page between all the process instances (since it is guaranteed to be the same in all the process instances before any write).

Once you make the first write to the new page, the OS assigns a private copy of the page to the process in which the write occurrs and the page attributes change to PAGE_EXECUTE_READWRITE.

N.B. The same applies to pages requested with the PAGE_READWRITE attributes. They are initially given the "PAGE_WRITECOPY" attributes and after the first write, they turn into PAGE_READWRITE.

N.B. PAGE_EXECUTE_WRITECOPY and PAGE_WRITECOPY are not valid parameters to the "VirtualAlloc" or "VirtualAllocEx" function.

Now if you have a section in your executable with the read, write, and execute access attributes (See section xyz in the image below), then the abovementioned applies to it.
The access protection attributes given to section xyz causes its memory page to be mapped with the "PAGE_EXECUTE_WRITECOPY" attributes. See image below.
If we design section xyz in a way that it is never written to (e.g. does not contain self-modifying code) throughout the whole lifetime of the process, then the page will always be PAGE_EXECUTE_WRITECOPY even at process exit.

If the attributes change to PAGE_EXECUTE_READWRITE, that means the page must have been written to e.g. when another process, mostly a debugger, had called the "WriteProcessMemory" function while stepping-over, tracing-over, or placing software breakpoints. That definitely means the process is being debugged. See images below.

Now our executable of question can call the "VirtualQuery" function to check the page protection attributes of section xyz. If it is something other than PAGE_EXECUTE_WRITECOPY, then a debugger is present and the process should quit.

The good thing about this trick is that, unlike the 0xCC-scanning trick, it can detect software breakpoints even if there are no longer active (removed by the debugger).

Also, most debuggers in their default settings are used to place software breakpoints on modules' entry points, which means the page protection attributes change even before the reverse engineer starts to debug the module.

A common way to bypass this trick for stepping-over and tracing-over is to use hardware breakpoints which is an available option in OllyDbg v1.10 and OllyDbg v2.01 (alpha 4).

A simple demo can be found here and its source code from here.

Any ideas or comments are very welcome.

You can follow me on Twitter @waleedassar

13 comments:

  1. The debugger behaviour is actually a bit different. If you call, for example, VirtualProtect() on a READWRITE data page to change to READWRITE (ie do nothing), then the previous attributes will come back as READWRITE the first time. If you do it again, it will come back as WRITECOPY, and then always WRITECOPY, no matter how many times you restart the file. If you rename the file, then you will get READWRITE again the first time, and then WRITECOPY forever after that.

    ReplyDelete
  2. Why would the OS want to share a process-specific page between all processes? this would make sense for shared libraries such as kernel32.dll which indeed are shared, but sharing a PROCESS-SPECIFIC page doesn't make sense for me. Could you please clarify this?

    ReplyDelete
    Replies
    1. All instances of the same process e.g. you have three notepad.exe processes.

      Delete
    2. Thanks for clarifying. another follow up question which is a bit more in depth if you don't mind:
      I assume shared libraries' pages are marked as "global" pages(which allows the sharability) in the paging structures once they are loaded more than once.

      But if I create a new process, would it immediately mark its pages as global? just for the slim chance that you might want to run another instance of it? it sounds very wasteful to me. Sounds to me it would make more sense to only do that when the same pages of the process are trying to be loaded up more than once (as in another instance of the program is started up).

      Delete
    3. In which case they shouldn't have PAGE_EXECUTE_WRITECOPY as the default value, in my understanding.

      Anyway, I enjoy reading your interesting blog entries. please keep it up!

      Delete
  3. I think I've reached the conclusion now that what it might do is mark the pages' protection as PAGE_EXECUTE_WRITECOPY while NOT marking the pages global, thus if another instance of the program is started, the OS will know if the pages have been tampered with - in which case it would reload them, or not - in which case it will mark them global.



    ReplyDelete
  4. You fail to mention how relocations and IAT fetching (assuming the IAT is in the code section, MS likes to do this), will break this check completely since the code section will already be written to long before you gain control.

    You could of course disable relocations, but that would in turn disable ASLR for your module, and that would hardly be worth it.

    ReplyDelete
    Replies
    1. Okay, dude. You did not even bother looking at the source code of my demo. I isolated the trick into a new section.

      #pragma comment(linker,"/SECTION:xyz,ERW")
      #pragma code_seg("xyz")

      Check this again:
      http://pastebin.com/62De887S

      Delete
    2. Regarding the relocations and ASLR, it is just easy to have a section with no addresses to be relocated. For example, imagine you have a dummy function called MyGetProcAddress that wraps up kernel32.GetProcAddress and is located in .text section. You can always use MyGetProcAddress to retrieve all subsequent addresses of your API calls. Similar to this, you can use all the strings passed to MyGetProcAddress by their relative addresses to your Executable's ImageBase.

      Delete
  5. Oh, yea, should have read the code, sorry.

    Didn't understand from the article that you had added a section, figured you just patch the existing code section.

    You could just populate a structure containing all absolute addresses from the default code section (with relocs) and pass it to the do_debugger_check(), and all issues are resolved.

    Good job sir.

    ReplyDelete
  6. can also use NtQueryVirtualMemory to see if the pfn is still shareable. can also use a similar method to prevent subsequent process creation if you wanted to limit people to one instance of your process. You can get a count of how many mapped pfn's or using your method you could include the mem_scn_shared flag then modify an important code path.

    ReplyDelete
  7. if you're not stepping into the main2() function then you can pass it and still have you're PAGE_EXECUTE_WRITECOPY set.
    I guess it's because the code isn't modified with single step interrupts.

    ReplyDelete
  8. While circumventing the PAGE_EXECUTE_WRITECOPY anti-debugging on Win7 (OllyDbg 2) worked, it failed on Win8. Even with hw breakpoints the debugger was detected.
    Can someone confirm this?

    ReplyDelete