Правильное применение сплайсинга при перехвате функций подготовленных к HotPatch

от автора

В прошлой статье я рассмотрел пять вариантов перехвата функций включая их вариации.

Правда в ней я оставил не рассмотренными две неприятных ситуации:
1. Вызов перехваченной функции в тот момент, когда ловушка снята.
2. Одновременный вызов перехваченной функции из двух разных нитей.

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

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

В этой статье более подробно будет рассмотрен сплайсинг точки входа функции подготовленной к HopPatch, т.к. данные функции предоставляют нам способ ухода от вышеперечисленных ошибок.

Перехват сплайсингом через JMP NEAR OFFSET или PUSH ADDR + RET (наиболее уязвимый к данным ошибкам) рассмотрен не будет, т.к. по хорошему, без реализации дизассемблера длин, заставить данный вариант перехвата работать как нужно не получится.

1. Реализуем приложение перехватывающее вызов CreateWindowExW

Для начала подготовим приложение, которое наглядно покажет нам потерю данных при перехвате API из-за того, что вызов перехваченной функции может происходить в тот момент, когда перехват с нее снят.

Создайте новый проект и разместите на главной форме три элемента: TMemo, TOpenDialog и TButton.

Суть приложения: при нажатии кнопки будет устанавливаться перехват на функцию CreateWindowExW и отображаться диалог. После закрытия диалога в TMemo будет выводится информация о всех созданных диалогом окнах.

Для этого нам потребуется часть кода из предыдущей статьи, а именно:

1. Декларация типов и констант для перехвата:

const   LOCK_JMP_OPKODE: Word = $F9EB;   JMP_OPKODE: Word = $E9;   type   // структура для обычного сплайса через JMP NEAR OFFSET   TNearJmpSpliceRec = packed record     JmpOpcode: Byte;     Offset: DWORD;   end;       THotPachSpliceData = packed record     FuncAddr: FARPROC;     SpliceRec: TNearJmpSpliceRec;     LockJmp: Word;   end;   const   NearJmpSpliceRecSize = SizeOf(TNearJmpSpliceRec);   LockJmpOpcodeSize = SizeOf(Word); 

2. Процедуры записи NEAR JMP и атомарной записи SHORT JMP

// процедура пищет новый блок данных по адресу функции procedure SpliceNearJmp(FuncAddr: Pointer; NewData: TNearJmpSpliceRec); var   OldProtect: DWORD; begin   VirtualProtect(FuncAddr, NearJmpSpliceRecSize,     PAGE_EXECUTE_READWRITE, OldProtect);   try     Move(NewData, FuncAddr^, NearJmpSpliceRecSize);   finally     VirtualProtect(FuncAddr, NearJmpSpliceRecSize,       OldProtect, OldProtect);   end; end;   // процедура атомарно изменяет два байта по переданному адресу procedure SpliceLockJmp(FuncAddr: Pointer; NewData: Word); var   OldProtect: DWORD; begin   VirtualProtect(FuncAddr, LockJmpOpcodeSize, PAGE_EXECUTE_READWRITE, OldProtect);   try     asm       mov  ax, NewData       mov  ecx, FuncAddr       lock xchg word ptr [ecx], ax     end;   finally     VirtualProtect(FuncAddr, LockJmpOpcodeSize, OldProtect, OldProtect);   end; end; 

3. Нeмного модифицированная процедура инициализация структуры THotPachSpliceData

// процедура инициализирует структуру для установки перехвата procedure InitHotPatchSpliceRec(const LibraryName, FunctionName: string;   InterceptHandler: Pointer; out HotPathSpliceRec: THotPachSpliceData); begin   // запоминаем оригинальный адрес перехватываемой функции   HotPathSpliceRec.FuncAddr :=     GetProcAddress(GetModuleHandle(PChar(LibraryName)), PChar(FunctionName));   // читаем два байта с ее начала, их мы будем перезатирать   Move(HotPathSpliceRec.FuncAddr^, HotPathSpliceRec.LockJmp, LockJmpOpcodeSize);   // инициализируем опкод JMP NEAR   HotPathSpliceRec.SpliceRec.JmpOpcode := JMP_OPKODE;   // рассчитываем адрес прыжка (поправка на NearJmpSpliceRecSize не нужна,   // т.к. адрес находится уже со смещением)   HotPathSpliceRec.SpliceRec.Offset :=     PAnsiChar(InterceptHandler) - PAnsiChar(HotPathSpliceRec.FuncAddr); end; 

