« Поставить закладку » « Сделать стартовой »

« Форумы » « Блоги » « Статьи » « Новости » « Файлы » « Realcoding IRC » « Site map » « Поиск »


Главная Главная
Анонсы Анонсы
Форумы Форумы
Каталог Каталог
Поиск Поиск
Опросы Опросы
Книжный магазин Книжный магазин
Реклама на сайте
Публикации Публикации
Партнеры Партнеры
Карта Карта сайта
Рассылки Рассылки
RSS экспорт
Настройки Настройки
О нас пишут О нас пишут
Контакты Контакты
Гостевая книга Гостевая книга


ПнВтСрЧтПтСбВс
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
    Популярное
Создание программ для Mac OS X. Часть 1:вступление и Objective-C

Добавление файла на C++

ГЛАВА 5. Классы

Часть VI. Порты и файлы

Часть VII. Ввод и вывод

Google для программистов

Как посылать и считывать данные с COM порта, менять параметры (биты данных, четность...)

Функция AccessResource

Chapter 4. Полезные и бесполезные мелочи

Web браузер как средство доступа к БД




    Архив файлов



    Сообщества

    Документация

    Кто на сайте
Вы не зарегистрированы.
Имя:

Пароль:

Запомнить

Регистрация позволит Вам пользоваться дополнительными сервисами.
Сейчас на сайте:
Гостей: 182
Пользователей: 0

Статьи:: Delphi :: Разные статьи :: Использование Debug API: пример перехвата вызовов функций Win32 API



отправить ссылку другу версия для печати  Обсудить на форуме

Использование Debug API: пример перехвата вызовов функций Win32 API



Использование Debug API: пример перехвата вызовов функций Win32 API

Эх... Благословясь, приступим к написанию статьи во второй раз. Почему "во второй"? Потому что у меня "посыпался" винт. Это просто рок какой-то: пока я пишу программку на заказ, у меня сначала глюкнул FlashFiler, потом упали Винды, потом грохнулся винт. Интересно, что дальше? Сгорит мать? А потом террористы подсунут бомбу, и я вообще взорвусь?! [Енота: смех - смехом, а ведь правда словно сглазил кто...] Итак:

С ЧЕГО ВСЕ НАЧИНАЛОСЬ:
С начала. Мне нужно было написать перехватчик вызовов WinSock. Дабы любая программа могла работать через SOCKS5-проксик. Я посчитал, что перехват вызовов DLL'ки проще, чем судорожные попытки написать драйвер (да и сейчас так считаю). Енота, правда, ехидно улыбалась и говорила "ну-ну", но я-таки справился. SOCKS сниффер еще пишу, но в принципах перехвата уже разобрался :-) [Енота: разобраться-то он действительно разобрался, а соксифиера нет до сих пор...]

КАК ВСЕ БУДЕТ:
Я предпочитаю не писать сухие статьи с кучей теории. Поскольку я люблю читать работающий исходный код, то и здесь будет только исходный код. Все пояснения я буду вставлять прямо в исходник - в виде комментариев. Впрочем, не надейтесь, что вам будет достаточно выдрать отсюда исходник, и он скомпилится. :-) Это не потому, что я специально что-то скрыл, а потому, что я вырезал кучу вспомогательных процедур, которые каждый может написать сам. Если вы, все же, паталогически ленивы - скачайте архив с полными рабочими исходниками. Оттуда точно заработает.

ИСХОДНИКИ:
Наконец-то... начнем.

