Quarantine is a good time to re-read some old but useful papers and check if you can catch trick with making KUSER_SHARED_DATA writable againSo lets see what we need:
for pSystemRangeStart PTE will be
4000000000 + pte_base = FFFF884000000000
Our base is FFFF800000000000 (value of pSystemRangeStart) - 800000000000 = FFFF000000000000
Notice that this base does not match with pSystemRangeStart
So our inverse function may looks like:
Ok, it seems that we have all puzzle pieces and can scan all PTE from FFFF884000000000 to FFFF887FFFFFFFF8. Yes, all 0x4000000000 bytes - 256Gb. Sure, we can wait for results before retirement
Lets think if there are some ways to reduce amount of memory for scanning
You may notice that all of PTE not nailed in the middle of nothing but occupy some memory. And this memory also must have some valid PTE - lets call it secondary PTE. And if some page for this secondary PTE is not valid we can skip 512 * 0x1000 = 0x200000 PTE. So I just wrote some code to check first this secondary PTE and load only PTE located in valid memory
Readed C75 pages for secondary PTE
183843 pages for PTE
0 BSODs
Good news - yes, you can detect writable KUSER_SHARED_DATA. You could do it with reading of just 8 bytes if you know PTE base, mask and shift
Bad news - there are lots of writable pages without NX bit. On clear windows 10 build 19624 with 2Gb of RAM there are 4760 such pages and only 2 mapped to some drivers:
On my work machines with 32Gb of RAM there are already 130698 such pages. I don`t know who allocated them and for what, but their very presence makes a threats detection virtually impossibly
- some logic to extract PTE base from MiGetPteAddress
- primitive to check if some address is valid - I used MmIsAddressValid
- primitive to read page from kernel memory
Start with some system info:
pSystemRangeStart: FFFF800000000000
PTE base address: FFFF880000000000
PTE mask: 7FFFFFFFF8
PTE shift: 9
Do some calculation
function to get offset to PTE looks like:
return (address >> 9) & 0x7FFFFFFFF8;
for pSystemRangeStart PTE will be
4000000000 + pte_base = FFFF884000000000
You may notice that this address located above pte_base
for last available address in system FFFFFFFFFFFFFFFF PTE will be
7FFFFFFFF8 + pte_base = FFFF887FFFFFFFF8
Also we need some inverse function to convert from PTE to page address. First we need sub pte_base, then divide on sizeof(MMPTE_HARDWARE) and then multiply to size of page. And add some base address. Try to calculate this base for example for PTE of pSystemRangeStart FFFF884000000000:
(FFFF884000000000 - FFFF880000000000) / sizeof(MMPTE_HARDWARE) * 0x1000 = 800000000000
for last available address in system FFFFFFFFFFFFFFFF PTE will be
7FFFFFFFF8 + pte_base = FFFF887FFFFFFFF8
Also we need some inverse function to convert from PTE to page address. First we need sub pte_base, then divide on sizeof(MMPTE_HARDWARE) and then multiply to size of page. And add some base address. Try to calculate this base for example for PTE of pSystemRangeStart FFFF884000000000:
(FFFF884000000000 - FFFF880000000000) / sizeof(MMPTE_HARDWARE) * 0x1000 = 800000000000
Our base is FFFF800000000000 (value of pSystemRangeStart) - 800000000000 = FFFF000000000000
Notice that this base does not match with pSystemRangeStart
So our inverse function may looks like:
return 0xFFFF000000000000 + (ptr_addr - pte_base) / sizeof(MMPTE_HARDWARE) * 0x1000;
Ok, it seems that we have all puzzle pieces and can scan all PTE from FFFF884000000000 to FFFF887FFFFFFFF8. Yes, all 0x4000000000 bytes - 256Gb. Sure, we can wait for results before retirement
Lets think if there are some ways to reduce amount of memory for scanning
You may notice that all of PTE not nailed in the middle of nothing but occupy some memory. And this memory also must have some valid PTE - lets call it secondary PTE. And if some page for this secondary PTE is not valid we can skip 512 * 0x1000 = 0x200000 PTE. So I just wrote some code to check first this secondary PTE and load only PTE located in valid memory
Results
On my work machine it tooks only several seconds to scan all PTE.Readed C75 pages for secondary PTE
183843 pages for PTE
0 BSODs
Good news - yes, you can detect writable KUSER_SHARED_DATA. You could do it with reading of just 8 bytes if you know PTE base, mask and shift
Bad news - there are lots of writable pages without NX bit. On clear windows 10 build 19624 with 2Gb of RAM there are 4760 such pages and only 2 mapped to some drivers:
[352] FFFFF80274360000 NX 0 -> \SystemRoot\System32\drivers\E1G6032E.sys
[383] FFFFF8027437F000 NX 0 -> \SystemRoot\System32\drivers\E1G6032E.sys
On my work machines with 32Gb of RAM there are already 130698 such pages. I don`t know who allocated them and for what, but their very presence makes a threats detection virtually impossibly