Hi,
My purpose is to help the author of mpatrol to make that program as easy to use than valgrind on Windows. So i tried to look at DLL injection and API hooking.
I have written a program (named valgrind :p) and a DLL to test those 2 technics. More precisely, I have:
- valgrind.exe : the program that will inject the DLL below
- valgrind.dll : the DLL that will be injected in an executable and that will do API hooking
- valgrind_test.exe : a executable that calls a function overloaded in valgrind.dll
I have taken some bits of code here and there in CodeProject. For the DLL injection, I used the VirtualAllocEx() / CreateRemoteThread() technic. For the API hooking, I enumerate all the modules and use ImageDirectoryEntryToData().
Here are the different codes:
valgrind.c
#include <stdio.h>
#include <string.h>
#include <windows.h>
#define CREATE_THREAD_ACCESS (PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_SUSPEND_RESUME | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ)
typedef HMODULE (*_load_library) (const char *);
typedef BOOL (*_free_library) (HMODULE);
typedef struct _Vg Vg;
struct _Vg
{
_load_library ll;
_free_library fl;
char *dll_fullname;
int dll_length;
struct {
HANDLE process1;
HANDLE thread;
HANDLE process2;
} child;
DWORD exit_code;
};
FARPROC
_vg_symbol_get (const char *module, const char *symbol)
{
HMODULE mod;
FARPROC proc;
printf (" * loading library %s... ", module);
mod = LoadLibrary(module);
if (!mod)
{
printf("failed\n", module);
return NULL;
}
printf ("done\n");
printf (" * retrieving symbol %s... ", symbol);
proc = GetProcAddress(mod, symbol);
if (!proc)
{
printf("failed\n", symbol);
goto free_library;
}
printf ("done\n");
FreeLibrary(mod);
return proc;
free_library:
FreeLibrary(mod);
return NULL;
}
Vg *
vg_new()
{
char buf[MAX_PATH];
Vg *vg;
HMODULE kernel32;
DWORD length;
kernel32 = LoadLibrary("kernel32.dll");
if (!kernel32)
{
printf("no kernel32.dll found\n");
return 0;
}
if (!GetProcAddress(kernel32, "CreateRemoteThread"))
{
printf("no CreateRemoteThread found\n");
goto free_kernel32;
}
vg = (Vg *)calloc (1, sizeof (Vg));
if (!vg)
goto free_kernel32;
vg->ll = (_load_library)_vg_symbol_get("kernel32.dll", "LoadLibraryA");
if (!vg->ll)
goto free_vg;
vg->fl = (_free_library)_vg_symbol_get("kernel32.dll", "FreeLibrary");
if (!vg->fl)
goto free_vg;
length = GetFullPathName("valgrind_dll.dll", MAX_PATH, buf, NULL);
if (!length)
{
printf ("can't get full path name\n");
goto free_vg;
}
vg->dll_fullname = _strdup(buf);
if (!vg->dll_fullname)
goto free_vg;
vg->dll_length = length + 1;
FreeLibrary(kernel32);
return vg;
free_vg:
free(vg);
free_kernel32:
FreeLibrary(kernel32);
return 0;
}
void
vg_del(Vg *vg)
{
if (!vg)
return;
if (vg->child.process2)
CloseHandle(vg->child.process2);
if (vg->child.thread)
CloseHandle(vg->child.thread);
if (vg->child.process1)
CloseHandle(vg->child.process1);
free(vg->dll_fullname);
free(vg);
}
int vg_dll_inject(Vg *vg, const char *prog)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
HANDLE process;
HANDLE remote_thread;
LPVOID remote_string;
DWORD exit_code;
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
printf (" * creating child process %s... ", prog);
if (!CreateProcess(NULL, (char *)prog, NULL, NULL, TRUE,
CREATE_SUSPENDED, NULL, NULL, &si, &pi))
{
printf ("failed\n * can't spawn child process %s\n", prog);
return 0;
}
printf ("done\n");
printf (" * waiting for the child process to initialize... ");
if (!WaitForInputIdle(pi.hProcess, INFINITE))
{
printf("failed\n * no process for %s\n", prog);
goto close_handles;
}
printf ("done\n");
printf (" * opening child process... ");
process = OpenProcess(CREATE_THREAD_ACCESS, FALSE, pi.dwProcessId);
if (!process)
{
printf("failed\n * no process for %s\n", prog);
goto close_handles;
}
printf ("done\n");
printf (" * allocating virtual memory... ");
remote_string = VirtualAllocEx(process, NULL, vg->dll_length, MEM_COMMIT, PAGE_READWRITE);
if (!remote_string)
{
printf("failed\n");
goto close_process;
}
printf ("done\n");
printf(" * writing process in virtual memory... ");
if (!WriteProcessMemory(process, remote_string, vg->dll_fullname, vg->dll_length, NULL))
{
printf("failed\n");
goto virtual_free;
}
printf ("done\n");
printf (" * execute thread... ");
remote_thread = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)vg->ll, remote_string, 0, NULL);
if (!remote_thread)
{
printf("failed\n");
goto virtual_free;
}
printf ("done\n");
WaitForSingleObject(remote_thread, INFINITE);
printf (" * getting exit code... ");
if (!GetExitCodeThread(remote_thread, &exit_code))
{
printf("failed\n");
goto close_thread;
}
printf ("done\n");
CloseHandle(remote_thread);
VirtualFreeEx(process, remote_string, vg->dll_length + 1, MEM_RELEASE);
printf(" * resume child process\n");
ResumeThread(pi.hThread);
vg->child.process1 = pi.hProcess;
vg->child.thread = pi.hThread;
vg->child.process2 = process;
vg->exit_code = exit_code;
return 1;
close_thread:
CloseHandle(remote_thread);
virtual_free:
VirtualFreeEx(process, remote_string, vg->dll_length, MEM_RELEASE);
close_process:
CloseHandle(process);
close_handles:
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return 0;
}
void
vg_dll_extract(Vg *vg)
{
HANDLE thread;
thread = CreateRemoteThread(vg->child.process2, NULL, 0,
(LPTHREAD_START_ROUTINE)vg->fl,
(void*)vg->exit_code, 0, NULL );
WaitForSingleObject(thread, INFINITE );
CloseHandle(thread );
}
int main(int argc, char *argv[])
{
Vg *vg;
vg = vg_new();
if (!vg)
return -1;
if (!vg_dll_inject(vg, "valgrind_test.exe"))
{
printf(" * injection failed\n * exiting...\n");
goto del_vg;
}
Sleep(2000);
printf(" * fin process\n");
vg_dll_extract(vg);
vg_del(vg);
printf(" * ressources freed\n");
return 0;
del_vg:
vg_del(vg);
return -1;
}
valgrind.dll
#include <stdio.h>
#include <windows.h>
#include <psapi.h>
#include <imagehlp.h>
typedef struct
{
char *func_name_old;
PROC func_proc_old;
PROC func_proc_new;
} Vg_Hook;
typedef LPVOID (WINAPI *vgd_heap_alloc_t) (HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes);
typedef BOOL (WINAPI *vgd_heap_free_t) (HANDLE hHeap, DWORD dwFlags, LPVOID lpMem);
typedef void (WINAPI *vgd_get_system_info_t)(LPSYSTEM_INFO lpSystemInfo);
typedef void (WINAPI *vgd_get_system_time_t)(LPSYSTEM_INFO lpSystemTime);
Vg_Hook vg_hooks_kernel32[2];
LPVOID WINAPI VG_HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes)
{
vgd_heap_alloc_t *ha;
LPVOID data;
printf("HeapAlloc !!!\n");
ha = (vgd_heap_alloc_t *)vg_hooks_kernel32[0].func_proc_old;
data = (*ha)(hHeap, dwFlags, dwBytes);
return data;
}
BOOL WINAPI VG_HeapFree(HANDLE hHeap, DWORD dwFlags, LPVOID lpMem)
{
vgd_heap_free_t *hf;
BOOL res;
printf("HeapFree !!!\n");
hf = (vgd_heap_free_t *)vg_hooks_kernel32[1].func_proc_old;
res = (*hf)(hHeap, dwFlags, lpMem);
return res;
}
void WINAPI VG_GetSystemInfo(LPSYSTEM_INFO lpSystemInfo)
{
vgd_get_system_info_t *gsi;
printf ("GetSystemInfo !!!\n");
gsi = (vgd_get_system_info_t *)vg_hooks_kernel32[0].func_proc_old;
(*gsi)(lpSystemInfo);
}
void WINAPI VG_GetSystemTime(LPSYSTEM_INFO lpSystemTime)
{
vgd_get_system_time_t *gst;
printf ("GetSystemTime !!!\n");
gst = (vgd_get_system_time_t *)vg_hooks_kernel32[1].func_proc_old;
(*gst)(lpSystemTime);
}
#define MakePtr( cast, ptr, addValue ) (cast)( (DWORD)(ptr)+(DWORD)(addValue))
void
_vgd_modules_hook_set(HMODULE module, const char *lib_name, PROC old_function_proc, PROC new_function_proc)
{
PIMAGE_IMPORT_DESCRIPTOR iid;
PIMAGE_THUNK_DATA thunk;
ULONG size;
iid = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(module, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &size);
if (!iid)
return;
while (iid->Name)
{
PSTR module_name;
module_name = (PSTR)((PBYTE) module + iid->Name);
if (_stricmp(module_name, lib_name) == 0)
break;
iid++;
}
if (!iid->Name)
return;
thunk = (PIMAGE_THUNK_DATA)((PBYTE)module + iid->FirstThunk );
while (thunk->u1.Function)
{
PROC *func;
func = (PROC *)&thunk->u1.Function;
if (*func == old_function_proc)
{
MEMORY_BASIC_INFORMATION mbi;
DWORD dwOldProtect;
VirtualQuery(func, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
if (!VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, &mbi.Protect))
return;
*func = *new_function_proc;
VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwOldProtect);
break;
}
thunk++;
}
}
void
_vgd_modules_hook(HMODULE main_module, const char *lib_name)
{
HMODULE mods[1024];
HMODULE lib_module;
DWORD mods_nbr;
unsigned int i;
unsigned int j;
lib_module = LoadLibrary(lib_name);
for (j = 0; vg_hooks_kernel32[j].func_name_old; j++)
vg_hooks_kernel32[j].func_proc_old = GetProcAddress(lib_module, vg_hooks_kernel32[j].func_name_old);
if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof(mods), &mods_nbr))
return;
for (i = 0; i < (mods_nbr / sizeof(HMODULE)); i++)
{
if (mods[i] == main_module)
continue;
for (j = 0; vg_hooks_kernel32[j].func_name_old; j++)
_vgd_modules_hook_set(mods[i], lib_name,
vg_hooks_kernel32[j].func_proc_old,
vg_hooks_kernel32[j].func_proc_new);
}
FreeLibrary(lib_module);
}
void
_vgd_modules_unhook(HMODULE main_module, const char *lib_name)
{
HMODULE mods[1024];
DWORD mods_nbr;
unsigned int i;
unsigned int j;
if (!EnumProcessModules(GetCurrentProcess(), mods, sizeof(mods), &mods_nbr))
return;
for (i = 0; i < (mods_nbr / sizeof(HMODULE)); i++)
{
if (mods[i] == main_module) continue;
for (j = 0; vg_hooks_kernel32[j].func_name_old; j++)
_vgd_modules_hook_set(mods[i], lib_name,
vg_hooks_kernel32[j].func_proc_new,
vg_hooks_kernel32[j].func_proc_old);
}
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ulReason, LPVOID lpReserved)
{
vg_hooks_kernel32[0].func_name_old = "GetSystemInfo";
vg_hooks_kernel32[0].func_proc_old = NULL;
vg_hooks_kernel32[0].func_proc_new = (PROC)VG_GetSystemInfo;
vg_hooks_kernel32[1].func_name_old = "GetSystemTime";
vg_hooks_kernel32[1].func_proc_old = NULL;
vg_hooks_kernel32[1].func_proc_new = (PROC)VG_GetSystemTime;
vg_hooks_kernel32[2].func_name_old = NULL;
vg_hooks_kernel32[2].func_proc_old = NULL;
vg_hooks_kernel32[2].func_proc_new = NULL;
switch (ulReason)
{
case DLL_PROCESS_ATTACH:
printf ("DLL attach\n");
printf ("hooking... ");
_vgd_modules_hook(hModule, "kernel32.dll");
printf ("finished\n");
break;
case DLL_PROCESS_DETACH:
printf ("DLL detach\n");
break;
}
return TRUE;
}
and the test code :
#include <stdio.h>
#include <windows.h>
int main()
{
SYSTEMTIME st;
GetSystemTime(&st);
return 0;
}
Now, the problems:
- If i don't hook the functions in valgrind.dll, the injection is correctly done (there is a message displayed when the DLLL process is attached and one when the test example is executed).
- If I hook several functions, the test program is executed, but an exception is raised (see the comment at the bottom of the test file. Sorry it is in french...)
I don't understand at all the problem.
Here is an archive of the code (with a Makefile for the gcc users or a visual studio solution) to test the code)
Does someone see the problem ?
thank you
|