原文地址: https://bbs.pediy.com/thread-128733.htm
bycon 2011-1-28 16:44 18658
菜鸟作品,大牛请无视。如有错误或纰漏,望指出
之前看过很多关于进程创建的拦截,都是勾在NtCreateProcess或者NtCreateSection上,拦截到的往往都是Explorer进程中的线程,此时如果将线程卡在内核中,就会导致桌面卡死。这不是我想要的。
百度GOOGLE了好久,没找到类似的解决办法,逆又逆不出来,就只有自己翻书动脑子了(不知道各大安全卫士是怎么实现的)。
既然不能卡死Explorer的线程,那咱就卡别人去,卡谁?我想你已经知道了——被创建者的主线程
相关知识:
每一个线程都是从内核空间的KiThreadStartup开始运行的,线程在被创建的时候就指定了它在系统空间的上下文以及返回用户空间的自陷框架。在KiThreadStartup中,线程将运行级别降低至APC_LEVEL后调用PspUserThreadStartup(如果是系统线程,这里调用的是PspSystemThreadStartup),PspUserThreadStartup将用户空间ntdll.dll中的函数LdrInitializeThunk作为APC函数挂入APC队列。当线程返回用户空间时,就会检测到APC函数的存在,并先加以执行,直到APC队列中不再有请求时才算正式回到用户空间。
回到用户空间的什么地方呢?这要看线程被创建的时候设定的自陷框架了。如果是主线程,则回到BaseProcessStartup,非主线程则是BaseThreadStartup。至于用户空间给定的线程入口(如主线程的OEP),则存放在寄存器EAX中,作为参数调用BaseProcessStartup或者BaseThreadStartup,再由这二者之一将其放在一个SEH保护域中加以调用。
实现:
如果上面看晕了,没关系,只需知道每个线程都是在KiThreadStartup中开始运行的,如果是用户线程,则会调用PspUserThreadStartup。注意此时的IRQL为APC_LEVEL。
而我选择的挂钩地点就是PspUserThreadStartup。E9跳转需要5个字节,我选择在PspUserThreadStartup+2的位置JMP,即
1 2 3 4 5 6 7 8 9 10 |
|
相关函数:
程序中的g_uStartSign,是用来判断线程是否是进程的主线程的。它记录的是线程在用户空间的开始地址,如果是主线程,它就是BaseProcessStartup,非主线程就是BaseThreadStartup。ETHREAD结构中有这个值。g_uStartSign的赋值是通过创建一个线程(肯定不是主线程)调用DeviceIoControl,然后通过ETHREAD活得,这个值是BaseThreadStartup的地址。BaseProcessStartup和BaseThreadStartup是kernel32.dll中的函数,kernel32.dll在每个进程中映射的位置都相同,因此这两个函数的地址也在每个进程中也都相同。
1 2 3 4 5 |
|
注意:BaseProcessStartup或BaseThreadStartup是创建者进程在用户空间通过BasepInitializeContext设置的,如果以R0下安全,R3下不安全来算,这么判断是否主线程是不严谨的。这是学习的时候的一个测试,后面没改回来。
我测试过,如果把主线程的自陷框架中的StartAddress从BaseProcessStartup改成BaseThreadStartup,进程一样能运行起来,而把非主线程改成BaseProcessStartup,这个线程却运行不起来了。。
这里还有一个问题。尽管主线程拦住了,但是该进程中的EXE文件映像和ntdll.dll都已经映射完毕,如果这时候创建一个远程线程,并将线程入口指定为OEP,程序一样能运行起来。因此,我们要拦截的不止是主线程(第一个线程),而是该进程所有的线程,但是不可能每个线程都要给用户选择一遍。。
我的处理方法是维护一张进程PID表,如果有新创建的进程等待用户选择的时候,将其PID添加到表中。之后如果有线程创建,则遍历这张PID表,如果线程所属的进程正在等待用户选择,则让该线程在一个通知事件对象g_eventNotify上等待,否则放行。如果用户允许该进程运行,则在PID表中将该进程PID删除,再通知等待在事件对象g_eventNotify上等待的所有线程,这些线程判断它们所属的进程是已经允许运行还是在等待判断,从而放其运行或者继续等待。注意同步的问题。
具体程序请看附件。我加了注释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
|
进程和父进程信息的获取主要来源于EPROCESS结构。
PspUserThreadStartup不是一个导出函数,需要对其定位。这里用的是搜索内存。先从SSDT中找到NtCreateThread,再从NtCreateThread中找到PspCreateThread,最后再通过PspCreateThread找PspUserThreadStartup
函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
注意:
(1)此程序以测试为目的,用到了硬编码,不利于移植。此程序在XP sp3下测试通过,没在别的平台上测试过。主要用到的硬编码:(见sys\ProcessMon.c)
1 2 3 4 5 |
|
(2)这里对于线程是不是主线程的判断是不严谨的。BaseProcessStartup或BaseThreadStartup是创建者进程在用户空间通过BasepInitializeContext设置的,如果以R0下安全,R3下不安全来算,这么判断是否主线程是不严谨的。这是学习的时候的一个测试,后面没改回来。
此外,大四了,快要去找工作了,今后未必有时间慢慢看书,慢慢些帖子了。特此感谢看雪,感谢那些在论坛上奉献的大小牛们。我们的成长离不开你们的帮助。