进程内的 API 拦截是比较常见的一种技术,通常是采用挪移的方法,把原代码复制到另外一块预留好的代码空隙处,然后将原函数的入口处指令改为调转至我们自己的实现,然后在自己的实现里根据情况再看要不要执行原来的代码。
今天中午虞美人调试程序的时候出现了怪事,在拦截 API 的那函数里,竟然发现在函数内访问参数时会出现访问违例!更奇怪的是,这个问题仅在某些 Windows Server 2003 英文版上出现。
经过反复调试和仔细分析,发现这一问题的根源是由于在上面不远处调用了 VirtualProtect(),以便能对代码中预留出的空间进行改写。如果不进行这个调用,那么就无法保存原函数的前面的指令,如果调用了它,就会导致本函数自己访问参数(或者局部变量)引发违例。猛然想起该函数虽然有改变保护模式的起始地址,但由于硬件的原因,真正影响到的是该地址所处的至少一个内存页。由于拦截函数自身和空隙的间距太近,可能处在同一页上,于是对空隙地址处的访问修改同时也影响到了函数自身。
可是怎么修改呢?总不能硬在其中插上 64K 的垃圾代码吧?灵光一闪,突然记起 #pragma 来,可以指定代码所处的代码节的名字。于是把空隙函数使用 #pragma code_seg() 指令包围了起来,编译后果然问题不再出现。
但别的操作系统为什么不会有事,尚不知道,大概是操作系统自身作了处理。