когдато было нужно было протестировать вызовы 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>>
Здравствуйте, Streamer1, Вы писали:
Как-то все излишне навернуто, у меня в своё время вроде как проще получалось, и без макросов.
Осбенно мне не понятно зачем использовать
WriteProcessMemory(GetCurrentProcess(), ...
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>