函数在编程语言中的作用,无论哪本教科书都会大书特书,例如怎样调用,怎样传参,函数体在别的源文件又该怎么引用。但,危险漫步觉得再详细的教科书,都没写要调用别的进程的函数该怎么办。
调用别的进程的函数,就是所谓的远程调用。这里的“远程”,与网络范畴的“远程”有所区别。网络的远程是指网络中的其它计算机,而这里的远程,则是在同一台机器上运行的别的进程。什么时候需要远程调用呢?要使中的角色完成某种特定动作,可以采用模拟客户端发送数据包的方法实现。但这种方法工作量较大,如果能找到中该动作所对应的函数,那就考虑使用远程调用的方法。
我们知道,运行在保护模式下的现代操作系统,例如windows,通常是进程隔离的,也就是一个进程无法使用另一个进程的资源,包括函数。但是,由于实际编程的需要,windows也预留了跨进程的访问接口,例如CreateRemoteThread可以跨进程创建线程,ReadProcessMemory/WriteProcessMemory则分别可以跨进程读写取内存,而VirtuaLAllocEx则可以跨进程申请内存空间。了解了这些API,我们就可以来实现和丰富远程调用,唱主角是CreateRemoteThread。CreateRemoteThread是个曝光率比较高的API,很多人一看到它,第一反应就是远程注入dll。当然,远程注入dll也是可以间接实现远程调用,不过过于繁琐,我们还是去繁就简,重新认识一下CreateRemoteThread。
看起来眼花缭乱,其实常用的参数就三个,分别解决远程调用的三个问题。首先,既然是远程调用,那就得指定进程,通过第一个参数hProcess,就可以传指定进程的句柄。接着就要开始调用了。远程调用函数没有本地调用函数这么便利,不是简单的call加函数名,而是要call函数的首地址——不过有什么所谓,这样更有黑客的感觉:)获取函数首地址的方法可以另写一篇文章,这里就不作展开。我们把得到的首地址,填ACreateRemoteThread的第四个参数lpStartAddress,就实现了函数的远程调用。是不是简单得令人愕然,想立即动手试试?别急,还有最后一个常用参数lpParameter,即第五个参数。我们知道,有些函数是要传参数的,lpParameter就是专门用来放参数。不过,这里只有一个位置,只能传入一个参数,远程注入dll需要远程调用loadlibrary,这个函数只需要一个参数,所以没有问题,但如果要调用的函数有多个参数怎办昵?暂且按下不表。
现在,远程调用的核心部分就介绍完了,接下来实验一下。需要远程调用的目标进程的核心代码如下:
代码很简单,先输出本进程的进程ID,然后等待击键动作,最后调用print。这个print函数就是要调用的函数,它将弹出一个提示框。接着看远程调用的核心代码:
代码也很简单,这里由于已知进程ID,所以利用OpenProcess来获取进程句柄,接着就是调用CreateRemoteThread。Print函数的起始地址是Ox401000,由于不用传参数,所以lpParameter没为NULL。
看看效果,首先运行目标程序。
这里显示进程ID是Ox13CO,请注意光标的位置。现在运行远程调用的程序,输入进程ID,回车。
提示框出现了,请再次注意光标的位置。光标并没有动,说明没有击键动作,按程序原来的流程,不击键是不会弹对话框的,这说明我们成功的远程调用了print函数。当然,这只是个极简单的范例,在实际运用中需要灵活组合各种跨进程的API,才可以解决很多限制实现远程调用,例如刚才说的多参数问题。解决CreateRemoteThread无法调用多参数函数的问题,需要绕点弯子,曲线救国。首先,用VirtuaLAuocEx申请一片内存空间,接着,用WriteProcessMemory向该内存空间写入一个间接调用函数,间接调用函数的伪代码如下:
根据实际需要填写这些参数,当然,需要把它们转换为机器码。最后,用CreateRemoteThread远程调用这个间接调用函数,就可以实现多参数的远程调用。
这只是一个抛砖引玉的例子,可能还有更好更巧的解决方法,同时也还有更多细节问题,例如如何获取函数的返回值要怎么获取,这些就都待大家自己去发掘和解决了,危险漫步这里就不多做解释了。