Перехват WinAPI с помощью Delphi
Рубрика: Delphi -> Программирование -> Журнал Хакер -> Статьи
Метки: delphi | sockets | win api | программирование
Просмотров: 13906
В одном из номеров нашего журнала в рубрике FAQ был задан вопрос: «Как можно перехватить данные, отправляемые сетевым приложением?». В ответе Step порекомендовал использовать функцию WinSock hook из пакета сетевых утилит – IP Tools. Возможности WinSock hooker мне настолько понравились, что я решил написать свой вариант подобной программы. И в этой статье хочу поделиться с тобой опытом, полученным при разработке.
Теория перехвата WinAPI
Начнем с теории, которую, правда, и так знает большинство наших читателей. Все API функции определены в динамических библиотеках. Сразу возникает вопрос: «Откуда приложение знает, в какой библиотеке объявлена нужная функция?». Ответ прост – в любом PE файле есть область, называемая таблицей импорта. В ней перечислена информация обо всех импортированных функциях, необходимых для корректной работы. Загрузчик PE считывает эту инфу и подгружает необходимые библиотеки в адресное пространство процесса программы. Например, чтобы узнать список всех DLL, подгруженных в адресное пространство процесса, можно воспользоваться утилитой от Марка Руссиновича «Process Explorer». Взгляни на рисунок, на нем отображен список dll процесса Opera.exe.
Обрати внимание на выделенную в нижней части окна «Process Explorer» библиотеку dll с именем WS2_32.dll. В ней определен весь набор сетевых функций WinSock API второй версии. Одну из функций из этой библиотеки нам предстоит сегодня научиться перехватывать. В перехвате нет ничего нетривиального. Все, что требуется от программиста, так это заставить процесс жертвы обращаться не к системной функции, а к нашей – подставной. Дальше остается только командовать. Перехватить API функции можно несколькими способами. Вот самые популярные:
У метода есть как плюсы, так и минусы. Из достоинств можно выделить возможность перехвата абсолютно любых функций, то есть не только тех, что определены в таблице импорта. Среди минусов – вероятность появления ошибок в многопоточных приложениях. Хотя при наличии головы на плечах это достаточно легко обходится. В качестве способа лечения подходит банальная остановка всех потоков приложения и их запуск после установки перехвата. Перехват API удобнее всего осуществлять в контексте процесса «жертвы», поэтому необходимо внедрить свой код в удаленный процесс. Существует несколько «устаканившихся» вариантов вторжения в чужие процессы:
У многих может сложиться впечатление, что перехват – дело объемное и сложное. Действительно, реализовать метод внедрения и перехвата – задача далеко не самая простая, но Delphi программистам сильно повезло. С легкостью организовать перехват функций и внедриться в чужой процесс поможет модуль advHookApi, написанный гениальным программером Ms Rem. Модуль спроектирован качественно, все функции удобно описаны, код оформлен красиво. Единственное разочарование в том, что сегодня уже нельзя выразить респект автору. Этот человек мертв. Очень грустно, что гениальных людей так рано забирает смерть.
Хакерский модуль
Итак, давай посмотрим, какими возможностями может похвастаться данный модуль.
Внедрение Dll в чужой процесс. Метод реализуется с помощью функции InjectDll(), которая описана следующим образом:
function InjectDll(Process: dword; ModulePath: PChar): boolean;
В качестве параметров нужно передать дескриптор процесса, в который будем внедрять dll, и путь к самой библиотеке. В случае успешного внедрения результат будет true. Скрытое внедрение Dll. Функция InjectDllEx() внедряет dll и производит шаманские действия над образом Dll в памяти. После таких настроек многие программы (антивирусы, персональные firewall) начинают нервно курить и не замечать черных дел твоей программы. Внедрение произвольного Exe файла. Осуществляется при помощи функции InjectExe().
function InjectExe(Process: dword; Data: pointer): boolean;
Для работы функции требуется передать два параметра: Дескриптор (handle) процесса, в который будем внедряться и адрес образа файла в текущем процессе.
Инъекция образа текущего процесса. Функция InjectThisExe() будет полезна, когда не хочется или нет возможности юзать библиотеки dll. Описание функции и параметров приводить не стану, так как они стандартные и ничем не отличаются от описания предыдущей.
function InjectThread(Process: dword; Thread: pointer; Info: pointer;
InfoLen: dword; Results: boolean): THandle;
У функции пять входных параметров: 1. Process – дескриптор процесса. 2. Thread – указатель на процедуру, которую будем внедрять. 3. Info – адрес данных для процедуры. 4. InfoLen – размер передаваемых данных. 5. Results – необходимость возврата результата (если true, то функция вернет переданные данные).
function HookCode(TargetProc, NewProc: pointer; var OldProc: pointer): boolean;
Функция устанавливает перехват нужной функции. В качестве параметров просит: 1. TargetProc – адрес перехватываемой функции. 2. NewProc – адрес функции, которая будет вызываться вместо перехватываемой. 3. OldProc – переменная, в которой будет сохранен адрес моста к оригинальной функции (пригодится, когда потребуется остановить перехват и вернуть все на место). Для перехвата функций экспортируемых из DLL в текущем процессе предусмотрена отдельная функция:
function HookProc(lpModuleName, lpProcName: PChar; NewProc: pointer; var OldProc: pointer): boolean;
Входных параметров четыре: 1. Имя модуля (dll). 2. Имя функции; будь внимателен, регистр в указании имени функции играет роль. 3. Указатель на функцию-замену. 4. Адрес к оригинальной функции.
Перехватывать функцию без передышки не имеет смысла, поэтому рано или поздно нужно останавливать процесс перехвата. Для этого в AdvApiHook реализована функция UnhookCode(), которая в качестве единственного параметра принимает указатель на адрес моста к оригинальной функции.
Отключение защиты системных файлов. В ОС Windows, базирующихся на ядре NT, нельзя просто взять и изменить системные файлы – защита System File Protection трогать их не даст. Для решения этой задачи в модуле определена функция DisableSFC(). Передавать параметры ей не требуется. В качестве результата возвращает булевское значение.
Завершение процесса через режим отладки. Наверняка, ты сталкивался с процессами, которые тяжело «убить». Стандартные функции вроде TerminateProceess() не помогают. Для устранения этой проблемы принято использовать так называемые отладочные функции. Сначала процесс переводится в отладочный режим, а потом уничтожается. Таким образом можно завершить практически любой «вредный» процесс. Автор AdvApiHook реализовал простую надстройку для завершения процесса через отладочный режим.
function DebugKillProcess(ProcessId: dword):Boolean;
В качестве единственного параметра функции нужно передать pid процесса. В случае успешного завершения процесса функция вернет true.
Колбасим WinSock Hooker
Довольно теории, пора переходить к практике. Сейчас я расскажу, как написать приложение для перехвата функции send(). Для начала создай в Delphi новый проект и нарисуй форму, хотя бы отдаленно похожую на мою. Как закончишь творческую часть, вставляй наш DVD диск, копируй с него модуль AdvApiHook и немедленно подключай к своему проекту.
Первое, что нам необходимо сделать – научить программу получать список всех запущенных процессов и их дескрипторов. Все эти данные будут отображаться в компоненте ListView. Именно из этого списка мы будем выбирать процесс-жертву. Для получения списка текущих процессов существует несколько способов. Рассмотрим наиболее простой из них – использование модуля tlHelp32, входящего в стандартную поставку Delphi. Ради получения процессов я завел отдельную процедуру – GetAllProcess(). Ее код ты увидишь на врезке.
Код процедуры GetAllProcess()
var
_SnapList : THandle;
_ProcEntry : TProcessEntry32;
begin
If NOT (EnableDebugPrivilege()) Then
begin
reLog.SelAttributes.Color := clMaroon;
reLog.Lines.Add('Не удалось получить привилегии отладчика!');
End;
lvProcessList.Items.Clear;
_ProcEntry.dwSize := SizeOf(TProcessEntry32);
_SnapList := CreateToolHelp32SnapShot(TH32CS_SNAPPROCESS, 0);
If (Process32First(_SnapList, _ProcEntry)) Then
begin
Repeat
with lvProcessList.Items.Add Do
begin
Caption :=IntToStr(_ProcEntry.th32ProcessID);
SubItems.Add(ExtractFileName(_ProcEntry.szExeFile));
end;
Until not (Process32Next(_SnapList, _ProcEntry));
end;
CloseHandle(_SnapList);
End;
В самом начале этой процедуры я вызываю функцию EnableDebugPrivilige(). Функция эта самописная и ее вид ты можешь посмотреть, открыв исходники с диска. Скажу лишь, что она требуется для получения отладочных привилегий. С этими привилегиями появляется возможность получать handle даже у системных процессов. Если функция вернула false, то я просто сообщаю об этом в лог и продолжаю выполнение процедуры. Получение списка процессов сводится к нескольким шагам. Первый этап знаменуется использованием API функции CreateToolHelp32SnapShot(). Она получает снимок объектов, определенных в первом параметре. Я указал константу TH32CS_SNAPPROCESS, которая подразумевает получение снимка одних лишь процессов, так как для сегодняшнего примера этого вполне достаточно. Помимо процессов ты можешь получить:
Если функция CreateToolHelp32SnapShot() выполнилась успешно, значит, нужно пробежаться по списку полученных объектов и вывести их в ListView. Для «пробежки» я использую функции Process32First() и Process32Next(). Параметры у обеих функций одинаковые:
Итак, список процессов у нас есть. Повесь вызов GetAllProcess() на событие OnCreate формы и запусти программу. Если ты не допустил в листинге ошибок, то после запуска ListView должен заполниться списком процессов.
Делаем захват
Теперь, когда у нас есть список процессов, можно приступать к реализации самой главной части – перехвату функций. Перехватывать функции удобнее всего из библиотеки dll. Принцип такой: внедряем библиотеку в чужой процесс, методом сплайсинга делаем перехват. Сейчас все кажется сложным и запутанным, но на самом деле все просто. Создавай в Delphi новый проект типа DLL и потихоньку перекатывай в него бездушные строчки кода из врезки «Код DLL».
Код DLL
library project1;
uses
Windows,
advApiHook,
Messages,
SysUtils;
type
TSocket=integer;
TSendProcedure=function (s: TSocket; var Buf; len, flags: Integer): Integer; stdcall;
var
_pOldSend: TSendProcedure;
_hinst, _h:integer;
procedure SendData(data:string; funcType:integer; Buff:pointer; len:integer);
var
d:TCopyDataStruct;
begin
case funcType of
10:
begin
d.lpData := Buff;
d.cbData := len;
d.dwData := 10;
end;
30:
begin
d.lpData := pchar(data);
d.cbData := length(data);
d.dwData := 30;
end;
end;
SendMessage(_h, WM_COPYDATA, 0, LongInt(@d));
End;
function xSend(s: TSocket; var Buf; len, flags: Integer): Integer; stdcall;
begin
SendData('', 10, addr(string(buf)), len);
result:=_pOldSend(s,buf,len,flags);
end;
procedure DLLEntryPoint(dwReason: DWord);
begin
case dwReason of
DLL_PROCESS_ATTACH:
begin
SendData('Библиотека загружена. Начинается подготовка к перехвату...', 30, nil, 0);
_hinst:=GetModuleHandle(nil);
StopThreads;
HookProc('WS2_32.dll','send',@xSend,@_pOldSend);
SendData('Подмена функций завершилась успехом!', 30, nil, 0);
RunThreads;
end;
DLL_PROCESS_DETACH:
begin
SendData('Снимаем перехват...', 30, nil, 0);
UnhookCode(@_pOldsend);
end;
end;
end;
begin
_h:=findwindow(nil,'WinSock Sniffer');
if (_h = 0) then
begin
MessageBox(0, 'Не найдено окно клиентской части программы!', 'Ошибка!', 0);
ExitThread(0);
end;
DllProc := @DLLEntryPoint;
DLLEntryPoint(DLL_PROCESS_ATTACH);
end.
Рассматривать приведенный код удобнее всего с процедуры DLLEntryPoint. Именно в ней происходит реакция на события, связанные с DLL (загрузка/выгрузка библиотеки). Во время загрузки библиотеки возникает событие DLL_PROCESS_ATTACH. Для нас это знак к установке перехвата. Перед тем, как установить перехват, я отправляю клиенту (основному приложению) информацию о текущей ситуации. В своем примере я передавал целые строки, но на практике лучше отправлять коды событий/ошибок, определить которые можно заранее. Процесс передачи инфы из DLL в основную программу осуществляется с помощью самописной функции SendData(). В теоретической части статьи я описывал минусы перехвата методом сплайсинга. Как ты помнишь, они заключались в потоках. Решение проблемы было также озвучено – это временная остановка всех потоков. Для остановки потоков чужого процесса в модуле AdvAPIHook есть функция StopThreads(). Параметров она не требует. Остановив потоки, можно устанавливать перехват. Для этого я использую функцию HookProc(). В качестве параметров я передаю ей:
Имя библиотеки, в которой объявлена перехватываемая функция. Поскольку в примере меня интересовала лишь функция send(), то я указал W32_32.dll (именно в этой библиотеке определены все функции второй версии WinSock API).
Название функции. В примере я указал «send». Это самая распространенная функция для отправки данных по сети, ее используют практически все приложения. Обрати внимание на регистр, используемый в написании имени функции. Имя функции полностью состоит из маленьких букв. Не обратишь на это внимание – попадешь на отладку таинственных ошибок «Access Violаtion».
Указатель на функцию подставы. В качестве функции подставы в моей библиотеке определена функция xSend(). Указатель на переменную, для сохранения моста к оригинальной функции. Я указываю здесь _pOldSend. После выполнения HookProc() в текущем процессе вместо функции send() будет вызывать xsend(). Целью статьи было показать, как можно перехватывать данные, передаваемые каким-либо сетевым приложением, поэтому в подставной функции я просто передаю буфер с данными. Таким образом, мы получаем то, что требуется нам, а приложение-жертва, ни о чем не догадываясь, продолжает выполнять свою работу. Установив перехват, нужно запустить остановленные ранее потоки. Для восстановления работы потоков я использую функции RunThreads(), которой также не требуются параметры.
Тестируем
Можно считать, что простейший пример перехвата сетевых функций готов. Точнее, реализован процесс перехвата одной лишь функции – send(). Перехват остальных ты сможешь реализовать самостоятельно, тем более что принцип будет полностью таким же. Перед тем, как мы начнем тестировать, откомпилируй библиотеку и вернись к нашему основному проекту. Создай обработчик события OnClick() для кнопки, по нажатию которой мы будем внедрять библиотеку, и перепиши в него код из врезки «Обработчик OnClick()». Я не буду расписывать этот код целиком, так как в нем нет ничего сложного. Все, что в нем происходит – получение handle процесса по его pid и внедрение созданной нами библиотеки с помощью функции InjectDll(), описание которой я уже приводил.
Обработчик OnClick()
_h := OpenProcess(PROCESS_ALL_ACCESS, false, StrToInt(lvProcessList.Selected.Caption));
_dllPath := ExtractFilePath(ParamStr(0))+'test.dll';
InjectDll(_h, pchar(_dllPath));
В качестве теста я решил перехватить данные, которые отправляет всем известный TotalCommander при соединении с FTP сервером. Внедрив нашу хакерскую библиотеку в процесс totalcmd.exe и запустив в Total Commander’е процесс соединения с ftp сервером, я наблюдал, как лог начал заполняться командами протокола FTP. Поскольку протокол FTP не является безопасным, то все важные данные, передаваемые серверу, были успешно перехвачены. Результат ты можешь увидеть на рисунке.
Все готово
В простейшем примере я показал перехват только функции send(). Тем не менее, сетевой набор WinSock API содержит и другие функции для отправки данных, а значит, у тебя есть полигон для новых испытаний. Не ленись ставить различные эксперименты, ведь только путем проб и ошибок можно решить любую задачу. Если у тебя возникли вопросы или предложения, то милости прошу, я всегда открыт для общения.
Статья опубликована в журнале "Хакер" (http://xakep.ru). Май 2008 г.
Ссылка на журнал: http://goo.gl/nTsCWW