procedure DoDebugLoop;
{ собственно, это главная процедура перехватчика. большую часть времени он крутится именно в ней }
var
Event: TDebugEvent;
{ стандартная Win32 стректура. для интересующихся:
ЕDebugEvent = record
dwDebugEventCode: DWORD;
// тип пришедшего события
dwProcessId: DWORD;
// Id прерванного процесса
dwThreadId: DWORD;
// Id прерванного потока
case Integer of
0: (Exception: TExceptionDebugInfo);
1: (CreateThread: TCreateThreadDebugInfo);
2: (CreateProcessInfo: TCreateProcessDebugInfo);
3: (ExitThread: TExitThreadDebugInfo);
4: (ExitProcess: TExitThreadDebugInfo);
5: (LoadDll: TLoadDLLDebugInfo);
6: (UnloadDll: TUnloadDLLDebugInfo);
7: (DebugString: TOutputDebugStringInfo);
8: (RipInfo: TRIPInfo);
// эти части смотрите сами - не могу же я все разжевывать! :-)
end;

следует добавить, что Microsoft - ребята странные. Функция GetThreadContext, при помощи которой реализуется пошаговая отладка и просмотр регистров, требует на входе хэндл процесса. а нам дают только его Id. после безуспешных поисков функции типа ConvertThreadIdToHandle [Енота: мечтатель, однако...] я решил, что придется заводить список запущенных потоков. в событии CREATE_THREAD_DEBUG_EVENT нам дают-таки хэндл. придется запоминать все созданные потоки (не забывая их забывать ( сорри :-) в EXIT_THREAD_DEBUG_EVENT). позже Sleepyhead сказал, что я все придумал очень правильно (ай да Кэтмар! ай да сукин сын! простите, классика :-) - так люди и делают. ну он большой, ему виднее :-) }
dwContinueStatus: DWORD;
{ как системе обрабатывать событие в ContinueDebugEvent. обнаружилось, что если это событие - не исключение (EXCEPTION_DEBUG_EVENT), то этот флажок системе "по сараю". а если исключение, то есть два варианта: DBG_CONTINUE - наш "отладчик" успешно обработал все сам, и DBG_EXCEPTION_NOT_HANDLED, что значиит - передать исключение системе на обработку }
CurThread: DWORD;
{ хэндл потока, найденный в нашем списке потоков (см. замечание чуть повыше) }
HProc: DWORD;
{ хэндл процесса, который мы отлаживаем }
Context: TContext;
{ контекст потока. проще говоря - содержание его регистров }
ThreadList: array[0..99] of record Id, Handle: DWORD; end;
{ тот самый пресловутый список потоков, который мы своими ручками будем создавать и поддерживать. в принципе, это должен быть список или динамический массив, ибо количество потоков, которые может создать программа, заранее не известно, но не будем заморачиваться. код-то демонстрационный! }
RetAddr: DWORD;
{ здесь будет храниться адрес возврата из перехваченной API-функции (так, на всякий случай. чтобы вы видели, как и откуда его можно добыть) }
BPAddr: DWORD;
{ в учебных целях мы будем перехватывать только одну функцию. поэтому вместо списка обойдемся просто переменной. здесь будет храниться адрес первого байтика перехваченной функции }
OrigByte: Byte;
{ а здесь будет храниться сам первый байтик }
RestoreBreak: Boolean;
{ флажок, который указывает обработчику события EXCEPTION_SINGLE_STEP надо ли восстанавливать точку останова. весь перехват выглядит так:
нашли стартовый адрес процедуры (это можно сделать просмотром таблицы экспорта у соответствующей DLL-ки. как именно - здесь не пишу. или разбирайтесь сами, или качайте мои исходники - там все есть. не то чтобы мне жалко, но к Debug API это имеет отношение весьма косвенное. опять же, если народ будет очень интересоваться, сделаю статью с quick overview формата PE);
запомнили ее первый байт;
записали вместо первого байта код $CC (это Int3 - DEBUG_EXCEPTION);

по приходу DEBUG_EXCEPTION:
проверили, точно ли мы прервались на адресе нашей точки останова. если нет - не делаем ничего. иначе:
восстановили первый байт;
установили флажок SINGLE_STEP;
установили флажок ResoteBreak;
ожидаем прихода события EXCEPTION_SINGLE_STEP;

по приходу EXCEPTION_SINGLE_STEP:
если установлен флажок RestoreBreak:
вернули на место $CC;
сбросили флажок ResoteBreak; }

