Очень хочется иногда посмотреть кто же имено
Ищем список и лок
Хуки хранятся теперь в связном списке, голова которого лежит в переменной CallbackListHead, а доступ синхронизируется через объект EX_PUSH_LOCK с именем CallbackListLock - думаю вы уже достаточно взрослые чтобы догадаться, что они не экспортируютсяВпрочем их несложно найти дизассемблированием экспортируемой функции CmUnRegisterCallback:
mov ebx, offset _CallbackListLock
mov eax, ebx
lock bts dword ptr [eax], 0
jnb short loc_62984B
mov ecx, ebx
call ExfAcquirePushLockExclusive
loc_62984B:
mov [ebp+var_1D], 1
push esi
mov esi, offset _CallbackListHead
некоторые пояснения - для работы с объектом синхронизации EX_PUSH_LOCK обычно используются функции ExfAcquirePushLockShared, ExfAcquirePushLockExclusive и ExfReleasePushLock. Они экспортируются начиная с xp, хотя и до сих пор не описаны в wdk. Функции имеют примерно следующие прототипы:
VOID FASTCALL ExfAcquirePushLockShared(PEX_PUSH_LOCK);
VOID FASTCALL ExfAcquirePushLockExclusive(PEX_PUSH_LOCK);
VOID FASTCALL ExfReleasePushLock(PEX_PUSH_LOCK);
В приведенном куске кода сначала проверяется через атомарную операцию bit test and set бит Locked.
Если он уже был установлен - то кто-то другой залочил этот объект и для ожидания снятия блокировки вызывается функция ExfAcquirePushLockExclusive.
Если не был - то Locked был установлен нашим потоком, а другим потокам придется его ждать.
Паттерн для поисков очень прост - находим lock bts и считаем что последняя перед ней инструкция, загрузившая в регистр смещение в секцию .data - лок, а следующая - CallbackListHead
Восстанавливаем структуру хука
Поскольку гугл и внимательный просмотр pdb от кернела висты ничем особо не помогли - пришлось восстанавливать структуру, содержащую хук, самостоятельно. Хорошим кандидатом для реверсинга является например функция CmpRegisterCallbackInternal, вызывающаяся из CmRegisterCallback и CmRegisterCallbackEx и принимающая на вход 5 аргументов:- указатель на callback-функцию
- context - PVOID как аргумент к callback-функции
- некий DWORD, который равен 1, если функцию зовут из CmRegisterCallback и 0 в противном случае
- Cookie - указатель на LARGE_INTEGER, который используется для идентификации при снятии установленных хуков - выходной параметр
- в регистре EAX - указатель на UNICODE_STRING Altitude
push 62634D43h ; Tag
push 38h ; NumberOfBytes - размер искомой структуры
push 1 ; PoolType
mov edi, eax
call ExAllocatePoolWithTag
mov esi, eax
...
mov [esi+4], esi ; подозрительно похоже на инициализацию головы LIST_ENTRY
mov [esi], esi
lea eax, [esi+8] ; и еще одной - по смещению 8
mov [eax+4], eax
mov [eax], eax
...
mov eax, [ebp+Context]
mov [esi+18h], eax ; Context хранится по смещению 0x18
mov eax, [ebp+Function]
mov [esi+1Ch], eax ; callback-функция - по смещению 0x1C
movzx eax, word ptr [edi] ; Altitude
mov [esi+22h], ax ; копируем длины UNICODE_STRING
mov [esi+20h], ax ; значит сама она живет по смещению 0x20
...
call CmpInsertCallbackInListByAltitude ; из названия понятно что происходит
mov ecx, [esi+10h]
mov eax, [ebp+Cookie] ; взяли адрес выходного параметра
mov [eax], ecx ; и пишем туда DWORD из поля со смещением 0x10
mov ecx, [esi+14h]
mov [eax+4], ecx ; и второй DWORD из поля со смещением 0x14
В принципе остальные поля не интересны - все что нам нужно мы уже узнали и теперь можем описать структуру хука примерно так:
typedef struct _CM_REGISTER_HOOK
{
/* Win32 Win64 offsets */
/* 0x00 0x00 */ LIST_ENTRY List;
/* 0x08 0x10 */ LIST_ENTRY List2;
/* 0x10 0x20 */ LARGE_INTEGER Cookie;
/* 0x18 0x28 */ PVOID Context;
/* 0x1C 0x30 */ PVOID Function;
/* 0x20 0x38 */ UNICODE_STRING Altitude;
} CM_REGISTER_HOOK, *PCM_REGISTER_HOOK;
более дотошный читатель может выяснить значение оставшихся 0x38 - 0x28 = 0x10 байт в структуре самостоятельно
Update 24.VIII.2010: опыты показали что под 64 битной windows 7 эта структура выглядит по другому:
typedef struct _CM_REGISTER_HOOK_W7
{
/* 0x00 */ LIST_ENTRY List;
/* 0x10 */ PVOID unknown;
/* 0x18 */ LARGE_INTEGER Cookie;
/* 0x20 */ PVOID Context;
/* 0x28 */ PVOID Function;
/* 0x30 */ UNICODE_STRING Altitude;
} CM_REGISTER_HOOK_W7, *PCM_REGISTER_HOOK_W7;
Навигация
Опыты показали что навигация по вышеописанным структурам осуществляется по первому полю LIST_ENTRY. Ниже дан весьма примитивный kernel mode код для итерации по зарегистрированным хукам. Здесь в Lock и в ListHead лежат адреса ранее найденных соотв-но CallbackListLock и CallbackListHead
PCM_REGISTER_HOOK cb_item;
PLIST_ENTRY Next;
/* lock */
ExfAcquirePushLockShared(Lock);
/* check if head is not empty */
if ( ListHead->Flink != NULL && ListHead->Flink != ListHead )
{
Next = ListHead->Flink;
while ( Next != NULL )
{
cb_item = (PCM_REGISTER_HOOK)Next;
... // perl yada yada operator
Next = Next->Flink;
if ( Next == ListHead )
break;
}
}
/* unlock */
ExfReleasePushLock(Lock);
Profit
Как снять ненужные хуки ? Думаю что самым правильным способом будет извлечь их Cookie и вызвать с ними функцию CmUnRegisterCallback. Правда она может быть в свою очередь перехвачена, так что менее правильный но тоже вполне рабочий метод - поменять Function на свою. И совсем неправильный вариант - пытаться вырезать чужой хук самостоятельно, применяя DKOM. Во-первых для этого придется использовать ExfAcquirePushLockExclusive, а во-вторых как вы могли заметить структура хука содержит не одно поле LIST_ENTRY, а целых два, поэтому вырезав хук только из одного списка можно сломать какие-нть invariantsPS: написание этой заметки отняло времени больше чем собственно сам reverse engeneering
Комментариев нет:
Отправить комментарий