CVE-2019-0708 metasploit EXP分析
2019-09-16 19:56

报告编号:B6-2019-091602

报告来源:360-CERT

报告作者:houjingyi233

更新日期:2019-09-16

0x00 漏洞背景

2019年09月07日rapid7在其metasploit-framework仓库公开发布了CVE-2019-0708的利用模块,漏洞利用工具已经开始扩散,已经构成了蠕虫级的攻击威胁。这里对该漏洞利用的原理做一个技术分析。

0x01 利用分析

漏洞原理部分就不再赘述了,简单来说就是由于UAF漏洞IcaChannelInputInternal->IcaFindChannel会返回一个指向的内容已经被释放了的指针,随后有一处间接调用,通过喷射占位让这里的rax寄存器指向我们的shellcode就可以RCE。

enter description here

enter description here

在windows 7上,非分页内存的起始地址是固定的。在虚拟机中可能由于环境不同导致该地址不同,由于EXP中硬编码了这个地址,所以可能导致漏洞利用失败。可以按照下面的方法自己修改这个硬编码的地址。

如果是virtualbox虚拟机,先使用下面的命令dump虚拟机的内存:

enter description here

然后下载安装google的内存取证工具rekall(https://github.com/google/rekall/releases/download/1.7.2rc1/Rekall_1.7.2.p1_Hurricane.Ridge_x64.exe),扫描得到的memdump文件得到非分页内存的起始地址:

enter description here

如果是vmware虚拟机创建一个快照使用同样的命令扫描得到的快照文件即可。如果你是按照默认步骤创建的,那么非分页内存的起始地址应该和这里一样都是0xfffffa8001802000。windows 7虚拟机的默认内存是2GB,这个值比真实机器的内存小,所以把GROOMSIZE从250 MB修改为50MB。此时,应该可以直接使用该EXP在虚拟机上实现稳定的RCE了。

EXP编写主要的两个问题就是如何喷射占位和如何编写shellcode。下面我们来看代码。

enter description here

回顾一下RDP协议的流程,client收到这个PAKID_CORE_CLIENTID_CONFIRM之后应该发送client device list announce request包,发送这个包之后执行exploit代码。

enter description here

enter description here

非分页内存的起始地址是0xfffffa8001802000,我们希望call [rax]时rax的值是0xfffffa8004a02048,0xfffffa8004a02048中的值是0xfffffa8004a02058,0xfffffa8004a02050处开始payload。因为payload以查找其的标志USERMODE_EGG/KERNELMODE_EGG开始(egg hunting),所以将0xfffffa8004a02048中的值设置为0xfffffa8004a02058刚好可以从payload真正的功能处开始。

enter description here

payload分为kernel mode payload和user mode payload,kernel mode payload由两个部分组成:KERNELMODE_EGG和(kernel mode 的)payload;user mode payload由四个部分组成:USERMODE_EGG,egg_loop,USERMODE_EGG和(user mode 的)payload。egg_loop用来查找KERNELMODE_EGG并跳转到(kernel mode 的)payload;(user mode 的)payload才是真正在用户态运行的反弹shell的payload(egg_loop其实也是在内核态运行的)。

准备好payload之后伪造UAF的结构体,在IcaChannelInputInternal中的ExAllocatePoolWithTag中可以看到,实际分配的内存会比发送的数据多0x38个字节。

enter description here

考虑进去这0x38个字节,又因为间接调用的偏移是0x100个字节,所以需要在第0xC8个字节放上0xfffffa8004a02048。

enter description here

做好这些准备工作之后就可以开始pool spray了。首先发送大量大小为0x128的spray_channel包(分配0x128+0x38=0x160字节大小的内存),再发送触发漏洞的包释放MS_T120 channel,由于其大小也是0x160字节,所以此时再发送大量和前面相同的spray_channel包就会占据被释放的MS_T120 channel的内存。

enter description here

enter description here

之后发送大量含有kernel mode payload或者user mode payload的包占据从0xfffffa8001802000开始到0xfffffa8004a02000的非分页内存,因为header的大小是0x48,所以最终0xfffffa8004a02048中的值是0xfffffa8004a02058,后面可能是kernel mode payload或者user mode payload。

enter description here

最后断开连接触发漏洞,call [rax]这里调用了user mode payload,如前所述,只是用来查找KERNELMODE_EGG并跳转到kernel mode payload。

enter description here

enter description here

接下来的代码基本修改自https://github.com/worawit/MS17-010/blob/master/shellcode/eternalblue_kshellcode_x64.asm。第一步是利用rdmsr/wrmsr hook KiSystemCall64并保存原来KiSystemCall64的地址,KernelApcDisable++启用内核APC然后直接返回IcaChannelInputInternal的上层函数。

enter description here

enter description here

在我们的KiSystemCall64中首先照抄原来的5行代码,保存寄存器,加锁防止接下来的代码重复执行,恢复KiSystemCall64为原来KiSystemCall64的地址加上0x1F防止重复执行开头的5行代码,调用下一段shellcode。

enter description here

enter description here

因为已经知道了KiSystemCall64的地址,所以通过PE文件MZ头找到ntoskrnl.exe加载到内核中的地址。

enter description here

分别通过KPCR和psgetcurrentprocess取得当前的ETHREAD和EPROCESS。

enter description here

通过psgetprocessimagefilename找到EPROCESS.ImageFilename的偏移,因为EPROCESS.ImageFilename和EPROCESS.ThreadListHead偏移为0x28,所以可以据此找到EPROCESS.ThreadListHead的偏移。

enter description here

enter description here

ETHREAD通过ThreadListEntry链接到EPROCESS.ThreadListHead,所以遍历ETHREAD.ThreadListEntry,当它和前面取得的ETHREAD的差小于0x700时说明得到了ETHREAD.ThreadListEntry的偏移。

enter description here

直接读取psgetprocessid函数地址+3处的值也就是汇编代码中的180h得到EPROCESS. UniqueProcessId的偏移, 因为EPROCESS.UniqueProcessId和EPROCESS. ActiveProcessLinks偏移为0x8,所以可以据此找到EPROCESS.ActiveProcessLinks的偏移。

enter description here

enter description here

enter description here

前面已经得到了EPROCESS.ImageFilename的偏移,所以遍历EPROCESS.ActiveProcessLinks计算每个EPROCESS.ImageFilename的hash和spoolsv.exe的hash对比,直到找到spoolsv.exe为止。如果一直没有找到就释放前面通过lock cmpxchg上的锁以便下一个被劫持的系统调用运行shellcode。

enter description here

找到spoolsv.exe之后先保存PEB的地址以便稍后查找CreateThread函数的地址,前面已经得到了EPROCESS.ThreadListHead和ETHREAD.ThreadListEntry的偏移,所以遍历ETHREAD,直接读取psgetthreadteb函数地址+3处的值也就是汇编代码中的0B8h得到KTHREAD.TEB的偏移, 因为KTHREAD.TEB和KTHREAD.Queue偏移为0x8,所以可以据此找到KTHREAD.Queue的偏移。根据注释中的解释,KTRHEAD.Queue为NULL时TEB.ActivationContextStackPointer也是NULL,而当TEB.ActivationContextStackPointer为NULL时会出现访问异常而crash掉。所以需要遍历ETHREAD直到找到一个KTRHEAD.Queue不为0的ETHREAD为止。

enter description here

enter description here

enter description here

接下来调用KeInitializeApc和KeInsertQueueApc插APC,在KernelApcRoutine中调用ZwAllocateVirtualMemory分配内存,将user mode payload的最后一部分真正在用户态运行的payload拷贝到这块分配的内存。

enter description here

然后根据前面保存的PEB地址通过LDR链找到kernel32.dll的地址然后找到CreateThread的地址,保存到SystemArgument1。

enter description here

最后KiUserApcDispatcher函数调用真正在用户态运行的payload,并且参数是CreateThread,因为此时还处于原来的被打断的线程,所以首先需要新建一个线程。

enter description here

0x02 时间线

2019-09-07 metasploit-framework仓库公开发布了CVE-2019-0708的利用模块

2019-09-16 360CERT发布分析

0x03 参考链接

  1. Playing with the BlueKeep MetaSploit module
  2. ETERNALBLUE Exploit Analysis and Port to Microsoft Windows 10