ProcessFinished: Boolean;
{ флажок, указывающий, завершился ли отлаживаемый процесс. Sleepyhead говорит, что иногда процесс не завершается корректно (к примеру, отладчик, который отлаживает отладчик, который отлаживает отладчик... [Енота: GNU's not Unix :-)]), поэтому если процесс не завершится сам, мы прибьем его руками }

begin
FillChar(ThreadList, SizeOf(ThreadList), 0);

HProc := 0;
{ хэндл процесса, который будем отлаживать. пока процесс не запущенным считается, соответственно - хэндла нету }
ProcessFinished := True;
{ поскольку процесс не запустился, то он считается завершенным :-) }
BPAddr := 0;
{ точку останова уточним, когда загрузится нужная DLL }
RestoreBreak := False;

repeat
if not WaitForDebugEvent(Event, INFINITE) then break;

{ ожидаем прихода отладочного события. в реальном отладчике здеесь вместо INFINITE лучше задать маленькую константу, ожидать в цикле, там же в цикле организовывать взаимодействие с юзверем. или вообще для интерфейса отдельный поток создать }
dwContinueStatus := DBG_EXCEPTION_NOT_HANDLED;
{ поскольку большинство исключений мы не обрабатываем, то по умолчанию так и говорим системе }
CurThread := GetThreadHandleFromList(ThreadList, Event.dwThreadId);
{ просто поиск в массиве ThreadList. Id нам известен, ищем хэндл }
case Event.dwDebugEventCode of
{ проверим - а что, собственно случилось? }
CREATE_PROCESS_DEBUG_EVENT:

{ запустился новый процесс. запомним его хэндл, и сбросим флажок ProcessFinished }
begin
HProc := Event.CreateProcessInfo.HProcess;
ProcessFinished := False;
AddThreadToList(ThreadList, Event.dwThreadId, Event.CreateProcessInfo.hThread);
end;
EXIT_PROCESS_DEBUG_EVENT:

{ процесс завершился - значит, можно смело закрывать наш перехватчик. заодно установим флажок ProcessFinished }
begin
ProcessFinished := True;
ContinueDebugEvent(Event.dwProcessId, Event.dwThreadId, DBG_CONTINUE);

{ это на всякий случай - чтобы ось точно прибила и процесс, и отладчик. в принципе, оно не надо, но смотри выше комментарий к ProcessFinished }
break; { все, из цикла отладки можно смело выходить }
end;

CREATE_THREAD_DEBUG_EVENT:
{ процесс запустил новый поток. здесь у нас есть единственная возможность запомнить его хэндл. так и делаем }
AddThreadToList(ThreadList, Event.dwThreadId, Event.CreateThread.hThread);
EXIT_THREAD_DEBUG_EVENT:

{ процесс завершил исполнение потока. забудем его хэндл }
DeleteThreadFromList(ThreadList, Event.dwThreadId);

LOAD_DLL_DEBUG_EVENT:
{ процесс загрузил какую-то DLL'ку. проверим, не та ли это, которая нам нужна. если та, установим точку останова. текст процедуры смотрите ниже }
ProcessDLLExport(HProc, DWORD(Event.LoadDll.lpBaseOfDll));
UNLOAD_DLL_DEBUG_EVENT:

{ процесс выгрузил какую-то DLL'ку. по-правилам, это надо бы обработать, но поскольку я перехватываю вызовы kernel32.dll, который всегда (за очень-очень редким исключением :-) линкуется статически, то это событие я просто игнорирую. а вообще-то надо запомнить адрес загрузки нужной нам DLL в LOAD_DLL_DEBUG_EVENT (ибо это единственный способ идентифицировать DLL'ку), а здесь проверять - не наша ли это. если наша - обнулить BPAddr. можете дописать сами - как любят говорить авторы книг: "в качестве упражнения" :-) [Енота: ага. а сам, когда видит в книге эту фразу, разражается потоком нецензурной лексики :-)] }
WriteLn('unloading DLL: ', IntToHex(DWORD(Event.UnloadDll.lpBaseOfDll), 8));

EXCEPTION_DEBUG_EVENT:
{ какое-то исключение. проверим поточнее... }
case Event.Exception.ExceptionRecord.ExceptionCode of
EXCEPTION_BREAKPOINT:

