Всем привет! Что-то я запутался в атрибутах KUSER_SHARED_DATA. Известно, что эта структура ядра отображается в каждый пользовательский процесс по одинаковому адресу 0x7FFE0000 на обоих системах х32/64. В ядре она так-же имеет фиксированный адрес 0xfffff780`00000000, даже не смотря на активный ASLR. Код (Text): 0: kd> dt _kuser_shared_data NtSystemRoot 0x7ffe0000 nt!_KUSER_SHARED_DATA +0x030 NtSystemRoot : [260] "C:\Windows" 0: kd> dt _kuser_shared_data NtSystemRoot 0xfffff78000000000 nt!_KUSER_SHARED_DATA +0x030 NtSystemRoot : [260] "C:\Windows" ;//-------- Одинаковая область памяти ------------------------ 0: kd> dq 0x7ffe0000 00000000`7ffe0000 0f99a027`00000000 0000002a`c7a26e51 00000000`7ffe0010 8c895d3c`0000002a 01db58f0`01db58f0 00000000`7ffe0020 ffffffd6`1729f800 86648664`ffffffd6 00000000`7ffe0030 0057005c`003a0043 006f0064`006e0069 00000000`7ffe0040 00000000`00730077 00000000`00000000 00000000`7ffe0050 00000000`00000000 00000000`00000000 00000000`7ffe0060 00000000`00000000 00000000`00000000 00000000`7ffe0070 00000000`00000000 00000000`00000000 0: kd> dq 0xfffff78000000000 fffff780`00000000 0f99a027`00000000 0000002a`c7a26e51 fffff780`00000010 8c895d3c`0000002a 01db58f0`01db58f0 fffff780`00000020 ffffffd6`1729f800 86648664`ffffffd6 fffff780`00000030 0057005c`003a0043 006f0064`006e0069 fffff780`00000040 00000000`00730077 00000000`00000000 fffff780`00000050 00000000`00000000 00000000`00000000 fffff780`00000060 00000000`00000000 00000000`00000000 fffff780`00000070 00000000`00000000 00000000`00000000 Дальше смотрим на атрибуты страниц - под юзером стоит только(R) причём страница чистая без атрибута "Dirty" (хотя каким-то образом тики в поле KSYSTEM_TIME обновляются), а вот в ядре страница грязная(D) с атрибутами уже W/E. Но что примечательно, это один и тот-же физический фрейм с номером PFN=0x01E6. Как такое может быть? Код (Text): 0: kd> !cmkd.ptelist -v 0x7ffe0000 VA=000000007FFE0000 PXE Idx=000 Va=FFFFF6FB7DBED000 Contents=2AB000004C2F5867 Hard Pfn=0004C2F5 Attr=---DA--UWEV PPE Idx=001 Va=FFFFF6FB7DA00008 Contents=015000004FB80867 Hard Pfn=0004FB80 Attr=---DA--UWEV PDE Idx=1FF Va=FFFFF6FB40001FF8 Contents=025000002A5CB867 Hard Pfn=0002A5CB Attr=---DA--UWEV PTE Idx=1E0 Va=FFFFF680003FFF00 Contents=82D00000001E6025 Hard Pfn=000001E6 Attr=----A--UR-V <----- 0: kd> !cmkd.ptelist -v 0xfffff78000000000 VA=FFFFF78000000000 PXE Idx=1EF Va=FFFFF6FB7DBEDF78 Contents=000000000019D063 Hard Pfn=0000019D Attr=---DA--KWEV PPE Idx=000 Va=FFFFF6FB7DBEF000 Contents=00000000001E8063 Hard Pfn=000001E8 Attr=---DA--KWEV PDE Idx=000 Va=FFFFF6FB7DE00000 Contents=00000000001E7063 Hard Pfn=000001E7 Attr=---DA--KWEV PTE Idx=000 Va=FFFFF6FBC0000000 Contents=00000000001E6163 Hard Pfn=000001E6 Attr=-G-DA--KWEV <----- Первое, что приходит на ум - страница ядра мапится в юзерспейс, но я попробовал написать код с MapViewOfFile() и обнаружил, что мой процесс отдал клиентскому уже другой физ.фрейм (скопировал?). Вопрос: какой механизм использует Windows для отображения KUSER_SHARED_DATA, и как могут назначаться разные атрибуты одному PFN ?
Атрибуты назначаются не самой физической странице (у них никаких атрибутов нет), а отображению. PFN - это просто число, порядковый номер физической страницы, а все атрибуты описывает "окно", которое на неё смотрит. В данном случае у тебя два разных "окна" - два PTE с разными атрибутами, которые описывают одну и ту же страничку. Через юзермодный путь никто не писал, поэтому D = 0, а через ядерный активно пишет само ядро - поэтому у него D = 1. И отобразить эту страничку на запись тебе ядро не даст, потому что так ты мог бы сразу испортить что-то во всех процессах, поэтому ядро просто создаёт копию этой странички в адресном пространстве твоего процесса и отдаёт её тебе с запрошенными правами. То же самое с DLL'ками: например, ntdll тоже грузится во все процессы по одному и тому же адресу, и тоже её физическая память делится со всеми. Но стоит тебе сделать VirtualProtect и что-то туда записать - ядру прилетает исключение, оно видит, что страничка CoW, создаёт её копию в твоём процессе - и разрешает запись. И теперь ты работаешь исключительно со своей копией, а остальные процессы всё так же разделяют память между собой.
Всё это логично, и ты говоришь о типичном маппинге. Но в случае с KUSER_SHARED_DATA у страниц ядра/юзера почему-то нет атрибута CoW, и при записи данных из юм в хвост структуры, на моей Win7-x64 данные прямиком уходят в ядро. Видимо это какой-то глюк отладчика WinDbg, поскольку механизм CoW всё-таки работает. Я тестил по такому алго.. Код (Text): 0: kd> !process 0 0 Test.exe PROCESS fffffa8004508310 SessionId: 1 Cid: 0cb0 Peb: 7fffffdf000 ParentCid: 0e70 Image: Test.EXE 0: kd> .process /p fffffa8004508310 <-----------// Подключаюсь к своему процессу Implicit process is now fffffa80`04508310 ;//--------- Записи PTE: юзера = FFFFF680003FFF00, и ядра = FFFFF6FBC0000000 ---------------- ;//--------- при этом фрейм один и тот же: PFN = 01E6 --------------------------------------- 0: kd> !cmkd.ptelist -v 7ffe0000 VA=000000007FFE0000 PDE Idx=1FF Va=FFFFF6FB40001FF8 Contents=0240000015968867 Hard Pfn=00015968 Attr=---DA--UWEV PTE Idx=1E0 Va=FFFFF680003FFF00 Contents=82800000001E6025 Hard Pfn=000001E6 Attr=----A--UR-V 0: kd> !cmkd.ptelist -v 0xfffff78000000000 VA=FFFFF78000000000 PDE Idx=000 Va=FFFFF6FB7DE00000 Contents=00000000001E7063 Hard Pfn=000001E7 Attr=---DA--KWEV PTE Idx=000 Va=FFFFF6FBC0000000 Contents=00000000001E6163 Hard Pfn=000001E6 Attr=-G-DA--KWEV ;//--------- Атрибуты в записях PTE ----------------------------------------------- 0: kd> dt _mmpte_hardware FFFFF680003FFF00 <-------- User nt!_MMPTE_HARDWARE +0x000 Valid : 0y1 +0x000 Owner : 0y1 +0x000 WriteThrough : 0y0 +0x000 CacheDisable : 0y0 +0x000 Accessed : 0y1 +0x000 Dirty : 0y0 +0x000 LargePage : 0y0 +0x000 Global : 0y0 +0x000 CopyOnWrite : 0y0 <------+---// биты CoW, Write, Execute сброшены +0x000 Write : 0y0 <------+ +0x000 PageFrameNumber : 0y000000000000000000000000000111100110 (0x1e6) <------- PFN +0x000 SoftwareWsIndex : 0y00000111101 (0x3d) +0x000 NoExecute : 0y1 0: kd> dt _mmpte_hardware FFFFF6FBC0000000 <-------- Kernel nt!_MMPTE_HARDWARE +0x000 Valid : 0y1 +0x000 Owner : 0y0 +0x000 WriteThrough : 0y0 +0x000 CacheDisable : 0y0 +0x000 Accessed : 0y1 +0x000 Dirty : 0y1 +0x000 LargePage : 0y0 +0x000 Global : 0y1 +0x000 CopyOnWrite : 0y0 <----------// бит CoW сброшен +0x000 Write : 0y1 +0x000 PageFrameNumber : 0y000000000000000000000000000111100110 (0x1e6) +0x000 SoftwareWsIndex : 0y00000000000 (0) +0x000 NoExecute : 0y0 И вообще атрибут CoW в дефолте назначается страницам, или только после VirtualProtect() + Write уже выделенной странице? Ладно, пробуем что-нибудь записать в хвост структуры. У меня в ней 75 элементов, которые занимают 0x05f0 байт: Код (Text): 0: kd> dt _kuser_shared_data 7ffe0000 TickCountMultiplier -v struct _KUSER_SHARED_DATA, 75 elements, 0x5f0 bytes +0x004 TickCountMultiplier : 0xf99a027 0: kd> ea 7ffe05f0 "wasm.in" <-------------- Запись строки в хвост! 0: kd> db 7ffe05f0 L20 00000000`7ffe05f0 77 61 73 6d 2e 69 6e 00-00 00 00 00 00 00 00 00 wasm.in......... 00000000`7ffe0600 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 0: kd> db 0xfffff780000005f0 L20 fffff780`000005f0 77 61 73 6d 2e 69 6e 00-00 00 00 00 00 00 00 00 wasm.in......... <---- Отобразилась в ядре! fffff780`00000600 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ Расширение CMKD отладчика от "CodeMachine" выводит карту регионов ядерной памяти KVAS (Kernel Virtual Addr Space) - как видим это "SharedSystemPage", причём этот атрибут имеет только страница KUSER_SHARED_DATA: Код (Text): 0: kd> !cmkd.kvas ### Start End Length Type 000 ffff080000000000 fffff67fffffffff ee8000000000 ( 238 TB) SystemSpace 001 fffff68000000000 fffff6ffffffffff 8000000000 ( 512 GB) PageTables 002 fffff70000000000 fffff77fffffffff 8000000000 ( 512 GB) HyperSpace 003 fffff78000000000 fffff78000000fff 1000 ( 4 KB) SharedSystemPage <-------// 004 fffff78000001000 fffff7ffffffffff 7ffffff000 ( 511 GB) CacheWorkingSet 005 fffff80000000000 fffff87fffffffff 8000000000 ( 512 GB) LoaderMappings 006 fffff88000000000 fffff89fffffffff 2000000000 ( 128 GB) SystemPTEs 007 fffff8a000000000 fffff8bfffffffff 2000000000 ( 128 GB) PagedPool 008 fffff90000000000 fffff97fffffffff 8000000000 ( 512 GB) SessionSpace 009 fffff98000000000 fffffa7fffffffff 10000000000 ( 1 TB) DynamicKernelVa 010 fffffa8000000000 fffffa80038fffff 3900000 ( 57 MB) PfnDatabase 011 fffffa8003800000 fffffa80c03fffff bcc00000 ( 2 GB) NonPagedPool 012 ffffffffffc00000 ffffffffffffffff 400000 ( 4 MB) HalReserved 0: kd> !cmkd.kvas 0xfffff78000000000 kvas : Show region containing fffff78000000000 ### Start End Length Type 003 fffff78000000000 fffff78000000fff 1000 ( 4 KB) SharedSystemPage Но например структуры "gSharedInfo, GdiSharedHandleTable, KernelCallbackTable" тоже мапятся из ядра в юзерспейс, но почему-то здесь не отображаются. Выходит у них какой-то другой механизм проецирования? Какие вообще бывают? А вот атрибуты в PTE системных DLL - здесь тоже бит CoW сброшен: Код (Text): 0: kd> !peb @$peb PEB at 000007fffffdf000 BeingDebugged: Yes ImageBaseAddress: 0000000000400000 Ldr 00000000777b2e40 Ldr.Initialized: Yes Base TimeStamp Module 400000 6770ae2b Dec 29 08:04:27 2024 F:\Test.EXE 77680000 66f77d60 Sep 28 08:52:00 2024 C:\Windows\SYSTEM32\ntdll.dll 77460000 66f77da6 Sep 28 08:53:10 2024 C:\Windows\system32\kernel32.dll 7fefd250000 66f77da7 Sep 28 08:53:11 2024 C:\Windows\system32\KERNELBASE.dll <---------// 7feff750000 56f58ae0 Mar 26 00:00:48 2016 C:\Windows\system32\msvcrt.dll 0: kd> !cmkd.ptelist 7fefd250000 -v VA=000007FEFD250000 PDE Idx=1E9 Va=FFFFF6FB41FFBF48 Contents=1200000008464867 Hard Pfn=00008464 Attr=---DA--UWEV PTE Idx=050 Va=FFFFF683FF7E9280 Contents=8F2000009C8F7025 Hard Pfn=0009C8F7 Attr=----A--UR-V 0: kd> dt _mmpte_hardware FFFFF683FF7E9280 nt!_MMPTE_HARDWARE +0x000 Valid : 0y1 +0x000 Owner : 0y1 +0x000 WriteThrough : 0y0 +0x000 CacheDisable : 0y0 +0x000 Accessed : 0y1 +0x000 Dirty : 0y0 +0x000 LargePage : 0y0 +0x000 Global : 0y0 +0x000 CopyOnWrite : 0y0 <-------- Бит CoW сброшен +0x000 Write : 0y0 +0x000 PageFrameNumber : 0y000000000000000010011100100011110111 (0x9c8f7) +0x000 SoftwareWsIndex : 0y00011110010 (0xf2) +0x000 NoExecute : 0y1 В общем что-то не складывается пока полная картина.. Для тестов я использовал такой код: 2 одинаковых процесса, просто во втором закоментировал VirtualProtect() и просто читал из адреса 0x7ffe05f0. Как результат CoW работает, и строка второму процессу не доступна, чего собственно и стоило ожидать. Код (ASM): format pe64 console include 'win64ax.inc' entry start ;//----------- .data OldProtect dq 0 ;//----------- section '.text' code readable executable start: sub rsp,8 cinvoke _getch invoke VirtualProtect,0x7ffe0000,0x1000,PAGE_READWRITE,OldProtect cinvoke printf,<10,' Ret: %x'>,eax ; возвращает ОК=1 mov rax,'wasm.in' mov qword[0x7ffe05f0],rax cinvoke printf,<10,' %s'>,0x7ffe05f0 @exit: cinvoke _getch cinvoke exit, 0 ;//----------- section '.idata' import data readable library msvcrt,'msvcrt.dll',kernel32,'kernel32.dll' include 'api\msvcrt.inc' include 'api\kernel32.inc'
Механизм проецирования можно посмотреть, тут инит ядра, тут мап в процесс. Не известно как это работает, но открыть на запись usd нельзя, иначе можно контролировать время и инжектить в процессы.
Интересно однако работает механизм "CopyOnWrite" на физ.уровне. Сейчас проверил атрибуты в РТЕ до и после вызова VirtualProtect() - таблица страниц не меняется вообще, а вот номер фрейма PFN в записи РТЕ уже становится другой. То-есть вирт.адрес остаётся прежний, а данные с него копируются в один из свободных фреймов физ.памяти: Код (Text): ;//------------ Смотрю атрибуты в РТЕ до вызова VirtualProtect() -------- ;//------------ РТЕ = FFFFF680003FFF00, PFN = 01E6 ---------------------- 0: kd> !pte 7ffe0000 VA 000000007ffe0000 PXE at FFFFF6FB7DBED000 PPE at FFFFF6FB7DA00008 PDE at FFFFF6FB40001FF8 PTE at FFFFF680003FFF00 contains 02D00000A4F07867 contains 03100000BAA4A867 contains 032000008A84B867 contains 83D00000001E6005 pfn a4f07 ---DA--UWEV pfn baa4a ---DA--UWEV pfn 8a84b ---DA--UWEV pfn 1e6 -------UR-V 0: kd> dt _mmpte_hardware FFFFF680003FFF00 nt!_MMPTE_HARDWARE +0x000 Valid : 0y1 +0x000 Owner : 0y1 +0x000 WriteThrough : 0y0 +0x000 CacheDisable : 0y0 +0x000 Accessed : 0y0 <---------- +0x000 Dirty : 0y0 <---------- +0x000 LargePage : 0y0 +0x000 Global : 0y0 +0x000 CopyOnWrite : 0y0 <---------- +0x000 Write : 0y0 <---------- +0x000 PageFrameNumber : 0y000000000000000000000000000111100110 (0x1e6) <---------- +0x000 SoftwareWsIndex : 0y00000111101 (0x3d) +0x000 NoExecute : 0y1 ;//------------ Теперь атрибуты в РТЕ после VirtualProtect() ------------------- ;//------------ РТЕ тот-же: FFFFF680003FFF00, а вот PFN уже: 12В51С ------------- 0: kd> !pte 7ffe0000 VA 000000007ffe0000 PXE at FFFFF6FB7DBED000 PPE at FFFFF6FB7DA00008 PDE at FFFFF6FB40001FF8 PTE at FFFFF680003FFF00 contains 02D0000064038867 contains 0310000019BFB867 contains 03200001128BC867 contains 83D000012B51C847 pfn 64038 ---DA--UWEV pfn 19bfb ---DA--UWEV pfn 1128bc ---DA--UWEV pfn 12b51c ---DА--UW-V 0: kd> dt _mmpte_hardware FFFFF680003FFF00 nt!_MMPTE_HARDWARE +0x000 Valid : 0y1 +0x000 Owner : 0y1 +0x000 WriteThrough : 0y0 +0x000 CacheDisable : 0y0 +0x000 Accessed : 0y1 <---------- +0x000 Dirty : 0y1 <---------- +0x000 LargePage : 0y0 +0x000 Global : 0y0 +0x000 CopyOnWrite : 0y0 <---------- +0x000 Write : 0y1 <---------- +0x000 PageFrameNumber : 0y000000000000000100101011010100011100 (0x12b51c) <---------- +0x000 SoftwareWsIndex : 0y00000111101 (0x3d) +0x000 NoExecute : 0y1 Но как показывают логи, бит CoW всё-равно не меняется ни в старом, ни в новом фрейме, и это остаётся загадкой. Инфа от Ahimov с намёком на выгружаемый пул подтолкнула на мысль, что может KUSER_SHARED_DATA юзает аналогичный механизм проекции "PageFile-Backed Sections" как описано у мягких здесь. Но как это проверить - пока хз. По сути записи в РТЕ могут быть разного типа, а не только "Hard". Например "Soft" описывает сброшенную в файл-подкачки "Pagefile" страницу, и т.д. Но опять-же при просмотре РТЕ как "Soft", вместо валидной маски я получую какую-то лажу (поле PageFileHigh не может иметь такое значение). Да и по ссылке выше система выставляет бит "Owner" именно в записи "Hard". В общем нужно копать дальше.. Код (Text): 0: kd> dt _mmpte FFFFF680003FFF00 -b nt!_MMPTE +0x000 u : <unnamed-tag> +0x000 VolatileLong : 0x83d00001`2b51c847 +0x000 Hard : _MMPTE_HARDWARE +0x000 Valid : 0y1 +0x000 Owner : 0y1 +0x000 WriteThrough : 0y0 +0x000 CacheDisable : 0y0 +0x000 Accessed : 0y1 +0x000 Dirty : 0y1 +0x000 LargePage : 0y0 +0x000 Global : 0y0 +0x000 CopyOnWrite : 0y0 +0x000 Write : 0y1 +0x000 PageFrameNumber : 0y000000000000000100101011010100011100 (0x12b51c) +0x000 SoftwareWsIndex : 0y00000111101 (0x3d) +0x000 NoExecute : 0y1 +0x000 Soft : _MMPTE_SOFTWARE +0x000 Valid : 0y1 +0x000 Unused : 0y11 +0x000 InStore : 0y0 +0x000 SwizzleBit : 0y0 +0x000 Protection : 0y00010 (0x2) +0x000 Prototype : 0y0 +0x000 Transition : 0y1 +0x000 PageFileLow : 0y1100 +0x000 UsedPageTableEntries : 0y1101010001 (0x351) +0x000 Reserved : 0y001010 (0xa) +0x000 PageFileHigh : 0y10000011110100000000000000000001 (0x83d00001) +0x000 Proto : _MMPTE_PROTOTYPE +0x000 Valid : 0y1 +0x000 ReadOnly : 0y1 +0x000 Unused0 : 0y01 +0x000 SwizzleBit : 0y0 +0x000 Protection : 0y00010 (0x2) +0x000 Prototype : 0y0 +0x000 Unused1 : 0y11001 (0x19) +0x000 ProtoAddress : 0y100000111101000000000000000000010010101101010001 (0x83d000012b51) +0x000 TimeStamp : _MMPTE_TIMESTAMP +0x000 MustBeZero : 0y1 +0x000 Unused : 0y011 +0x000 SwizzleBit : 0y0 +0x000 Protection : 0y00010 (0x2) +0x000 Prototype : 0y0 +0x000 Transition : 0y1 +0x000 PageFileLow : 0y1100 +0x000 Reserved : 0y0010101101010001 (0x2b51) +0x000 GlobalTimeStamp : 0y10000011110100000000000000000001 (0x83d00001) +0x000 Trans : _MMPTE_TRANSITION +0x000 Valid : 0y1 +0x000 Write : 0y1 +0x000 WriteThrough : 0y1 +0x000 CacheDisable : 0y0 +0x000 SwizzleBit : 0y0 +0x000 Protection : 0y00010 (0x2) +0x000 Prototype : 0y0 +0x000 Transition : 0y1 +0x000 PageFrameNumber : 0y000000000000000100101011010100011100 (0x12b51c) +0x000 Unused : 0y1000001111010000 (0x83d0) +0x000 Subsect : _MMPTE_SUBSECTION +0x000 Valid : 0y1 +0x000 Unused0 : 0y011 +0x000 SwizzleBit : 0y0 +0x000 Protection : 0y00010 (0x2) +0x000 Prototype : 0y0 +0x000 Unused1 : 0y11001 (0x19) +0x000 SubsectionAddress: 0y100000111101000000000000000000010010101101010001 (0x83d000012b51) +0x000 List : _MMPTE_LIST +0x000 Valid : 0y1 +0x000 OneEntry : 0y1 +0x000 filler0 : 0y01 +0x000 SwizzleBit : 0y0 +0x000 Protection : 0y00010 (0x2) +0x000 Prototype : 0y0 +0x000 Transition : 0y1 +0x000 filler1 : 0y00101011010100011100 (0x2b51c) +0x000 NextEntry : 0y10000011110100000000000000000001 (0x83d00001) 0: kd>
GdiSharedHandleTable проецируется как секция вот: Код (Text): // map a READ_ONLY view of the hmgr shared handle table into the // process's address space // PVOID BaseAddress = NULL; SIZE_T CommitSize = 0; OBJECT_ATTRIBUTES ObjectAttributes; UNICODE_STRING UnicodeString; HANDLE SectionHandle = NULL; ntStatus = ObOpenObjectByPointer( gpHmgrSharedHandleSection, 0L, (PACCESS_STATE) NULL, SECTION_ALL_ACCESS, (POBJECT_TYPE) NULL, KernelMode, &SectionHandle); if (NT_SUCCESS(ntStatus)) { ntStatus = ZwMapViewOfSection( SectionHandle, NtCurrentProcess(), &BaseAddress, 0L, 0L, NULL, &CommitSize, ViewUnmap, 0L, PAGE_READONLY ); if (NT_SUCCESS(ntStatus)) { // // set table address // // we must set the GdiSharedHandleTable value // to the shared table pointer so that if GDI32 gets // unloaded and re-loaded it can still get the pointer. // // NOTE: we also depend on this pointer being initialized // *BEFORE* we make any GDI or USER call to the kernel // (which has the automatic side-effect of calling this // routine. // Peb->GdiSharedHandleTable = (PVOID)BaseAddress; } KernelCallbackTable не может загружать ядро, так как это таблица ссылок на интернал(не экспортируемые, в отличие от Ki*) юзер процедуры. Для такой операции вначале юзер слой должен передать массив в ядро, а оно вернуть назад этот массив. В этих действиях нужды небыло и указатель на таблицу загружался при инит юзер, может в новых версиях поменялось ?
Ahimov, ну вот здесь тоже у ZwMapViewOfSection() атрибут стоит "PAGE_READONLY", в то время как для CoW нужен "PAGE_WRITECOPY". Каким образом/механизмом расшариваются такие секции, когда я пытаюсь получить к ней доступ на запись в своём процессе? --- Сообщение объединено, 30 дек 2024 --- Блин, сейчас переписал свой тест/код, пытаясь в VirtualProtect() вместо "PAGE_READWRITE" установить "PAGE_WRITECOPY". Оказывается для структуры KUSER_SHARED_DATA система запрещает данный атрибут, отправив прожку в крэш. Тогда создал ещё одну секцию "МАР" с дефолтным атрибутом(R) - это сработало, и в записи РТЕ получил заветный бит CoW. Теперь сомнений нет, что KUSER_SHARED_DATA мапится по какой-то другой схеме, не используя стандартный "CopyOnWrite". Осталось узнать этот механизм.. Код (ASM): format pe64 console include 'win64ax.inc' entry start ;//----------- section '.MAP' data readable map db 0 ;//----------- section '.data' data readable writeable OldProtect dq 0 ;//----------- section '.text' code readable executable start: sub rsp,8 cinvoke _getch invoke VirtualProtect,map,0x1000,\;0x7ffe0000,0x1000,\ PAGE_WRITECOPY,OldProtect cinvoke printf,<10,' Ret: %x'>,eax invoke VirtualProtect,0x7ffe0000,0x1000,\ PAGE_READWRITE,OldProtect cinvoke printf,<10,' Ret: %x'>,eax mov rax,'wasm.in' mov qword[0x7ffe05f0],rax cinvoke printf,<10,' %s'>,0x7ffe05f0 @exit: cinvoke _getch cinvoke exit, 0 ;//----------- section '.idata' import data readable library msvcrt,'msvcrt.dll',kernel32,'kernel32.dll' include 'api\msvcrt.inc' include 'api\kernel32.inc' Далее логи в отладчике. Секция "МАР" идёт первой и расположена по адресу 0x00401000: Код (Text): ;//----------- до VirtualProtect() ----------------------------- 0: kd> !pte 401000 VA 0000000000401000 PXE at FFFFF6FB7DBED000 PPE at FFFFF6FB7DA00000 PDE at FFFFF6FB40000010 PTE at FFFFF68000002008 contains 02E0000085DCC867 contains 02F00000AEECD867 contains 0300000039BCE867 contains 9940000036BD5025 pfn 85dcc ---DA--UWEV pfn aeecd ---DA--UWEV pfn 39bce ---DA--UWEV pfn 36bd5 ----A--UR-V 0: kd> dt _mmpte_hardware FFFFF68000002008 nt!_MMPTE_HARDWARE +0x000 Valid : 0y1 +0x000 Owner : 0y1 +0x000 WriteThrough : 0y0 +0x000 CacheDisable : 0y0 +0x000 Accessed : 0y1 +0x000 Dirty : 0y0 +0x000 LargePage : 0y0 +0x000 Global : 0y0 +0x000 CopyOnWrite : 0y0 <-------------- CoW сброшен +0x000 Write : 0y0 +0x000 PageFrameNumber : 0y000000000000000000110110101111010101 (0x36bd5) +0x000 SoftwareWsIndex : 0y00110010100 (0x194) +0x000 NoExecute : 0y1 ;//----------- после VirtualProtect() ------------------------------ ;//----------- в атрибутах РТЕ появился бит(С) --------------------- 0: kd> !pte 401000 VA 0000000000401000 PXE at FFFFF6FB7DBED000 PPE at FFFFF6FB7DA00000 PDE at FFFFF6FB40000010 PTE at FFFFF68000002008 contains 02E0000085DCC867 contains 02F00000AEECD867 contains 0300000039BCE867 contains 9940000036BD5225 pfn 85dcc ---DA--UWEV pfn aeecd ---DA--UWEV pfn 39bce ---DA--UWEV pfn 36bd5 C---A--UR-V 0: kd> dt _mmpte_hardware FFFFF68000002008 nt!_MMPTE_HARDWARE +0x000 Valid : 0y1 +0x000 Owner : 0y1 +0x000 WriteThrough : 0y0 +0x000 CacheDisable : 0y0 +0x000 Accessed : 0y1 +0x000 Dirty : 0y0 +0x000 LargePage : 0y0 +0x000 Global : 0y0 +0x000 CopyOnWrite : 0y1 <-------------- CoW взведён! +0x000 Write : 0y0 +0x000 PageFrameNumber : 0y000000000000000000110110101111010101 (0x36bd5) +0x000 SoftwareWsIndex : 0y00110010100 (0x194) +0x000 NoExecute : 0y1
Вот ещё вариант, где к предыдущим добавлены 2 команды: !pfn и !vad. Первую нашёл по этому линку где говорится, что расшаренные/промапленные страницы могут использовать тип РТЕ "Prototype" - в данном случае у себя получил "Blink/Share count = 00000036" до VirtualProtect(), и на 1 один меньше 35 после (когда сработал CoW). А со второй !vad ситуация вообще не понятна - здесь атрибуты у системных DLL стоят уже "WRITECOPY" (хотя в записях РТЕ их нету), однако до VirtualProtect() приватных страниц было 95, а после стало 99 (сумма в столбце Commit). Настораживает так-же тип "Pagefile-backed section" для файлов подкачки, и их кол-во. В поле "Start" указаны адреса, без младших 12-бит: Код (Text): 0: kd> !process 0 0 Test.exe PROCESS fffffa8003efb7b0 SessionId: 1 Cid: 0f50 Peb: 7fffffdf000 ParentCid: 0880 DirBase: 111d60000 ObjectTable: fffff8a00388d100 HandleCount: 7. Image: Test.EXE 0: kd> !cmkd.ptelist 0xfffff78000000000 -v ptelist : Using fffff78000000000 as VA=FFFFF78000000000 PXE Idx=1EF Va=FFFFF6FB7DBEDF78 Contents=000000000019D063 Hard Pfn=0000019D Attr=---DA--KWEV PPE Idx=000 Va=FFFFF6FB7DBEF000 Contents=00000000001E8063 Hard Pfn=000001E8 Attr=---DA--KWEV PDE Idx=000 Va=FFFFF6FB7DE00000 Contents=00000000001E7063 Hard Pfn=000001E7 Attr=---DA--KWEV PTE Idx=000 Va=FFFFF6FBC0000000 Contents=00000000001E6163 Hard Pfn=000001E6 Attr=-G-DA--KWEV 0: kd> !pfn 000001E6 PFN 000001E6 at address FFFFFA8000005B20 Flink FFFFF800029FC680 Blink/Share count 00000036 PteAddress FFFFF6FBC0000000 Reference count 0001 Used entry count 0000 Cached Color 0 Priority 5 Restore pte 00000080 Containing page 0001E7 Active PRW Shared ReadInProgress WriteInProgress 0: kd> !process fffffa8003efb7b0 1 PROCESS fffffa8003efb7b0 .......... VadRoot fffffa8003e38170. Vads 22. Clone 0. Private 95. Modified 0. Locked 0. .......... 0: kd> !vad fffffa8003e38170 VAD level start end commit fffffa8003f4bd70 ( 3) 10 1f 0 Mapped READWRITE Pagefile-backed section fffffa80045daf80 ( 4) 20 2f 0 Mapped READWRITE Pagefile-backed section fffffa8004291d60 ( 2) 30 6f 6 Private READWRITE fffffa80045b76a0 ( 3) 70 73 0 Mapped READONLY Pagefile-backed section fffffa8004111810 ( 1) 80 80 1 Private READWRITE fffffa8003e61180 ( 4) 90 f6 0 Mapped READONLY \Windows\System32\locale.nls fffffa800560c4b0 ( 3) 120 12f 6 Private READWRITE fffffa80040e8110 ( 5) 130 22f 32 Private READWRITE fffffa80040e4160 ( 4) 2f0 2ff 6 Private READWRITE fffffa8003c67980 ( 2) 400 403 2 Mapped Exe EXECUTE_WRITECOPY \Test.EXE fffffa8003c3a520 ( 4) 77830 7794e 4 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\kernel32.dll fffffa800558d150 ( 3) 77950 77aee 15 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\ntdll.dll fffffa80045dc1d0 ( 4) 7efe0 7f0df 0 Mapped READONLY Pagefile-backed section fffffa8003e38170 ( 0) 7f0e0 7ffdf 0 Private READONLY fffffa80056ffc10 ( 3) 7ffe0 7ffef -1 Private READONLY <-------------------- KUSER_SHARED_DATA fffffa80041b1f70 ( 2) 7fffb 7fffb 1 Private READWRITE fffffa8004465dc0 ( 4) 7fefd4f0 7fefd556 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\KernelBase.dll fffffa8004305160 ( 3) 7fefd9d0 7fefda6e 7 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\msvcrt.dll fffffa8004515e30 ( 4) 7feffc50 7feffc50 0 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\apisetschema.dll fffffa80041b4d60 ( 1) 7fffffb0 7fffffd2 0 Mapped READONLY Pagefile-backed section fffffa8004305700 ( 3) 7fffffdd 7fffffde 2 Private READWRITE fffffa800453c5e0 ( 2) 7fffffdf 7fffffdf 1 Private READWRITE Total VADs: 22 Average level: 3 Maximum depth: 5 ;//------------------------------------------------------------------------------ ;//----------------- После VirtualProtect() ------------------------------------- ;//------------------------------------------------------------------------------ 0: kd> !process fffffa8004615060 1 PROCESS fffffa8004615060 .......... VadRoot fffffa8003e38170 Vads 22 Clone 0 Private 99. Modified 0. Locked 0. .......... 0: kd> !pfn 000001E6 PFN 000001E6 at address FFFFFA8000005B20 Flink FFFFF800029FC680 Blink/Share count 00000035 PteAddress FFFFF6FBC0000000 Reference count 0001 Used entry count 0000 Cached Color 0 Priority 5 Restore pte 00000080 Containing page 0001E7 Active PRW Shared ReadInProgress WriteInProgress 0: kd> !vad fffffa8003e38170 VAD level start end commit fffffa8003f4bd70 ( 3) 10 1f 0 Mapped READWRITE Pagefile-backed section fffffa80045daf80 ( 4) 20 2f 0 Mapped READWRITE Pagefile-backed section fffffa8004291d60 ( 2) 30 6f 6 Private READWRITE fffffa80045b76a0 ( 3) 70 73 0 Mapped READONLY Pagefile-backed section fffffa8004111810 ( 1) 80 80 1 Private READWRITE fffffa8003e61180 ( 4) 90 f6 0 Mapped READONLY \Windows\System32\locale.nls fffffa800560c4b0 ( 3) 120 12f 6 Private READWRITE fffffa80040e8110 ( 5) 130 22f 32 Private READWRITE fffffa80040e4160 ( 4) 2f0 2ff 8 Private READWRITE fffffa8003c67980 ( 2) 400 403 2 Mapped Exe EXECUTE_WRITECOPY \Test.EXE fffffa8003c3a520 ( 4) 77830 7794e 4 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\kernel32.dll fffffa800558d150 ( 3) 77950 77aee 15 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\ntdll.dll fffffa80045dc1d0 ( 4) 7efe0 7f0df 0 Mapped READONLY Pagefile-backed section fffffa8003e38170 ( 0) 7f0e0 7ffdf 0 Private READONLY fffffa80056ffc10 ( 3) 7ffe0 7ffef -1 Private READONLY fffffa80041b1f70 ( 2) 7fffb 7fffb 1 Private READWRITE fffffa8004465dc0 ( 4) 7fefd4f0 7fefd556 3 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\KernelBase.dll fffffa8004305160 ( 3) 7fefd9d0 7fefda6e 7 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\msvcrt.dll fffffa8004515e30 ( 4) 7feffc50 7feffc50 0 Mapped Exe EXECUTE_WRITECOPY \Windows\System32\apisetschema.dll fffffa80041b4d60 ( 1) 7fffffb0 7fffffd2 0 Mapped READONLY Pagefile-backed section fffffa8004305700 ( 3) 7fffffdd 7fffffde 2 Private READWRITE fffffa800453c5e0 ( 2) 7fffffdf 7fffffdf 1 Private READWRITE Total VADs: 22 Average level: 3 Maximum depth: 5