Перехват WinAPI
От: Streamer1 Украина  
Дата: 02.05.06 12:40
Оценка: 8 (3)
когдато было нужно было протестировать вызовы API в коде программы, сырцов которой у меня небыло...
по мотивам обсуждений в какихто форумах написал сырец для облегчения написания логгеров, перехватывающих WinAPI и пишущих в файл обращения:

intercept.h:
#ifndef __INTERCEPT_H_
#define __INTERCEPT_H_

#define METHOD_JUMP        // закоментировать для метода перехвата поиском в таблице .idata!


#pragma pack(1)
struct jmp_far
{
  BYTE instr_push;  //здесь будет код инструкции push
  DWORD arg;         //аргумент push
  BYTE  instr_ret;    //здесь будет код инструкции ret
};
#pragma pack()


#ifndef METHOD_JUMP
bool InterceptFunction(char *NameDllIntercept, char *NameFunIntercept, void *myfun, void *apifun);
#else
bool InterceptFunction(char *NameDllIntercept, char *NameFunIntercept, void *myfun, void *apifun, char *argold, jmp_far *argjump);
extern DWORD _interceptapiwritten;
#endif

#ifdef METHOD_JUMP
#define INTERCEPT_BEGIN(a) WriteProcessMemory(GetCurrentProcess(), (void*)adr_##a, (void*)old_##a, 6, &_interceptapiwritten);
#define INTERCEPT_END(a) WriteProcessMemory(GetCurrentProcess(), (void*)adr_##a, (void*)&jump_##a, 6,&_interceptapiwritten);
#define InterceptAPIF(a,b) InterceptFunction(a, #b, &ic##b, &adr_##b, old_##b, &jump_##b);
#define INTERCEPT_DECLARE_INFO(a) unsigned char old_##a##[6]; jmp_far jump_##a##; DWORD adr_##a##;
#define INTERCEPT_REALCALL(a) a
#else
#define INTERCEPT_BEGIN(a)
#define INTERCEPT_END(a)
#define InterceptAPIF(a,b) InterceptFunction(a, #b, &ic##b, &adr_##b);
#define INTERCEPT_DECLARE_INFO(a) DWORD adr_##a##;
#define INTERCEPT_REALCALL(a) ((BOOL (__stdcall*)(...))adr_##a)
#endif


#endif


intercept.cpp:
#include <stdio.h>
#include <windows.h>

#include "intercept.h"