{ это - точка останова. здесь мы уточним: наша или нет. дело в том, что система сама генерирует это событие, когда процесс загрузился, но перед тем, как он запущен (полсе того, как системный загрузчик загрузил процесс и все его DLL'ки. как раз перед тем, как исполнить первую инструкцию процесса). плюс - мало ли, какой код внутри исследуемого процесса может быть? так что... }
begin
dwContinueStatus := DBG_CONTINUE;

{ скажем системе, что это исключение мы обработали сами, пусть не напрягается }
Context.ContextFlags := CONTEXT_CONTROL or CONTEXT_INTEGER or CONTEXT_SEGMENTS;
GetThreadContext(CurThread, Context);

{ получили контекст прерванного потока. больше всего нас интересуют IP и Flags. остальные регистры запросили просто для полноты картины }
if (BPAddr <> 0) and (Context.EIP = BPAddr + 1) then
begin
{ если мы уже установили нашу точку останова и прервались именно на ней... }
RetAddr := ReadProcessLong(HProc, Context.ESP);
{ то получим адрес возврата из перехваченной нами функции. он нам не нужен, на самом-то деле, это просто пример - откуда его брать. если вам нужны параметры - ReadProcessLong(HProc, Context.ESP + 4) будет первым, ...+ 8) - вторым, и так далее... кстати, ReadProcessLong - просто обертка для системной функции ReadProcessMemory. читает 4 байтика. для удобства. думаю, что у вас не будет проблем сделать себе такую же :-) }
WriteLn('Return address: 0x', IntToHex(RetAddr, 8));
{ дальше - уменьшим IP на еденичку (чтобы исполнить ту инструкцию, которую мы заменили на нашу точку останова)... реально, EIP-1 хранится в BPAddr. так и запишем... }
Context.EIP := BPAddr;
{ ...и восстановим оригинальный первый байтик этой инструкции }
WriteProcessByte(HProc, BPAddr, OrigByte);
{ установим флажок для того, чтобы система генерировала событие EXCEPTION_SINGLE_STEP. в этом событии надо будет вернуть точку останова на место, иначе перехват состоится ровно один раз :-) [Енота: а то бы читатель сам не догадался...] }
RestoreBreak := True;
Context.EFlags := Context.EFlags or EFLAGS_TRACE;

{ вышеприведенной инструкцией мы сообщаем системе, что хотим получать по событию (EXCEPTION_SINGLE_STEP) после каждой исполненной в отлаживаемом процессе машинной команды. кстати, значение константы EFLAGS_TRACE = $100 }
Context.ContextFlags := CONTEXT_CONTROL;
SetThreadContext(CurThread, Context);

{ установим новое значение регистров потока }

end;
end;
EXCEPTION_SINGLE_STEP:

{ выполнена одна машинная команда. скорее всего, возниконовение этого события - результат выполнения нашей точки останова, но кто знает? проверим флажки. если надо - восстановим точку останова }
begin
dwContinueStatus := DBG_CONTINUE;

{ скажем системе, что это исключение мы обработали сами, пусть не напрягается }
Context.ContextFlags := CONTEXT_CONTROL;
GetThreadContext(CurThread, Context);
if RestoreBreak and (Context.EIP >= BPAddr) and (Context.EIP <= BPAddr + 32) then
begin

{ это действительно "наше" событие. восстановим точку останова, чтобы перехватчик работал и дальше }
OrigByte := WriteInt3(HProc, BPAddr);
RestoreBreak := False;

Context.EFlags := Context.EFlags and not EFLAGS_TRACE;
{ сбросим флажок трассировки, ибо больше это событие нам не надо }
end
else
if RestoreBreak then
Context.EFlags := Context.EFlags or EFLAGS_TRACE;

{ вернем флажок трассировки, если событие не наше - нам ведь надо нашего дождаться. у меня система сама скидывает сей флаг, так что на всякий случай... }

Context.ContextFlags := CONTEXT_CONTROL;
SetThreadContext(CurThread, Context);
end;
end;
end;

