Introduction

The present document analyzes previous disclosed use-after-free in Hewlett Packard Enterprise Intelligent Management Center, a network management platform. The vulnerability was reported by Steven Seeley to the Zero Day Initiative (ZDI) disclosure program identified as CVE-2017-12561.

The flaw allows to perform an unauthenticated remote attack and execute arbitrary code on vulnerable installations. HP fixed this vulnerability in October 2017, and all vulnerable instances should be updated to version HPE Intelligent Management Center (iMC) iMC Plat 7.3 E0504P4 or earlier to avoid being exposed according to the released security security bulletin.

Background

Before going deep into the details, we must be aware of some technical topics, namely the target installation environment and an overview of ASN.1 BER Encoding as the packets to communicate with the server are in this format.

Installation Environment

Following the exercise’s description, the target was installed in a Microsoft Windows Server 2008 R2 virtual machine. It was a good challenge because everything was in this OS is obsolete. After looking at the manual, I could configure the SQL Server and the sa account. The installation of the patched version was straightforward - the same for applying the vulnerable binary as it was simply by installing its components.

The first task is to know how the software works. After reading the documentation,installing both vulnerable and patched versions of HP iMC, and saving the respective DBman binaries for further analysis using IDA.

As seen with the netstat command, port 2810 regarding DBman is listening, and the service is ready to be analyzed.

DBman Running in MS Windows Server 2088 R2
DBman Running in MS Windows Server 2088 R2

Quick Intro to ASN.1 BER Encoding

ASN.1 (Abstract Syntax Notation One) is a notation for describing abstract types. A type is a set of values. A value of a given ASN.1 type is an element of the type’s set. ASN.1 has four types: simple types, which are “atomic” and have no components; structured types, which have components; tagged types, which are derived from other types; and other types, which include the CHOICE type and the ANY type.

The following table shows a list of ASN.1 types.

Type Tag number (decimal) Tag number (hexadecimal)
INTEGER 2 02
BIT STRING 3 03
OCTET STRING 4 04
NULL 05 05
OBJECT IDENTIFIER 6 06
SEQUENCE and SEQUECE OF 16 10
SET and SET OF 17 11
PrintableString 19 13
T61String 20 14
UTCTime 23 17

BER Encoding

BER (Basic Encoding Rules) is a subset of the ASN.1 standard. Its purpose is to allow to encode or represent values of each ASN.1 type as octets. BER uses a form of encoding commonly known as Tag-Length-Value.
Each item is encoded as a tag, indicating what type it is, a length implying the object’s size, and a value containing the object’s actual contents.

There are various methods to encode values using BER. For more detail, see here.

Vulnerability Analysis

Patch Diffing

The patch code (E0504) for this vuln should not suffer a lot of changes since it is a close release from the vulnerable version (E0504P04).

Using IDA with the BinDiff plugin can give an idea of what functions were modified.

BinDiff output in IDA
BinDiff output in IDA

The matched functions tab shows two modified functions, sub_4562C0 and sub_46CFD0.

On the one hand, checking sub_4562C0, nothing, at first sight, looks to be relevant.
On the other hand, sub_46CFD0 seems to be interesting as it appears inside a big switch-case where the condition is the opcode (10012) described in the advisory.

Opcode 10012 Switch-case Code
Opcode 10012 Switch-case Code

These are good hints to discover the flow and trigger the use-after-free vulnerability.

sub_46CFD0 function
sub_46CFD0 function

Inspecting this function, nothing looks to be related to a use-after-free. The next step is analyzing the code related to the 10012 opcode.

Code Analysis

Firstly, the code checks the number of bytes received from the socket according to the documentation.

Recv Size Buffer
Recv Size Buffer

ACE recv API
ACE recv API

It reads 4 bytes of data and allocates a buffer of that size. So from this code, we can think the software expects a packet with the format: opcode+size_buffer+buffer_data

Testing it with a simple python client it logs some odd errors because everything should have the same size.

#!/usr/bin/python3

from pwn import *
from struct import pack, unpack


def main():

    host = '192.168.1.133'
    port = 2810 # default port according to documentation
    conn = remote(host, port)

    #sends --> opcode,size of buffer being allocated, buffer data

    payload = b''
    payload += pack('>i',10012) # opcode
    payload += pack('>i',144) # size of buffer
    payload += pack('>i',0x41*144) # buffer data
    
    conn.sendline(payload)