Весь этот код разместим в отдельном модуле SpliceHelper, он нам потребуется в следующих главах.

Теперь перейдем к главной форме, нам потребуются две глобальных переменных:

var   HotPathSpliceRec: THotPachSpliceData;   WindowList: TStringList; 

В переменной HotPathSpliceRec будет содержаться информация о перехватчике. Вторая будет содержать в себе список созданных окон.

В конструкторе формы произведем инициализацию структуры THotPachSpliceData.

procedure TForm1.FormCreate(Sender: TObject); begin   // инициализируем структуру для перехватчика   InitHotPatchSpliceRec(user32, 'CreateWindowExW',     @InterceptedCreateWindowExW, HotPathSpliceRec);   // пишем прыжок в область NOP-ов   SpliceNearJmp(PAnsiChar(HotPathSpliceRec.FuncAddr) - NearJmpSpliceRecSize,     HotPathSpliceRec.SpliceRec); end; 

Создадим функцию перехватчик, вызываемую вместо оригинальной функции.

function InterceptedCreateWindowExW(dwExStyle: DWORD; lpClassName: PWideChar;   lpWindowName: PWideChar; dwStyle: DWORD; X, Y, nWidth, nHeight: Integer;   hWndParent: HWND; hMenu: HMENU; hInstance: HINST; lpParam: Pointer): HWND; stdcall; var   S: string;   Index: Integer; begin   // снимаем перехват   SpliceLockJmp(HotPathSpliceRec.FuncAddr, HotPathSpliceRec.LockJmp);   try       // запоминаем информацию о созданном окне     Index := -1;     if not IsBadReadPtr(lpClassName, 1) then     begin       S := 'ClassName: ' + string(lpClassName);       S := IntToStr(WindowList.Count + 1) + ': ' + S;       Index := WindowList.Add(S);     end;       // вызываем оригинальную функцию     Result := CreateWindowExW(dwExStyle, lpClassName, lpWindowName, dwStyle,       X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);       // добавляем информацию о вызове в список     if Index >= 0 then     begin       S := S + ', handle: ' + IntToStr(Result);       WindowList[Index] := S;     end;         finally     // восстанавливаем перехват     SpliceLockJmp(HotPathSpliceRec.FuncAddr, LOCK_JMP_OPKODE);   end; end; 

И осталось в завершение реализовать обработчик кнопки.

procedure TForm1.Button1Click(Sender: TObject); begin   // перехватываем CreateWindowExW   SpliceLockJmp(HotPathSpliceRec.FuncAddr, LOCK_JMP_OPKODE);   try     // Создаем список в котором будет хранится информация о созданных окнах     WindowList := TStringList.Create;     try       // открываем диалог       OpenDialog1.Execute;       // по завершении отображаем полученный список       Memo1.Lines.Text := WindowList.Text;     finally       WindowList.Free;     end;   finally     // снимаем перехват     SpliceLockJmp(HotPathSpliceRec.FuncAddr, HotPathSpliceRec.LockJmp);   end; end; 

Все готово, можно запускать программу на исполнение.

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

Запустите программу, нажмите кнопку и закройте диалог нажатием кнопки «Отмена», должно получиться так:

image

Таким образом мы выяснили, что при открытии обычного TOpenDialog создается 14 окон различных классов.

Теперь давайте выясним, на самом ли деле это так.

2. Создаем вспомогательную утилиту для просмотра дерева окон приложения.

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

Можно конечно воспользоваться сторонними программами, наподобие Spy++ но мы же программисты, что нам стоит реализовать ее самостоятельно, тем более и время на реализацию копеечное.

Создайте новый проект и поместите на главной форме TTreeView после чего реализуйте следующий код:

