Chapter 5. Browse and You’re Owned

Note

Sunday, April 6, 2008

Dear Diary,

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:

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.dll
Implements 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:

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):

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:

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.

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.

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).