Внедрение кода в чужой процесс в Windows NT




Теперь осталось показать, как вышеописанные DLL можно внедрить в процесс, избранный в качестве жертвы эксперимента. (Нелишне напомнить, что для нашего примера процесс-жертва должен иметь окна со стандартными сообщениями MessageBox).

Внедрить код – значит, записать некоторую программу в чужой процесс и исполнить ее от имени этого процесса. Таким образом, внедренный код становится частью процесса и получает доступ ко всем ресурсам, которыми обладает процесс. В отличие от DOS, семейство ОС Windows (на ядре NT) – операционные системы с разделяемой памятью, т.е каждое приложение выполняется в своем адресном пространстве, не пересекающемся с другими, и не имеет непосредственного доступа к памяти чужого приложения. Таким образом, внедрение кода является нетривиальной задачей. Существует несколько способов внедрить свой код:

1. «Вручную».

2. При помощи хуков.

Внедрение 1

Рассмотрим наиболее эффективный, на наш взгляд, способ внедрения – первый. Он заключается в записи короткого участка машинного кода в память процесса, который должен присоединить DLL к этому процессу, запустить ее код, после чего Dll сможет выполнять любые действия от имени данного процесса. В принципе можно и не присоединять DLL, а реализовать нужные действия во внедряемом машинном коде, но это будет слишком трудоемкой задачей, поскольку все смещения для данных потеряют смысл, и вы не сможете корректно обратиться к ним, не настроив соответствующим образом смещения (морока:().

При присоединении DLL загрузчик автоматически устанавливает все смещения в соответствии с адресом, по которому загружена DLL. Следует также отметить, что для записи кода в процесс и его исполнения необходимо открыть процесс с доступом как минимум:

PROCESS_CREATE_THREAD|PROCESS_VM_WRITE|PROCESS_VM_OPERATION.

ПРЕДУПРЕЖДЕНИЕ При реализации данного метода необходимо указать компилятору выравнивать структуры ПОБАЙТОВО. Иначе структура с машинным кодом будет содержать совершенно не тот код, что был запланирован.

Общая схема внедрения:

Открыть процесс (OpenProcess).

Выделить в нем память (VirtualAllocEx – доступно только для WinNT).

Записать внедряемый код в эту память (WriteProcessMemory).

Исполнить его(CreateRemoteThread).

Внедряемый машинный код должен (по нашему сценарию) произвести такие действия:

call LoadLibrary – вызвать функцию LoadLibrary из kernel32.dll для загрузки присоединяемой библиотеки (одной из разбиравшихся выше).

Call ExitThread – вызвать функцию ExitThread из kernel32.dll для корректного завершения данного потока.

В WinNT стартовый адрес отображения системных DLL (user32, kernel32 и т. д.) один и тот же для всех приложений. Это означает, что адрес некоторой функции из системной DLL в одном приложении будет актуален и в другом приложении. То есть точки входа функций системных DLL всегда одни и те же.

Ниже приведен пример процедуры, внедряющей dll с заданным именем в процесс с заданным PID (идентификатором процесса) (их можно наблюдать в закладке «процессы» диспетчера задач или получить с помощью стандартных API-функций).

//структура описывает поля, в которых содержится код внедрения struct INJECTORCODE { BYTE instr_push_loadlibrary_arg; //инструкция push DWORD loadlibrary_arg; //аргумент push   WORD instr_call_loadlibrary; //инструкция call [] DWORD adr_from_call_loadlibrary;   BYTE instr_push_exitthread_arg; DWORD exitthread_arg;   WORD instr_call_exitthread; DWORD adr_from_call_exitthread;   DWORD addr_loadlibrary; DWORD addr_exitthread; //адрес функции ExitTHread BYTE libraryname[100]; //имя и путь к загружаемой библиотеке };   BOOL InjectDll(DWORD pid, char *lpszDllName) { HANDLE hProcess; BYTE *p_code; INJECTORCODE cmds; DWORD wr, id;   //открыть процесс с нужным доступом hProess=OpenProcess(PROCESS_CREATE_THREAD|PROCESS_VM_WRITE| PROCESS_VM_OPERATION, FALSE, pid); if(hProcess == NULL) { MessageBoxA(NULL, "You have not enough rights to attach dlls", "Error!", 0); return FALSE; }   //зарезервировать память в процессе p_code = (BYTE*)VirtualAllocEx(hProcess, 0, sizeof(INJECTORCODE), MEM_COMMIT, PAGE_EXECUTE_READWRITE); if(p_code==NULL) { MessageBox(NULL, "Unable to alloc memory in remote process", "Error!", 0); return FALSE; }   //инициализировать машинный код cmds.instr_push_loadlibrary_arg = 0x68; //машинный код инструкции push cmds.loadlibrary_arg = (DWORD)((BYTE*)p_code + offsetof(INJECTORCODE, libraryname));   cmds.instr_call_loadlibrary = 0x15ff; //машинный код инструкции call cmds.adr_from_call_loadlibrary = (DWORD)(p_code + offsetof(INJECTORCODE, addr_loadlibrary));   cmds.instr_push_exitthread_arg = 0x68; cmds.exitthread_arg = 0;   cmds.instr_call_exitthread = 0x15ff; cmds.adr_from_call_exitthread = (DWORD)(p_code + offsetof(INJECTORCODE, addr_exitthread));   cmds.addr_loadlibrary = (DWORD)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");   cmds.addr_exitthread = (DWORD)GetProcAddress(GetModuleHandle("kernel32.dll"),"ExitThread");   if(strlen(lpszDllName)>99) { MessageBox(NULL, "Dll Name too long", "Error!", 0); return FALSE; } strcpy((char*)cmds.libraryname, lpszDllName);   /*После инициализации cmds в мнемонике ассемблера выглядит следующим образом: push adr_library_name;аргумент ф-ции loadlibrary call dword ptr [loadlibrary_adr]; вызвать LoadLibrary push exit_thread_arg;аргумент для ExitThread call dword ptr [exit_thread_adr];вызвать ExitThread */   //записать машинный код по зарезервированному адресу WriteProcessMemory(hProcess, p_code, &cmds, sizeof(cmds), &wr);   //выполнить машинный код HANDLE z = CreateRemoteThread(hProcess, NULL, 0, (unsigned long (__stdcall *)(void *))p_code, 0, 0, &id);   //ожидать завершения удаленного потока WaitForSingleObject(z, INFINITE); //освободить память VirtualFreeEx(hProcess, (void*)p_code, sizeof(cmds), MEM_RELEASE);   return TRUE; }

Внедрение 2

Второй способ внедрения исполняемого кода (через хуки) наиболее прост в использовании. Он основан на технологии хуков, а именно: если установить хук на поток чужого процесса, то, как только поток получит сообщение, соответствующее заданному типу хука, система автоматически подключит DLL c хуком к данному процессу. Недостатком данного способа в том, что нельзя внедрить DLL в процесс, не имеющий очереди сообщений. Данная DLL будет присоединена к чужому процессу лишь до тех пор, пока запущена программа, установившая хук. Как только вы завершите эту программу, dll автоматически будет отключена. Первый способ лишен таких недостатков.

С другой стороны, первый способ будет работать лишь в WinNT, по причине использования функции VirtualAllocEx, которая резервирует память в заданном (отличном от того, в котором происходит вызов этой функции) процессе. Теоретически, данную проблему можно обойти, если писать код в некоторую часть отображения exe-файла чужого процесса, например в заголовок DOS, который после загрузки не используется. Но ОС не всегда позволяет писать в эту область памяти, даже если попытаться изменить разрешения при помощи VirtualProtextEx.

Есть еще и третий способ внедрения, но он наиболее опасен, так как может привести к краху системы. При помощи данного метода ОС сама внедряет указанную dll во все без исключения процессы операционной системы, даже защищенные. Для реализации необходимо прописать в реестре по пути Hkey_local_machine\software\microsoft\windowsnt\currentversion\windows в ключе AppInit_DLLs полный путь к своей dll.

Как отлаживать такие выкрутасы

Большинство программистов для отладки своих программ используют встроенные отладчики компиляторов. Они просты в использовании и удовлетворяют большинству требований, предъявляемых при отладке. Но если некоторый программный код будет внедрен и исполнен в рамках другого, постороннего процесса встроенный отладчик использовать очень тяжело. Для этих целей удобно применить системный отладчик SoftIce, который грузится раньше операционной системы, работает в нулевом кольце и поэтому имеет доступ к любым объектам ОС. Обсудим, как отлаживать внедренный код, выполняющий перехват API функций внутри постороннего процесса.

Все виды отладки условно можно разделить на 3 группы:

отладка кода загрузчика (на ассемблере), который, будучи внедренным в чужой процесс, исполняется как отдельный поток и присоединяет Dll от имени процесса.

отладка функций, выполняющихся при старте данной Dll. Обычно это функции, которые выполняют подмену кода внутри тела API-функции для передачи управления функции-двойнику.

отладка функций–двойников, которые получают управление при вызове перехваченной API-функции.

Отладка кода загрузчика

Итак, есть 2 процесса:

Процесс, который внедряет код. Обозначим его П1.

Процесс, в который внедряют код. Обозначим его П2.

Задача заключается в том, чтобы поставить точку останова в П2 перед выполнением внедренного кода. Изначально неизвестно, по какому адресу будет внедрен код в П2. При этом предполагается, что П2 уже загружен и висит где-то в памяти. Для простоты запускаем П1 в каком-либо встроенном отладчике и трассируем, для того, чтобы узнать по какому адресу в П2 будет выделена память. Узнав этот адрес, включаем SoftIce (ctrl+d). Подключаемся к П2 (addr П2-name), при этом SoftIce установит контекст адресов, соответствующий процессу П2. Устанавливаем точку останова по узнанному адресу (bpx address). Закрываем SoftIce(ctrl+d). Выполняем П1. При этом он создает поток в П2. Когда этот поток начинает исполняться, на первой инструкции внедренного кода выскакивает окно SoftIce.

Отладка функций, выполняющихся при старте DLL

В примере это функция InterceptFunctions библиотеки intercpt.dll, которая вызывается из DllMain при присоединении библиотеки к процессу и выполняет перехват функций.

Для начала необходимо откомпилировать эту библиотеку с отладочной информацией, которую в дальнейшем SoftIce будет использовать для вывода инструкций на языке С. В MS Visual C это делается так: Project->Settings->C/C++ список Debug Info – там необходимо выбрать тип символьной информации – Program database for Edit and Continue, а так же Project->Settings->Link список Category -> debug, установить галочку в поле Debug info и выбрать формат отладочной информации, например Microsoft Format.

Альтернатива – можно просто установить тип конфигурации проекта, при этом все параметры для получения отладочной информации будут установлены автоматически. Это делается так: Build->Set Active Configuration -> Win32 Debug.

Теперь можно приступать к использованию SoftIce. Для начала нужно загрузить в отладчик символьную информацию из данной Dll, при этом сама dll еще загружена не будет. Символьная информация понадобится впоследствии, для установки точек останова и представления кодов на языке высокого уровня. Это делается при помощи утилиты Symbol Loader из комплекта SoftIce.

Вначале необходимо открыть модуль dll (пункт File->Open Module).

Затем необходимо загрузить его в отладчик (Module -> Load). При успешном выполнении всех этих операций на экране Symbol Loader должно быть что-то вроде этого:

Рисунок 1

Теперь приступим к главному. Необходимо поставить точку останова на функцию InterceptFunctions из dll, при этом сама Dll пока еще не присоединена к процессу! Запускаем SoftIce. Создаем точку останова по символьному имени:

bpx InterceptFunctions, (InterceptFunctions – символьное имя функции из таблицы символов. Чтобы просмотреть всю таблицу, можно воспользоваться командой sym). Теперь при помощи написанной ранее программы внедряем эту dll в указанный процесс. Должно произойти следующее: Dll присоединяется к процессу, выполняется DllMain, которая вызывает IntercptFunctions и в этот момент должен произойти останов и вылезти окно отладчика. При этом весь код из dll будет представлен на языке высокого уровня.

Отладка функций – двойников, получающих управление при вызове перехваченных API функций

Для начала необходимо загрузить символьную информацию о Dll перехвата.

В данном примере это intercpt.dll.

В утилите Symbol Loader выбираем File->Open Module, затем, в меню Module->Load, загружаем символьную информацию в отладчик. Dll пока еще не присоединена ни к какому процессу.

Далее, зная имена функций-двойников ставим точку останова на имя функции. Например, функция-двойник Intercept_MessageBoxA, которая будет вызываться всякий раз, когда произойдет вызов функции MessageBoxA из программы. Поставим точку останова на нее – в окне отладчика набираем: bpx Intercept_MessageBoxA.

Теперь можно присоединить intercpt.dll к какому-либо процессу.

Когда этот процесс вызовет перехваченную функцию MessageboxA, управление будет передано на функцию Intercpt_MessageBoxA и сработает точка останова.

Тестирование

Чтобы опробовать все вышесказанное в деле, сначала подыщите на своем компьютере какое-либо приложение, имеющее в своем составе окна сообщений типа MessageBox. Подопытное приложение написано нами самими, оно называется MESS.EXE и выводит друг за другом три окна сообщений, коллаж из которых показан на рисунке:

Рисунок 2

Затем, откомпилируйте примеры внедряемых DLL, описанных выше. Результат компиляции мы назвали у себя METOD1.DLL и METOD2.DLL.

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

int main(int argc, char* argv[]) { if(argc<3) { printf("Parameters: PID, Dllname"); getch(); return 0; } InjectDll(atol(argv[1]), argv[2]); return 0; }

При запуске этой программы (назовем ее ATTACH.EXE) в качестве параметров надо будет указать идентификатор процесса, в который мы внедряем свой код, и имя DLL, которую следует прицепить к внешнему процессу.

Скопируйте все три полученных модуля METOD1.DLL, METOD2.DLL, ATTACH.EXE в один каталог (например, C:\TEST\). Теперь можно приступать к тестированию.

Запустите программу-жертву (в нашем случае это MESS.EXE). Откройте Диспетчер задач, найдите в нем запущенный процесс (mess.exe):

Рисунок 3

и определите его PID (в нашем случае PID mess.exe равен 1076).

Теперь из командной строки запустите программу внедрения кода первой DLL:

АТТАСН.EXE 1076 C:\TEST\ METOD1.DLL

В результате при попытке вызвать окно MessageBox в программе MESS.EXE вы будете получать одно и то же изображение:

Рисунок 4

Перехват функции API произошел!

Заключение

“Не так страшен черт, как программы MicroSoft…” Тем не менее, если читатель вдумчиво пропустил через себя изложенный материал, то увидел, что, как обычно, все гениальное – просто. И даже такая вещь, как перехват API в Windows NT, не требует сверхсложного программного кода и может быть реализована по первому желанию.

Список литературы

Для подготовки данной работы были использованы материалы с сайта https://www.rsdn.ru/



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2020-04-01 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: