|
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
Topic:
LPC ports
Overview:
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
Impact:
Denial of service to possible local promotion.
Background:
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
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.
Details:
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. [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]
Background
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
Workarounds:
none known.
Recommendations:
Install the hotfix(es) from Microsoft.
Limit local logon rights.
References:
Microsoft's security bulletin:
http://www.microsoft.com/technet/security/bulletin/MS00-070.asp
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:
http://www.microsoft.com/technet/support/kb.asp?ID=266433
(may take a couple days to appear)
[1] Undocumented Windows NT
http://www.amazon.com/exec/obidos/ASIN/0764545698/
[2] Windows NT/2000 Native API Reference
http://www.amazon.com/exec/obidos/ASIN/1578701996
[3] BindView Security Advisory on NtImpersonateClientOfPort
http://razor.bindview.com/publish/advisories/adv_NTPromotion.html
|