[ advisories | exploits | discussions | news | conventions | security tools | texts & papers ]
 main menu
- feedback
- advertising
- privacy
- FightAIDS
- newsletter
- news
- read forum
- new topic
- search

- meetings list
- recent additions
- add your info
 top 100 sites
- visit top sites
- sign up now
- members

- add your url
- add domain
- search box
- link to us

- our projects
- free email
 m4d network
- security software
- secureroot
- m4d.com
Home : Advisories : Various security vulnerabilities with LPC ports

Title: Various security vulnerabilities with LPC ports
Released by: BindView
Date: 3rd October 2000
Printable version: Click here
BindView Security Advisory


Various security vulnerabilities with LPC ports

Issue Date: October 3, 2000

Contact: Todd Sabin 


LPC ports


There are various flaws in the implementation of LPC ports.

Affected Systems:

Windows NT4 up to and including SP6a

Window 2000 up to and including SP1


Denial of service to possible local promotion.


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

[1] and [2].

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 */


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 [1] and [2].  [1] has better examples, but

[2] 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


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.


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.


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.


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.  [I don't as yet understand the exact

circumstances under which a normal server will call

NtReplyWaitReplyPort.  If someone has more info on this, please drop

me some mail.]

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.


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.


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.


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.


porttool -c \DbgSsApiPort


porttool -c \DbgUiApiPort


none known.


Install the hotfix(es) from Microsoft.

Limit local logon rights.


Microsoft's security bulletin:


Microsoft's Hotfix:

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

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

Microsoft's Knowledge Base article:


(may take a couple days to appear)

[1] Undocumented Windows NT


[2] Windows NT/2000 Native API Reference


[3] BindView Security Advisory on NtImpersonateClientOfPort


(C) 1999-2000 All rights reserved.