题目要求:

设计和实现一个软件,其功能如下:
1、显示所有的进程列表;
2、选中一个进程,显示该进程的所有IAT中的函数;
3、选中一个IAT函数,实现Hooking和 inline Hooking,在Hooking的函数中显示”组号:姓名”。

显示所有进程列表

  1. 函数CreateToolhelp32Snapshot用于获取指定进程的快照或者所有进程的快照。并且返回快照的句柄。
  2. 函数Process32First用于获取进程快照中的第一个进程的信息,并且将这个进程的信息保存在一个PROCESSENTRY32的结构体中。在这个结构体中保存着进程的ID号和名称。
  3. 函数Process32Next用于获取快照中下一个进程的信息。我们循环调用这个函数,那么可以遍历进程快照中的所有进程。
    源程序如下:
#include <windows.h> 
#include<iostream>
#include <tlhelp32.h>
#include <stdio.h>
using namespace std;
DWORD GetProcessId(char*myprocess)//枚举进程函数
{
	DWORD Pid = -1;
	HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	//创建系统快照
	PROCESSENTRY32 lPrs;
	ZeroMemory(&lPrs, sizeof(lPrs));
	lPrs.dwSize = sizeof(lPrs);
	char *targetFile = myprocess;
	bool flag = false;
	if (Process32First(hSnap, &lPrs)) {//取得系统快照里第一个进程信息
		//printf("name\t\t\tpid\t\tsize\n");
		printf("%-50s%u\t\t%u\n", lPrs.szExeFile, lPrs.th32ProcessID, lPrs.dwSize);
		if (strstr(targetFile, lPrs.szExeFile))//判断进程信息是否是explorer.exe
		{
			Pid = lPrs.th32ProcessID;
			flag = true;
		}
	}
	while (1)
	{
		ZeroMemory(&lPrs, sizeof(lPrs));
		lPrs.dwSize = (&lPrs, sizeof(lPrs));
		if (!Process32Next(hSnap, &lPrs))//继续枚举进程信息
		{
			break;
		}
		if (strstr(targetFile, lPrs.szExeFile))//判断进程信息是否是explorer.exe
		{
			Pid = lPrs.th32ProcessID;
			flag = true;

		}
		printf("%-50s%u\t\t%u\n", lPrs.szExeFile, lPrs.th32ProcessID, lPrs.dwSize);

	}
	if(flag)
		return Pid;
	else return -1;
}
int main() {
	printf("start.....\n");
	char myprocess[50] = "cloudmusic.exe";
	DWORD myPid = GetProcessId(myprocess);
	printf("%u\n", myPid);
	getchar();
	return 0;
}

显示进程所有的IAT函数

首先获得模块的句柄:

// 取得主模块的模块句柄(即进程模块基地址)
 HMODULE hModule = ::GetModuleHandleA(NULL);

然后把进程基址赋给pDosHeader,即起始基址就是PE的IMAGE_DOS_HEADER

IMAGE_DOS_HEADER* pDosHeader = (IMAGE_DOS_HEADER*)hModule;

然后定位到PE header
基址hMod加上IMAGE_DOS_HEADER结构的e_lfanew成员到达IMAGE_NT_HEADERS
NT文件头的前4字节是文件签名("PE00" 字符串),然后是20字节的IMAGE_FILE_HEADER结。即到达IMAGE_OPTIONAL_HEADER结构的地址,获取了一个指向IMAGE_OPTIONAL_HEADER结构体的指针。

IMAGE_OPTIONAL_HEADER* pOpNtHeader = (IMAGE_OPTIONAL_HEADER*)((BYTE*)hModule + pDosHeader->e_lfanew + 24);

定位导入表:
通过IMAGE_OPTIONAL_HEADER结构中的DataDirectory结构数组中的第二个成员中的VirturalAddress字段定位到IMAGE_IMPORT_DESCRIPTOR结构的起始地址即获得导入表中第一个IMAGE_IMPORT_DESCRIPTOR结构的指针(导入表首地址)