type   TdlgWindowTree = class(TForm)     WindowTreeView: TTreeView;     procedure FormCreate(Sender: TObject);   private     procedure Sys_Windows_Tree(Node: TTreeNode;       AHandle: HWND; ALevel: Integer);   end;   ...   procedure TdlgWindowTree.FormCreate(Sender: TObject); begin   Sys_Windows_Tree(nil, GetDesktopWindow, 0); end;   procedure TdlgWindowTree.Sys_Windows_Tree(Node: TTreeNode;   AHandle: HWND; ALevel: Integer); type   TRootNodeData = record     Node: TTreeNode;     PID: Cardinal;   end; var   szClassName, szCaption, szLayoutName: array[0..MAXCHAR - 1] of Char;   szFileName : array[0..MAX_PATH - 1] of Char;   Result: String;   PID, TID: Cardinal;   I: Integer;   RootItems: array of TRootNodeData;   IsNew: Boolean; begin   //Запускаем цикл пока не закончатся окна   while AHandle <> 0 do   begin     //Получаем имя класса окна     GetClassName(AHandle, szClassName, MAXCHAR);     //Получаем текст (Его Caption) окна     GetWindowText(AHandle, szCaption, MAXCHAR);     // Получаем имя модуля     if GetWindowModuleFilename(AHandle, szFileName, SizeOf(szFileName)) = 0 then       FillChar(szFileName, 256, #0);     TID := GetWindowThreadProcessId(AHandle, PID);       // Раскладка процесса     AttachThreadInput(GetCurrentThreadId, TID, True);     VerLanguageName(GetKeyboardLayout(TID) and $FFFF, szLayoutName, MAXCHAR);     AttachThreadInput(GetCurrentThreadId, TID, False);       // Результат     Result := Format('%s [%s] Caption = %s, Handle = %d, Layout = %s',       [String(szClassName), String(szFileName), String(szCaption),       AHandle, String(szLayoutName)]);       // Смотрим в какое место добавлять окно     if ALevel in [0..1] then     begin       IsNew := True;       for I := 0 to Length(RootItems) - 1 do         if RootItems[I].PID = PID then         begin           Node := RootItems[I].Node;           IsNew := False;           Break;         end;       if IsNew then       begin         SetLength(RootItems, Length(RootItems) + 1);         RootItems[Length(RootItems) - 1].PID := PID;         RootItems[Length(RootItems) - 1].Node :=             WindowTreeView.Items.AddChild(nil, 'PID: ' + IntToStr(PID));         Node := RootItems[Length(RootItems) - 1].Node;       end;     end;       // Пускаем рекурсию     Sys_Windows_Tree(WindowTreeView.Items.AddChild(Node, Result),       GetWindow(AHandle, GW_CHILD), ALevel + 1);       //Получаем хэндл следующего (не дочернего) окна     AHandle := GetNextWindow(AHandle, GW_HWNDNEXT);   end; end; 

Собственно все, можно запускать на выполнение:

image

3. Анализируем результаты

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

Смотрим:

image

Красным выделено окно с классом Auto-Suggest DropDown, давайте посмотрим что оно из себя представляет:

image

А оно оказывается содержит в себе еще 4 окна, два скролбара, ListView, который к тому-же чайлдом держит SysHeader32. А вот это уже интересно. Хэнлы окна в обоих приложениях совпадают, но ни ListView, ни SysHeader32, даже двух скролов в первом приложении мы не видим.

Но, то что мы их не видим в первом списке еще ничего не означает. Создание этих окон происходило в тот момент, когда наш перехватчик был снят, а это могло произойти только по одной причине — по причине того, что вызов CreateWindowExW может привести к рекурсивному вызову самого себя.

Значит нужно реализовать код перехватчика таким образом, чтобы не требовалось снятие и восстановление перехвата.

4. Вызов перехваченной функции без снятия кода перехвата.

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

image

Здесь показано начало функции MessageBoxW. Самой первой инструкцией идет ничего не делающая инструкция MOV EDI, EDI, предваряющаяся пятью инструкциями NOP.

Именно так в большинстве своем и выглядят функции, подготовленные к перехвату посредством HotPatch, в том числе и перехваченная нами CreateWindowExW.

В случае перехвата функции вместо выделенных семи байт, занятых ничем не делающими инструкциями будет расположен следующий код:

image

