Содержание материала

Code:

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;

 

Добавить комментарий

Не использовать не нормативную лексику.

Просьба писать ваши замечания, наблюдения и все остальное,
что поможет улучшить предоставляемую информацию на этом сайте.

ВСЕ КОММЕНТАРИИ МОДЕРИРУЮТСЯ ВРУЧНУЮ, ТАК ЧТО СПАМИТЬ БЕСПОЛЕЗНО!


Защитный код
Обновить