COMMAND

    LPC ports

SYSTEMS AFFECTED

	WinNT, 2000

PROBLEM

    Following is based on a BindView Security Advisory by Todd Sabin.
    There are various flaws in the implementation of LPC ports.

    LPC  ports  are  a  mostly undocumented client/server interprocess
    communication mechanism  which are  used by  NT system components.
    Recently, they have been  fairly well documented by  third parties
    in:

        http://www.amazon.com/exec/obidos/ASIN/0764545698/
        http://www.amazon.com/exec/obidos/ASIN/1578701996

    The main method of communication with ports is by passing messages
    from client to server and back.  These messages are of the form:

        typedef struct lpc_msg {
            unsigned short data_len;
            unsigned short msg_len;    /* normally data_len + sizeof (struct lpc_msg) */
            unsigned short msg_type;
            unsigned short address_range_offset;
            unsigned long pid;         /* process  id of client */
            unsigned long tid;         /* thread   id of client  */
            unsigned long mid;         /* message  id for this message */
            unsigned long callback_id; /* callback id for this message */
            /* unsigned char buff[0];  data_len bytes of data for this message */
        } LPC_MSG;

    One common usage goes something like this:

        Server ()
        {
            HANDLE hPort;
            NtCreatePort (&hPort, "MyPort");
        
            while (1) {
                NtReplyWaitReceivePort (hPort, NULL, msg_receive);
                if (msg_receive->type == connection_request) {
                    NtAcceptConnectPort ();
                    NtCompleteConnectPort ();
                } else {
                    ...
                    NtReplyPort (hPort);
                }
            }
        }
        
        
        Client ()
        {
            HANDLE hPort;
            NtConnectPort (&hPort, "MyPort");
            while (1) {
                ...
                NtRequestWaitReplyPort (hPort, msg_in, msg_out);
                ...
            }
        }

    For  complete  examples,  see  URLs  above.   First one has better
    examples, but second  one has more  complete documentation of  the
    individual calls.  Also, there will be a proof of concept  utility
    available at

        http://razor.bindview.com/tools

    which can be used for reproducing the vulnerabilities.

    The  interesting  thing  about  the  way  ports  are  used is that
    although a server receives  a new handle from  NtAcceptConnectPort
    for each client that connects, it usually doesn't use that  handle
    when  communicating  with  its  clients.   Instead,  it  uses  the
    original handle it got from the NtCreatePort call.  How then  does
    the kernel know for which  client a particular reply is  intended?
    It uses the pid, tid, and mid from the message to figure out where
    the message should end up.

    There are several problems with the LPC ports implementation.

    1.  Connection Stealing  [NT4 only]
    ===================================
    It is  possible for  any process  to call  NtAcceptConnectPort and
    hijack  port  connections.   The  NtAcceptConnectPort  api doesn't
    require an existing port handle  to call.  All that's  required is
    an LPC_MSG  with the  correct triple  of pid,  tid, mid.   If  the
    correct  triple  are  specified  for  an  outstanding   connection
    request, the call  succeeds and the  process is given  a handle to
    the  port.   It  can  then  process  requests  from  that  client.
    Presumably,   it   could   also   impersonate   the   client  with
    NtImpersonateClientOfPort.

    Note:  Win2k  has  a  new  API  NtSecureConnectPort which allows a
    client  to  verify  that  the  port's  server  is  running  with a
    particular SID.   Also, the  Win2k version  of NtAcceptConnectPort
    verifies that the calling process is the same as the process  that
    created the server port, which prevents this attack.

    Repro:

        start porttool -s \BaseNamedObjects\Foo
        start porttool -c \BaseNamedObjects\Foo
        porttool -s1
        (enter pid, tid, and mid printed by porttool -s ...)

    2.  Denial of Service -- BSOD  [NT4 only]
    =========================================
    As described above,  when a server  process receives a  connection
    request from a client, it is supposed to call  NtAcceptConnectPort
    and NtCompleteConnectPort  to complete  the connection.   However,
    if,  upon  receipt  of  a  connection  message,  the  server calls
    NtReplyPort()  instead  of  NtAcceptConnectPort(),  then  a kernel
    exception will be triggered, resulting in a BSOD.

    Repro:

        start porttool -s2 \BaseNamedObjects\Foo
        porttool -c \BaseNamedObjects\Foo

    3.  Spoofed Replies [W2K, NT4]
    ==============================
    Using NtReplyPort  (or any  of the  NtReply...Port calls),  anyone
    can reply to a waiting client of any server, provided the attacker
    can supply the correct triple of pid, tid, and mid.

    Aside from the obvious denial of service problems, there are  also
    potential  methods  of  exploiting  this  to  gain privilege.  One
    possibility: the  ncalrpc RPC  protocol sequence  uses LPC  as the
    transport mechanism.   When an  RPC client  connects to  a  server
    using this transport, it first resolves which LPC port the  server
    is  listening  on  by  contacting  the  RPC  portmapper,  which is
    listening on the well-known endpoint "\RPC Control\epmapper".   By
    spoofing a reply to a  portmapper request, an attacker could  fake
    a client into connecting to a  port which he is listening on,  and
    then impersonate the client.

    Repro:

        start porttool -s \BaseNamedObjects\Foo
        start porttool -c \BaseNamedObjects\Foo
        porttool -s3 \BaseNamedObjects\Foo2
        (enter pid, tid, mid from porttool -s ...)

    4.  Impersonation of (somewhat) arbitrary processes [NT4, W2K (harder)]
    =======================================================================
    As earlier reported in [3], NT4 was vulnerable to an attack  which
    let anyone impersonate any other process on the machine by calling
    NtImpersonateClientOfPort with the target's pid and tid, and a mid
    of  0.   This  worked  because  if  a  thread is not making an LPC
    request, its recorded mid will  be 0.  Apparently the  patch which
    fixes this adds a check  to be sure that the  mid is not 0.   This
    still  allows  anyone  to  impersonate  any process, provided that
    process is currently making an  LPC request, and the attacker  can
    supply the proper mid.

    W2K has an added check which makes this attack more difficult, but
    still possible under  some circumstances.   The attack will  still
    work on an LPC  server, provided that server  is in the middle  of
    an NtReplyWaitReplyPort  call, and  the attacker  can provide  the
    proper pid, tid, mid, and cid.

    Repro on NT4:

        start porttool -s \BaseNamedObjects\Foo
        start porttool -c \BaseNamedObjects\Foo
        porttool -s4 \BaseNamedObjects\Foo2
          [in another window] porttool -c \BaseNamedObjects\Foo2
        (enter pid, tid, mid, cid from porttool -s)

    Repro on W2K:

        start porttool -s4b \BaseNamedObjects\Foo
        start porttool -c \BaseNamedObjects\Foo
        porttool -s4 \BaseNamedObjects\Foo2
          [in another window] porttool -c \BaseNamedObjects\Foo2
        enter pid, tid, mid, and cid port porttool -s4b.

    Note that the pid  and tid in this  case are the ones  that belong
    to  porttool  -s4b  itself,  not  the  ones it prints from the lpc
    message.

    5.  Reading and writing other processes' address space [W2K, NT4]
    =================================================================
    Besides the  normal method  of passing  request/reply data  in the
    message itself, there  is another means  by which LPC  clients and
    servers can pass  data.  NtReadRequestData  and NtWriteRequestData
    allow an  LPC server  to read  and write  to certain  parts of the
    client's address space, as specified by the client in the  request
    message.  To enable this, a client fills out a struct like this:

        struct addr_ranges {
            unsigned long num_entries;
            struct addr_entry {
                void *addr;
                size_t len;
            } addrs[N];  /* where N is num_entries */
        };

    The client then puts this structure within the data portion of its
    message, and sets  the address_range_offset of  the LPC_MSG to  be
    the  offset  of  the  struct  in  the  message,  and  then makes a
    NtRequestWaitReplyPort () call as usual.  Now the server can  read
    or    write    to    the    specified    address    ranges    with
    Nt{Read,Write}RequestData.

    5a.  Reading/writing portions of other clients' address spaces
    ==============================================================
    The  previous  description  is  how  things  are supposed to work.
    However, due to a bug, it is possible for one client of a port  to
    use these  calls as  if it  were the  server, and  access areas of
    memory in  a second  client, provided  that the  second client  is
    making a  call using  the address_range_offset  feature, and again
    assuming the attacker can provide the proper pid, tid, mid.

    Repro:

        start porttool -s5a \BaseNamedObjects\Foo
        start porttool -c5a-1 \BaseNamedObjects\Foo
        porttool -c5a-2 \BaseNamedObjects\Foo
        (enter pid, tid, mid, cid from porttool -s5)

    5b.  Reading/writing arbitrary areas of other processes
    =======================================================
    Through a more elaborate attack, it is also possible for a  server
    to  read  and  write  arbitrary  areas  of other processes address
    spaces, whether or not those  processes are clients of the  server
    or not.

    When a client make a call using the address_range_offset  feature,
    the  kernel  keeps  a  copy  of  the  request on a list inside the
    server's port object, and then removes it when the matching  reply
    is sent.  The  list is searched based  on the mid and  callback_id
    of the LPC_MSG.

    The way  Nt{Read,Write}RequestData work  is the  kernel takes  the
    LPC_MSG passed in, looks up  the thread referenced by the  pid and
    tid, and verifies that the  thread is currently making a  lpc call
    that matches  the mid  the server  specified.   Then, it  verifies
    that the server  port actually has  an outstanding request  with a
    matching mid and callback_id.  It does this is by looking for  the
    MSG on the list mentioned earlier.

    Now, since  the target  thread's mid  must match  some mid  on the
    server's outstanding  request list,  in theory,  the server should
    only be able to access its clients' address spaces.  However,  the
    mid is  only a  32 bit  integer, so  there are  only 2^32 possible
    mids.  Once they've all  been used, they will start  being reused,
    meaning  that  there's  the  potential  for collisions, provided a
    server doesn't  send replies  to its  clients during  the time  it
    takes to wrap the mid counter.  Testing shows that it takes  about
    21.5 hours on a  PII 300 to run  through 2^32 mids using  a client
    and server that do nothing but request/reply in a tight loop.

    So, the attack goes  like this: A server  starts and listens on  a
    port.   Then, a  client connects  to it  and send  requests to  it
    using  the  address_range_offset  feature.   The  address   ranges
    specified here are what the  server will be able to  access later,
    so they  need to  be known  up front.   To increase  the odds of a
    successful  collision,  the  client  can  send  several   thousand
    requests to the server.  Now, this would normally require  several
    thousand  threads  since  the  server  needs  to  have  these   as
    _outstanding_  requests  later  in  the  attack, it can't reply to
    them.

    However,  the  outstanding  request  list  is  searched by mid and
    callback_id, as mentioned previously,  but replies are sent  based
    only on mid, so  it's actually possible for  the server to send  a
    reply to the client without having it removed from its outstanding
    request  list.   Before  replying,  the  server  just  changes the
    callback_id so it won't match; the reply is sent as usual, but  it
    stays on the outstanding request list.

    Now, once the  server has lots  of outstanding requests  for later
    use, the attacker does requests/replies in a tight loop until  the
    mids roll  back to  the point  where the  server has some matching
    mids.  Finally, the server specifies the target's pid and tid, and
    tries every  mid that  it has  stored.   If it  misses, it can try
    again the next time around until it succeeds.

    Repro:

        start porttool -s5b \BaseNamedObjects\Foo
        start porttool -s5b-2 \BaseNamedObjects\Foo2
        porttool -c5b \BaseNamedObjects\Foo \BaseNamedObjects\Foo2
        (wait until mids wrap around)

        start porttool -s \BaseNamedObjects\Foo3
        porttool -c \BaseNamedObjects\Foo3

        (in window for porttool -s5b)
        enter pid, tid, mid, cid from porttool -s

    6.  Consuming kernel memory.   [W2K, NT4]
    =========================================
    As mentioned in 5, the kernel keeps copies of all outstanding  LPC
    messages in kernel memory.  It allocates memory from an LPC 'zone'
    for these messages.   When a message  is finished, it's  memory is
    returned to the zone and can be  reused.  If is no memory left  in
    the  zone  when  a  new  request  comes in, then additional kernel
    memory will be  allocated and added  to the zone.   Once added  to
    the LPC zone, the memory is never released.

    Using the trick about mismatched callback ids from number 5,  it's
    possible for a rogue server  to arrange that none of  the messages
    which are sent to  it get freed back  to the lpc zone.   This will
    result in the size of the lpc zone growing continually.  Once  the
    server exits (or is killed),  the memory will finally be  returned
    to the lpc zone,  but the memory in  the zone will never  be freed
    back to the general kernel memory pool.

    Repro:

        start porttool -s6 \BaseNamedObjects\Foo
        porttool -c6 \BaseNamedObject\Foo

    7.  Fragile LPC servers  -- BSOD   [NT4 only]
    =============================================
    If a client connects  to either of \DbgSsApiPort  or \DbgUiApiPort
    and sends a garbage request, the machine will BSOD.

    Repro:

        porttool -c \DbgSsApiPort

    or

        porttool -c \DbgUiApiPort

SOLUTION

    Install the hotfix(es) from Microsoft or limit local logon rights.
    Microsoft's Hotfix:

        NT4: http://www.microsoft.com/Downloads/Release.asp?ReleaseID=24650
        W2K: http://www.microsoft.com/Downloads/Release.asp?ReleaseID=24649