Как известно, Patch-Guard защищает определенные критические части ОС, полный перечень тут:
0 : A generic data region
1 : Modification of a function or .pdata
2 : A processor IDT
3 : A processor GDT
4 : Type 1 process list corruption
5 : Type 2 process list corruption
6 : Debug routine modification
7 : Critical MSR modification
8 : Object type
9 : A processor IVT
a : Modification of a system service function
b : A generic session data region
c : Modification of a session function or .pdata
d : Modification of an import table
e : Modification of a session import table
f : Ps Win32 callout modification
10 : Debug switch routine modification
11 : IRP allocator modification
12 : Driver call dispatcher modification
13 : IRP completion dispatcher modification
14 : IRP deallocator modification
15 : A processor control register
16 : Critical floating point control register modification
17 : Local APIC modification
18 : Kernel notification callout modification
19 : Loaded module list modification
1a : Type 3 process list corruption
1b : Type 4 process list corruption
1c : Driver object corruption
1d : Executive callback object modification
1e : Modification of module padding
1f : Modification of a protected process
20 : A generic data region
21 : A page hash mismatch
22 : A session page hash mismatch
23 : Load config directory modification
24 : Inverted function table modification
25 : Session configuration modification
26 : An extended processor control register
27 : Type 1 pool corruption
28 : Type 2 pool corruption
29 : Type 3 pool corruption
2a : Type 4 pool corruption
2b : Modification of a function or .pdata
2c : Image integrity corruption
2d : Processor misconfiguration
2e : Type 5 process list corruption
2f : Process shadow corruption
30 : Retpoline code page corruption
101 : General pool corruption
102 : Modification of win32k.sys
И если большая часть того, что защищается понятно из названия, то для Object type это как-то не очевидно. Попробуем разобраться.
Предположим, есть задача перехвата создания/открытия объекта Device.
Ядро предоставляет ограниченный набор объектов, которые можно перехватить:
The ObRegisterCallbacks routine registers a list of callback routines for thread, process, and desktop handle operations.
Как видно, создание объекта Device не входит в этот набор и данная api не подходит.
Но выход есть. Так как Device это объект ядра, у него есть набор предопределенных функций:
struct _OBJECT_TYPE_INITIALIZER
{
...
VOID (*DumpProcedure)(VOID* arg1, struct _OBJECT_DUMP_CONTROL* arg2);
LONG (*OpenProcedure)(enum _OB_OPEN_REASON arg1, CHAR arg2, struct _EPROCESS* arg3, VOID* arg4, ULONG* arg5, ULONG arg6);
VOID (*CloseProcedure)(struct _EPROCESS* arg1, VOID* arg2, ULONG arg3, ULONG arg4);
VOID (*DeleteProcedure)(VOID* arg1);
union
{
LONG (*ParseProcedure)(VOID* arg1, VOID* arg2, struct _ACCESS_STATE* arg3, CHAR arg4, ULONG arg5, struct _UNICODE_STRING* arg6, struct _UNICODE_STRING* arg7, VOID* arg8, struct _SECURITY_QUALITY_OF_SERVICE* arg9, VOID** arg10);
LONG (*ParseProcedureEx)(VOID* arg1, VOID* arg2, struct _ACCESS_STATE* arg3, CHAR arg4, ULONG arg5, struct _UNICODE_STRING* arg6, struct _UNICODE_STRING* arg7, VOID* arg8, struct _SECURITY_QUALITY_OF_SERVICE* arg9, struct _OB_EXTENDED_PARSE_PARAMETERS* arg10, VOID** arg11);
};
LONG (*SecurityProcedure)(VOID* arg1, enum _SECURITY_OPERATION_CODE arg2, ULONG* arg3, VOID* arg4, ULONG* arg5, VOID** arg6, enum _POOL_TYPE arg7, struct _GENERIC_MAPPING* arg8, CHAR arg9);
LONG (*QueryNameProcedure)(VOID* arg1, UCHAR arg2, struct _OBJECT_NAME_INFORMATION* arg3, ULONG arg4, ULONG* arg5, CHAR arg6);
UCHAR (*OkayToCloseProcedure)(struct _EPROCESS* arg1, VOID* arg2, VOID* arg3, CHAR arg4);
...
};
Объект Device создается функцией IoCreateDevice, которая в свою очередь вызывает ObCreateObject + ObInsertObject.
ObInsertObject выполняет поиск имени на предмет уже существующего через ObpLookupObjectName => ParseProcedure и на этом шаге у объекта можно подменить ParseProcedure на свою, получив управление.
Однако, все эти манипуляции видит Patch Guard, и выдает на это свой вердикт ввиде BSOD'a:
Bug Check 0x109: CRITICAL_STRUCTURE_CORRUPTION
0x8 Object type
CRITICAL_STRUCTURE_CORRUPTION (109)
This BugCheck is generated when the kernel detects that critical kernel code or
data have been corrupted. There are generally three causes for a corruption:
1) A driver has inadvertently or deliberately modified critical kernel code
or data. See http://www.microsoft.com/whdc/driver/kernel/64bitPatching.mspx
2) A developer attempted to set a normal kernel breakpoint using a kernel
debugger that was not attached when the system was booted. Normal breakpoints,
"bp", can only be set if the debugger is attached at boot time. Hardware
breakpoints, "ba", can be set at any time.
3) A hardware corruption occurred, e.g. failing RAM holding kernel code or data.
Arguments:
Arg1: a3a0016274a8d5a6, Reserved
Arg2: b3b70de8c72b012a, Reserved
Arg3: ffffc209daaa6970, Failure type dependent information
Arg4: 0000000000000008, Type of corrupted region, can be
...
8 : Object type <=== Here I am!
...
Соответственно, это и есть ответ на вопрос: что такое защита Object type в Patch-Guard.
Правда остался еще один вопрос: как же в итоге поймать создание device object?
Довольно просто(если у вас есть ELAM driver) - с помощью провайдера Microsoft-Windows-Threat-Intelligence, eventId 31 THREATINT_DEVICE_OBJECT_LOAD(для unload'a eventId 32).
TSS blog
TSS_TSS@mail.ru
вторник, 26 декабря 2023 г.
Что такое защита Object type в Patch-Guard?
понедельник, 31 октября 2022 г.
Autochk и запись на диск
Забавный факт - если писать на диск данные во время работы Autochk, то система зависнет:
Воспроизвести можно так: fsutil dirty set C: и перезагрузка.
Включенный verifier даст больше информации, чем скрин выше:
[FltMgr] Mini-filter verification enabled for "test" filter.
KDTARGET: Refreshing KD connection
Driver Verifier: Enabled for test.sys, flags 0x209bb, build 9200, key IeDrfOgBNzQtNzJiutJGkF
Autochk cannot lock system volume due to a sharing violation.
This is caused by a driver holding a file opened for write access.
Check all newly added driver NtCreateFile and NtOpenFile calls or run:
!FindAutochkBlockers
From "kd" to list the files which may be preventing autochk from running.
Break instruction exception - code 80000003 (first chance)
0033:000007f7`1955e790 cc int 3
kd> k
# Child-SP RetAddr Call Site
00 0000000a`53ac9ba0 0000000a`53bc6d90 0x000007f7`1955e790
01 0000000a`53ac9ba8 00000000`00000000 0x0000000a`53bc6d90
kd> !FindAutochkBlockers
Kernel handle Error reading handle count.
007c: Object: fffffa801ad93f20 GrantedAccess: 00000004
Type: File Flag: 00000000
File Name: \Program Files (x86)\Test\2022_10_21_11_01_54_372837.log
00a0: Object: fffffa801ae19d60 GrantedAccess: 00000004
Type: File Flag: 00000000
File Name: \Program Files (x86)\Test\2022_10_21_11_01_54_683864.log
00cc: Object: fffffa801b23e070 GrantedAccess: 00000001
Type: File Flag: 00000000
File Name:
00d8: Object: fffffa801ad7a770 GrantedAccess: 0012019f
Type: File Flag: 00000000
File Name: \$Extend\$RmMetadata\$TxfLog\$TxfLogContainer00000000000000000002
00dc: Object: fffffa801ad7aa20 GrantedAccess: 0012019f
Type: File Flag: 00000000
File Name: \$Extend\$RmMetadata\$TxfLog\$TxfLogContainer00000000000000000001
00e0: Object: fffffa801ada1350 GrantedAccess: 0012019f
Type: File Flag: 00000000
File Name: \$Extend\$RmMetadata\$TxfLog\$TxfLog.blf
021c: Object: fffffa801b2f4690 GrantedAccess: 00000004
Type: File Flag: 00000000
File Name: \Program Files (x86)\Test\2022_10_21_11_01_52_111448.log
024c: Object: fffffa801aa56f20 GrantedAccess: 0013019f
Type: File Flag: 00000000
File Name: \Program Files (x86)\Test\data
Причина всего этого: Autochk.exe не может залочить том из-за того, что мой драйвер держит несколько файлов открытыми на запись.
Исправить это можно отказавшись от записи на время работы Autochk.exe.
А факт окончания работы Autochk.exe можно определить через функцию FsRtlAreVolumeStartupApplicationsComplete.
понедельник, 17 мая 2021 г.
Как найти wpf callout's без анализа кода в дизассемблере?
Для начала пишем тестовый драйвер, который устанавливает пару callout's (через FwpsCalloutRegister0).
Далее, просто перечисляем все теги памяти, ищем нужный(WFP callouts):
!poolused
...
WfpC 12 74384 0 0 WFP callouts , Binary: netio.sys
...
!poolfind
WfpC
fffffa801a148000 : tag WfpC, size 0x12000, Nonpaged pool
Затем выводим содержимое памяти:
dqs fffffa801a148000 L1000 (или dqs fffffa801a148000 L0x12000, но тут уже нужно логировать в log file)
kd> dqs fffffa801a148000 L1000
fffffa80`1a148000 00000000`00000000
fffffa80`1a148008 00000000`00000000
fffffa80`1a148010 00000000`00000000
fffffa80`1a148018 00000000`00000000
fffffa80`1a148020 00000000`00000000
fffffa80`1a148028 00000000`00000000
fffffa80`1a148030 00000000`00000000
fffffa80`1a148038 00000000`00000000
fffffa80`1a148040 00000000`00000000
fffffa80`1a148048 00000001`00000002
fffffa80`1a148050 00000000`00000000
fffffa80`1a148058 fffff880`01ebac40 tcpip!IPSecInboundTransportFilterCalloutClassifyV4
fffffa80`1a148060 fffff880`01dab624 tcpip!IPSecAleConnectCalloutNotify+0x2
fffffa80`1a148068 00000000`00000000
...
fffffa80`1a14c808 00000000`00000000
fffffa80`1a14c810 fffff880`05d0d700 mpsdrv!MpsQueryUserCallout
fffffa80`1a14c818 fffff880`05d0d8e0 mpsdrv!MpsDummyFilterNotify
fffffa80`1a14c820 00000000`00000000
...
fffffa80`1a14ce38 00000000`00000000
fffffa80`1a14ce40 fffff880`05dde19c Ndu!NduFlowEstablishedClassify
fffffa80`1a14ce48 fffff880`05dde810 Ndu!NduCalloutNotify
fffffa80`1a14ce50 00000000`00000000
...
fffffa80`1a14d078 00000000`00000000
fffffa80`1a14d080 fffff880`074d4000 test+0x1000 <== а вот и callout's тестового драйвера
fffffa80`1a14d088 fffff880`074d4500 test+0x1500
fffffa80`1a14d090 00000000`00000000
И не нужно мучатся, в дизасме выискивать смещения на эти структуры, как это делается в некоторых статьях.
Тем более, что на разных версиях ОС эти смещения указывают на совсем другое, и все эти описанные сакральные знания становятся бесполезными. А теги памяти с очень большой вероятностью меняться не будут.
понедельник, 27 июля 2020 г.
Может ли колбек PsSetCreateThreadNotifyRoutine выполняться на APC_LEVEL?
Реальность утверждает, что да. По крайней мере на windows 8.
Кратенько суть: есть установленный колбек на создание новых потоков в драйвере, в нем среди прочего вызывается ZwClose, и, внезапно, колбек вызывается на IRQL == APC_LEVEL.
И это дело ловит верифаер:
DRIVER_VERIFIER_DETECTED_VIOLATION (c4)
A device driver attempting to corrupt the system has been caught. This is
because the driver was specified in the registry as being suspect (by the
administrator) and the kernel has enabled substantial checking of this driver.
If the driver attempts to corrupt the system, bugchecks 0xC4, 0xC1 and 0xA will
be among the most commonly seen crashes.
Arguments:
Arg1: 000000000002001f, ID of the 'IrqlZwPassive' rule that was violated.
Arg2: fffff8800109ba10, A pointer to the string describing the violated rule condition.
Arg3: 0000000000000000, Reserved (unused).
Arg4: 0000000000000000, Reserved (unused).
Debugging Details:
------------------
DV_VIOLATED_CONDITION: ZwClose should only be called at IRQL = PASSIVE_LEVEL.
LAST_CONTROL_TRANSFER: from fffff803b91ed0ea to fffff803b90ec930
STACK_TEXT:
fffff880`049cf4d8 fffff803`b91ed0ea : 00000000`00000000 00000000`000000c4 fffff880`049cf640 fffff803`b91714b8 : nt!DbgBreakPointWithStatus
fffff880`049cf4e0 fffff803`b91ec742 : 00000000`00000003 fffff880`049cf640 fffff803`b9171e90 00000000`000000c4 : nt!KiBugCheckDebugBreak+0x12
fffff880`049cf540 fffff803`b90f2144 : 00000000`00000000 fffff880`0109925d 00000000`00000000 00000000`00000000 : nt!KeBugCheck2+0x79f
fffff880`049cfc60 fffff880`0109921f : 00000000`000000c4 00000000`0002001f fffff880`0109ba10 00000000`00000000 : nt!KeBugCheckEx+0x104
fffff880`049cfca0 fffff880`01090529 : ffffffff`80000498 fffff803`b96d4180 fffff980`032c4ee0 fffffa80`1c505b00 : VerifierExt!SLIC_abort+0x47
fffff880`049cfce0 fffff880`0109054e : fffff880`049cfdb0 fffff880`049cfdf0 fffffa80`1c505b00 00000000`00000000 : VerifierExt!SLIC_ZwClose_entry_irqlzwpassive+0x1d
fffff880`049cfd10 fffff880`08f6c9be : 00000000`000010a0 fffff880`049d0180 00000000`00000004 fffff880`049d0180 : VerifierExt!ZwClose_wrapper+0x1a
fffff880`049cfe40 fffff803`b94a668d : fffff803`b930d7a8 fffffa80`1bb3d680 fffff803`b930d7a8 fffff880`049d03d0 : xxx!ThreadNotify+0x73
fffff880`049cfea0 fffff803`b94a9b3a : fffffa80`18f68b00 00000000`00000000 fffff6fc`01dca000 fffff880`049d00e0 : nt!PspInsertThread+0x61d
fffff880`049d0080 fffff803`b940f8c0 : 00001000`18f68b00 00000000`00000000 fffff803`b93c6a80 00000000`00000000 : nt!PspCreateThread+0x216
fffff880`049d0320 fffff803`b940ed7a : 00000000`00000000 00000000`00000001 fffffa80`1c638150 fffff880`049d0679 : nt!PsCreateSystemThreadEx+0x134
fffff880`049d0590 fffff803`b93d5702 : 00000000`00000006 00000000`00000000 00000000`00000000 fffff880`049d0820 : nt!PsCreateSystemThread+0x3a
fffff880`049d05f0 fffff803`b93d7e21 : 00000000`00000001 00000000`00000000 00000000`00000000 00000000`00000002 : nt!PopFlushVolumes+0x2c2
fffff880`049d06e0 fffff803`b90f1053 : fffffa80`18f68b00 fffff803`b90efb00 00000000`c0000004 00000000`00000001 : nt!NtSetSystemPowerState+0x495
fffff880`049d0820 fffff803`b90f6230 : fffff803`b93e983c 00000000`00534310 00000000`00000006 00000000`00000005 : nt!KiSystemServiceCopyEnd+0x13
fffff880`049d09b8 fffff803`b93e983c : 00000000`00534310 00000000`00000006 00000000`00000005 00000000`00000004 : nt!KiServiceLinkage
fffff880`049d09c0 fffff803`b90f1053 : fffffa80`18f68b00 fffffa80`18f68b00 fffff880`c0000004 0000003e`e9aafac8 : nt! ?? ::OKHAJAOM::`string'+0x3584
fffff880`049d0b00 000007fc`fed7447b : 000007f7`8f091f3c 00000000`00000000 00000000`00000000 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x13
0000003e`e9aafae8 000007f7`8f091f3c : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`000003ea : ntdll!NtShutdownSystem+0xa
Текущий IRQL == APC_LEVEL, а ZwClose действительно должна быть вызвана на PASSIVE_LEVEL, так что вердикт от верифаера справедлив.
Кто задирает IRQL до APC_LEVEL? Функция NtSetSystemPowerState:
NtSetSystemPowerState:
PAGELK:0000000140360C2E mov rbx, cr8
PAGELK:0000000140360C32 mov cr8, r13 <== r13 равна единице, это APC_LEVEL
На x64 регистр CR8 содержит информацию о приоритизации внешних прерываний (0-3биты), то есть текущий irq level.
Почему так происходит:
При выполнении NtShutdownSystem система создает до 8ми системных потоков, которые скидывают данные томов на диск. Подсчитывается количество томов, обходом списка PopVolumeDevices, затем создаются системные потоки (кол-во равно кол-ву томов, но не более 8ми). Для синхронизации доступа к списку томов используется FastMutex, который задирает IRQL до APC_LEVEL. Код выше с CR8 это просто заинлайненный код захвата fast mutex.
Псевдокод примерно такой(очень упрощенно):
ExAcquireFastMutex() <= IRQL становится равным APC_LEVEL
1) обход списка томов, для их подсчета
2) создание системных потоков которые flush'ат данные томов на диск через ZwFlushBuffersFile(кеш файловой системы скидывается на диск)
3) системные потоки при создании триггерят колбеки, установленные через PsSetCreateThreadNotifyRoutine(IRQL все еще равен APC_LEVEL), в колбеках могут вызываться функции, которые работают только на PASSIVE_LEVEL.
ExReleaseFastMutex()
Это является багом в ядре ОС, т.к. правильная реализация должна использовать guarded mutex.
Несмотря на то, что в msdn пишут, что с win8 фаст и гвардед мьютексы реализованы одинаково, видно, что это не так.
воскресенье, 21 июля 2019 г.
Обход Patch-Guard
Если кратко, то через подмену указателя в структуре, которую не контролирует Patch-Guard можно перехватывать довольно много всего в системе(начиная с vista). В моем случае технология использовалась в одном продукте, которому уже несколько лет. И перехватывались именно сисколы(как и в InfinityHook). И хотя в либе используется "Circular Kernel Context Logger", а у меня в коде "NT Kernel Logger" совпадение крайне забавное. Дойти до подобной идеи, как мне кажется, не тривиально. В коде библиотеки есть несколько грубых ошибок, которые будут приводить к потере сисколов, и только это позволяет мне думать, что человек дошел до идеи сам, а не получил эту информацию от бывших сотрудников, которые имели доступ к этой либе в svn на моей работе :)
среда, 15 мая 2019 г.
Транзакции в windows. Часть 1.
Что такое транзакция?
* атомарность(atomic),
* согласованность (consistent),
* изолированность(isolated)
* долговечность(durable) - ACID.
Под атомарностью подразумевается то, что все операции в рамках транзакции либо выполнятся успешно, либо не выполнятся вообще и откатятся(rollback) в исходное состояние.
Под согласованностью понимается то, что данные будут согласованы после транзакций.
Под изолированностью понимается то, что к примеру, процесс обновления системы не виден для человека, который ей пользуется в данный момент.
Под долговечностью понимается сохранность данных при каком-либо сбое. Применительно к windows это может означать внезапную остановку системы или отключение питания системы. Данные транзакции при этом будут сохранены.
Как принцип ACID реализуется в windows ?
Создание транзации => выполнение действий в рамках транзакции => подтверждение изменений ( commit ) или откат изменений ( rollback ) обеспечивает атомарность и согласованность действий в рамках транзакции.
Изолированность заложена в сам процесс транзаций, к примеру, созданный ключ реестра в рамках транзации не виден для обычных реестровых api, он виден только для транзакционных api работы с ключем.
Долговечность достигается за счет компонента, под названием Common Log File System (CLFS). Это высокопроизводительная, обще-целевая подсистема логирования, которая к тому же устойчива к сбоям системы.
Как транзакции используются в windows ?
На примере работы обновления windows попробуем получить имена создаваемых ключей реестра и создаваемых файлов при коммите транзакции.
Нас интересует unitOfWork->ActionType == UoWAddThisKey, который приводит нас к функции NTSTATUS CmpCommitAddKeyUoW( CM_KCB_UOW *uow, PLARGE_INTEGER lastWriteTime ).
Из структуры CM_KCB_UOW можно получить имя создаваемого ключа:
typedef struct _CM_KCB_UOW
{
...
CM_KEY_CONTROL_BLOCK *KeyControlBlock;
...
} CM_KCB_UOW;
typedef struct _CM_KEY_CONTROL_BLOCK
{
...
PCM_NAME_CONTROL_BLOCK NameBlock;
...
} CM_KEY_CONTROL_BLOCK;
typedef struct _CM_NAME_CONTROL_BLOCK
{
...
union
{
CM_NAME_HASH NameHash;
struct
{
ULONG ConvKey;
struct _CM_KEY_HASH *NextHash;
USHORT NameLength;
WCHAR Name[1] ; // The actual string value
};
};
} CM_NAME_CONTROL_BLOCK;
Имея эту информацию, можно получить список создаваемых ключей при обновлении(ставим брекпойнт и запускаем любое обновление windows):
bp CmpCommitAddKeyUoW "da /c 64 poi(poi(esi+18)+4*7)+e;.echo '---CmpCommitAddKeyUoW---'; g"
Вывод:
'---CmpCommitAddKeyUoW---------'
9694cd4e "X86_MICROSOFT-WINDOWS-B..AGER-PCAT.RESOURCES_31BF3856AD364E35_0.0.0.0_SV-SE_2877C192B6493CCD*"
'---CmpCommitAddKeyUoW---------'
a0a0ba6e "PACKAGE_20_FOR_KB935509~31BF3856AD364E35~X86~~6.0.1.9.........CMN.."
'---CmpCommitAddKeyUoW---------'
a0b5f28e "X86_MICROSOFT-WINDOWS-B..AGER-PCAT.RESOURCES_31BF3856AD364E35_0.0.0.0_TR-TR_D1850BD9A5053EBE0"
'---CmpCommitAddKeyUoW---------'
a05c96fe "PACKAGE_21_FOR_KB935509~31BF3856AD364E35~X86~~6.0.1.9.0.\.....SeTd.."
'---CmpCommitAddKeyUoW---------'
a064d986 "X86_MICROSOFT-WINDOWS-B..AGER-PCAT.RESOURCES_31BF3856AD364E35_0.0.0.0_ZH-CN_A2E229D7553D10DD0"
'---CmpCommitAddKeyUoW---------'
a0791426 "PACKAGE_22_FOR_KB935509~31BF3856AD364E35~X86~~6.0.1.9.X.y.....Ntfo"
'---CmpCommitAddKeyUoW---------'
a058a3a6 "X86_MICROSOFT-WINDOWS-B..AGER-PCAT.RESOURCES_31BF3856AD364E35_0.0.0.0_ZH-HK_A18D22655618836D0.0.X..."
'---CmpCommitAddKeyUoW---------'
9e773b16 "PACKAGE_23_FOR_KB935509~31BF3856AD364E35~X86~~6.0.1.9.H;w.....FMfn..."
'---CmpCommitAddKeyUoW---------'
a0d26696 "X86_MICROSOFT-WINDOWS-B..AGER-PCAT.RESOURCES_31BF3856AD364E35_0.0.0.0_ZH-TW_A6DE672D52ADED4D0.0.0"
'---CmpCommitAddKeyUoW---------'
9eef0f46 "PACKAGE_24_FOR_KB935509~31BF3856AD364E35~X86~~6.0.1.9.x.......FSim."
'---CmpCommitAddKeyUoW---------'
96e7f5e6 "X86_MICROSOFT-WINDOWS-SERVICINGSTACK-MSG_31BF3856AD364E35_0.0.0.0_NONE_62795FA07331A3BC442....IoNm"
'---CmpCommitAddKeyUoW---------'
Примерно также можно получить и значения:
a0c19858 "PendingXmlIdentifier"
'---CmpCommitSetValueKeyUoW----'
a0c198d0 "AdvancedInstallersNeedResolving"
'---CmpCommitSetValueKeyUoW----'
a20f12b8 "InstallClient"
'---CmpCommitSetValueKeyUoW----'
a20f1310 "InstallName"
'---CmpCommitSetValueKeyUoW----'
a20f1358 "InstallLocation"
'---CmpCommitSetValueKeyUoW----'
a20f1430 "CurrentState"
'---CmpCommitSetValueKeyUoW----'
a20f1470 "Visibility"
'---CmpCommitSetValueKeyUoW----'
Для файлов интересна функция:
8519e67e 8bff mov edi,edi
8519e680 55 push ebp
8519e681 8bec mov ebp,esp
8519e683 56 push esi
8519e684 ff7508 push dword ptr [ebp+8]
8519e687 33f6 xor esi,esi
8519e689 e8fd3ff7ff call Ntfs!IoGetTransactionParameterBlock
8519e68e 85c0 test eax,eax
Прототип не известен, но сразу на входе есть вызов IoGetTransactionParameterBlock( PFILE_OBJECT FileObject ), которая есть в msdn.
Ставим брекпойнт, чтобы получить список создаваемых и открываемых файлов при коммите транзакции:
bp 8519e689 "!object poi(esp);.echo '---File---'; g"
Object: 831da480 Type: (82b93a80) File
ObjectHeader: 831da468 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \windows\winsxs\msil_microsoft.web.management_31bf3856ad364e35_6.0.6000.16386_none_c30fe7d58014a975\ {HarddiskVolume1}
'---File---'
Object: 83301c88 Type: (82b93a80) File
ObjectHeader: 83301c70 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \Windows\winsxs {HarddiskVolume1}
'---File---'
Object: 831da480 Type: (82b93a80) File
ObjectHeader: 831da468 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \windows\winsxs\msil_microsoft_vsavb_b03f5f7f11d50a3a_6.0.6000.16386_none_6728c2d6cd97e7f4\ {HarddiskVolume1}
'---File---'
Object: 83301c88 Type: (82b93a80) File
ObjectHeader: 83301c70 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \Windows\winsxs {HarddiskVolume1}
'---File---'
Object: 831da480 Type: (82b93a80) File
ObjectHeader: 831da468 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \windows\winsxs\msil_miguicontrols.resources_31bf3856ad364e35_6.0.6000.16386_ru-ru_0c31b4feb872e0ff\ {HarddiskVolume1}
'---File---'
Object: 83301c88 Type: (82b93a80) File
ObjectHeader: 83301c70 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \Windows\winsxs {HarddiskVolume1}
'---File---'
Object: 831da480 Type: (82b93a80) File
ObjectHeader: 831da468 (old version)
HandleCount: 0 PointerCount: 1
Directory Object: 00000000 Name: \windows\winsxs\msil_miguicontrols_31bf3856ad364e35_6.0.6000.16386_none_ac1216923fb00239\ {HarddiskVolume1}
Поддержка транзакций в ядре windows
Transaction ( KTRANSACTION )
Transaction Manager ( KTM )
Resource Manager ( KRESOURCEMANAGER )
Enlistment ( KENLISTMENT )
Жизненный цикл этих объектов, как собственно и любых других в windows состоит из инициализации, создания, работы с объектами и их удаления.
Инициализация
Phase1Initialization => Phase1InitializationDiscard => TmInitSystem =>
=> TmpTransactionManagerInitialization / TmpTransactionInitialization / TmpResourceManagerInitialization / TmpEnlistmentInitialization => инициализация и создания соответствующих объектов.
Пример инициализации KTRANSACTION:
BOOLEAN TmpTransactionInitialization()
{
OBJECT_TYPE_INITIALIZER ObjectTypeInitializer;
UNICODE_STRING DestinationString;
NTSTATUS status;
RtlInitUnicodeString( &DestinationString, L"TmTx" );
TmpTransactionTypeName.Buffer = NULL;
status = RtlDuplicateUnicodeString( 0, &DestinationString, &TmpTransactionTypeName );
if ( !NT_SUCCESS(status) )
return FALSE;
RtlZeroMemory( &ObjectTypeInitializer, sizeof(OBJECT_TYPE_INITIALIZER) );
ObjectTypeInitializer.Length = sizeof(OBJECT_TYPE_INITIALIZER);
ObjectTypeInitializer.InvalidAttributes = OBJ_OPENLINK;
ObjectTypeInitializer.GenericMapping.GenericRead = TmpTransactionMapping[0];
ObjectTypeInitializer.GenericMapping.GenericWrite = TmpTransactionMapping[1];
ObjectTypeInitializer.GenericMapping.GenericExecute = TmpTransactionMapping[2];
ObjectTypeInitializer.GenericMapping.GenericAll = TmpTransactionMapping[3];
ObjectTypeInitializer.PoolType = NonPagedPool;
ObjectTypeInitializer.DefaultNonPagedPoolCharge = sizeof(KTRANSACTION);
ObjectTypeInitializer.ValidAccessMask = TRANSACTION_ALL_ACCESS | TRANSACTION_RIGHT_RESERVED1;
ObjectTypeInitializer.CloseProcedure = TmpCloseTransaction;
ObjectTypeInitializer.DeleteProcedure = TmpDeleteTransaction;
status = ObCreateObjectTypeEx( &TmpTransactionTypeName, &ObjectTypeInitializer, 0, &TmTransactionObjectType );
if ( !NT_SUCCESS(status) )
return FALSE;
return TRUE;
}
Полностью функция инициализации выглядит так:
BOOLEAN TmInitSystem()
{
if ( !TmpTransactionManagerInitialization() )
return FALSE;
if ( !TmpTransactionInitialization() )
return FALSE;
if ( !TmpResourceManagerInitialization() )
return FALSE;
if ( !TmpEnlistmentInitialization() )
return FALSE;
ExInitializePagedLookasideList(&TmpLogWriteLookasideList, 0, 0, 0, 0x214, 'lLmT', 0);
KeInitializeMutex(&TmpAllProtocolsListMutex, 0);
InitializeListHead(&TmpAllProtocolsList);
TmpAllProtocolsListCount = 0;
KeInitializeMutex(&TmpAllPropReqsListMutex, 0);
InitializeListHead(&TmpAllPropReqsList);
KeInitializeMutex(&TmpAllCRMListMutex, 0);
InitializeListHead(&TmpAllCRMList);
TmpAllCRMListCount = 0;
TmpNamespaceInitialize( FIELD_OFFSET( KTM, NamespaceLink ), &TmpTmNamespace, FIELD_OFFSET( KTM, TmIdentity ) );
TmpNamespaceInitialize( FIELD_OFFSET( KTRANSACTION, GlobalNamespaceLink ), &TmpTransactionsNamespace, FIELD_OFFSET( KTRANSACTION, UOW ) );
KeInitializeEvent( &TmpTransactionFreezeCompleteEvent, NotificationEvent, FALSE );
KeInitializeEvent( &TmpTransactionThawEvent, NotificationEvent, FALSE );
KeInitializeMutex( &TmpFreezeMutex, 0 );
KeInitializeEvent( &TmpTransactionFreezeCancelEvent, NotificationEvent, FALSE );
KeInitializeTimer( &TmpTransactionThawTimer );
KeInitializeDpc( &TmpTransactionThawDpc, TmpTransactionThawDpcRoutine, NULL );
if ( !TmpInitializeKtmRmSecurityDescriptor() )
return FALSE;
return TRUE;
}
Где хранятся транзакции? Логично было бы предположить, что как и процессы/потоки они хранятся в связанных списках, но нет.
Транзакции хранятся в KTMOBJECT_NAMESPACE. В основе этой структуры лежит AVL дерево. Функции для работы с данной структурой:
TmpNamespaceLock
TmpNamespaceUnlock
TmpNamespaceInitialize
TmpNamespaceEnumerate
TmpNamespaceEnumerateObject
TmpNamespaceForEach
TmpNamespaceLookup
TmpNamespaceReplace
TmpNamespaceInsert
TmpNamespaceRemove
TmpNamespaceRename
TmpNamespaceCompareGuids
TmpNamespaceAllocateEntry
TmpNamespaceFreeEntry
В TmInitSystem вызывается инициализация глобальных переменных TmpTmNamespace / TmpTransactionsNamespace для хранения менеджеров транзакций и самих транзакций. Кроме того, в KRESOURCEMANAGER / KTM есть локальные namespace'ы. Подробнее про namespace'ы я расскажу в следующей статье.
Создание объектов
NtCreateTransaction
NtCreateEnlistment
NtCreateResourceManager
NtCreateTransactionManager
Общая реализация также стандартна:
1) try + ProbeForWrite/ProbeForRead
2) проверка аргументов (флаги, длина юникодных строк и так далее)
3) создание объекта через ObCreateObject
4) инициализация созданного объекта (TmInitializeResourceManager/TmpInitializeEnlistment/TmInitializeTransaction/TmInitializeTransactionManager)
Работа с объектами
NtCommitTransaction
NtRollbackTransaction
NtCommitEnlistment
NtRollbackEnlistment
NtRecoverEnlistment
NtSetInformationEnlistment
NtQueryInformationEnlistment
NtSetInformationTransaction
NtCreateKeyTransacted
...
И так далее, подробнее про функции и механизм commit'a / rollback'a будет рассказано в отдельной статье.
Удаление объектов
Рассмотрим на примере объекта транзакции. Скажем, в доке https://docs.microsoft.com/en-us/windows/desktop/ktm/transactions упоминается следующее поведение для транзакций:
"A transaction is an object that defines a logical unit of work. The transaction is alive as long as there is a handle referencing the transaction and it is considered active if the transaction has not yet been committed or rolled back. If a transaction is created and all handles to it have been closed before a commit or rollback occurs, the transaction will be rolled back."
Заглянем в код, CloseProcedure'ой для транзакции является функция TmpCloseTransaction.
VOID TmpCloseTransaction( IN PEPROCESS Process OPTIONAL, IN PVOID Object, IN ULONG GrantedAccess, IN ULONG_PTR ProcessHandleCount, IN ULONG_PTR SystemHandleCount)
{
if ( SystemHandleCount == 1 || ProcessHandleCount == 1 )
{
TmRollbackTransaction( (KTRANSACTION *)Object, 0 );
}
}
Msdn не обманул.
Для DeleteProcedure'ы транзакции код выглядит как-то так:
VOID TmpDeleteTransaction( PVOID object )
{
KTM *tm = NULL;
KTRANSACTION *transaction = (KTRANSACTION*)object;
SetFlag( transaction->Flags, KTRANSACTION_FLAG_DELETED );
if ( transaction->State == KTransactionUninitialized )
return;
if ( transaction->TmNamespaceLink.Links.Parent )
{
tm = transaction->Tm;
if ( tm )
{
TmpNamespaceRemove( (PVOID)transaction, &tm->Transactions, (PVOID)transaction->Tm );
transaction->Tm = NULL;
}
}
if ( transaction->GlobalNamespaceLink.Links.Parent )
TmpNamespaceRemove( (PVOID)transaction, &TmpTransactionsNamespace, NULL );
if ( transaction->Description.Buffer )
RtlFreeUnicodeString( &transaction->Description );
if ( transaction->TreeTx != transaction )
{
ObfDereferenceObject( transaction->TreeTx );
transaction->TreeTx = NULL;
}
}
вторник, 3 апреля 2018 г.
Каким алгоритмом жмется память в win10 при memory compression?
Любопытство не порок, а вполне себе мотиватор. Поэтому я, недолго думая, взял отладчик в руки и углубился в ядро десятки.
Чтобы минимизировать потерю времени на анализ, я подумал над тем, о чем говорилось в видео.
Запись в store с сжатыми страничками идет асинхронно, то есть искать это можно, но это долго и не эффективно.
А вот получение странички из store идет синхронно при hard fault'е.
Соответственно, искать стоит не то место, где страничка сжимается, а место, где она разжимается в памяти.
Страничные фолты обрабатываются через прерывания, имя функции обработчика page fault'a я не помнил, но ничего мешает сдампить idt и вспомнить:
kd> !idt
Dumping IDT:
...
0c: fffff8030be0b900 nt!KiStackFault
0d: fffff8030be0ba40 nt!KiGeneralProtectionFault
0e: fffff8030be0bb40 nt!KiPageFault <================ обработчик
10: fffff8030be0c140 nt!KiFloatingErrorFault
11: fffff8030be0c2c0 nt!KiAlignmentFault
...
После небольшого анализа в IDA получаем цепочку KiPageFault => MiIssueHardFault => MiIssueHardFaultIo => SmPageRead.
Префикс Sm в функции SmPageRead для меня был новым, вспоминая видео и слово store, можно предположить что префикс Sm означает store manager или что-то вроде того.
Смотрим тело функции и видим, что это просто обертка над store manager'ом, который и рулит всеми операциями связанными с имплементацией memory compression фичи:
__int64 SmPageRead(union _MM_STORE_KEY *a1, unsigned __int64 a2)
{
...
SmKeyConvert(a1, (union _SM_PAGE_KEY *)&v7);
return SMKM_STORE_MGR<SM_TRAITS>::SmPageRead(v3, &v7, v2, v5, v4);
}
Далее оставалось просто построить граф вызовов функции и прочекать их содержимое на предмет кода, связанного с расжатием данных.
В итоге поиск привел к ST_STORE<SM_TRAITS>::StDmSinglePageCopy, которая вызывала RtlDecompressBufferEx. То есть использовалась стандартная функция.
Первый аргумент у неё CompressionFormat, осталось лишь поставить брекпойнт и выяснить значение в отладчике.
Бряк сработал, на х64 первые 4 аргумента передаются в регистрах, смотрим чему равен rcx, он равен трем.
Это соответствует флагам COMPRESSION_FORMAT_DEFAULT | COMPRESSION_FORMAT_LZNT1 | COMPRESSION_ENGINE_STANDARD.
То есть алгоритм сжатия - LZ compression. На этом моё любопытство было удовлетворено.