最近忙个程序,遇到不少问题,当然也都解决了。我感觉我记性不大好。别以后再碰见了想不起来为啥,就当这个是记事吧。
这个问题的目的是监控所有对NtWaitForSingleObject的调用,并尽可能的回溯出调用堆栈,然后用OutputDebugString输出。
这个问题比较复杂,因为NtWaitForSingleObject是ring3下最底层的Wait系列函数了,很多函数都会用到,比如那个OutputDebugString。这样就有个重入问题。 一开始我用的是hook NtWaitForSingleObject函数,跳到我的函数里,鉴于这些native函数太简单,就没有跳转回去,直接把他的3行代码写到我的函数里,当然之前加上对是否是重入的判断。这个判断很简单,就判断一下返回地址是否是OutputDebugString里所使用的地址,是的话直接跳过监控。这种方法大致可行。 虽然可能因为那个不完美的判断漏过一些。用这种方法,编个EXE测试是可行的。但是应用到目标就不可以了,目标比较变态,经常遇见C语言写的函数不能用,得写__declspec(naked)的才行。
后来想想 就放弃了跳转到我的hook,直接把NtWaitForSingleObject写成个0xCC,然后再异常处理里玩他,方法是先写回原值,然后回溯、输出。在EXE里也灰常成功。 弄到dll里,果然很诡异。把几个局部变量弄成全局的,好了一些,把几个函数也弄成汇编的,又好了一些。但是始只能回溯几次。
为什么在EXE里测试,灰常成功那? 一想,原来是同步问题。我的EXE是单线程的。目标里面,是多线程的。明显是当某个线程正在恢复时,其他线程又给写成0xCC了。
啊,这需要个同步呀。然而我做掉的,就是用来同步的。还是模拟个传说中的 自旋锁 吧:
DWORD g_SpinCount = 0;
void __declspec(naked) MySpinEnter()
{
__asm
{
BEGIN:
mov eax,0;
lea ecx,g_SpinCount;
mov edx,1
lock cmpxchg [ecx],edx;
jz RETURN;
call dword ptr[SwitchToThread];
jmp BEGIN;
RETURN:
ret;
}
}
void __declspec(naked) MySpinLeave()
{
__asm
{
mov eax,0;
lock xchg eax,g_SpinCount;
ret;
}
}
哎,谁知道怎么让编译器在函数开头加一些恶心的代码,直接翻译我的代码那? 给说一下,一起交流交流呗....