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:
62522
.SYSTEM
.launchDownloader
.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.
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.
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
.
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.
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.
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 :)