//=============================================================================
// Эта функция ищет в таблице импорта - .idata нужный адрес и меняет на
// адрес процедуры-двойника
#ifndef METHOD_JUMP
bool InterceptFunction(char *NameDllIntercept, char *NameFunIntercept, void *myfun, void *apifun)
{
  int i;
  char strbuf[2048];
  // Начало отображения в памяти процесса
  BYTE *pimage = (BYTE*)GetModuleHandle(NULL);
  BYTE *pidata;
  // Стандартные структуры описания PE заголовка
  IMAGE_DOS_HEADER *idh;
  IMAGE_OPTIONAL_HEADER *ioh;
  IMAGE_SECTION_HEADER *ish;
  IMAGE_IMPORT_DESCRIPTOR *iid;
  DWORD *isd;  //image_thunk_data dword

  // Получаем указатели на стандартные структуры данных PE заголовка
  idh = (IMAGE_DOS_HEADER*)pimage;
  ioh = (IMAGE_OPTIONAL_HEADER*)(pimage + idh->e_lfanew
                                 + 4 + sizeof(IMAGE_FILE_HEADER));
  ish = (IMAGE_SECTION_HEADER*)((BYTE*)ioh + sizeof(IMAGE_OPTIONAL_HEADER));
  //если не обнаружен магический код, то у этой программы нет PE заголовка
  if (idh->e_magic != 0x5A4D)
  {
    MessageBox(NULL, "Not exe hdr", "Error!", 0);
    return false;
  }

  //ищем секцию .idata
  for(i=0; i<16; i++)
    if(strcmp((char*)((ish+ i)->Name) , ".idata") == 0) break;
  if(i==16)
  {
    MessageBox(NULL, "Unable to find .idata section", "Error!", 0);
    return false;
  }

  // Получаем адрес секции .idata(первого элемента IMAGE_IMPORT_DESCRIPTOR)
  iid = (IMAGE_IMPORT_DESCRIPTOR*)(pimage + (ish +i)->VirtualAddress );

  // Получаем абсолютный адрес функции для перехвата
  *(DWORD*)apifun = (DWORD)GetProcAddress(
                        GetModuleHandle(NameDllIntercept), NameFunIntercept);
  if(*(DWORD*)apifun == 0)
  {
    sprintf(strbuf, "Can`t get addr %s", NameFunIntercept);
    MessageBox(NULL, strbuf, "Error!", 0);
    return false;
  }

  // В таблице импорта ищем соответствующий элемент для
  // библиотеки user32.dll
  while(iid->Name)  //до тех пор пока поле структуры не содержит 0
  {
    if(strcmp((char*)(pimage + iid->Name), NameDllIntercept) ==0 ) break;
    iid++;
  }

  // Ищем в IMAGE_THUNK_DATA нужный адрес
  isd = (DWORD*)(pimage + iid->FirstThunk);
  while(*isd!=*(DWORD*)apifun && *isd!=0)  isd++;
  if(*isd == 0)
  {
    sprintf(strbuf, "addr %s not found in .idata", NameFunIntercept);
    MessageBox(NULL, strbuf, "Error!", 0);
    return false;
  }

  // Заменяем адрес на свою функцию

  DWORD buf =  (DWORD)myfun;
  DWORD op;

  DWORD written;

  // Обычно страницы в этой области недоступны для записи
  // поэтому принудительно разрешаем запись
  VirtualProtect((void*)(isd),4,PAGE_READWRITE, &op);

  // Пишем новый адрес
  WriteProcessMemory(GetCurrentProcess(), (void*)(isd),
                    (void*)&buf,4,&written);
  //восстанавливаем первоначальную защиту области по записи
  VirtualProtect((void*)(isd),4,op, &op);
  //если записать не удалось - увы, все пошло прахом:
  if(written!=4)
  {
    MessageBox(NULL, "Unable rewrite address", "Error!", 0);
    return false;
  }
  return true;
}
#endif
//=============================================================================
#ifdef METHOD_JUMP
DWORD _interceptapiwritten;
bool InterceptFunction(char *NameDllIntercept, char *NameFunIntercept, void *myfun, void *apifun, char *argold, jmp_far *argjump)
{
  char sbuf[2048];
  unsigned char *old = argold;
  jmp_far &jump = *argjump;

#define adr_ (*(DWORD*)apifun)

  adr_ = (DWORD)GetProcAddress(GetModuleHandle(NameDllIntercept),
                    NameFunIntercept);
  if(adr_ == 0)
  {
    sprintf(sbuf, "Can't get addr %s", NameFunIntercept);
    MessageBox(NULL, sbuf, "Error!", 0);
    return false;
  }

  // Зададим машинный код инструкции перехода, который затем впишем
  // в начало полученного адреса:
  jump.instr_push = 0x68;
  jump.arg = (DWORD)myfun;
  jump.instr_ret = 0xC3;


DWORD op;
DWORD *isd = (DWORD*)adr_;  //image_thunk_data dword
// поэтому принудительно разрешаем запись
VirtualProtect((void*)(isd),4,PAGE_READWRITE, &op);

  //Прочитаем и сохраним первые оригинальные 6 байт стандартной API функции
  ReadProcessMemory(GetCurrentProcess(),(void*) adr_,
                    (void*)old, 6, &_interceptapiwritten);

//Запишем команду перехода на нашу функцию поверх этих 6-ти байт
   WriteProcessMemory(GetCurrentProcess(), (void*)adr_,
     (void*)&jump, sizeof(jmp_far), &_interceptapiwritten);

//восстанавливаем первоначальную защиту области по записи
VirtualProtect((void*)(isd),4,op, &op);

   return true;
}
#endif
//=============================================================================