if not ContinueDebugEvent(Event.dwProcessId, Event.dwThreadId, dwContinueStatus) then break;
{ все. смело позволяем отлаживаемому процессу исполняться дальше }
until False;
{ сюда мы попадем только при каком-нибудь сбое или завершении процесса. на всякий случай (по совету SleepyHead'а) проверим: а точно наш отлаживаемый процесс завершился? если нет - прибьем руками }
if not ProcessFinished then
begin
repeat
TerminateProcess(HProc, RetAddr);
if not WaitForDebugEvent(Event, INFINITE) then break;
if (Event.dwDebugEventCode = EXIT_PROCESS_DEBUG_EVENT) then break;
if not ContinueDebugEvent(Event.dwProcessId, Event.dwThreadId, DBG_CONTINUE) then break;
until False;
ContinueDebugEvent(Event.dwProcessId, Event.dwThreadId, DBG_CONTINUE);
end;
{ все. закончили :-) }
end;

{ а вот процедурка, которая устанавливает точку останова }
procedure ProcessDLLExport(PrcH, Base: DWORD);
var
DLLName: string;
ExpTbl: TExportHeader;
N: DWORD;
begin
if (BPAddr <> 0) then exit;
{ если уже установлена - не делать ничего }
if not FindExportTable(PrcH, Base, ExpTbl) then exit;
{ если не смогли найти в DLL'ке таблицу экспорта (мало ли...) - тоже ничего не делать }
DLLName := ANSILowerCase(GetASCIIZString(PrcH, ExpTbl.NameRVA + Base));
{ получили имя DLL'ки }
if (DLLName <> 'kernel32.dll') then exit;
{ не наша? если да - снова не делаем ничего }
N := FindExportIndexByName(PrcH, Base, 'AllocConsole', ExpTbl);
N := FindExportByIndex(PrcH, Base, N, ExpTbl);
{ нашли по таблице экспорта точку входа (если не нашли - опять же ничего делать не надо }
if (N = 0) then exit;
{ а если нашли - запомним необходимую информацию и установим останов }
BPAddr := N;
OrigByte := WriteInt3(PrcH, N);

{ WriteInt3 просто возвращает в качестве результата старый байтик, и на его место записывает код $CC - инструкция Int3. когда система встречает эту инструкцию, она генерирует исключение EXCEPTION_BREAKPOINT }
end;

Все. Не так страшен черт, как его малюют [Енота: или: не так страшен Гейтс... :-)]. Остались мелочи.
Если вы запускаете процесс сами, не забудьте указать в CreateProcess флажок DEBUG_ONLY_THIS_PROCESS, чтобы отладчик мог работать, и чтобы процессы, которые может запустить отлаживаемая программа не отлаживались нами (а зачем нам дочерние процессы? если хотим перехватывать вызовы и в них, проще будет ловить непосредственно CreateProcess, и для каждого "новорожденного" запускать свою копию отладчика. Тем более, что если мы присоединяемся к уже запущенному процессу, то система по умолчанию ставит флажок DEBUG_ONLY_THIS_PROCESS. Так что перехватывать CreateProcess надежнее).
Если же вы хотите присоединиться к уже запущенному процессу, то узнайте его Id (с помощью TaskManager в NT или программно), и смело пишите DebugActiveProcess(ProcessId). В дальнейшем никаких различий между работой с процессом, запущенным нами и процессом, к которому мы присоединились "на лету" уже нет.
И еще: учтите, что если наш отладчик завершится, то система автоматически прибьет и процесс, который мы имели счастье отлаживать. Способа "отсоединиться" от процесса нет: взялся за гуж, не говори, что не дюж. :-)
Также замечу, что полезно обрабатывать возможные ошибки при вызове системных функций. Здесь я их - в основном - смело игнорирую, но вам бы лучше так не поступать.

Полные рабочие исходники можно взять с нашего сайта: http://www.piranha-home.org./Если кто-то поможет в деле перевода статьи на английский - буду очень благодарен.

Со всеми замечаниями смело обращайтесь на keith@piranha-home.org. Также буду не прочь узнать - помог ли я вам. Просто напишите письмецо, если вам понравилась (и даже если не понравилась) статейка. Если вы скажете пару добрых слов Еноте, будет совсем здорово. :-) [Енота: как трогательно... :-)]

зыж (P.S. по-аглицки :-)
Если вдруг нечаянно вкрались ошибки - простите. Пишу в WordPad'е, так что проверки правописания нет. :-) А ставить Word специально для проверок... Да ну его в колодец!
 




Рубрика: Разные статьи