Собственно это и есть установленный нами перехватчик.
Вместо инструкции MOV EDI, EDI размещен код JMP -7, передающий управление на предыдущую инструкцию.
Вместо пяти инструкций NOP, расположен прыжок на начало функции перехватчика.

Если мы начнем выполнение не с адреса начала функции CreateWindowExW, а с адреса ее первой полезной инструкции PUSH EBP, то мы не затронем установленный нами перехватчик, а раз так, то и нет смысла его снимать.

В виде кода это выглядит таким образом:

type   TCreateWindowExW = function(dwExStyle: DWORD; lpClassName: PWideChar;     lpWindowName: PWideChar; dwStyle: DWORD; X, Y, nWidth, nHeight: Integer;     hWndParent: HWND; AMenu: HMENU; hInstance: HINST; lpParam: Pointer): HWND; stdcall;   function InterceptedCreateWindowExW(dwExStyle: DWORD; lpClassName: PWideChar;   lpWindowName: PWideChar; dwStyle: DWORD; X, Y, nWidth, nHeight: Integer;   hWndParent: HWND; hMenu: HMENU; hInstance: HINST; lpParam: Pointer): HWND; stdcall; var   S: string;   Index: Integer;   ACreateWindowExW: TCreateWindowExW; begin     // запоминаем информацию о созданном окне   Index := -1;   if not IsBadReadPtr(lpClassName, 1) then   begin     S := 'ClassName: ' + string(lpClassName);     S := IntToStr(WindowList.Count + 1) + ': ' + S;     Index := WindowList.Add(S);   end;     // вызываем оригинальную функцию   @ACreateWindowExW := PAnsiChar(HotPathSpliceRec.FuncAddr) + LockJmpOpcodeSize;   Result := ACreateWindowExW(dwExStyle, lpClassName, lpWindowName, dwStyle,     X, Y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);     // добавляем информацию о вызове в список   if Index >= 0 then   begin     S := S + ', handle: ' + IntToStr(Result);     WindowList[Index] := S;   end;   end; 

Рассчитав адрес первой полезной инструкции, равный смещению от начала функции на два байта, мы запоминаем его во временной переменной ACreateWindowExW, после чего вызываем функцию привычным нам образом.

Давайте посмотрим что получится в этом случае, вот это мы ожидаем:

image

И именно это мы и находим в выдаваемом нам списке:

image

Ну вот мы и нашли наших «потеряшек», все таки 26 окон создается при вызове TOpenDialog, а не 14.

Все дело было в пресловутом рекурсивном вызове, который можно увидеть в стеке вызова процедур, если установить брякпойнт в начале функции InterceptedCreateWindowExW.

image

5. Ошибка при вызове перехватываемой функции из разных нитей.

С этой ошибкой то же все просто. Если постоянно снимать и восстанавливать перехватчик функции, то в какой-то момент нам будет выдана ошибка в функции SpliceLockJmp на инструкции «lock xchg word ptr [ecx], ax». Дело в том что в этот момент может завершиться операция возвращения атрибутов страницы по адресу перехватчика из другой нити и, не смотря на то, что мы в своей нити разрешили запись по данному адресу, реальные атрибуты страницы будут совершенно другими.

Именно с таким поведением столкнулся автор этой ветки: перехват recv.

Решать данную ошибку нужно таким-же способом как показано выше.
Правда при этом нужно не забывать и об обработчике перехвата, он тоже должен быть ThreadSafe, но реализация обработчика остается на ваше усмотрение.

6. Всегда ли можно пропустить первые два байта перехватываемой функции?

Интересный вопрос и ответ на него — нет, не всегда.
Когда функции подготавливаются к перехвату по методу HotPatch, Microsoft гарантирует только то, что перед ними всегда будет пять инструкций NOP и каждая такая функция будет начинаться с двухбайтовой инструкции. Больше нам ничего не гарантируется.

Если рассмотреть код MessageBoxW или CreateWindowExW, то можно увидеть что первая их полезная инструкция PUSH EBP занимает один байт. Таким образом, раз она не удовлетворяет условиям, тело данной функции предваряется пустым вызовом MOV EDI, EDI. Тоже будет верно и для функций начинающихся с инструкций длиной три и более байт. Однако, если функция начинается с двухбайтовый инструкции, не имеет смысла раздувать ее тело пустой заглушкой, ведь все условия для HotPatch соблюдены (пять NOP и 2 байта).

