set_pwd API hook分析
标准的API hook的流程如下:
- 获取API的函数地址。
- 修改API起始地址字节,使之跳转到自定义的函数。
- 在自定义的函数中实现unhook(脱钩),恢复原API起始地址处字节。
- 执行自定义的代码以及原API函数,再次hook该API函数,以便下次拦截,最后返回。
执行命令gcc -m32 ./set_pwd.c -o set_pwd.exe
编译该程序。
用IDA反编译该程序得到如下伪代码:
在伪代码的适当位置加入断点(main、VirtualProtect、memcopy等处),在IDA中新建调试,多次按F7,直到进入main函数。
main函数首先对几个变量进行初始化,各个变量的作用暂不得而知。
1. 准备
要想获得API函数的地址,首先要获取模块地址,再获取模块内某个函数的地址。
因此先关注如下汇编代码:
GetModuleHandleA()
函数的原型如下:
HMODULE GetModuleHandleA([in, optional] LPCSTR lpModuleName);
由此可见,本程序由GetModuleHandleA()
函数获取了kernel32.dll动态链接库的句柄。
然后再关注:
GetProcAddress()
函数的原型如下:
FARPROC GetProcAddress([in] HMODULE hModule, [in] LPCSTR lpProcName);
由此可见,本程序在获取完kernel32.dll的句柄之后,将其作为参数和WriteFile即目标函数名一起压入栈,然后调用GetProcAddress()
函数,获取了WriteFile函数的地址。
至此第一步获取API函数地址完成。
2. 上钩
要完成修改和跳转,首先要更改要修改位置的权限为可读可写可执行(原为可读可执行)。因此需要用到VirtualProtect()函数,其原型如下:
BOOL VirtualProtect(
[in] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flNewProtect,
[out] PDWORD lpflOldProtect
);
四个参数分别是起始地址、修改的长度、新的属性、原来的属性存放的位置。其参数传递由下图中的代码完成。
var_18应当是用来存储lpAddress的临时变量。
首先压入lpflOldProtect,即指向变量的指针,该变量接收指定页面区域中先前的属性值。如果此参数为NULL或未指向有效变量,则函数失败。
再压入flNewProtect,即内存保护选项。该参数可以是内存保护常数之一。这里选择40h,即可读可写可执行。
接着压入dwSize,即要更改访问保护属性的区域的大小,以字节为单位。受影响页面的区域包括包含从lpAddress参数到范围内的一个或多个字节的所有页面(lpAddress+dwSize)
。这意味着跨越页面边界的 2 字节范围会导致更改两个页面的保护属性。此处选择5个字节。
最后压入lpAddress,即需要更改访问保护属性的页区域的起始页地址。
然后对函数进行调用,完成对属性的修改。此时EAX的值为1,即修改成功。
紧接着,程序将完成修改工作,先关注第一次memcpy函数的调用:
将原来的地址lpAddress拷贝到pOrgByte中待用。
将自定义函数的地址-5放到edx中,然后计算了一下原函数和新函数之间的距离,在Src+1的位置存储了这个值,此时查看Src处的值其实是0xE9拼上距离差的四个字节,0xE9是跳转指令,此时的Src数组就变成了跳转至新函数的指令。
最后memcpy函数将Size(5字节)大小的内存区域由Src处拷贝到void * var_18即原位置上,成功修改成跳转到新函数的指令。
之后的VirtualProtect函数又将修改位置的属性修改回原属性。
之后程序调用了CreateFileA函数创建了一个名为pwd.txt的文件,这并非本文重点。
然后,程序调用WriteFile函数,其原型如下:
BOOL WriteFile(
[in] HANDLE hFile,
[in] LPCVOID lpBuffer,
[in] DWORD nNumberOfBytesToWrite,
[out, optional] LPDWORD lpNumberOfBytesWritten,
[in, out, optional] LPOVERLAPPED lpOverlapped
);
首先用strlen算出待压入字符串的长度,然后压入lpOverlapped参数即如果使用FILE_FLAG_OVERLAPPED打开hFile参数, 则需要指向OVERLAPPED结构的指针,否则此参数为 NULL。此处为NULL。
再者压入lpNumberOfBytesWritten,即指向变量的指针,该变量接收使用同步 hFile参数时写入的字节数。此处为NumberOfByteWritten。
之后压入nNumberOfBytesToWrite,即要写入文件或设备的字节数。此处为刚才计算的字符串长度,
接着压入lpBuffer,即指向包含要写入文件或设备的数据的缓冲区的指针。此处为Str。
最后压入hFile,即文件或 I/O 设备的句柄。此处为CreateFile的返回值。
单步步入WriteFile发现该处指令已经被修改为跳转到MyWriteFile,即上钩成功。
3. 脱钩
进入MyWriteFile之后,首先进行脱钩操作,步入unhook函数。
此处与外部基本相同,先得到API函数地址,然后修改属性,将原指令覆盖到现在的指令上,最后复原属性,完成脱钩。
4. 执行
结束脱钩操作,就可以开始进行自定义操作了。此处仍然选择调用系统中的WriteFile函数,并新定义了一个Str为6B6F6F68h翻译为hook,以此作为新的写入值调用WriteFile,红框中的为五次参数压入。
最后以True作为MyWriteFile的返回值(恒成功)。
至此完成一次hook操作。
之后程序关闭hFile句柄,打开pwd.txt文件,打印打开成功与否,读入文件内容,打印读取失败与否(成功不打印)。
由下面代码结合上面代码可知String1是读取的输入字符串,String2是读取的文件内的字符串,由前面的分析得知此时文件内字符串为hook。
接下来只有当两者相同时才能进入congratulations分支,因此我们在程序中输入hook,验证是否成功。
合影留念!
附录
// set_pwd.c
// gcc -m32 ./set_pwd.c -o set_pwd.exe
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
BYTE pOrgByte[5] = {
0,
};
typedef BOOL(WINAPI *PFWriteFile)(HANDLE hFile, LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped);
void unhook() {
DWORD dwOldProtect;
PBYTE pWriteFile;
FARPROC pFunc;
pFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
pWriteFile = (PBYTE)pFunc;
VirtualProtect(pWriteFile, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect);
memcpy(pWriteFile, pOrgByte, 5);
VirtualProtect(pWriteFile, 5, dwOldProtect, &dwOldProtect);
}
BOOL __stdcall MyWriteFile(HANDLE hFile, LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped) {
FARPROC pFunc;
char buf[] = "hook";
unhook();
pFunc = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");
((PFWriteFile)pFunc)(hFile, buf, strlen(buf), lpNumberOfBytesWritten,
lpOverlapped);
return TRUE;
}
int main() {
HANDLE hFile;
HMODULE hKernel32;
FARPROC pWriteFile;
FILE *fp = NULL;
FILE *fkey = NULL;
PBYTE pEditFunc;
BYTE pJmpCode[6] = {
0xE9,
0,
};
char pwd[15];
char key[15];
int ret;
DWORD dwOldProtect, pOffset, dwWritenSize;
char buf[] = "real_pwd";
hKernel32 = GetModuleHandle("kernel32.dll");
pWriteFile = GetProcAddress(hKernel32, "WriteFile");
pEditFunc = (PBYTE)pWriteFile;
if (VirtualProtect(pEditFunc, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect)) {
memcpy(pOrgByte, pEditFunc, 5);
pOffset = (ULONGLONG)MyWriteFile - (ULONGLONG)pWriteFile - 5;
memcpy(&pJmpCode[1], &pOffset, 4);
memcpy(pWriteFile, &pJmpCode[0], 5);
VirtualProtect(pWriteFile, 5, dwOldProtect, &dwOldProtect);
}
hFile =
CreateFile("pwd.txt", GENERIC_ALL, 0, NULL, CREATE_ALWAYS, 0x80, NULL);
/*
if(hFile!=NULL)
printf("open1 success\n");
else
printf("open1 fail\n");
*/
WriteFile(hFile, buf, strlen(buf), &dwWritenSize, NULL);
CloseHandle(hFile);
if (fp = fopen("pwd.txt", "r"))
printf("open success\n");
else
printf("open fail\n");
if (fp != NULL)
fscanf(fp, "%s", pwd);
else
printf("scan fail\n");
printf("plz input key\n");
scanf("%s", key);
if (fp != NULL)
fclose(fp);
ret = lstrcmp(key, pwd);
if (ret == 0)
printf("congratulations!\n");
else
printf("try again!\n");
system("Pause");
return 0;
}