Перехват 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>>
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.