欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

反远程线程注入的思路

程序员文章站 2022-03-29 11:57:28
作者: churui          某日,遇到一个奇怪的程序(你也许并不关心它的名字,我们就姑且称它为程...

作者: churui


         某日,遇到一个奇怪的程序(你也许并不关心它的名字,我们就姑且称它为程序A)。这个程序是十分霸道的,在与我的程序(你也肯定不会关心它的名字,所以我们称为程序B)同时执行的时候,总能从程序B(这里也就是进程B了)的数据段中读取到一些内容。这一点让我非常不爽,于是我决定给B加入自我保护的功能,让A不能轻易的读取。听起来有点象“磁心大战”?呵呵,总之交锋就是这样开始的,初衷也非常简单,而争夺的过程倒是几经波折。
       在战斗开始之前,还需要说明的是,这里的A和B都是GUI程序,而且B总是先于A执行。
       考虑一下A读取B的数据段,我能想到的也就是三种方法:
1.读取映象文件 
2.使用ReadProcessMemory   
3.注入进程B,然后直接读取。
对于第一种方法,只要B加一个简单的壳,A就无计可施。所以比较实用的还是后两种方法。经过观察,发现A在执行过程中给进程B注入了一个DLL,所以我判断A可能使用的是第三种方法。
       A给B注入DLL,一般来说也就是三种方法。但是不管哪种方法,我认为最终总要调用ntdll!LdrLoadDll。于是,最原始的办法就是在进程B中hook LdrLoadDll这个API,拦截可疑的DLL。实现的过程并不复杂,可惜没有效果。也就是说,A在注入DLL的过程中,根本没有被B拦截到。
       我使用IceSword,监视A的启动过程。发现A在启动时会在每个进程(当然,除了一些特殊的系统进程如Idle,csrss等等)中创建一个远线程。因此,我希望能把创建远线程拦截下来。考虑到一般创建远线程之前总要先用kernel32!OpenProcess得到进程句柄,我就试图在ring3使用hook api拦截所有的OpenProcess。可惜我的努力又失败了。本来hook OpenProcess工作的挺好,但是只要A一启动,对OpenProcess的hook马上失效。考虑到前面hook ntdll!LdrLoadDll也没有奏效,我认为A一定采取了某些防止hook api的手段。
       在这种情况下,我打算到ring0去解决问题。由于之前我只有win32的开发经验,而driver几乎没有做过,拿着罗云彬老大翻译的Kmd教程中文版恶补两天,又在driverdevelop找了一些源码和资料,终于拼凑出了一个勉强能运行的driver。功能倒也简单,就是通过修改SSDT(System Service Dispatch Table)。使得ntdll!NtOpenProcess执行到核心态的时候,能够被我拦截下来。
       这次还是失败了,而且失败的情况也和ring3如出一辙:本来ntdll!NtOpenProcess能够被拦截下来的,一旦A启动后,所有的拦截立刻失效。让我啼笑皆非的是,我做了另外一个简单的程序C,唯一的作用就是使用kernel32!OpenProcess去打开B,结果当A启动后,连这个C也能成功的打开B了。使用IceSword一看,原来A启动后,SSDT会被自动改回最初的内容,真让我无语。
       在driverdevelop上向等bmyyyud,zhaock等几位大虾请教了好几次,得出了以下的结论:ntdll中导出了NtOpenProcess和ZwOpenProcess,而两者实际上是一回事。ntosknrl中也导出了NtOpenProcess和ZwOpenProces,而两者完全不同:ntosknrl!NtOpenProcess实际上就是“正常的”SSDT入口,而ntosknrl!ZwOpenProcess,不知道它是干什么的。如果修改SSDT,其实很容易被别人发现并且破掉(把入口换回NtOpenProcess就可以了)。那么有没有其他方法?看有的资料上说可以换int 2 e的中断处理函数,而xp中既然用sysenter/syscall了,看来这一招也不灵。最终,bmyyyud大虾提示我,可以改正常ntosknrl!NtOpenProcess的入口代码。(非入口~~~~)
       修改ntosknrl!NtOpenProcess的入口代码比修改SSDT难度要大一些。首先ntosknrl!NtOpenProcess的代码所在页面具有只读属性,在修改页面前需要先修改CR0寄存器的第16个bit,否则一定是蓝屏没商量,对于我这样的新手来说,这还真有点让人无所适从。其次,为了修改入口代码,最方便的方法是使用微软的detours,可惜detours中引用了很多ring3的API,无法直接使用。只好拿来detours的源码,把不必要的细枝末节都剪裁掉,把所有使用到ring3 API的部分都尽量去掉或者用ring0的API代替。只留下最关键的部分,试着run了一把,没想到居然能运行。
       这样做完之后,发现A程序调用ntosknrl!NtOpenProcess并且被我拦截了,颇感欣慰……没想到见鬼的是,在A程序调用ntosknrl!NtOpenProcess失败的情况下,它仍然给B创建了一个远线程。这个时候zhaock大虾告诉我,即使不调用NtOpenProcess,也可以调用PsLookupProcessByProcessId,ObOpenObjectByPointer来达到同样的目的。看来还需要拦截ntosknrl!NtCreateThread,保险起见,把ntosknrl!NtReadVirtualMemory也拦截下来。一切做完后,试运行,终于把A彻底拦住了,它再也没能用B中读出一个字节….. 我忽然想到了huyg师兄曾经说的“资料不是最重要的,蛮干才是最重要的”……