UNICODE_STRING到wchar_t*的转换

这两天做了点Native API的东东,很近似驱动了。以前没有接触过驱动方面的东西,结果今天程序出了莫名其妙的bug。还好只是native api而没在内核,不然估计要看一天的蓝天白云下的雪崩了。

代码是注入的钩子,由于加载很早,先于exe本身的启动,故无法以dll注入的方式实现,只能以动态代码注入的形式,这样一来就无法在C语言级进行调试,只能通过反复的注释掉代码或插入CC指令、编译运行然后附加调试器的方法调试。

最后发现问题出在从UNICODE_STRING到C字符串的转换上。

开始是这么写的,并且90%的情况都能工作正常。

const wchar_t *str = punistr->Buffer;

这样写对么?看上去是对的,反正UNICODE_STRING初始化后最后是有个0字符的。

但是就有10%的概率不正常。仔细在一堆汇编里检查才发现,UNICODE_STRING中Buffer是个正常的字符串,而Length只到中间就结束了,操作系统这么做起到一个方便的截断字符串的功能,常用于取完整文件路径的目录。

于是修改代码为:

wchar_t str[256];
wcsncpy(str, punistr->Buffer, punistr->Length / 2);

对么?看上去解决了上面的问题,但是又崩了。再次仔细在一堆汇编里检查后发现原来strncpy不会往最后填写0字符。好吧,我承认这个错误不该在我这个水平上犯,但实际上我一直在用C++,很少用到strncpy,是故不知道或者忘了。

再次修改代码:

wchar_t str[256];
wcsncpy(str, punistr->Buffer, punistr->Length / 2);
str[punistr->Length / 2] = 0;

恩。它终于活过来了。立日志一篇提醒以后的自己。从这件事看来我现在还不适合写驱动诶。

VC++类成员函数委托(第一版)

前置博文:

2010.10.3:《有关C++类成员函数的指针调用

2012.12.18:《C++成员函数回调的一种实现

本研究为XDL项目(Xiaofei Development Library)的一部分。

 

实现基于X86动态代码的函数委托,并提供基于模板类的代码中显式调用。

目前实现的功能:

1、除pascal外任何调用约定的C++类成员函数通过cdecl调用约定的C函数指针进行调用的适配器。

2、thiscall调用约定的C++类成员函数通过stdcall调用约定的C函数指针进行调用的适配器。

其中1多用于代码中调用的委托,2多用于系统API传入回调函数(如WndProc、ThreadProc)。因为目前仅做到了通过RTTI识别函数指针类型的调用约定,而未做到识别参数的个数与长度,如果要做到识别参数,RTTI的parser庞大到一个小型编译器的地步,并且我对文法分析已经忘的差不多了。所以对于以上两点之外的调用约定转换考虑到用到的机会几乎为零,暂时舍弃。

使用方法举例:

class Worker
{
public:
	void ThreadProc(void *param)
	{
		return;
	}
};

int main()
{
	Worker worker;
	Delegate<void(*)(void*)> callback(&Worker::ThreadProc, &worker);
	callback((void*)12345);
	_beginthread(callback, 0, (void*)12345);
	for(;;);
	return 0;
}

主要实现代码:

static HANDLE xdl_exec_heap_handle = NULL;

void* xdl_exec_heap_malloc(unsigned int size)
{
	if (xdl_exec_heap_handle == NULL)
		xdl_exec_heap_handle = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
	return HeapAlloc(xdl_exec_heap_handle, 0, size);
}

void xdl_exec_heap_free(void *p)
{
	HeapFree(xdl_exec_heap_handle, 0, p);
}

static char xdl_fn_adapter_get_callconv(const char *type_raw_name)
{
	char *p = (char*)type_raw_name;
	if (*p++ == '.' && *p++ == 'P')
	{
		if (*p == '6')
			return *++p;
		else if (*p++ == '8')
		{
			for ( ; !(*p == '@' && *(p + 1) == '@'); ++p);
			return *(p + 3);
		}
	}
	return -1;
}

