Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: WinAPI и системное программирование > Перехват вызова API под x64


Автор: over 11.7.2013, 18:03
Пытаюсь перехватить вызов MessageBoxW под х64, инструкцию меняю следующим кодом:

Код

  hm := GetModuleHandle('user32.dll');
  pa := GetProcAddress(hm, 'MessageBoxW');
  if not VirtualProtect(pa, SizeOf(Pointer) + 1, PAGE_EXECUTE_READWRITE, OldProtect) then Exit;
  byte(pa^) := $E9;
  DWORD(Pointer(DWORD(pa) + 1)^) :=  DWORD(@NewMessageBoxW) - DWORD(pa) - (SizeOf(Pointer) + 1);


В x32 отрабатывает как нужно (и в своем приложении и через dll)
В x64 работает, но на половину, т.е. в своем приложении все ок, а если перенести этот код в dll и подгрузить ее - ошибка на вызове MessageBoxW

В чем может быть причина? 

Автор: over 11.7.2013, 18:42
Вроде понял в чем причина,

DWORD(@NewMessageBoxW) - DWORD(pa) - (SizeOf(Pointer) + 1) - при использотивании в dll, длина прыжка > 2гб.

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


Автор: Illusion Dolphin 11.7.2013, 19:05
Ошибка как минимум тут:
Код

DWORD(pa) 

и тут
Код

DWORD(@NewMessageBoxW)

Для этого теперь используйте NativeInt/NativeUInt. По моему опыту такого править надо много где smile В инете все  примеры кривые.

