set_pwd API hook分析

set_pwd API hook分析

标准的API hook的流程如下:

  1. 获取API的函数地址。
  2. 修改API起始地址字节,使之跳转到自定义的函数。
  3. 在自定义的函数中实现unhook(脱钩),恢复原API起始地址处字节。
  4. 执行自定义的代码以及原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;
}

上述拙见如有谬误,敬请斧正!转载请注明出处,侵权请联系删除。

壬寅年孟夏月廿九
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未济672

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值