Vulnerabilities in browsers and browser add-ons are all the rage these days, so I decided to have a look at some ActiveX controls. The first one on my list was Cisco’s online meeting and web-conferencing software called WebEx, which is widely used in business. After spending some time reverse engineering the WebEx ActiveX control for Microsoft’s Internet Explorer, I found an obvious bug that I could have found in a few seconds if I had fuzzed the control instead of reading the assembly. Fail.
I used the following process to search for a vulnerability:
I used Windows XP SP3 32-bit and Internet Explorer 6 as the platform for all the following steps.
Step 1: List the registered WebEx objects and exported methods.
Step 2: Test the exported methods in the browser.
Step 3: Find the object methods in the binary.
Step 4: Find the user-controlled input values.
Step 5: Reverse engineer the object methods.
A download link for the vulnerable version of WebEx Meeting Manager can be found at http://www.trapkit.de/books/bhd/.
After downloading and installing the WebEx Meeting Manager software, I fired up COMRaider[45] to generate a list of the exported interfaces the control provides to the caller. I clicked the Start button in COMRaider and selected Scan a directory for registered COM servers to test the WebEx components installed in C:\Program Files\Webex\.
As Figure 5-1 illustrates, two objects are registered in the WebEx install directory, and the object with GUID {32E26FD9-F435-4A20-A561-35D4B987CFDC}
and ProgID WebexUCFObject.WebexUCFObject.1
implements IObjectSafety
. Internet Explorer will trust this object since it’s marked as safe for initialization and safe for scripting. That makes the object a promising target for “browse and you’re owned” attacks, since it’s possible to call its methods from within a web page.[46]
Microsoft also provides a handy C# class called ClassId.cs
[47] that lists various properties of ActiveX controls. To use that class, I added the following lines to the source file and compiled it with the command-line version of Visual Studio’s C# compiler (csc
):
[..] namespace ClassId { class ClassId { static void Main(string[] args) { SWI.ClassId_q.ClassId clsid = new SWI.ClassId_q.ClassId(); if (args.Length == 0 || (args[0].Equals("/?") == true || args[0].ToLower().StartsWith("-h") == true) || args.Length < 1) { Console.WriteLine("Usage: ClassID.exe <CLSID>\n"); return; } clsid.set_clsid(args[0]); System.Console.WriteLine(clsid.ToString()); } } }
To compile and use the tool, I ran the following commands in a command-prompt window:
C:\Documents and Settings\tk\Desktop>csc /warn:0 /nologo ClassId.cs
C:\Documents and Settings\tk\Desktop>ClassId.exe {32E26FD9-F435-4A20-A561-35D4B987CFDC}
Clsid: {32E26FD9-F435-4A20-A561-35D4B987CFDC} Progid: WebexUCFObject.WebexUCFObject.1 Binary Path: C:\Program Files\WebEx\WebEx\824\atucfobj.dllImplements IObjectSafety: True
Safe For Initialization (IObjectSafety): True
Safe For Scripting (IObjectSafety): True
Safe For Initialization (Registry): False Safe For Scripting (Registry): False KillBitted: False
The output of the tool shows that the object was indeed marked as safe for initialization and safe for scripting using IObjectSafety
.
I then clicked the Select button in COMRaider to see a list of the public methods exported by the object with GUID {32E26FD9-F435-4A20-A561-35D4B987CFDC}
. As illustrated in Figure 5-2, a method called NewObject()
is exported by the object and takes a string value as input.
After I generated lists of the available objects and exported methods, I wrote a little HTML file that calls the NewObject()
method with the help of VBScript:
Example 5-1. HTML file to call the NewObject()
method (webex_poc1.html)
01 <html> 02 <title>WebEx PoC 1</title> 03 <body> 04 <object classid="clsid:32E26FD9-F435-4A20-A561- 35D4B987CFDC" id="obj"></object> 05 <script language='vbscript'> 06 arg = String(12, "A") 07 obj.NewObject arg 08 </script> 09 </body> 10 </html>
In line 4 of Example 5-1, the object with GUID or ClassID {32E26FD9-F435-4A20-A561-35D4B987CFDC}
is instantiated. In line 7 the NewObject()
method is called with a string value of 12 As as a parameter.
To test the HTML file, I implemented a little web server in Python that would serve the webex_poc1.html file to the browser (see Example 5-2):
Example 5-2. Simple web server implemented in Python that serves the webex_poc1.html file to the browser (wwwserv.py)
01 import string,cgi 02 from os import curdir, sep 03 from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer 04 05 class WWWHandler(BaseHTTPRequestHandler): 06 07 def do_GET(self): 08 try: 09 f = open(curdir + sep + "webex_poc1.html") 10 11 self.send_response(200) 12 self.send_header('Content-type', 'text/html') 13 self.end_headers() 14 self.wfile.write(f.read()) 15 f.close() 16 17 return 18 19 except IOError: 20 self.send_error(404,'File Not Found: %s' % self.path) 21 22 def main(): 23 try: 24 server = HTTPServer(('', 80), WWWHandler) 25 print 'server started' 26 server.serve_forever() 27 except KeyboardInterrupt: 28 print 'shutting down server' 29 server.socket.close() 30 31 if __name__ == '__main__': 32 main()
While the ActiveX control of WebEx is marked as safe for scripting (see Figure 5-1), it has been designed so that it can be run only from the webex.com domain. In practice, this requirement can be bypassed with the help of a Cross-Site Scripting (XSS)[48] vulnerability in the WebEx domain. Since XSS vulnerabilities are quite common in modern web applications, it shouldn’t be hard to identify such a vulnerability in the webex.com domain. To test the control without the need of an XSS vulnerability, I just added the following entry to my Windows hosts
file (see C:\WINDOWS\system32\drivers\etc\hosts\):
127.0.0.1 localhost, www.webex.com
After that, I started my little Python web server and pointed Internet Explorer to http://www.webex.com/ (see Figure 5-3).
So far I had collected the following information:
There is a WebEx object with ClassID {32E26FD9-F435-4A20-A561-35D4B987CFDC}
.
This object implements IObjectSafety
and is therefore a promising target, since its methods can be called from within the browser.
The object exports a method called NewObject()
that takes a user-controlled string value as input.
To reverse engineer the exported NewObject()
method, I had to find it in the binary atucfobj.dll
. To achieve this, I used a technique similar to the one Cody Pierce describes in one of his great MindshaRE articles.[49] The general idea is to extract the addresses of the invoked methods from the arguments of OLEAUT32!DispCallFunc
while debugging the browser.
If a method of an ActiveX control gets invoked, the DispCallFunc()
[50] function usually performs the actual call. This function is exported by OLEAUT32.dll
. The address of the invoked method can be determined with the help of the first two parameters (called pvInstance
and oVft
) of DispCallFunc()
.
To find the address of the NewObject()
method, I started Internet Explorer from within WinDbg[51] (also see Section B.2 for a description of the debugger commands) and set the following breakpoint at OLEAUT32!DispCallFunc
(see also Figure 5-4):
0:000> bp OLEAUT32!DispCallFunc "u poi(poi(poi(esp+4))+(poi(esp+8))) L1;gc"
The debugger command bp OLEAUT32!DispCallFunc
defines a breakpoint at the beginning of DispCallFunc()
. If the breakpoint is triggered, the first two parameters of the function are evaluated. The first function parameter is referenced using the command poi(poi(esp+4))
, and the second parameter is referenced by poi(esp+8)
. These values are added together, and their sum represents the address of the invoked method. Subsequently, the first line (L1
) of the method’s disassembly is printed to the screen (u poi(result of the computation)
), and the execution of the control is resumed (gc
).
I then started Internet Explorer with the g
(Go) command of WinDbg and navigated to http://www.webex.com/ again. As expected, the breakpoint triggered in WinDbg showed the memory address of the called NewObject()
method in atucfobj.dll
.
As illustrated in Figure 5-5, the memory address of the NewObject()
method was 0x01d5767f
in this example. The atucfobj.dll
itself was loaded at address 0x01d50000
(see ModLoad: 01d50000 01d69000 C:\Program Files\WebEx\WebEx\824\atucfobj.dll
in Figure 5-5). So the offset of NewObject()
in atucfobj.dll
was 0x01d5767f - 0x01d50000 = 0x767F
.
Next, I disassembled the binary C:\Program Files\WebEx\WebEx\824\atucfobj.dll with IDA Pro.[52] In IDA, the imagebase of atucfobj.dll
was 0x10000000
. So NewObject()
was located at address 0x1000767f
(imagebase + offset of NewObject()
: 0x10000000 + 0x767F
) in the disassembly (see Figure 5-6).
Before I started reading the assembly, I had to ensure what function argument holds the user-controlled string value provided through the VBScript in Example 5-1. Since the argument is a string, I guessed that my value was being held in the second parameter, lpWideCharStr
, shown in IDA. I wanted to be sure, however, so I defined a new breakpoint at the NewObject()
method and had a look at the arguments in the debugger (see Section B.2 for a description of the following debugger commands).
As illustrated in Figure 5-7, I defined the new breakpoint at the address of NewObject()
(0:009> bp 01d5767f
), continued the execution of Internet Explorer (0:009> g
), and again navigated to the http://www.webex.com/ domain. When the breakpoint was triggered, I inspected the value of the second function argument of NewObject()
(0:000> dd poi(esp+8)
and 0:000> du poi(esp+8)
). As the debugger output shows, the user-controlled data (a wide-character string consisting of 12 As) was indeed passed to the function through the second argument.
Finally, I had all information I needed to start auditing the method for security bugs.
To recap, I found an obvious vulnerability that happens while the ActiveX control processes the user-supplied string value that gets passed to NewObject()
. Figure 5-8 illustrates the code path to reach the vulnerable function.
In sub_1000767F
the user-provided wide-character string is converted to a character string using the WideCharToMultiByte()
function. After that, sub_10009642
is called, and the user-controlled character string is copied into another buffer. The code in sub_10009642
allows a maximum of 256 user-controlled bytes to be copied into this new character buffer (pseudo C code: strncpy (new_buffer, user_controlled_string, 256)
). The function sub_10009826
is called, and it calls sub_100096D0
, which then calls the vulnerable function sub_1000B37D
.
Example 5-3. Disassembly of the vulnerable function sub_1000B37D
(created in IDA Pro)
[..] .text:1000B37D ; int __cdecl sub_1000B37D(DWORD cbData
, LPBYTE lpData, int, int, int) .text:1000B37D sub_1000B37D proc near .text:1000B37D.text:1000B37D SubKey= byte ptr −10Ch
.text:1000B37D Type= dword ptr −8 .text:1000B37D hKey= dword ptr −4 .text:1000B37D cbData= dword ptr 8 .text:1000B37D lpData= dword ptr 0Ch .text:1000B37D arg_8= dword ptr 10h .text:1000B37D arg_C= dword ptr 14h .text:1000B37D arg_10= dword ptr 18h .text:1000B37D .text:1000B37D push ebp .text:1000B37E mov ebp, esp .text:1000B380 sub esp, 10Ch .text:1000B386 push edi.text:1000B387 lea eax, [ebp+SubKey] ; the address of SubKey is saved in eax
.text:1000B38D push [ebp+cbData] ; 4th parameter of sprintf(): cbData
.text:1000B390 xor edi, edi.text:1000B392 push offset aAuthoring ; 3rd parameter of sprintf(): "Authoring"
.text:1000B397 push offset aSoftwareWebexU ;
2nd parameter of sprintf(): "SOFTWARE\\..
.text:1000B397 ; ..Webex\\UCF\\Components\\%s\\%s\\Install"
.text:1000B39C push eax ; 1st parameter of
sprintf(): address of SubKey
.text:1000B39D call ds:sprintf ; call to sprintf()
[..].data:10012228 ; char aSoftwareWebexU[]
.data:10012228 aSoftwareWebexU db 'SOFTWARE\Webex\UCF\Components\%s\%s\Install',0
[..]
The first argument of sub_1000B37D
, called cbData
, holds a pointer to the user-controlled data stored in the new character buffer (see new_buffer
in the description of Figure 5-8). As I said before, the user-controlled wide-character data is stored in this new buffer as a character string with a maximum length of 256 bytes. Example 5-3 shows that the sprintf()
function at address .text:1000B39D
copies the user-controlled data pointed to by cbData
into a stack buffer called SubKey
(see .text:1000B387
and .text:1000B39C
).
Next, I tried to retrieve the size of this SubKey
stack buffer. I opened IDA Pro’s default stack frame displays by pressing ctrl-k. As shown in Figure 5-9, the stack buffer SubKey
has a fixed size of 260 bytes. If the information from the disassembly shown in Example 5-3 is combined with the information on the stack layout of the vulnerable function, the call to sprintf()
can be expressed with the C code in Example 5-4.
Figure 5-9. Determining the size of the SubKey
stack buffer using IDA Pro’s default stack frame displays
Example 5-4. Pseudo C code of the vulnerable call to sprintf()
[..] int sub_1000B37D(DWORD cbData, LPBYTE lpData, int val1, int val2, int val3) { char SubKey[260]; sprintf(&SubKey, "SOFTWARE\\Webex\\UCF\\Components\\%s\\%s\\Install", "Authoring", cbData); [..]
The sprintf()
library function copies the user-controlled data from cbData
as well as the string “Authoring
” (9 bytes) and the format string (39 bytes) into SubKey
. If cbData
is filled with the maximum amount of user-controlled data (256 bytes), a total of 304 bytes of data will be copied into the stack buffer. SubKey
can only hold up to 260 bytes, and sprintf()
doesn’t perform any length check. Therefore, as shown in Figure 5-10, it’s possible to write user-controlled data out of the bounds of SubKey
, which leads to a stack buffer overflow (see Section A.1).