Автор: over 11.7.2013, 19:11
пробовал и с NativeInt, но не помогло (

Автор: Illusion Dolphin 11.7.2013, 19:20
Цитата

пробовал и с NativeInt, но не помогло ( 

Значит ошибка в 17й строчке

Автор: over 11.7.2013, 21:09
тут видимо проблема в длине прыжка, которая в некоторых случаях больше 2gb

Автор: Illusion Dolphin 11.7.2013, 22:11
Цитата

тут видимо проблема в длине прыжка, которая в некоторых случаях больше 2gb 

Проблема в том, что длинна указателя - 8 байт вместо 4 под x86 и всё. Проверяйте вычисления, убирая Cardinal, Integer, DWORD и другую глупость. 

Автор: Illusion Dolphin 12.7.2013, 10:30
Кстати, я в своём коде (работает под 32/64) не заметил установки опкодов $E9 (JMP), возможно, что стоит кроме того пересмотреть алгоритм сплайсинга.

Добавлено через 6 минут и 46 секунд
И не забывайте восстанавливать старые права через VirtualProtect и FlushInstructionCache рекомендуется также. 

Автор: over 12.7.2013, 11:15
Уже всю голову сломал, как только не пробовал.. наверное что то упускаю.. Если не сложно, приведите рабочий участок кода перехвата.

Автор: Illusion Dolphin 12.7.2013, 12:31
Вот такой, с вас пиво  smile :
Код

function GetActualAddr(Proc: Pointer): Pointer;
type
  PAbsoluteIndirectJmp = ^TAbsoluteIndirectJmp;
  TAbsoluteIndirectJmp = packed record
    OpCode: Word;   //$FF25(Jmp, FF /4)
    Addr: Cardinal;
  end;
var J: PAbsoluteIndirectJmp;
    P: PPointer;
begin
  J := PAbsoluteIndirectJmp(Proc);
  if (J.OpCode = $25FF) then
  begin
    {$ifdef Win32}
    P := PPointer(J.Addr);
    {$endif}
    {$ifdef Win64}
    P := PPointer(TNativeUInt(Proc) + J.Addr + 6{Instruction Size});
    {$endif}
    if IsBadReadPtr(P, SizeOf(NativeUInt)) then
      Exit(nil);
    Result := P^;
  end else
    Result := Proc;
end;

function HookCode(PEModule: HModule; Recursive: Boolean; TargetAddress, NewAddress: Pointer; var OldAddress: Pointer): Integer;
var
  HookedModules: string;

  function HookModule(ImageDosHeader: PImageDosHeader; TargetAddress, NewAddress: Pointer; var OldAddress: Pointer): Integer;
  var
    Address: Pointer;
    ImportCode: ^Pointer;
    BytesWritten: NativeUInt;
    ImageNTHeaders: PImageNTHeaders;
    ImageImportEntry: ^TImageImportEntry;
    Module: string;
    OldProtect: Cardinal;
    ModuleHandle: HModule;
  begin
    Result := 0;

    if OldAddress = nil then
      OldAddress := GetActualAddr(TargetAddress);

    //check the header and see if there is one, if there isn't then exit hook routine
    If ImageDosHeader.e_magic <> IMAGE_DOS_SIGNATURE then Exit;
    //Loads the API headers into ImageNTHeaders
    ImageNTHeaders := Pointer(NativeInt(ImageDosHeader) + ImageDosHeader._lfanew);

    //checks if there are API header? (I think)
    If ImageNTHeaders^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress = 0 then Exit;
    //Gets just the API header addresses? (I think)
    ImageImportEntry := Pointer(NativeUInt(ImageDosHeader) + ImageNTHeaders^.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

    //Loops through each API header looking for the name you specified ready to patch!
    while ImageImportEntry^.Name <> 0 do
    begin
      //If its found then go into
      Module := string(AnsiString(PAnsiChar(NativeUInt(ImageDosHeader) + ImageImportEntry^.Name)));

      if Recursive then
        if Pos(LowerCase(Module), HookedModules) = 0 then
        begin
          //Writes the redirection of API
          HookedModules := HookedModules + LowerCase(Module);
          ModuleHandle := GetModuleHandle(PWideChar(Module));
          if (ModuleHandle > 0) and (ImageDosHeader <> Pointer(ModuleHandle)) then
            HookModule(Pointer(ModuleHandle), TargetAddress, NewAddress, OldAddress);
        end;

      //Sets the Address of the Table?
      ImportCode := Pointer(NativeUInt(ImageDosHeader) + ImageImportEntry.LookupTable);

      //TODO: remove magic constant
      while (ImportCode^ <> nil) and (NativeUInt(ImportCode^) > 4) do
      begin
        Address := ImportCode^;

        if Address <> OldAddress then
          Address := GetActualAddr(Address);

        //checks address and writes our one!
        if Address = OldAddress then
        begin
          if VirtualProtect(ImportCode, SizeOf(Pointer), PAGE_EXECUTE_READWRITE, OldProtect) then
          begin
            if WriteProcessMemory(GetCurrentProcess, ImportCode, @NewAddress, SizeOf(Pointer), BytesWritten) then
            begin
              //x64_test CloseHandle(FileCreate('d:\dbg\' + IntToStr(NativeUInt(ImportCode))));
              VirtualProtect(ImportCode, SizeOf(Pointer), OldProtect, @OldProtect);
              FlushInstructionCache(GetCurrentProcess, ImportCode, SizeOf(Pointer));
              Inc(Result);
            end;
          end;
        end;

        //increment the importcode until it finds the correct address
        Inc(ImportCode);
      end;

      //keep stepping thru each API Header
      Inc(ImageImportEntry);
    end;
  end;

begin
  Result := 0;
  if PEModule > 0 then
    Result := HookModule(Pointer(PEModule), TargetAddress, NewAddress, OldAddress);
end;

Автор: over 13.7.2013, 17:58
Спасибо огромное, все заработало, правда пришлось немного допилить:

Код

  {$ifdef Win32}
  ImageNTHeaders : PImageNtHeaders;
  {$endif}
  {$ifdef Win64}
  ImageNTHeaders : PImageNtHeaders64;
  {$endif}

Автор: Illusion Dolphin 13.7.2013, 20:44
Цитата

  {$ifdef Win32}
  ImageNTHeaders : PImageNtHeaders;
  {$endif}
  {$ifdef Win64}
  ImageNTHeaders : PImageNtHeaders64;
  {$endif} 

Лишнее. Смотрите Winapi.Windows.pas

Автор: Чучмек 22.7.2013, 19:47
Перехват функции реализуется через запись  инструкции jmp по адресу функции.
Для x64 один из джампов выглядит так:
Код

48FF2501000000   jmp qword ptr [rel $00000001]

В простейшем случае (без сохранения работоспособности перехватываемой функции)
Код

type
 PJampRec64=^TJampRec64;
 TJampRec64=packed record
   jb0,jb1,jb2:byte;
   jra:Cardinal;
   rb:byte;
   fa:pointer;
 end;


function MyMessageBoxW(hWnd: HWND; lpText, lpCaption: PWideChar; uType: UINT):integer;
begin
ShowMessage('call MessageBoxW');
end;



procedure TForm1.FormCreate(Sender: TObject);
var
 JampRec64:PJampRec64;
 op:cardinal;
begin
JampRec64:=GetProcAddress(GetModuleHandle('user32.dll'),'MessageBoxW');
VirtualProtect(JampRec64,SizeOf(TJampRec64),PAGE_EXECUTE_READWRITE,op);
JampRec64^.jb0:=$48;
JampRec64^.jb1:=$FF;
JampRec64^.jb2:=$25;
JampRec64^.jra:=1;
JampRec64^.fa:=@MyMessageBoxW;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
MessageBox(0,'','',0);
end;


Добавлено @ 19:55
p.s Поправка на Illusion Dolphin 
Цитата(Чучмек @  22.7.2013,  19:47 Найти цитируемый пост)
Перехват функции реализуется через запись  инструкции jmp по адресу функции.

Или через правку таблицы импорта.

Автор: bems 23.7.2013, 13:38
я писал в начало перехватываемой функции jmp -8, в расчете на то что перед функцией есть минимум 6 инструкций nop
эти 6 свободных байт заменял на call qword ptr [offset], где offset указывает на место в dos-stub куда я записал абсолютный адрес функции-диспетчера хуков.
эта функция по адресу возврата на стеке (результат call qword ptr [offset]) определяет адрес хука, подменяет адрес на стеке и делает ret в хук

Автор: over 24.7.2013, 12:37
Так и не нашел универсального способа под x64, перепробовал и через jmp и редактирование таблицы импорта..
в x64 7-ке почти все отрабатывает нормально, а под x64 XP, либо вообще не работает либо кое как (как пример, перехват MessageBoxW отрабатывает, а StartDocW из gdi32 никак не хочет).

Вот и не могу понять в чем разница..

Автор: Illusion Dolphin 24.7.2013, 13:17
Цитата

 а под x64 XP, либо вообще не работает либо кое как 

Ну код, который дал я - работает под XP x64, так что у вас что-то в реализации хромает вероятно. Нарпимер, может быть вы неправильно сигнатуру описали функции и она под x64 падает. А вообще с чего вы взяли, что тчо-то не работает? И как выражается эта "неработа" ?

Автор: over 24.7.2013, 15:01
Весь код библиотеки приводить смысла нет, вот кусок:

Код

function NStartDocW(DC: HDC; const p2: TDocInfoW): Integer; stdcall;
begin
  Result := 0;
  MessageBoxA(0,'NStartDocW','',0);
end;

procedure DLLEntryPoint(dwReason: DWord);
var
  OldProc: pointer;
  DI: TDocInfoW;
begin
  case dwReason of
    DLL_PROCESS_ATTACH:
      begin
        HookCode(GetModuleHandle('gdi32.dll'), True, GetProcAddress(GetModuleHandle('gdi32.dll'), 'StartDocW'), @NStartDocW, OldProc);
        StartDocW(0, DI);
      end;
    DLL_PROCESS_DETACH:
      begin
        ...
      end;
  end;
end;


В свое приложение гружу с помощью LoadLibrary.

В процессе отладки стало понятно, что в HookModule эта проверка не проходит ни разу
Код

        //checks address and writes our one!
        if Address = OldAddress then

Автор: Illusion Dolphin 24.7.2013, 16:07
Цитата

function NStartDocW(DC: HDC; const p2: TDocInfoW): Integer; stdcall;

Неверно, внимательно смотрите сигнатуру тут Всё верно, спасибо bems http://msdn.microsoft.com/en-us/library/windows/desktop/dd145114%28v=vs.85%29.aspx (lpdi [in] A pointer to a DOCINFO structure containing the name of the document file and the name of the output file.
)

Цитата

  HookCode(GetModuleHandle('gdi32.dll'), True, GetProcAddress(GetModuleHandle('gdi32.dll'), 'StartDocW'), @NStartDocW, OldProc);

Так, я тут не спец, но вы пытаетесь найти импорт  функции StartDocW из 'gdi32.dll' внутри библиотеки 'gdi32.dll'. Замените на 

Код

HookCode(GetModuleHandle(nil), True, GetProcAddress(GetModuleHandle('gdi32.dll'), 'StartDocW'), @NStartDocW, OldProc);


Цитата

DLL_PROCESS_ATTACH:

Тут нельзя делать этого. Читаем http://msdn.microsoft.com/en-us/library/windows/desktop/ms682583%28v=vs.85%29.aspx - remarks

Добавлено @ 16:09
А ещё не забудьте что в таком подходе не будет отловлены функции, которые были найдены через LoadLibrary/GetProcAddress . Чтобы охватить эти случаи желательно перехватить ещё LdrLoadDll + GetProcAddress

Автор: bems 24.7.2013, 17:55
Цитата(Illusion Dolphin @  24.7.2013,  16:07 Найти цитируемый пост)
A pointer to a DOCINFO structure
const это поинтер, если сама структура не помещается в регистр

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)