Most stable way of finding executable pages in kernel?

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 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?

@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?

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.

1 Like

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.

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

“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.

@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 :slight_smile:

@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.

@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…).

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

1 Like

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.

2 Likes

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).

1 Like

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)

1 Like

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

1 Like

Thank you everyone for their share of thoughts and comments

Well, i discussed this with my supervisor, and as others have mentioned using pool descriptors is not gonna be good enough, and also we can’t force our customers to use HVCI or have any assumption on their underlying system configurations.

Very interesting method was suggested by Scott, right now we are going to first try the parsing the page table method then after that we will try the periodic memory dump and parsing the page table from dump as suggested by scott, although the latter seems to be more challenging since i couldn’t find any good sample code for it and I’m not sure if this works on all Windows versions or not. ( if there is any good sample code for this latter method please let me know)

also if anyone knows any other method that can be implemented without any assumption on the underlying configurations (for example only working with HVCI or…) please do share.

Thank you again,

David

With respect, if you even want to refer to sample code that isn’t your own from some previous project, I suspect that you still don’t understand how hard this is. As an example to cite, look at the history of the debugger SoftICE and the reasons why it was discontinued.

If you want to continue, I suggest that you do not look for a single solution that will work on all versions of Windows with all possible configurations, but limit yourself to a few that you can plausibly accomplish and then provide instructions for your installer to reject other configurations or convert the system to one of them. Apple has proved time and again that if consumer users believe something to be valuable, they will bend over backwards or tie themselves in knots to meet whatever the requirements to have it are. It will be the problem of your marketing department :wink:

we need this for our anti-cheat engine,

OMG…

There is a cheater on the thread A, and anti-cheater on the thread B, with the former claiming to be giving a business to the latter…

Why don’t you want simply to wait until your “opponent” calls your DriverEntry() routine (i.e. something that he is asking for the assistance with on another thread)?

because most of the cheaters will load their drivers manually, and we need a way to find these manually mapped drivers

Well, in order to be in a position to load a driver manually (i.e. by means of kernel memory modifications) you’ve got to get to the kernel somehow, right? However, this part (presumably) requires a driver,which, in turn, seems to be simply eliminating the need for any extra drivers that you are so desperate to detect.

Another point to consider is that the cheater may actually do everything without ANY software -level assistance on the target machine,
in the first place. There are 2 potential ways of achieving this goal. First, they can examine ( and modify) the target system’s memory by means of plugging a PCIE FPGA that is controlled from the remote machine via USB cable, into it(check the archives for more info). The second way is running the target system inside a hypervisor, and launching the attack from the host system,rather than from the target system itself.

If your opponents take either of the above approaches they will remain totally transparent to the system-level software running on the target machine…

Anton Bassov

plugging a PCIE FPGA that is controlled from the remote machine via USB cable

Yeah… probably not anymore due to DMAR/IVRS.

Peter

designed to protect against thunderbolt and other external memory access. but if they have physical access to the system, then …