void* xdl_fn_adapter(const void* fn, const void *obj, const type_info *_In_Type, const type_info *_Out_Type)
{
	int size = 0;
	char *buf = NULL;
	int pos = 0;
	if (obj == NULL)	//c function
	{
		size = 5;
		buf = (char*)xdl_exec_heap_malloc(size);
		buf[pos++] = '\xe9';	//jmp
		*(int*)&buf[pos] = (int)fn - ((int)&buf[pos] + 4); pos += 4;
	}
	else	//c++ member function
	{
		char in_callconv = xdl_fn_adapter_get_callconv(_In_Type->raw_name());
		char out_callconv = xdl_fn_adapter_get_callconv(_Out_Type->raw_name());
		if (out_callconv == 'A')	// cdecl
		{
			size = 45;
			buf = (char*)xdl_exec_heap_malloc(size);
			*(int*)&buf[pos] = '\x55\x8b\xec\x81';	//push ebp; mov ebp,esp; sub esp,100
			*(int*)&buf[pos + 4] = '\xec\x00\x01\x00';
			*(int*)&buf[pos + 8] = '\x00\x56\x8d\x75';	//push esi; lea esi,[ebp+8]
			*(int*)&buf[pos + 12] = '\x08\x57\x8d\xbd';	//push edi; lea edi,[ebp-100]
			*(int*)&buf[pos + 16] = '\x00\xff\xff\xff';
			*(int*)&buf[pos + 20] = '\xb9\x40\x00\x00';	//mov ecx,40
			*(int*)&buf[pos + 24] = '\x00\xf3\xa5\x5f';	//rep movsd; pop edi
			*(short*)&buf[pos + 28] = '\x5e\x68'; pos += 30;	//pop esi; push
			*(int*)&buf[pos] = (int)obj; pos += 4;
			if (in_callconv == 'E' || in_callconv == 'I')		//thiscall or fastcall
				buf[pos++] = '\x59';	//pop ecx
			if (in_callconv == 'I')
				buf[pos++] = '\x5a';	//pop edx
			buf[pos++] = '\xe8';	//call
			*(int*)&buf[pos] = (int)fn - ((int)&buf[pos] + 4);	pos += 4;
			*(int*)&buf[pos] = '\x8b\xe5\x5d\xc3'; pos += 4;	//mov esp,ebp; pop ebp; retn
		}
		else if (in_callconv == 'E' && out_callconv == 'G')	//thiscall to stdcall
		{
			size = 10;
			buf = (char*)xdl_exec_heap_malloc(size);
			buf[pos++] = '\xb9';	//MOV ECX
			*(int*)&buf[pos] = (int)obj; pos += 4;
			buf[pos++] = '\xe9';	//JMP
			*(int*)&buf[pos] = (int)fn - ((int)&buf[pos] + 4);	pos += 4;	
		}
	}
	return buf;
}

void xdl_fn_adapter_free(void *p)
{
	xdl_exec_heap_free(p);
}

template<typename _CFn>
struct Delegate
{
public:
	Delegate()
	{
		m_obj = NULL;
		m_fn_in = NULL;
		m_fn_out = NULL;
		m_type_in = NULL;
	}
	Delegate(const Delegate& d)
	{
		m_obj = d.m_obj;
		m_fn_in = d.m_fn_in;
		m_fn_out = NULL;
		m_type_in = d.m_type_in;
	}
	~Delegate()
	{
		if (m_fn_out)
			xdl_fn_adapter_free(m_fn_out);
	}
	Delegate(_CFn fn)
	{
		new(this) Delegate();
		bind(fn);
	}
	template<class _Fn>
	Delegate(_Fn fn, void* obj)
	{
		new(this) Delegate();
		bind(fn, obj);
	}

	void bind(_CFn fn)
	{
		m_fn_in = fn;
	}

	template<class _Fn>
	void bind(_Fn fn, void *obj)
	{
		m_obj = obj;
		m_fn_in = xdl_force_cast<void*>(fn);
		m_type_in = &typeid(_Fn);
	}

	operator _CFn()
	{
		if (!m_fn_out)
		{
			m_fn_out = xdl_fn_adapter(m_fn_in, m_obj, m_type_in, &typeid(_CFn));
		}
		assert(m_fn_out);
		return (_CFn)m_fn_out;
	}
private:
	const void* m_obj;
	const void* m_fn_in;
	void* m_fn_out;
	const type_info *m_type_in;
};