В этом случае, если мы применим способ описанный выше, ничего кроме ошибки нам увидеть не удастся.

Пример такой функции — RtlCreateUnicodeString.
Она начинается с полезной инструкции PUSH $0C.

image

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

Стало быть перед нами встала задача — обеспечить вызов затертой инструкции и обеспечить работоспособность функции даже с установленным кодом перехвата:

image

В принципе машинный код затертой инструкции у нас есть и хранится в структуре HotPathSpliceRec.LockJmp, но вызвать напрямую мы ее не можем по нескольким причинам.

Ну во первых данная структура расположена в куче (ну точнее не в куче, а в выделенной памяти, т.к. Delphi не работает с механизмом Heap напрямую) у которой нет атрибутов исполнения, т.е. если мы каким-то образом выполним CALL по адресу HotPathSpliceRec.LockJmp то получим ошибку.

Можно конечно выставить правильные атрибуты страницы, но это слишком топорно, все-же исполняемый код не должен перемешиваться с областью данных.

Во вторых даже если мы и передадим выполнение на эту инструкцию, мы должны после нее заставить выполнится инструкцию JMP на правильный адрес (в данном случае это будет $77B062FB, см. предыдущую картинку) с учетом оффсета вызываемой инструкции.

В третьих, помимо вызова, мы должны разместить на стеке в правильном порядке параметры, передаваемые вызываемой функции, что как минимум приведет нас к необходимости использования асм вставок.

Попробуем решить все по порядку.

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

Т.е. грубо пишем перехватчик таким образом:

function TrampolineRtlCreateUnicodeString(DestinationString: PUNICODE_STRING;   SourceString: PWideChar): Integer; stdcall; begin   asm     db $90, $90, $90, $90, $90, $90, $90   end; end;   function InterceptedRtlCreateUnicodeString(DestinationString: PUNICODE_STRING;   SourceString: PWideChar): Integer; stdcall; begin   Result := TrampolineRtlCreateUnicodeString(DestinationString, SourceString);   ShowMessage(DestinationString^.Buffer); end; 

В данном случае перехватчик будет заниматься вызовом трамплина и логированием.

Внутри функции-трамплина зарезервировано 7 байт, что как раз хватит нам для записи двухбайтовой затертой инструкции и пятибайтовой NEAR JMP.
Сама функция расположена в области кода, и с ее вызовом затруднений возникнуть не должно.

А теперь важный нюанс.
Если писать эти 7 байт на место зарезервированного блока, то мы столкнемся с одной неприятной особенностью Delphi. Дело в том что компилятор Delphi практически всегда генерирует для функций пролог и эпилог.

К примеру допустим после патча код нашей функции стал выглядеть таким образом:

function TrampolineRtlCreateUnicodeString(DestinationString: PUNICODE_STRING;   SourceString: PWideChar): Integer; stdcall; begin   asm     push $0C        // выполняем затертый параметр     jmp $77B062FB   // делаем прыжок на правильную инструкцию   end; end; 

В действительности он превратится в следующее:

image

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

Этого нам абсолютно не нужно, поэтому мы поступим проще, а именно код трамплина будет писаться прямо с начала данной функции, перезатирая инструкции пролога функции.

Ну а ведь в действительности, данные инструкции нам абсолютно не нужны, т.к. после прыжка в тело перехваченной функции и ее выполнения, управление вернется не в искореженную нашими действиями функцию-трамплин, а непосредственно в то место, откуда производился ее вызов, т.е. в функцию — обработчик перехвата.

Таким образом реализуем инициализацию перехватчика следующим способом:

