日期:2013-12-15  浏览次数:20549 次


作者: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的话这个调用会导致蓝屏