на примере MessageBox, использовать так:
#include <windows.h>
#include "intercept.h"

INTERCEPT_DECLARE_INFO(MessageBoxA);
BOOL WINAPI icMessageBoxA(HWND hwnd, char* text,
                                  char *hdr, UINT utype)    // обработчик перехватываемой функции
{
  INTERCEPT_BEGIN(MessageBoxA);        // сделали возможность вызвать реальный код MessageBoxA

  char str1[32768];
  //здесь вы выполняем любые свои действия
  sprintf(str1, "%s\n\nAPI intercepted", text);
// вызываем оригинальную функцию через указатель
//  ((BOOL (__stdcall*)(HWND, char*, char*, UINT))adr_MessageBoxA)(hwnd,
//            str1, hdr, utype);
//  MessageBoxA(hwnd, str1, hdr, utype);
  INTERCEPT_REALCALL(MessageBoxA)(hwnd, str1, hdr, utype);    // вызвали реальный код MessageBoxA

  INTERCEPT_END(MessageBoxA);        // опять восстановили перехват
  return TRUE;
}


чтобы перехват работал, нужно в начале работы программы, или в DllMain (если это dll) сделать вызов
функции InterceptAPIF, указав в параметрах из какой DLL'ки перехватывать функцию и имя функции для перехвата:

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fwdreason, LPVOID lpvReserved)
{
    int err;
    if(fwdreason == DLL_PROCESS_ATTACH)
    {
        err = InterceptAPIF("USER32.DLL",MessageBoxA);
    }
}


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

если в intercept.h определен макрос METHOD_JUMP, то используется метод перехвата заменой кода самой функции на JUMP, при этом INTERCEPT_BEGIN/INTERCEPT_END восстанавливает/опять заменяет оригинальный код

если макрос не определен, то используется метод перехвата путем замены адреса функции в таблице
импорта .idata — не всегда работает, т.к. функция API может вызыватся без указания импорта в EXE файле,
в этом случае INTERCEPT_BEGIN/INTERCEPT_END ничего не делают (служат для совместимости с METHOD_JUMP)

вот... может комуто пригодится, а может ктото доработает до более приличного вида...
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re: Перехват WinAPI
От: MShura  
Дата: 02.05.06 15:27
Оценка:
S>когдато было нужно было протестировать вызовы API в коде программы, сырцов которой у меня небыло...
S>по мотивам обсуждений в какихто форумах написал сырец для облегчения написания логгеров, перехватывающих WinAPI и пишущих в файл обращения:
....

S>вот... может комуто пригодится, а может ктото доработает до более приличного вида...


Для ведения логов уже есть готовые и удобные утилиты типа strace (название из Linux):
http://www.bindview.com/Services/RAZOR/Utilities/Windows/strace_readme.cfm
Re[2]: Перехват WinAPI
От: Streamer1 Украина  
Дата: 02.05.06 17:26
Оценка:
Здравствуйте, MShura, Вы писали:

для ведения логов вызовов WinAPI, без модификации исходников...
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re: Перехват WinAPI
От: srggal Украина  
Дата: 03.05.06 09:17
Оценка:
Здравствуйте, Streamer1, Вы писали:

Как-то все излишне навернуто, у меня в своё время вроде как проще получалось, и без макросов.

Осбенно мне не понятно зачем использовать
WriteProcessMemory(GetCurrentProcess(), ...

... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[3]: Перехват WinAPI
От: MShura  
Дата: 03.05.06 10:44
Оценка: 2 (1) +1
S>для ведения логов вызовов WinAPI, без модификации исходников...

Эта утилита осуществляет перехват вызовов на уровне ядра.
Никаких модификаций исходников не требуется.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.