// процедура инициализирует структуру для установки перехвата и подготавливает трамплин для вызова procedure InitHotPatchSpliceRecEx(const LibraryName, FunctionName: string;   InterceptHandler, Trampoline: Pointer; out HotPathSpliceRec: THotPachSpliceData); var   OldProtect: DWORD;   TrampolineSplice: TNearJmpSpliceRec; begin   // запоминаем оригинальный адрес перехватываемой функции   HotPathSpliceRec.FuncAddr :=     GetProcAddress(GetModuleHandle(PChar(LibraryName)), PChar(FunctionName));   // читаем два байта с ее начала, их мы будем перезатирать   Move(HotPathSpliceRec.FuncAddr^, HotPathSpliceRec.LockJmp, LockJmpOpcodeSize);     // Подготавливаем трамплин   VirtualProtect(Trampoline, LockJmpOpcodeSize + NearJmpSpliceRecSize,     PAGE_EXECUTE_READWRITE, OldProtect);   try     Move(HotPathSpliceRec.LockJmp, Trampoline^, LockJmpOpcodeSize);     TrampolineSplice.JmpOpcode := JMP_OPKODE;     TrampolineSplice.Offset := PAnsiChar(HotPathSpliceRec.FuncAddr) -       PAnsiChar(Trampoline) - NearJmpSpliceRecSize;     Trampoline := PAnsiChar(Trampoline) + LockJmpOpcodeSize;     Move(TrampolineSplice, Trampoline^, SizeOf(TNearJmpSpliceRec));   finally     VirtualProtect(Trampoline, LockJmpOpcodeSize + NearJmpSpliceRecSize,       OldProtect, OldProtect);   end;     // инициализируем опкод JMP NEAR   HotPathSpliceRec.SpliceRec.JmpOpcode := JMP_OPKODE;   // рассчитываем адрес прыжка (поправка на NearJmpSpliceRecSize не нужна,   // т.к. адрес находится уже со смещением)   HotPathSpliceRec.SpliceRec.Offset :=     PAnsiChar(InterceptHandler) - PAnsiChar(HotPathSpliceRec.FuncAddr); end; 

Сама инициализация и вызов перехваченной функции выглядит следующим образом:

type   UNICODE_STRING = record     Length: WORD;     MaximumLength: WORD;     Buffer: PWideChar;   end;   PUNICODE_STRING = ^UNICODE_STRING;     function  RtlCreateUnicodeString(DestinationString: PUNICODE_STRING;     SourceString: PWideChar): BOOLEAN; stdcall; external 'ntdll.dll';   ...   procedure TForm2.FormCreate(Sender: TObject); begin   // инициализируем структуру для перехватчика и трамплин   InitHotPatchSpliceRecEx('ntdll.dll', 'RtlCreateUnicodeString',     @InterceptedRtlCreateUnicodeString, @TrampolineRtlCreateUnicodeString,     HotPathSpliceRec);   // пишем прыжок в область NOP-ов   SpliceNearJmp(PAnsiChar(HotPathSpliceRec.FuncAddr) - NearJmpSpliceRecSize,     HotPathSpliceRec.SpliceRec); end;   procedure TForm2.Button1Click(Sender: TObject); var   US: UNICODE_STRING; begin   // перехватываем RtlCreateUnicodeString   SpliceLockJmp(HotPathSpliceRec.FuncAddr, LOCK_JMP_OPKODE);   try     RtlCreateUnicodeString(@US, 'Test UNICODE String');   finally     // снимаем перехват     SpliceLockJmp(HotPathSpliceRec.FuncAddr, HotPathSpliceRec.LockJmp);   end; end; 

Теперь можно нажать на кнопку и увидеть результат перехвата в виде сообщения.

В качестве заключения

В итоге вариант реализации сплайсинга, показанный в шестой главе, является наиболее универсальным в случае перехвата функций, подготовленных к HotPatch-у. Он будет работать корректно и в случае заглушки MOV EDI, EDI и в случае наличия полезной инструкции в начале перехватываемой функции. Он не подвержен ошибкам, описанным в самом начале статьи, но правда перехватить обычные функции при помощи данного алгоритма не получится, впрочем об этом я уже писал ранее.

Извиняюсь что приходится дробить информацию на куски и выдавать не все сразу, но как мне посоветовали еще год назад, лучше давать материал малыми порциями, чтобы было время для его переваривания 🙂

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

Исходный код к примерам можно забрать по данной ссылке.

— © Александр (Rouse_) Багель
Май, 2013

ссылка на оригинал статьи http://habrahabr.ru/post/181157/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *