Windows System Software -- Consulting, Training, Development -- Unique Expertise, Guaranteed Results

Home NTDEV
Before Posting...
Please check out the Community Guidelines in the Announcements and Administration Category.

More Info on Driver Writing and Debugging


The free OSR Learning Library has more than 50 articles on a wide variety of topics about writing and debugging device drivers and Minifilters. From introductory level to advanced. All the articles have been recently reviewed and updated, and are written using the clear and definitive style you've come to expect from OSR over the years.


Check out The OSR Learning Library at: https://www.osr.com/osr-learning-library/


Most stable way of finding executable pages in kernel?

david_mk85david_mk85 Member Posts: 6
edited January 5 in NTDEV

Hi everyone.
We want to write a driver that scans and finds executable pages in kernel so we can find anything suspicious/manually mapped drivers

What is the most stable way of iterating through all the executable pages in kernel so we can scan their content? we don't want to use any undocumented method if possible, unless there is absolutely no other way of doing it.

we need this for our anti-cheat engine, because most of the cheaters will load their drivers manually, and we need a way to find these manually mapped drivers.

Comments

  • Peter_Viscarola_(OSR)Peter_Viscarola_(OSR) Administrator Posts: 8,235

    This just plain and simple isn’t supported. Seriously. The OS has no provision to allow you to safely enumerate and/or interrogate memory pages that you do not own.

    Peter

    Peter Viscarola
    OSR
    @OSRDrivers

  • david_mk85david_mk85 Member Posts: 6
    edited January 5

    @Peter_Viscarola_(OSR) said:
    This just plain and simple isn’t supported. Seriously. The OS has no provision to allow you to safely enumerate and/or interrogate memory pages that you do not own.

    Peter

    Thank you peter for the response.
    So what is the most stable way of doing it? i understand that there is no official way of doing it, but we are just looking for the best way of doing it without any chance of getting BSOD.

    Also, Is enumeration the kernel memory pages the only way of finding these manually mapped drivers?

  • ThatsBerkanThatsBerkan Member Posts: 35
    edited January 5

    Windows 10 has a function to walk the pages using an enumeration index (MiWalkPage-something), but as Peter stated, everything about paging tables on Windows is internal and non-documented/non-exported. (Normal) kernel drivers should never mess with the page tables. If you still want to mess with it... you would have to lock the process's address space and do tons of shenanigans, OR build an hypervisor.

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 13,760

    So what is the most stable way of doing it?

    There is no stable way. That's what Peter said. There are hacky ways, of course. You can fetch the CR3 register, which points to the page tables, map that to virtual and read them directly. But how will you decide whether a given executable page is legitimate or not?

    You need to assume that the cheaters are smarter than you are. If they know you're looking for them, they'll just start managing the executable bit.

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

  • david_mk85david_mk85 Member Posts: 6

    @Tim_Roberts said:

    So what is the most stable way of doing it?

    There is no stable way. That's what Peter said. There are hacky ways, of course. You can fetch the CR3 register, which points to the page tables, map that to virtual and read them directly. But how will you decide whether a given executable page is legitimate or not?

    Thank you Tim for the response,

    What about doing tricks with Pool descriptors to find these mapped drivers? is it possible to find RWX buffers in kernel by parsing pool descriptors?

  • david_mk85david_mk85 Member Posts: 6
    edited January 6

    @ThatsBerkan said:
    Windows 10 has a function to walk the pages using an enumeration index (MiWalkPage-something), but as Peter stated, everything about paging tables on Windows is internal and non-documented/non-exported. (Normal) kernel drivers should never mess with the page tables. If you still want to mess with it... you would have to lock the process's address space and do tons of shenanigans, OR build an hypervisor.

    Thank you for the response, is the function that you mentioned only available in Win10? i couldn't find anything related similar to MiWalkPage, do you know the exact name of it?

    There has to be other ways of finding RWX buffers in kernel other than parsing the page table, do you any method of finding these buffers other than parsing page tables?

  • ThatsBerkanThatsBerkan Member Posts: 35
    edited January 6

    MiWalkPageTables and MiWalkPageTablesRecursively.
    These functions like I stated, are NOT exported, INTERNAL to Windows 10, so you won't find any documentation online.
    You need to learn to use a disassembler program if you want to continue that path to catch cheaters.

    Now to your other question, let me respond with what happens when you allocate memory in the System Process:

    1. The function turns your allocation size to a count of pages using the BYTES_TO_PAGES(NumberOfBytes) macro.
      We will call the result of that macro NumberOfPages.
    2. The function reserves NumberOfPages amount of PTE entries.
    3. Using a for-loop on the NumberOfPages, it will allocate pages of memory (a page is 0x1000 bytes) by calling MiGetPage() (optionally specifying a "color").
    4. For each of thoses pages we just allocated, we will set their id to the PTEs we've previously reserved, so the PTEs points to that physical memory (the page(s)).
    5. One or multiple PFN entries are added to the MmPfnDatabase, describing which process owns thoses pages, and their state.

    So as you can see, 2 things changed on the system after allocating memory in the System Process.
    The MmPfnDatabase array, and the PTE entries. Which means there is only 2 ways (afaik) of detecting what you're trying to achieve. Now what's harder is defining what is a valid executable page and what is not. The kernel does have executable memory outside of the allocation done for drivers codes sections. Previous to 1909 I believe (or 2004 ?), the documentation changed, all allocations are executable by default and you have to opt-in to use a Nx (non-executable) variant. Literally all drivers to this day uses the ExAllocatePool(NonPagedPool) and not ExAllocatePool(NonPagedPoolNx). The integrity/security of the allocations done by drivers wasn't really a thing developers took care of, until those last years.

    Also the System Process doesn't have VAD entries to back up the kernel range allocations, so you can't even compare to that, like you would do for a user address. Each process also has its own kernel page tables entries (the upper 256 to 512 entries), even thought the kernel is accessible in every processes. It's sort-of "synchronized" between processes. But a cheater can finely modify it and make it point somewhere else in a specific process. So you even have that to take care of. It can be achieved, but it's an enormous work to do. You need experience and a good understanding of Windows' internals (and I'm not talking about the book there, I'm talking about hard and pure messing with Windows 10's code, something experienced cheat developers do nearly everyday).

    It used to be a common thing to call cheaters "kids living in mom's basement". It's true for most of them, sure. But today ? It's another story. As Peter stated, you "have to assume they are smarter than you are". People DO THIS FOR A LIVING. They will do whatever it takes. Good luck.

  • Tim_RobertsTim_Roberts Member - All Emails Posts: 13,760

    There has to be other ways of finding RWX buffers in kernel other than parsing the page table,...

    Why? Who else would care? And why do you assume the pages are RWX? To protect itself, surely a patched-in driver would shift to RX as soon as it was loaded.

    Tim Roberts, [email protected]
    Providenza & Boekelheide, Inc.

  • Daniel_TerhellDaniel_Terhell Member Posts: 1,356

    In principle, drivers need to be signed. So the cheaters would need to boot the system with driver signing disabled or test signing enabled. Consider adding a non-signed and test-signed driver with your product and see if they load (or check the boot configuration). If they do then stop the show.

    //Daniel

  • ThatsBerkanThatsBerkan Member Posts: 35

    "Stop the show". Lol. You wont believe how much drivers built for a specific purpose allows multiple applications opening a handle to it, barely checks for integrity, allocates memory that is executable by default when all they are doing is reading and writting into it, that doesn't unload properly, that doesn't follow the standards, that doesn't check the parameters when processing a DEVICE_CONTROL call, etc...

    He is asking a valid question for today's anticheat standards; that any decent anticheat should be checking. The only anticheat that's "properly" doing it and that I'm aware of to this date is EasyAntiCheat Oy.

  • david_mk85david_mk85 Member Posts: 6

    @Tim_Roberts said:

    There has to be other ways of finding RWX buffers in kernel other than parsing the page table,...

    Why? Who else would care? And why do you assume the pages are RWX? To protect itself, surely a patched-in driver would shift to RX as soon as it was loaded.

    Well, how about using pool descriptors, or something similar, to find the executable kernel regions? i know that pool descriptors and kernel functions that deal with them are basically wrappers around page tables, and that's what makes them a better option considering they are "less undocumented", and obviously making the approach less low level level, which will make the life of a developer like me a lot easier, i don't want to deal with page tables unless i absolutely have no other options to do so.

    I am just starting to learn more about how they are structured in memory and how kernel allocates memory, but it seems like we can actually use these instead of page tables to find executable kernel regions.

    and no, i am not only looking for RWX, we only care about any region that is executable, whether it is RWX or RX doesnt matter.

    I just want to find out all the other possible methods that i can use to solve this issue other than page tables. I don't want to look like a fool in front of my supervisor when i say "parsing page tables is the only way of solving this issue" then finding out there are other ways to do it as well :)

  • david_mk85david_mk85 Member Posts: 6

    @ThatsBerkan said:
    MiWalkPageTables and MiWalkPageTablesRecursively.
    These functions like I stated, are NOT exported, INTERNAL to Windows 10, so you won't find any documentation online.
    You need to learn to use a disassembler program if you want to continue that path to catch cheaters.

    Now to your other question, let me respond with what happens when you allocate memory in the System Process:

    1. The function turns your allocation size to a count of pages using the BYTES_TO_PAGES(NumberOfBytes) macro.
      We will call the result of that macro NumberOfPages.
    2. The function reserves NumberOfPages amount of PTE entries.
    3. Using a for-loop on the NumberOfPages, it will allocate pages of memory (a page is 0x1000 bytes) by calling MiGetPage() (optionally specifying a "color").
    4. For each of thoses pages we just allocated, we will set their id to the PTEs we've previously reserved, so the PTEs points to that physical memory (the page(s)).
    5. One or multiple PFN entries are added to the MmPfnDatabase, describing which process owns thoses pages, and their state.

    So as you can see, 2 things changed on the system after allocating memory in the System Process.
    The MmPfnDatabase array, and the PTE entries. Which means there is only 2 ways (afaik) of detecting what you're trying to achieve. Now what's harder is defining what is a valid executable page and what is not. The kernel does have executable memory outside of the allocation done for drivers codes sections. Previous to 1909 I believe (or 2004 ?), the documentation changed, all allocations are executable by default and you have to opt-in to use a Nx (non-executable) variant. Literally all drivers to this day uses the ExAllocatePool(NonPagedPool) and not ExAllocatePool(NonPagedPoolNx). The integrity/security of the allocations done by drivers wasn't really a thing developers took care of, until those last years.

    Also the System Process doesn't have VAD entries to back up the kernel range allocations, so you can't even compare to that, like you would do for a user address. Each process also has its own kernel page tables entries (the upper 256 to 512 entries), even thought the kernel is accessible in every processes. It's sort-of "synchronized" between processes. But a cheater can finely modify it and make it point somewhere else in a specific process. So you even have that to take care of. It can be achieved, but it's an enormous work to do. You need experience and a good understanding of Windows' internals (and I'm not talking about the book there, I'm talking about hard and pure messing with Windows 10's code, something experienced cheat developers do nearly everyday).

    It used to be a common thing to call cheaters "kids living in mom's basement". It's true for most of them, sure. But today ? It's another story. As Peter stated, you "have to assume they are smarter than you are". People DO THIS FOR A LIVING. They will do whatever it takes. Good luck.

    Thank you for the detailed answer, OK so MiWalkPageTables/MiWalkPageTablesRecursively are only available in Windows 10, correct? because if so, we can't use this approach. we need to cover all versions vista+

    so other than parsing page tables, what other techniques do you suggest for solving this issue? do you think using pool descriptors is possible for solving this? or any other method? considering how vast the kernel is, there has to be many methods of finding executable regions in kernel.

    @Daniel_Terhell said:
    In principle, drivers need to be signed. So the cheaters would need to boot the system with driver signing disabled or test signing enabled. Consider adding a non-signed and test-signed driver with your product and see if they load (or check the boot configuration). If they do then stop the show.

    //Daniel

    Oh believe me, finding these in kernel is our best shot right now, you won't believe the amount of stolen certificate, bought/new certificates that these cheaters use, also there is a sea of exploitable drivers out there that they use to load their drivers. blocking them or detecting exploiting drivers is not an option right now, trust me we tried its not gonna work.
    but right now i am not even worried about other methods, the task that was giving to me is solving this issue by finding executable regions in kernel, so any help is appreciated, i am just looking to find ALL the possible ways of solving this issue. so far the only good way i found is parsing page tables.

  • ThatsBerkanThatsBerkan Member Posts: 35
    edited January 8

    @david_mk85 said:

    ...do you think using pool descriptors is possible for solving this? or any other method?...

    A pool descriptor is added only if the cheater is allocating memory for his driver by calling the generic memory allocation functions, like ExAllocatePool, ExAllocatePoolWithTag (and it's new 1909+ variants), MmAllocateContiguousMemory (and its Cached/Specifiy variants), etc...
    So you will miss cheaters that are allocating memory for their drivers using other functions.

    As well, there is no generic way to loop thru the pool descriptors for allocations that are less than a page size. Indeed, Windows only provides a way to loop thru the big pool descriptors, and to have your allocation be in the big pool descriptors, your memory allocation must be equal or above to 0x1000 (>= a page size).

    It will normally not be an issue as the header of an executable file is already a page size.

    Again, the problem here is detecting and differentiating the bad and the good. Memory from the ExAllocatePool function for example is usually already executable (obviously depends on the flag being passed to the function, but no one usually uses the Nx ones).

    But sure, if done properly, you will catch a 100% of the monkeys using ExAllocatePool to map their drivers (which is basically all the generic driver mappers you can find on the internet to this day...).

  • Peter_Viscarola_(OSR)Peter_Viscarola_(OSR) Administrator Posts: 8,235

    but no one usually uses the Nx ones)

    True for OLDER drivers; Should not be the case for NEWER drivers.... which should all be calling ExInitializeDriverRuntime(DrvRtPoolNxOptIn);

    As much as I'd like to chastise the OP for his quest... it seems clear to me that he understands the consequences of what he's asking to do. He appears to know that it's not supported, and a dangerous game that he's playing.

    Several years ago we were approached by one of the anti-cheat companies to do anti-cheat stuff like this. We respectfully declined the project, because we couldn't stomach the level of hackery necessary to do what they needed done. Not saying it's evil; Just, you know, "Do no harm"...

    So... yeah... manually parsing the page tables is really going to be his best bet. At least you have a structure there that's defined in hardware, and therefore is less likely to change in a service pack (or whatever). Sure, this brings with it a raft of problems, but...

    Peter

    Peter Viscarola
    OSR
    @OSRDrivers

  • Scott_Noone_(OSR)Scott_Noone_(OSR) Administrator Posts: 3,375

    I've thought of (and written) so many different answers...This is what I've ended up with:

    Let Windows worry about this and require your customers to run Win10 with HVCI enabled. Done. It's the only way to know that you don't now (and won't in the future) have any WX pages. As a bonus you also get enforcement of the MS recommended driver block policy.

    If your supervisor is convinced that finding executable pages at a particular moment in time will tell you something (I have no idea what), then understand that you don't have access to the locks necessary to walk the page tables safely. Consider periodically creating memory snapshots with something like LiveKD and walking the tables in the resulting crash dump. You at least have a stable image, get to write all code in user mode, and won't crash the machine if you get it wrong.

    Lastly, make sure you understand the threat model that you're defending against. I used to think all this stink around anti-cheat was stupid (I loved my Game Genie) but turns out the cheat authors are both very clever AND motivated by significant financial gain. Be careful not to create something incredibly complicated that impacts the reliability and performance of the system for minimal protection against cheating...

    Speaking of drivers and cheating: Riot got a lot of shit a couple of years ago for introducing their Vanguard anti-cheat driver. The developers clearly understand the problem space, it loads at boot, is heavily obfuscated with VMProtect (which puts me off from using it), and appears to have taken a significant development effort. Even with all that they're still playing catch up: Riot plans to take action against increase in Valorant cheating.

    -scott
    OSR

  • ThatsBerkanThatsBerkan Member Posts: 35
    edited January 8

    How would you do a kernel memory dump without using any third party programs, and obviously from kernel ?
    All I've seen on the NT documentation is a function that initialize a crash dump header (KeInitializeCrashDumpHeader).

  • Scott_Noone_(OSR)Scott_Noone_(OSR) Administrator Posts: 3,375

    You could use NtSystemDebugControl (see LiveDump for a start). Still undocumented but better than walking page tables...

    (Note that I admittedly haven't tried this is a couple of years but it appears to still be available in 2004)

    -scott
    OSR

  • MBond2MBond2 Member Posts: 233

    as a aside, it is clear that the level of technical skill demanded to write and effective cheating system is impressively high. Maybe not as high as that needed to use network monitoring software to steal state secrets, but the mark of really good malware is that it does not cause observable effects that cause people to go looking.

    but your underlying problem has only two theoretical solutions

    • you can run your security protection in a hypervisor underneath the OS
    • you can make the end PC a 'dumb terminal' and have all processing done on a physically secured server

    or you change the world and reintroduce rings as hardware enforced security boundaries inside the machine.

    but the point is that it is an n/p hard problem to find or prevent 'all' cheating software that runs on machines that you do not control

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Upcoming OSR Seminars
OSR has suspended in-person seminars due to the Covid-19 outbreak. But, don't miss your training! Attend via the internet instead!
Writing WDF Drivers 7 Dec 2020 LIVE ONLINE
Internals & Software Drivers 25 Jan 2021 LIVE ONLINE
Developing Minifilters 8 March 2021 LIVE ONLINE