IMAGE_IMPORT_DESCRIPTOR* pImportDesc = (IMAGE_IMPORT_DESCRIPTOR*)((BYTE*)hModule + pOpNtHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

通过两重循环输出IAT函数,外层函数是对模块循环,内层循环对一个模块里面的所有函数进行循环输出:

while (pImportDesc->FirstThunk)
 {
  char* pszDllName = (char*)((BYTE*)hModule + pImportDesc->Name);
  printf("模块名称:%s\n", pszDllName);
  DWORD n = 0;
  //一个IMAGE_THUNK_DATA就是一个导入函数
  IMAGE_THUNK_DATA* pThunk = (IMAGE_THUNK_DATA*)((BYTE*)hModule + pImportDesc->OriginalFirstThunk);
  while (pThunk->u1.Function)
  {
   //取得函数名称
   char* pszFuncName = (char*)((BYTE*)hModule+pThunk->u1.AddressOfData+2); //函数名前面有两个..
   printf("function name:%-25s,  ", pszFuncName);
   //取得函数地址
   PDWORD lpAddr = (DWORD*)((BYTE*)hModule + pImportDesc->FirstThunk) + n; //从第一个函数的地址,以后每次+4字节
   printf("addrss:%X\n", lpAddr);
   n++; //每次增加一个DWORD
   pThunk++;
  }
  printf("\n");
  pImportDesc++;
 }

实现Inline hooking

Inline hooking是一种拦截目标函数调用的方法,主要用于抗病毒软件,沙箱和恶意软件。 一般的想法是将函数重定向到我们自己的函数,以便在函数执行之前和/或之后执行处理。 这可能包括:检查参数,匀场,记录,欺骗返回的数据和过滤呼叫。 Rootkit倾向于使用钩子来修改从系统调用返回的数据,以隐藏其存在,而安全软件则使用它们来防止/监视潜在的恶意操作。
hooking通过直接修改目标函数(内联修改)中的代码来放置,通常通过用跳转覆盖前几个字节; 这允许在函数进行任何处理之前重定向执行。 大多数引擎引擎使用32位相对跳转,占用5个字节的空间。
如何实现:
将使用一个基于trampoline的钩子,它允许我们截取功能,同时仍然可以调用原件。这个钩子由3部分组成:

  1. Hook - 一个5字节的相对跳转,写入目标函数以挂接它,跳转将从挂钩函数跳转到我们的代码。
  2. proxy这是我们指定的函数(或代码),钩子放在目标函数上将跳转到。
  3. trampoline用于绕过钩子,所以我们可以正常地调用挂钩功能。

使用trampoline的原因是:
假设我们要钩住MessageBoxA,从代理功能中打印出参数,然后显示消息框:为了显示消息框,我们需要调用MessageBoxA(它们重定向到我们的代理函数,这反过来又调用MessageBoxA)。 显然从我们的代理函数中调用MessageBoxA将导致无限递归,并且程序由于堆栈溢出而最终崩溃。
我们可以简单地从代理函数中取消MessageBoxA,调用它,然后重新挂接它; 但是如果多个线程同时调用MessageBoxA,这将导致竞争条件,并可能导致程序崩溃。
相反,我们可以做的是存储MessageBoxA的前5个字节(这些被我们的钩子覆盖),然后当我们需要调用非挂钩的MessageBoxA时,我们可以执行存储的前5个字节,然后是5个字节的跳转 MessageBoxA(直接挂钩)。
只要前5个字节不是相对指令,它们可以在任何地方执行。
在这个例子中,函数的前5个字节组成了3个指令mov edi,edi; push ebp; mov ebp,esp,但是,例如,如果第一个指令是10个字节长,我们只存储5个字节trampoline将执行一半的指令,导致程序爆炸。 为了解决这个问题,我们必须使用反汇编来获取每个指令的长度。 最好的情况是前n个指令总共5个字节,最糟糕的情况是如果第一个指令是4个字节,而第二个指令是16个(x86指令的最大长度),则必须存储20个字节(4 + 16),这意味着trampoline的大小必须是25个字节(空间最多可达20个字节的指令,5个字节跳回挂钩的功能)。重要的是,返回跳转必须跳转到挂接的函数n个字节,其中n是我们存储在trampoline中的指令。

代码编写

首先,我们需要定义代理函数。我们把hooking函数重定向。对于这个例子,我们只需要在显示消息框之前打印出参数。

int WINAPI NewMessageBoxA(HWND hWnd, LPCSTR lpText, LPCTSTR lpCaption, UINT uType)
{
	printf("MessageBoxA called!ntitle: %sntext: %snn", lpCaption, lpText);
	return OldMessageBoxA(hWnd, lpText, lpCaption, uType);
}

int WINAPI NewMessageBoxW(HWND hWnd, LPWSTR lpText, LPCTSTR lpCaption, UINT uType)
{
	printf("MessageBoxW called!ntitle: %wsntext: %wsnn", lpCaption, lpText);
	return OldMessageBoxW(hWnd, lpText, lpCaption, uType);
}

OldMessageBox只是一个typedef,它将指向25个字节的可执行内存,该挂钩功能将存储trampoline。

typedef int (WINAPI *TdefOldMessageBoxA)(HWND hWnd, LPCSTR lpText, LPCTSTR lpCaption, UINT uType);
typedef int (WINAPI *TdefOldMessageBoxW)(HWND hWnd, LPWSTR lpText, LPCTSTR lpCaption, UINT uType);
TdefOldMessageBoxA OldMessageBoxA = (TdefOldMessageBoxA)VirtualAlloc(NULL, 25, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
TdefOldMessageBoxW OldMessageBoxW = (TdefOldMessageBoxW)VirtualAlloc(NULL, 25, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

现在对于hooking函数,我们将具有以下参数:

  • name - 挂钩功能的名称。
  • dll - 目标函数所在的dll。
  • proxy - 指向代理函数的指针(NewMessageBox)。
  • original - 指向25字节的可执行内存的指针,存储trampoline。
  • length - 指向一个变量的指针,该变量接收存储在trampoline中的指令值。

在hooking函数内部,我们将获取目标函数的地址,然后使用Hacker Dissasembler Engine(HDE32)来拆分每个指令并获取长度,直到我们有5个或更多个字节值得整个指令(hde32_disasm返回长度的第一个参数指向的指令)。

LPVOID FunctionAddress;
DWORD TrampolineLength = 0;
 
FunctionAddress = GetProcAddress(GetModuleHandleA(dll), name);
if(!FunctionAddress)
 return FALSE;
 
//拆分每个指令的长度,直到我们有5个以上的字节值
while(TrampolineLength < 5)
{
 LPVOID InstPointer = (LPVOID)((DWORD)FunctionAddress + TrampolineLength);
 TrampolineLength += hde32_disasm(InstPointer, &disam);
}

为了构建实际的trampoline,我们首先将目标函数中的TrampolineLength字节复制到trampoline缓冲区(传递给参数“original”中的函数),然后我们将复制的字节用n字节附加到目标函数中,n是Trampoline的长度。

相对跳转是距离跳转结束的距离,即:(destination - (source + 5))。 跳跃的来源将是trampoline地址+trampoline长度,目的地将是hooking函数+trampoline长度。

DWORD src = ((DWORD)FunctionAddress + TrampolineLength);
DWORD dst = ((DWORD)original + TrampolineLength + 5);
BYTE jump[5] = {0xE9, 0x00, 0x00, 0x00, 0x00};

//将n个字节从目标函数存储到trampoline中
memcpy(original, FunctionAddress, TrampolineLength);
 
//设置跳转的第二个字节(偏移量),跳转到我们想要的位置
*(DWORD *)(jump+1) = src - dst;
 
//将跳转复制到trampoline的末端
memcpy((LPVOID)((DWORD)original+TrampolineLength), jump, 5);

在我们可以编写跳转函数之前,我们需要确保内存是可写的(通常不可以),我们通过使用VirtualProtect将保护设置为PAGE_EXECUTE_READWRITE来实现。

//确保该功能是可写的
DWORD OriginalProtection;
if(!VirtualProtect(FunctionAddress, 8, PAGE_EXECUTE_READWRITE, &OriginalProtection))
 return FALSE;

要摆放hooking,我们需要做的就是创建一个从目标函数跳转到代理的跳转,然后我们可以用它覆盖目标的前5个字节。 为了避免在写入跳转时调用函数的任何风险,我们必须一次性写完。而atomic functions只能用于基础2(2,4,8,16等)的大小; 我们的跳转是5个字节,我们可以复制的最接近的大小是8,所以我们必须制作一个自定义函数SafeMemcpyPadded,它将源缓冲区用来从目的地的字节缓冲到8个字节,这样最后3个字节保持不变 复制后。

cmpxchg8b将edx:eax中保存的8个字节与目标进行比较,如果它们相等,则复制ecx:ebx中保存的8个字节,我们将edx:eax设置为目标字节,以使副本始终发生。

//构建并写hooking
*(DWORD *)(jump+1) = (DWORD)proxy - (DWORD)FunctionAddress - 5;
SafeMemcpyPadded(FunctionAddress, Jump, 5);

void SafeMemcpyPadded(LPVOID destination, LPVOID source, DWORD size)
{
 BYTE SourceBuffer[8];
 
 if(size > 8)
  return;
 
 //使用来自目的地的字节来填充源缓冲区
 memcpy(SourceBuffer, destination, 8);
 memcpy(SourceBuffer, source, size);
 
 __asm 
 {
  lea esi, SourceBuffer;
  mov edi, destination;
 
  mov eax, [edi];
  mov edx, [edi+4];
  mov ebx, [esi];
  mov ecx, [esi+4];
 
  lock cmpxchg8b[edi];
 }
}

现在要做的就是恢复页面保护。 刷新指令缓存,并将length参数设置为Trampoline的长度。

//恢复页面保护
VirtualProtect(FunctionAddress, 8, OriginalProtection, &OriginalProtection);
 
//清楚CPU指令缓存
FlushInstructionCache(GetCurrentProcess(), FunctionAddress, TrampolineLength);
 
*length = TrampolineLength;
return TRUE;

hooking功能可以这样简单地调用。

DWORD length;
HookFunction("user32.dll", "MessageBoxA", &NewMessageBoxA, OldMessageBoxA, &length);

通过从OldMessageBox(Trampoline)将length字节复制到hooking函数来完成脱钩。

代码链接:http://pan.baidu.com/s/1bo0ZabH

posted on 2017-05-09 10:43  yingtaomj  阅读(392)  评论(0编辑  收藏  举报