пятница, 20 августа 2010 г.

CmRegisterCallback(Ex) on Vista

полагаю что функция, про которую я сегодня будут писать всякое, в представлении не нуждается - про нее знает даже детский юмористический журнал ксакеп
Очень хочется иногда посмотреть кто же имено тут такой умный поставил этот хук и например снять его с мясом чтоб неповадно было. Техника просмотра установленных хуков под windows xp достаточно подробно описана в первом номере журнала no bunkum (кому объяснения в статье кажутся недостаточно подробными - могут курить файл base\ntos\config\cmhook.c из wrk). Это хорошая новость. Плохая новость - начиная с висты формат хранения хуков, установленных функциями CmRegisterCallback(Ex), радикально поменялся и требует больше ударов в бубен и человеческих жертвоприношений


Ищем список и лок

Хуки хранятся теперь в связном списке, голова которого лежит в переменной 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, а целых два, поэтому вырезав хук только из одного списка можно сломать какие-нть invariants

PS: написание этой заметки отняло времени больше чем собственно сам reverse engeneering

Комментариев нет:

Отправить комментарий