admin 管理员组

文章数量: 887021

前言:最近为了完成课题,需要写系统API的钩子程序。然而系统API种类繁多,手动调配是不可能的。而只改写一些API来应付课题又不甘心,想起之前读某位大佬的论文,提到了写爬虫程序并改写的思路,遗憾的是并没有代码。今天仿照大佬的思路,自己动手了一遍,遇到了诸多问题,以这篇博客记录之。

一、爬取程序

首先,我进入了MSDN的网站(https://docs.microsoft/zh-cn/windows/desktop/api/ ),仔细的考量了一下其网站结构,可以发现,想要进入详细的API介绍页面需要三步:

  • 选择头文件
  • 选择详细的API
  • 查看API详细信息

这里以Readfile为例,如果我们想要看到readfile这个API的详细情况,我们需要做三步:点击fileapi.h头文件->找到API->爬取所需要的信息,网站结构如下图:

头文件界面

API界面

于是,我们的爬取路线就很明朗了:

  • 爬取头文件网址作为下级线索
  • 爬API网址作为下级线索
  • 爬你所需要的信息

完成这三步后便得到我们所需要的信息了,我在这里选择了爬取API名称、API原型、API所在的DLL这三条数据。

二、处理原型函数

为了写我们的hook程序,我们应用了easyhook这个开源工具。为此,我们需要编写自己的钩子函数。以WriteFile为例,我们需要将以下我们爬到的函数原型:

BOOL WriteFile(
  HANDLE       hFile,
  LPCVOID      lpBuffer,
  DWORD        nNumberOfBytesToWrite,
  LPDWORD      lpNumberOfBytesWritten,
  LPOVERLAPPED lpOverlapped
);

改写成我们需要的钩子函数:

BOOL WINAPI new_WriteFile(
	HANDLE       hFile,
	LPCVOID      lpBuffer,
	DWORD        nNumberOfBytesToWrite,
	LPDWORD      lpNumberOfBytesWritten,
	LPOVERLAPPED lpOverlapped
) 
{
	senddata("Writefile");
	return WriteFile(
		hFile,
		lpBuffer,
		nNumberOfBytesToWrite,
		lpNumberOfBytesWritten,
		lpOverlapped
	);
}

其中senddata(string str)函数是向我们的监控进程发送数据,这里可以改写成你自己函数,改写完了后再返回原API调用,这样我们就相当于写了一个新的系统API,这个API看起来似乎没什么不同,但其实中间中转过了我们的程序。
除了新的API,我们还要写对应的钩子语句,方便easyhook将我们的API覆盖原来的API:

	HOOK_TRACE_INFO hHook_WriteFile = { NULL };
	LhInstallHook(
		GetProcAddress(GetModuleHandle(TEXT("kernel32")), "WriteFile"),
		new_WriteFile,
		NULL,
		&hHook_WriteFile);
	ULONG ACLEntries_WriteFile[1] = { 0 };
	LhSetExclusiveACL(ACLEntries_WriteFile, 1, &hHook_WriteFile);

好了,将我们爬取到的API改写成这两部分,并放入我们的DLL就可以开始注入工作了。然而,这只是几百个系统调用中的一个而已。为了完成自动化配接任务,我写了以下一段代码,这段代码的作用是读取API.txt中的内容,即我们爬取的函数原型,改写成两部分,分别至Hook.txt(我们的钩子代码)和API_New.txt(新的API)。

#include "pch.h"
#include <iostream>
#include<fstream>
#include<string>
#include<vector>
#include <regex>

using namespace std;
int main()
{
	int lastpos = 0;
	int i = 0;
	ifstream API_Origin("D:\API.txt");
	ofstream API_Hook("Hook.txt");//储存钩子
	ofstream API_New("API_New.txt");//储存新的API
	//函数原型 
	string API_now;
	//用于获得参数的正则表达式
	regex regPere("[^ ][_a-zA-Z]+(?=,|\n)");

	//储存参数 0号位置为返回类型,1号位置为函数名称,后面的为参数
	vector<string> peres;//用于储存参数
	if (!API_Origin.is_open())
	{
		cout << "can not open this file" << endl;
		return 0;
	}
	//读取API文件中的一个API至我们的缓冲区,即‘;’前的内容
	getline(API_Origin, API_now, ';');
	
	cout << API_now;

	while (API_now[i] != ' ') i++;
	peres.insert(peres.end(),API_now.substr(lastpos, i-lastpos));
	lastpos = i + 1;
	while (API_now[i] != '(') i++;
	peres.insert(peres.end(), API_now.substr(lastpos, i-lastpos));

//写我们的新函数
	API_New << peres[0] << " WINAPI new_" << peres[1]<<API_now.substr(i,API_now.size()-i)
			<<"{\n"<<"	senddat(\""+peres[1]+"\");\n"<<"	return "<<peres[1]<<"(\n\t";
	sregex_iterator it(API_now.begin(), API_now.end(), regPere);
	sregex_iterator end;
	for (; it != end; ++it)
	{
		API_New << it->str() << ",\n\t";
	}

	API_New << ");\n}";

	return cin.get();

}

先来看看效果吧:


API果然被改写成了我们所需要的形式,总结一下,这段代码主要干了几件事:

  • 在函数返回值和函数名间加上了WINAPI
  • 改写了新的函数名
  • 增添了函数内容
  • 攥写返回值

因为比较晚了,代码比较粗糙,有空再改进,这里原本想得是利用一个vector来存储各种参数信息,后来写着写着觉得太麻烦,于是想尝试着用正则表示式,来获取各个参数,由于以前没用过正则,加上C++的正则机制又比较坑,没想到踩了更多坑。于是把这里的正则单独领出来讲讲。

三、正则的使用

先阐述一下目的吧,我们的函数原型如下:

BOOL WriteFile(
  HANDLE       hFile,
  LPCVOID      lpBuffer,
  DWORD        nNumberOfBytesToWrite,
  LPDWORD      lpNumberOfBytesWritten,
  LPOVERLAPPED lpOverlapped
);

而返回值需要如下:

	return WriteFile(
		hFile,
		lpBuffer,
		nNumberOfBytesToWrite,
		lpNumberOfBytesWritten,
		lpOverlapped
	);

所以我们得单独把原型里的每个参数拎出来,这种提取子字符串显然是用正则比较方便。细化一下需求,即:提取空格与逗号或换行符(最后一个参数)之间的字符串,但不包括空格与逗号或换行符。
一开始时我用了以下的形式:

“(?<= )[a-zA-Z_]+(?=,|\n)”

并在网上通过了测试:
当我得意洋洋的放在VS里跑时,却得到了下面的错误:

网上并找不到合理的解释,经过多次调试后我认为是C++中正则版本不太一样。这时候经过室友提示,可以在regex后加basic参数,然而试了还是不行。后来我改成了一下形式:

[^ ][_a-zA-Z]+(?=,|\n)

成功的过了调试,总结原因就是C++并不支持“?<=”这种语法(也可能是我没找对版本),以上的模板用于匹配两个特定字符间的字符串均可适用。

本文标签: 自己的 官网 系统 正则表达式 hook