main()

DBman logs the runtime errors in C:\Program Files\iMC\dbman\log\dbman_debug.log. The following logs show the result triggered by the above script.

Log Data length error
Log Data length error

Data length error code
Data length error code

From the decompiled code, we identified the error message. This code block is executed when the size of buffer_data is not equal to size_buffer. In other words, it fails the check ( *(_DWORD *)recv_size_buffer == size_data ).

Let’s examine the code in case this check is valid.

Data Buffer size check condition
Data Buffer size check condition

The first condition is checking if the size of the buffer sent in the packet corresponds to the data buffer size. After, check if the buffer data from the packet is correctly allocated to a buffer.

Data buffer length check function
Data buffer length check function

If everything goes well, an ASN object is created and releases the buffer sent with the packet using the free function. After it, as a good practice, the reference to this object is Nullified to clean the pointer.

Valid Buffer data length
Valid Buffer data length

After this, a function named AsnRemoteReservedFileRemove looks to initialize an ASN object using octets.

Decompiled asnRemoteReservedFileRemove Function
Decompiled asnRemoteReservedFileRemove Function

After this function appears, another function with a variable with AsnRemoteReservedFileRemove::BDec attributed.
Again an ASN reference should be related to ANS.1 format. Searching on the internet for DBman and ASN keywords appeared, and some blog posts about other vulnerabilities in DBman.

Decompiled asnRemoteReservedFileRemove::BDec Function
Decompiled asnRemoteReservedFileRemove::BDec Function

I read this post about opcode 10020. The post mentions how DBman packets are constructed and confirms the use of ASN.1 BER encoded objects in the communication packets.

Obs: After researching what SNACC is, I found out that is a ASN.1 compiler to C/C++

The post mentions a client named Intelligent Deployment Monitoring Agent.

In fact, it makes sense if the DBman service is listening, a client to communicate with the server should exist.

Decompiled Deploy.jar classes
Decompiled Deploy.jar classes

Inspecting the AsnRemoteReservedFileRemove.class, we the similarity with the decompiled we have seen previously.

asnRemoteReservedFileRemove Function
asnRemoteReservedFileRemove Function

It seems to be a custom implementation of ASN.1 BER Encoded packets for DBman. Now we can build our client as we know the packet structure accepted by the server.

####################################
# ASN.1 BER encoded packets script #
####################################

#!/usr/bin/python3

from pwn import *
from struct import pack, unpack
from pyasn1.type.univ import Sequence, OctetString, Integer, Null
from pyasn1.codec.ber import encoder
import time


class AsnRemoteReservedFileRemove():
    def __init__(self, reservedFilePath, backupPath, backFileExt, time):
        #super(AsnRemoteReservedFileRemove, self).__init__()
        self.reservedFilePath = reservedFilePath
        self.backupPath = backupPath
        self.backFileExt = backFileExt
        self.time = time

    def encode(self):
        self.message = Sequence()
        self.message.setComponentByPosition(0, OctetString(self.reservedFilePath))
        self.message.setComponentByPosition(1, OctetString(self.backupPath))
        self.message.setComponentByPosition(2, OctetString(self.backFileExt))
        self.message.setComponentByPosition(3, Integer(self.time))
        self.message.setComponentByPosition(4, Null())
        return encoder.encode(self.message)

def main():

    host = '192.168.1.133'
    port = 2810 # default port according to documentation
    conn = remote(host, port)
    
    # sends --> opcode,size of buffer being allocated, buffer data    

    msg_obj = AsnRemoteReservedFileRemove("C:\\Users\\Eduardo\\down.PS1", "C:\\Users\\Eduardo\\imc_patched\down.PS1",".log", time.time())
    asnEncoded_msg = msg_obj.encode()

    payload = b''
    payload += pack('>i',10012) # opcode
    payload += pack('>i',len(asnEncoded_msg)) # size of buffer
    payload += asnEncoded_msg # buffer data

    conn.sendline(payload)

main()

With this script, I could unintentionally trigger an error that appeared to crash DBman.

DBman crash log
DBman crash log

Why this failed?

Somehow the if condition was true. Inside it is the string “dbman_decode_len failed” which was the one logged when the crash happened. So this function must somehow be trying to decode the ASN.1 object sent in the packet, which failed during the process.

dbman_decode_len function prototype
dbman_decode_len function prototype

