AnyConnect Elevation of Privileges, Part 1

AnyConnect Elevation of Privileges, Part 1

The Cisco AnyConnect (CAC) Secure Mobility Client doesn’t have the brightest security track record. CVE-2015-4211 and CVE-2015-6305 are only two out of the fourteen CVEs that have been assigned to it just in 2015. This spiked my curiosity and prompted me to confirm if Cisco had properly fixed the underlying issue of these vulnerabilities.

In this multi-part article, I will explain how I reverse engineered CAC (one of its binaries and a network protocol used by it) to understand how the vulnerable functionality worked and how it could be further exploited. From Google Project Zero advisory and respective proof of concept (POC) code, I learned that:

  • A CAC related process is locally listening for commands on port 62522.
  • This process is running as SYSTEM.
  • The vulnerability is related with a function called launchDownloader.
  • The format of the network packets that trigger the vulnerability.
  • The vpndownloader.exe binary is vulnerable to DLL planting.

Having a look at the current connections, it is possible to identify that the vpnui.exe process is connected to port 62522, which is open by the vpnagent.exe process.

Listening sockets
Listening and connected sockets.

After attaching a debugger to the vpnagent.exe process and inspecting the assembly code for a while, it was possible to understand that the code that deals with the commands serialization into and from network packets is contained in the vpncommon.dll library. This library exported symbols shedded some light on the existing commands.

Available commands
Available commands.

The name of the exported symbols indicate that the protocol being used is based on a Type, Length and Value (TLV) structure and is used in a Inter-Process Communication (IPC) mechanism. One interesting TLV available is the CLaunchClientAppTlv.

CLaunchClientAppTlv
Exported symbols related with the CLaunchClientAppTlv class.

It seemed that this command is the one that was being used to exploit the vulnerabilities. Looking at the functions of the CLaunchClientAppTlv class, I noticed that one of the constructors receives as a parameter, a reference to an instance of a class called CIpcMessage. Once again the exported symbols of the vpncommon.dll library help understand what this class is all about.

IPC message class
Exported symbols related with the CIpcMessage class.

Reconstructing the class from the exported symbols undecorated name and by looking at the disassembled code of the get methods of the class, I was able to understand how the class fields are organized and the type of each field.

  1class CIpcMessage {
  2private:
  3    /* Offset 0x00 */ unsigned long idTag;
  4    /* Offset 0x04 */ unsigned short headerLength;
  5    /* Offset 0x06 */ unsigned short dataLength;
  6    /* Offset 0x08 */ IIpcResponseCB * ipcResponseCB;
  7    /* Offset 0x0c */ void * msgUserContext;
  8    /* Offset 0x10 */ unsigned long requestMsgId;
  9    /* Offset 0x14 */ void * returnIpcObject;
 10    /* Offset 0x18 */ unsigned char messageType;
 11    /* Offset 0x19 */ unsigned char messageId;
 12
 13public:
 14    /**
 15     * 702d8a52:    mov eax, ecx
 16     * 702d8a54:    ret
 17     */
 18    unsigned char * getBuffer() {
 19        return (unsigned char *)this;
 20    }
 21
 22    /**
 23     * 702db2c5:    xor eax, eax
 24     * 702db2c7:    cmp dword [ecx], 0x4353434f
 25     * 702db2cd:    setz al
 26     * 702db2d0:    ret
 27     */
 28    bool isIdTagValid() {
 29        return (this->idTag == 0x4353434f);
 30    }
 31
 32    /**
 33     * 702db2d1:    movzx eax, word [ecx+0x6]
 34     * 702db2d5:    ret
 35     */
 36    unsigned int getDataLength() {
 37        return this->dataLength;
 38    }
 39
 40    /**
 41     * 702db2d6:    movzx eax, byte [ecx+0x19]
 42     * 702db2da:    ret
 43     */
 44    unsigned char /*enum IPC_MESSAGE_ID*/ getMessageID() {
 45        return this->messageId;
 46    }
 47
 48    /**
 49     * 702db2db:    movzx eax, byte [ecx+0x18]
 50     * 702db2df:    and eax, 0x1f
 51     * 702db2e2:    ret
 52     */
 53    unsigned char /*enum IPC_MESSAGE_TYPE*/ getMessageType() {
 54        return (this->messageType & 0x1F);
 55    }
 56
 57    /**
 58     * 702db2e3:    xor eax, eax
 59     * 702db2e5:    test byte [ecx+0x18], 0x80
 60     * 702db2e9:    jnz 0x702db2f1
 61     * 702db2eb:    cmp [ecx+0x8], eax
 62     * 702db2ee:    jz 0x702db2f1
 63     * 702db2f0:    inc eax
 64     * 702db2f1:    ret
 65     */
 66    bool isRequestMessage() {
 67        return (!(this->messageType & 0x80) && this->ipcResponseCB != 0x00000000);
 68    }
 69
 70    /**
 71     * 702db2f2:    movzx eax, byte [ecx+0x18]
 72     * 702db2f6:    shr eax, 0x7
 73     * 702db2f9:    ret
 74     */
 75    bool isResponseMessage() {
 76        return (this->messageType >> 7);
 77    }
 78
 79    /**
 80     * 702db2fa:    mov eax, [ecx+0x8]
 81     * 702db2fd:    ret
 82     */
 83    IIpcResponseCB * getIpcResponseCB() {
 84        return this->ipcResponseCB;
 85    }
 86
 87    /**
 88     * 702db2fe:    mov eax, [ecx+0xc]
 89     * 702db301:    ret
 90     */
 91    void * getMsgUserContext() {
 92        return this->msgUserContext;
 93    }
 94
 95    /**
 96     * 702db302:    mov eax, [ecx+0x10]
 97     * 702db305:    ret
 98     */
 99    unsigned int getRequestMsgId() {
100        return requestMsgId;
101    }
102
103    /**
104     * 702db313:    mov eax, [ecx+0x14]
105     * 702db316:    ret
106     */
107    void * getReturnIpcObject() {
108        return this->returnIpcObject;
109    }
110
111    /**
112     * 702db324:    movzx eax, word [ecx+0x4]
113     * 702db328:    add eax, ecx
114     * 702db32a:    ret
115     */
116    unsigned char * getDataBuffer() {
117        return (unsigned char *)(this->headerLength + (unsigned long)this);
118    }
119
120    /**
121     * 702db32b:    movzx eax, word [ecx+0x6]
122     * 702db32f:    movzx ecx, word [ecx+0x4]
123     * 702db333:    add eax, ecx
124     * 702db335:    ret
125     */
126    unsigned int getLength() {
127        return (this->dataLength + this->headerLength);
128    }
129}

Taking the network packet created by the Google POC into consideration, it was clear that these fields map one-to-one with the data sent to the socket.

 The Google POC uses a Global Unique Identifier (GUID) in place of the fields ipcResponseCB, msgUserContext, requestMsgId, and returnIpcObject which all combined have the same length as a GUID.

In the next part of this article, I will focus on the packets being sent over the socket. Hope it was an interesting read :)