Смерть защитам Николай «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 не была инициализирована, но смещение нам пригодится, чтобы этот адрес найти в гуще кода ядра. |