一个WinApi Hook程序(上)

项目地址:https://github.com/HDRorz/WriteFileHook

起因是各系统日志太多,机器也太多,查日志和监控都非常痛苦,所以有意做一套日志收集系统。其实各大公司都有这种东西,现成的也有Elasticsearch + Logstash + Kibana这套解决方案。然而上头就有点推进困难,也畏首畏尾。其实日志系统的demo也做了3、4版了,各种方案都有。

我接到开发任务的时候大概是16年10月份,那时方案设计是直接在日志中间件里埋点,异步调用一个webapi统一存储,同时还实现了服务存活检测(

当然当时我还有另一个脑洞,就是无需修改原系统,直接hook writeFile的API,把读到的内容发到日志收集系统。

原系统架构是.net的,hook部分也使用.net实现。这里使用了nektra公司的Deviare2。(nektra还有SpyStudio这样的好东西,然而并用不大来)

Deviare2官方提供了Demo,不过我还是花了很久才搞成功我想要的功能。

总结一下,这个问题在于对WinApi不熟悉,主要是函数传递是引用还是指针还是值,以及对.net(C#)互操作的不熟悉。

MSDN上的WinApi全家桶,或者找网上下载的中文版手册(chm)

System.Runtime.InteropServices类库和互操作文档

其中最重要的有

用平台调用封送数据(主要是基本类型互转)

使用非托管 DLL 函数

默认封送处理行为

字符串

封送处理字符串

字符串的默认封送处理

结构体

传递结构

封送类、结构和联合

对象的默认封送处理

字符串的难点在于在C++里字符串就有很多种定义形式,比如说char*、char[]、LPSTR,还有t_char、w_char、LPCSTR、LPWSTR、LPCWSTR。然而.net里只有char[],string,stringbuilder。

结构体的难点在于在C++里的结构体在函数上可能是struct或者struct*,然而.net可以是struct或者class。

其中大致结果如下

字符串作为返回或者入参或者结构体成员时
IN char[]		[DllImport("kernel32.dll", CharSet = CharSet.Ansi)]、[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]String
IN w_char[]		[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]、[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]String
IN t_char[]		[MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]String
IN LPSTR,char*		[MarshalAs(UnmanagedType.LPStr)]String
IN LPWSTR,w_char*	[MarshalAs(UnmanagedType.LPWStr)]String
IN LPTSTR,t_char*	[MarshalAs(UnmanagedType.LPTStr)]String

字符串作为函数出参时
OUT char[]		[DllImport("kernel32.dll", CharSet = CharSet.Ansi)]、[Out, MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]StringBuilder
OUT w_char[]		[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]、[Out, MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]StringBuilder
OUT t_char[]		[Out, MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]StringBuilder
OUT LPSTR,char*		[Out, MarshalAs(UnmanagedType.LPStr)]StringBuilder
OUT LPWSTR,w_char*	[Out, MarshalAs(UnmanagedType.LPWStr)]StringBuilder
OUT LPTSTR,t_char*	[Out, MarshalAs(UnmanagedType.LPTStr)]StringBuilder

结构体值传递和指针时
void A(struct a)	void A(struct a)
void A(struct* a)	void A(ref struct a)

这里在推荐一个站点http://www.pinvoke.net/,里面提供了WinApi 各种类型、枚举、结构体、函数的C++和C#描述。

然而C# Pinvoke互操作并不是万能,任何数组相关的包括字符串,C#都要求预定义数组长度(字符串就是字符数组,字符串也要定义长度),数组长度上限为0x1FFFFFFF。然而即使没定义到上限,在x86下会出现OutOfMemory错误,在x64下也会出现

无法封送处理“parameter #2”: 内部限制: 结构太复杂或太大。

这种时候微软爸爸也有解决方案,有一个C++/CLI的操作可以供我们使用。

C++/CLI这个东西教程真的看不懂。

官方教程 大致是C++/CLI 任务 和 C++/CLI 迁移入门 这两节。

中文博客的话 http://www.cnblogs.com/TianFang/p/4931879.html 这种差不多也是抄了一遍MSDN。

namespace WinApiReader {

	public ref class SystemHandle
	{
	public:

		unsigned int ProcessId;
		unsigned char ObjectType;
		unsigned char Flags;
		unsigned short Value;
		unsigned int Address;
		unsigned int GrantedAccess;
	};

	public ref class SystemHandleInfo
	{
	public:

		int Count;
		array<SystemHandle^> ^SystemHandles;
	};

	public ref class WinApiReader
	{
	public:

		int QueryProcessHandleInfo(int ProcessId, [System::Runtime::InteropServices::OutAttribute] SystemHandleInfo^% HandleInfo);
	};
}
int WinApiReader::WinApiReader::QueryProcessHandleInfo(int ProcessId, [System::Runtime::InteropServices::OutAttribute] SystemHandleInfo^% HandleInfo)
HandleInfo = gcnew SystemHandleInfo();
HandleInfo->SystemHandles = gcnew array<SystemHandle^>(outCount);

总结一下,大致是修改了托管类声明时的语法,用了gcnew关键字替代了C#里的new关键字,用来实例化托管类。在标注托管类型时会在类型后面再加一个^符号,假如是托管类型引用传递时,还会在后面再跟一个%符号。别的语法就看VS的自动提示了,实在不会就google(baidu真的救不了你

 

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注