Вышел MySQL 5.1.30, первый стабильный рели....

MySQL

После публикации 29 тестовых версий анонсирован первый стабильный релиз MySQL 5.1, пригодный для промышленной эксплуатации и обеспечивающий увеличение производительности для "тяжелых" SQL запросов, по сравнению с MySQL 5.0, примерно на 15-20%. Главные новшества появившиеся в MySQL 5.1:


Подробнее... | Рубрика: MySQL | Добавлено: 28.11.2008

Тестирование параллельных программ.

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

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


Подробнее... | Рубрика: Тестирование | Добавлено: 28.11.2008

Архитектура AMD64 (EM64T).

Архитектура AMD

Аннотация. В статье кратко рассматривается архитектура AMD64 компании AMD и ее реализация EM64T компании Intel. Описаны особенности архитектуры, ее возможности, достоинства и недостатки.


Подробнее... | Рубрика: Архитектура AMD | Добавлено: 27.11.2008

Остальные статьи:

Платформа 2009. Определяя будущее
Windows Vista Bridge Sample Library - упра...
Оптимизация 64-битных программ
Подгрузка через AJAX HTML-кода, содержащег...
Обзор нового релиза самой мощной Ajax библ...
Firebug 1.3 и 1.4 alpha — что нового и инт...
Релиз Microsoft Silverlight 2.0. Что новог...
XML документация в C#
Курсоры в MySQL 5
Microsoft опубликовала подробности о сесси...
Microsoft делится подробностями о том, что...
Тестируем новый javascript от нового брауз...
MySQL Query Cache
Использование провайдеров компиляции в As...
Чего мы ждем от C# 4.0
Delphi 2009 и C++Builder 2009
Джоэл Спольски и Джеф Этвуд запустили новы...
Поиск кода Google /* что нового? */
10 jQuery скриптов для улучшения интерфейс...
Генераторы отчетов FastReport 4 и QuickRep...


Цитата дня (все,добавить):

Портал фрилансеров

работа на дому


    Рубрикатор

Программирование

C/С++
Обучение
Windows API
XAML
Моделирование
Паттерны
Visual Basic 7 .NET
WxWidgets
Функции WinApi
Функции С++
Разработка под Mac OS
Eiffel
Visual Studio 2008
UI дизайн
Алгоритмы
Конкурсные статьи
Turbo Pascal
Visual Studio
CASE-средства
Visual Studio 2005
Без VCL
Delphi
Тех. документация
Тестирование
Software Testing
ООП
TCP/IP
Google Android
Windows Installer
.NET Framework
Драйвера
C# C Sharp
Справка
Проектирование
Информ. системы
Visual Basic
Assembler
Оптимизация кода
Gtk+
Компоненты
Реинжиниринг
Управление проектами
Extreeme programming
Lotus Notes
Алгебраическое проектирование


Интернет технологии

PHP
Perl
ASP
WAP
Cookies
SSI
CGI
Web Servers
VB Script
DNS
CSS
XML
Html
Java Script
Java2ME
Firewall
Flash
.htaccess
Apache
VRML
Протоколы
Поисковые системы
Технология JAVA
Учебник по PHP
Учебник по JavaScript
Учебник по XML
Java Q&A
AJAX
DHTML
XHTML
Dreamweaver
Web 2.0
Python
Вебмастеру
Cisco
Ruby on Rails
Silverlight

Базы данных

Access
InterBase
MySQL
Oracle
ADO .NET
Основы SQL
Учебник по Access 2002
MS
Microsoft FoxPro
Доступ к данным
XML в MS SQL Server 2000
ODBC и MyODBC
Обучение
Caché
DB2
PostgresSQL
Sybase
Теория
Хранилища данных
Безопасность
Реляционные данные
MySQL и mSQL

Остальное:

Разное
Обзоры книг
Безопасность
Графика и дизайн
Юмор
Linux
Фракталы
Microsoft Axapta
Многоядерность
Сети
Microsoft Office
Работа
MS-DOS
Криптография
Графика и игроделание
Новости SDK
Системы защиты
Учебник по AutoCad
CVS
Windows XP
Windows Server 2003
Windows Vista
Windows 7
Мероприятия