Type packetstorm
Reporter Todd Sabin
Modified 2000-10-04T00:00:00


                                            `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 */  
} 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 [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 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:  
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