C++成员函数回调的一种实现

成员函数回调即C语言函数(如Windows API)调用C++类成员函数,相当于C#中的委托。

实现这个功能有两个难点,一是C语言中不允许从成员函数指针到万能指针void*的类型强转,这个可以通过union来绕过。

第二是从C调用带this指针的C++要求每个函数C函数指针能够保存不同的this指针。这个必须通过动态代码技术实现。

这两个问题记得都困扰了很久,今天终于写出一种方案,能够简单地将stdcall调用约定的C函数转到thiscall调用约定的C++函数,这样便能实现WindowProc直接窗口子类化到类成员函数中,而不需要再通过窗口句柄hash表来查找窗口类指针。动态代码也很简单,因为stdcall与thiscall对堆栈处理相同,故只需要向ecx中填写this指针然后简单地jump到成员函数的代码地址就可以。

static HANDLE xdl_exec_heap_handle = NULL;

void* xdl_thiscall_adapter(void *obj, void *fn)
{
	if (xdl_exec_heap_handle == NULL)
		xdl_exec_heap_handle = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
	unsigned char *buf = (unsigned char*)HeapAlloc(xdl_exec_heap_handle, 0, 10);
	buf[0] = 0xB9;	//MOV ECX
	*(unsigned int*)&buf[1] = (unsigned int)obj;
	buf[5] = 0xE9;	//JMP
	*(unsigned int*)&buf[6] = (unsigned int)fn - ((unsigned int)buf + 10);
	return (void*)buf;
}

void xdl_thiscall_adapter_delete(void *p)
{
	HeapFree(xdl_exec_heap_handle, 0, p);
}

template<class _Out, class _In>
__inline _Out xdl_force_cast(_In value)
{
	union
	{
		_In in;
		_Out out;
	} u = {0};
	u.in = value;
	return u.out;
}

template<class _Obj, class _Fn>
__inline LPVOID xdl_thiscall_adapter(_Obj* obj, _Fn fn)
{
	return xdl_thiscall_adapter((void*)obj, xdl_force_cast<void*>(fn));
}

有关__declspec(property)的多态性

MSVC为了方便C++编写COM客户程序引入了property关键字。可能有些人不知道VC有这个功能,但是property的概念相信大家都清楚。例如:

class A
{
public:
	int _width;
	int get_width()
	{
		return _width;
	}
	void set_width(int value)
	{
		_width = value;
	}
	__declspec(property(get=get_width,put=set_width)) int width;
}

int main()
{
	A a;
	a.width = 3;
	int w = a.width;
	return 0;
}

这样的代码大家都不会陌生,可以大大方便我们的使用。

但是有个问题。虚函数与多态。假如写出下面的代码:

class A
{
public:
	virtual int get_width()
	{
		return 1;
	}
	__declspec(property(get=get_width)) int width;
}

class AA : public A
{
public:
	virtual int get_width()
	{
		return 2;
	}
}

那么编译器会如何操作呢。经过试验,发现在这个问题上VC还是有一些BUG的。具体结果如下:

1、A a; int w = a.width;

这样当然调用的是A::get_width,没有任何问题。

2、A *a = new A(); int w = a->width;

这样调用的也是A::get_width,正确。

3、AA aa; int w = aa.width;

注意了,这次调用的还是A::get_width,错误。

4、A *a = new AA(); int w = a->width;

这样调用的是AA::get_width,正确。

也就是说,编译器仅将property绑定到基类的操作方法上,而并没有识别派生类中有没有重写这个方法,所以使用值类型的对象调用时产生的还是前期绑定汇编,调用call被硬编码到基类中的函数上。而如果通过指针调用,编译器产生的代码为通过虚函数表调用的后期绑定汇编代码,由于后期绑定的特性,自动导向了派生类中的函数。即VC的property本身并不支持多态。事实上,COM函数都是以指针方式调用的,也就是说在property关键字原本用到的地方是不可能写出值类型的成员变量寻址的。

认识到这样的情况,如果要编写此类程序(一般为类似与VB与.NET的窗口应用,如对窗口的Text赋值),可以考虑人为制定一些编码规范,如将class仅作为指针类型使用,将struct作为数据结构的值类型使用。显然struct仅需要构造函数而无需继承。