Checking the decompiled jar, there is no reference to this string. Analyzing the function looks like it is trying to check if the size from the data_buffer is right and tries to store the length in a variable if the check is acceptable.

For curiosity checked aLiuan814ucj variable reference. It is located in the data section, which seems to be a string already present at the compilation time.

aLiuan814ucj in data section
aLiuan814ucj in data section

Looking in the decompiled jar and found it being used as a key with DES symmetric algorithm.

aLiuan814ucj in deploy.jar
aLiuan814ucj in deploy.jar

So sub_454120 should be related to DES decryption using liuan814 as the key. Not sure if this is the case, but since the function is about decoding the ASN.1 object, it seems to be trying to decipher the data_buffer and check if everything goes well. \

The function sub_454BA0 with the rest division by 8 appears to check if the data_buffer octets are compliant. As it deals with ASN.1 OctetString types, the remainder should always be zero. If not, there is something wrong with the object.

Check data_buffer ASN.1 octets
Check data_buffer ASN.1 octets

When successful returns the buffer size. If the payload triggered the error, the ASN.1 object octets remainder after division should not be zero. Printing the size of the encoded BER object, it outputs 90.

Payload Size that triggers the crash
Payload Size that triggers the crash

90 % 8 = 11,25

The remainder is not zero, so DBman can’t decode the object and logs the error. DBman, after that, crashes and reboots. Something unusual is happening after the logging function (sub_475470). In this case function, sub_45EC20 should have an issue.

Code location where the crash occurs
Code location where the crash occurs

Use-After-Free (UAF)

Root Cause Analysis

Checking sub_45EC20, there is no relevant information.

IDA subEC20 decompiled function
IDA subEC20 decompiled function

Checking the disassembled view of this function, there is no interesting information too.

IDA subEC20 disassembled function
IDA subEC20 disassembled function

But there are two paths the flow can take. Let’s debug the software to get more details. Olly Debugger was the debugger used for this task.

Attaching dbman.exe process to Olly Debugger
Attaching dbman.exe process to Olly Debugger

Putting a breakpoint in 0045EC20, which is the address of sub_45EC200. The plan is to execute the payload and iterate with Ollydbg and see what happens when the application crashes.

Debugging subEC20
Debugging subEC20

Iterating through these instructions at address 0045EC63 (call EAX), there is a call to a resolved address (0045EAB0) in runtime.

Resolving 45EAB0 function address in runtime
Resolving 45EAB0 function address in runtime

Also, we can see the payload in memory.

Payload in memory
Payload in memory

The application flow is redirected to a new complete function that could be resolved by looking at the decompiler. Keeping stepping forward, it appears to be a free function. The pointer to be freed is 027A95E0.

Jump to free function
Jump to free function

After jumping to that function, the HeapFree function frees the pointer 027A95E0. According to the documentation, this function frees a memory block allocated from a heap.

The pointer seen before is as pMemory argument (a pointer to the memory block to be freed).

Call to HeapFree function
Call to HeapFree function

Checking the contents pointed by this pointer before being freed.

Block content before being freed
Block content before being freed

After executing the HeapFree function, the block content is overwritten by junk data. 027A95E0 points to a new address -> 027A9760.

After freeing the pointer
After freeing the pointer

Continuing to step forward, we reached a part of the code where is used the pointer 027A95E0(released before). The content (027A9760) of the pointer is copied to EAX.

The next instruction is to use the content of this pointer as a reference (DWORD PTR DS: [EAX + 14]). After dereferencing, the relative address points to junk data, as seen in the image below.

Dereferencing relative address based in the freed pointer
Dereferencing relative address based in the freed pointer

The junk data is copied to EDX, which will be used with a Call instruction a few instructions below. Stepping to reach the call EDX, we see the redirection address FEEEFEEE.

Access Violation
Access Violation

Redirecting to this address triggers an access violation, and the program crashes.

Crash after access violation, use-after-free
Crash after access violation, use-after-free

Conclusion: The crash occurs because the application tries to dereference a previously freed pointer, resulting in a Use-After-Free.

Extra

After resolving the call to EAX, the address of sub_0045EAB0 in OllyDbg, checking that function in IDA, there is a call to operator delete.

sub_0045EAB0 function in IDA
sub_0045EAB0 function in IDA

Comparing the code decompiled by IDA and what was presented by OllyDbg, delete and free are equivalent functions as described here.