Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #81, СЕНТЯБРЬ 2005 г.

Смерть защитам

Николай «gorl» Андреев

Хакер, номер #081, стр. 081-124-3


[функция NtWriteVirtualMemory из ntdll.dll]

B8 15 01 00 00 mov eax, 115h

BA 00 03 FE 7F mov edx, 7FFE0300h

FF 12 call dword ptr [edx]

С2 14 00 retn 14h

Первый байт кода функции – это опкод инструкции MOV eax,imm32. Четыре следующие за ним байта – это, собственно imm32, то есть данные, запихиваемые в регистр eax, то есть индекс функции в SDT. И так для всего Native API.

Естественно, подмена записей в SDT – это далеко не единственный способ перехвата на уровне ядра. Можно патчить функции прямо в ntoskrnl.exe, можно перехватывать sysenter, можно даже создать свою таблицу SDT и подменить KeServiceDescriptorTable, однако все это не необходимый геморрой для разработчиков защит, поэтому они ограничиваются одним самым банальным способом. Зря. Сейчас я покажу, как просто хакеры избавляется от подобного перехвата.

[убираем перехваты в kernel mode]

Чтобы убрать перехват, реализованный подменой записи в SDT, требуется найти оригинал таблицы и обратить подмену. Естественно, в user mode всего этого сделать не удастся. Ну, найти все адреса еще может быть, а вот заменить адрес на старый – нет, тут придется спускаться в ring0. Хорошо, что мы это уже умеем делать и у нас даже есть нормальная, работающая функция (она прилагалась к моей статье «Абсолютный ноль» в декабрьском номере Хакера за 2004 год). Итак, первое, что мы сделаем – это загрузим свою копию ядра и найдем в ней SDT. Ядро – это обычно ntoskrnl.exe, однако в boot.ini может быть прописан и другой файл, поэтому не следует ориентироваться на одно известное имя, лучше грамотно его получить с помощью Native API функции NtQuerySystemInformation, экспортируемой ntdll.dll.

NTSTATUS (WINAPI * _NtQuerySystemInformation)(UINT, PVOID, ULONG, PULONG);

HMODULE hntdll = GetModuleHandle("ntdll.dll");

*(FARPROC *)&_NtQuerySystemInformation = GetProcAddress(hntdll, "ZwQuerySystemInformation");

DWORD rc=_NtQuerySystemInformation(

SystemModuleInformation,pModules,4,&dwNeededSize);

if (rc==STATUS_INFO_LENGTH_MISMATCH)

{

Modules=(PMODULES)GlobalAlloc(GPTR,dwNeededSize);

rc=_NtQuerySystemInformation(

SystemModuleInformation,pModules,dwNeededSize,NULL);

}

else return FALSE;

if (!NT_SUCCESS(rc)) return FALSE;

// адрес ядра

DWORD dwKernelBase = (DWORD)pModules->smi.Base;

// адрес имени файла ядра

PCHAR pKernelName = pModules->smi.ModuleNameOffset

+ pModules->smi.ImageName;

Отлично, теперь у нас есть имя файла, и мы можем загрузить свою копию ядра. Делается это также как и с обычной DLL, только с предупреждением системы, что не нужно грузить DllMain:

// DONT_RESOLVE_DLL_REFERENCES – не грузим DllMain

HMODULE hKernel = LoadLibraryEx(pKernelName,0,DONT_RESOLVE_DLL_REFERENCES);

if (!hKernel) return FALSE;

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

Назад на стр. 081-124-2  Содержание  Вперед на стр. 081-124-4