Remote Procedure Calls


The Remote Procedure Call (RPC) is an integral part of Windows operating systems. Essentially, RPC is a client/server protocol that application developers can use to call procedures on a local or remote node. Although developers often need to direct a client application to specifically connect to a remote machine, the connection details and data marshalling are done behind the scenes by the RPC layer. This behavior shelters developers from the details of how data is passed between the two machines and the manner in which procedures are called.

There are two primary RPC protocols: Open Network Computing (ONC) RPC (sometimes called SunRPC) and Distributed Computing Environment (DCE) RPC. Chapter 10, "UNIX II: Processes," discusses ONC RPC as it pertains to UNIX applications. Microsoft uses DCE RPC, which is quite different, but from a code-auditing perspective, the basic procedures for locating exposed code are similar. Microsoft RPC programs have some additional complications, discussed in the following sections.

RPC Connections

Before you get into the details of auditing RPC programs, you need to be aware of some basics of how clients and servers communicate. Before a client can call a remote procedure, it needs to create a binding to the destination interface. A binding is an application-level connection between the client and server. It contains connection details, including the authentication state, and is expressed structurally in RPC programs through binding handles. Binding handles are used to subsequently perform operations such as calling procedures, establishing authentication, and so on.

The following sections refer to an endpoint mapper, which is an RPC component used to establish bindings. Most of the endpoint mapper's operation is handled implicitly from a code-auditing standpoint, so you don't need to concern yourself too much with it. Just be aware it exists and is responsible for establishing a binding between the RPC client and server.

RPC Transports

The Windows RPC layer is transport independent, meaning it can package its data structures on top of a variety of underlying protocols. When you see a function that takes a "protocol sequence" argument, it's referring to the protocol used to transport RPC data between two endpoints. The selected transport can definitely affect the application's security, as explained in the following sections. These RPC protocols are divided into three categories, described in the next three sections.

NCACN

The network computing architecture connection-oriented protocol (NCACN) is for RPC applications that need to communicate remotely across a network. Protocols in these categories are connection oriented, meaning they provide reliable, two-way, end-to-end connections for the duration of a session. Table 12-3 lists the protocols available in this category.

Table 12-3. NCACN Protocol Sequences

Protocol Sequence

Description

ncacn_nb_tcp

NetBIOS over TCP

ncacn_nb_ipx

NetBIOS over Internetwork Packet Exchange (IPX)

ncacn_nb_nb

NetBIOS Enhanced User Interface (NetBEUI)

ncacn_ip_tcp

RPC data sent over regular TCP/IP connections

ncacn_np

RPC data sent over named pipes

ncacn_spx

RPC data sent over Sequenced Packet Exchange (SPX)

ncacn_dnet_nsp

DECnet transport

ncacn_at_dsp

AppleTalk DSP

ncacn_vns_spp

Vines scalable parallel processing transport

ncacn_http

RPC over HTTP (which runs on top of TCP)


NCADG

The network computing architecture datagram protocol (NCDAG) is also reserved for RPC applications that need to communicate with remote nodes across a network. Unlike NCACN protocols, however, the NCADG protocols provide a connectionless transport. Table 12-4 lists the valid protocol sequences.

Table 12-4. NCADG Protocol Sequences

Protocol Sequence

Description

ncadg_ip_udp

RPC traffic sent over User Datagram Protocol (UDP)

ncadg_ipx

RPC traffic sent over IPX


NCALRPC

The network computing architecture local remote procedure call protocol (NCALRPC) is used by RPC applications in which the client and server reside on the same machine. Local RPC calls, also know as local procedure calls (LPC), are a function of the OS and don't require any further qualification; that is, there's no requirement for other protocols or IPC mechanisms to be used to send RPC data between the client and the server. Hence, the only protocol sequence for local RPC calls is simply ncalrpc.

Microsoft Interface Definition Language

When auditing RPC servers, you should start with procedures that can be called remotely with untrusted user input. A lot of RPC servers define their interface in terms of the available procedures and what arguments those procedures take. Microsoft provides Microsoft Interface Definition Language (MIDL), a simplified language for defining these interfaces. MIDL has a C-like structure, which makes it fairly easy for most programmers to use. Look for .idl files when you're reviewing code; they contain the definitions that generate C/C++ stubs for RPC applications. The structure of these files and how they produce the client and server interfaces RPC applications use are covered in the following sections.

IDL File Structure

An IDL file is composed of two main parts: an interface header and an interface body. These two sections define an RPC interface for a program and are quite easy to follow.

IDL Interface Header

An interface header appears at the beginning of an interface definition and is enclosed in square brackets ([ and ]). Within those brackets is a series of interface-specific attributes separated by commas. These attributes have the following syntax:

attribute_name(attribute_arguments)


For example, an attribute with the name version and the argument 1.1 would appear as version(1.1). Many attributes can be used, but the main ones are uuid, version, and endpoint. The first two simply provide the universal unique ID (UUID) of the RPC interface and the version number of the application this interface definition represents. The endpoint attribute specifies where the RPC server receives requests from. Endpoint transports are described in terms of a protocol sequence and a port. The protocol sequence describes what transports the RPC interface is accessible over. The format of the port (or, more appropriately, the endpoint) is specific to the protocol sequence. Putting all this information together, here's an example of an interface header:

[    uuid(12345678-1234-1234-1234-123456789012),    version(1.1),    endpoint("ncacn_ip_tcp:[1234]") ]


In this example, the RPC server accepts requests only via TCP/IP on port 1234.

IDL Definition Body

After the interface definition header is the definition body, which details all the procedures available for clients to use and the arguments those procedures take. The definition body begins with the interface keyword, followed by the interface's human-readable name and the interface definition enclosed in curly braces. Here's an example of a definition body:

interface myinterface {     ... definition goes here ... }


Inside the curly braces are the definitions for procedures that can be called by clients and are implemented elsewhere in the application. The remote procedure prototypes are similar to C function prototypes, except each function and argument to a function can contain additional attributes enclosed in square brackets. Again, you might encounter quite a few of these attributes, but most of them are fairly self-explanatory. Typically, the only information that needs to be indicated is whether the argument is for input (function attribute in) or output (function attribute out). An example of an interface definition is shown:

interface myinterface {     int RunCommand([in] int command,                 [in, string] unsigned char *arguments,                 [out, string] unsigned char *results); }


This interface definition is quite simple; it provides just one interface for running a command. It fails to address some important considerations, such as authentication and maintaining session state. However, it does show what a basic interface looks like, so you can move on to the details in the following sections.

Compiler Features

The Microsoft IDL compiler includes a few options that can improve an RPC application's security. The range attribute provides a method for restricting the values of a numeric field. It can be used to restrict data types along with attributes such as size_is and length_is. Here's an example:

interface myinterface2 {     int SendCommand([in, range(0, 16)] int msg_id,                 [in, range(0, 1023)] int msg_len,                 [in, length_is(msg_len)] unsigned char *msg); }


This interface restricts the value of msg_len to a known range and forces the length of msg to match. These types of rigid interface restrictions can prevent vulnerabilities in the code. Of course, defining restrictions doesn't help if the compiler does not apply them. The /robust switch must be used as a compilation option. This compiler switch handles the range keyword and builds in additional consistency checks. This capability is available only in Windows 2000 and later.

Application Configuration Files

In addition to IDL files, each interface has application configuration files (ACFs). Whereas the IDL file describes an interface specification that clients and servers need to adhere to, the ACF describes attributes that are local to the client or server application and affect certain behaviors. For example, code and nocode attributes can be used in an ACF to direct the MIDL compiler to not bother generating stubs for various parts of the interface because they aren't used in this application. ACFs have the same format as their IDL counterparts, except the attributes they specify don't alter the interface definition. They have an attribute list defined in square brackets followed by the interface keyword and an interface definition. The definition must be identical to the one in the IDL file that defines the same interface.

You should note a couple of points about ACFs and IDL files. First, they are optional. An application doesn't need to make an ACF to build a working RPC application. If the ACF doesn't exist, no special options are enabled. Further, the contents of the ACF can be put in an IDL file; it doesn't matter to the MIDL compiler. So you often encounter ACF attributes in an IDL file.

RPC Servers

Now you have a basic idea of what to audit and where to start. Next, you need to examine how an RPC server might control the exposure of its network interfaces. This means you need to be familiar with how the RPC interface is registered and what impact registration might have on the application's attack surface.

Registering Interfaces

The basic registration of an RPC interface is achieved with one of two functions, described in the following paragraphs.

The RpcServerRegisterIf() function is the primary means for registering an interface with the endpoint mapper:

void RPC_ENTRY RpcServerRegisterIf(RPC_IF_HANDLE IfSpec,          UUID *MgrTypeUuid, RPC_MGR_EPV *MgrEpv)


The first parameter is an RPC interface handle, which is a structure generated automatically by the MIDL compiler. The second argument associates a UUID with the third argument, an entry point vector (EPV). The EPV is a table of function pointers to the RPC routines available to clients connecting to the interface. Generally, the second and third arguments are NULL, which causes no UUID to be associated with the EPV and accepts the default EPV generated by the MIDL compiler.

The RpcServerRegisterIfEx() function gives developers more control in registering an RPC interface:

RPC_STATUS RPC_ENTRY RpcServerRegisterIfEx(RPC_IF_HANDLE IfSpec,         UUID *MgrTypeUuid, RPC_MGR_EPV *MgrEpv,         unsigned int Flags, unsigned int MaxCalls,         RPC_IF_CALLBACK_FN *IfCallback)


This function can be used to restrict the interface's availability. Of particular note is the last parameter, which is a security callback function. It's called whenever a client attempts to call a procedure from the interface being registered. This function is intended to evaluate each connecting client and whether it should have access to the interface. It's called automatically whenever a client attempts to access an interface. The Flags parameter also has some interesting side effects on how the server behaves. These are the two most security-relevant flags:

  • RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH Normally, registering a security callback function doesn't prevent unauthenticated RPC calls from being rejected automatically. Specifying this flag negates that behavior, permitting unauthenticated calls. This flag requires the callback function to permit or deny the request based on other criteria.

  • RPC_IF_ALLOW_LOCAL_ONLY Requests are allowed only from local named pipes (ncacn_np) or local RPC (ncalrpc). All requests from other protocol sequences or via remote named pipes are rejected automatically.

RPC interfaces can also be registered through the following function:

RPC_STATUS RPC_ENTRY RpcServerRegisterIf2(RPC_IF_HANDLE IfSpec,         UUID *MgrTypeUuid, RPC_MGR_EPV *MgrEpv,         unsigned int Flags, unsigned int MaxCalls,         unsigned int MaxRpcSize,         RPC_IF_CALLBACK_FN *IfCallbackFn)


This function is identical to RpcServerRegisterIfEx(), except it contains an additional parameter, MaxRpcSize, used to specify a maximum size in bytes for RPC messages. It can be especially useful for preventing buffer manipulation attacks when the message size is fixed or within a known range.

A quick glance at these three functions should make it clear that how a server is registered has a impact on security. For example, take a look at the following server registration:

RpcServerRegisterIfEx(hSpec, NULL, NULL, 0, 20, NULL)


The preceding registration has fairly relaxed security compared with this one:

RpcServerRegisterIfEx(hSpec, NULL, NULL,                       RPC_IF_ALLOW_LOCAL_ONLY, 20,                       MyCallback)


This registration allows only locally originated requests to be processed and has a security callback function. Of course, having a security callback function isn't enough; it has to perform its job. You see how this is done in "Authenticating Requests" later in this chapter.

Binding to an Endpoint

After an interface is registered with the RPC runtime, the server needs to bind to endpoints so that clients can contact it, which is a two-step process. The first step is to register protocol sequences that the server should accept connections on. These protocol sequences are the ones described previously in the "RPC Transports" section. They are bound by using the RpcServerUseProtseq() family of functions. Take a look at the prototype for RpcServerUseProtseq():

RPC_STATUS RPC_ENTRY RpcServerUseProtseq(unsigned char *ProtSeq,         unsigned int MaxCalls, void *SecurityDescriptor)


This function causes the current process to listen for RPC requests over a specific protocol, so it affects all RPC servers in the current process. Each call allows you to specify one protocol sequence as the first parameter, so an RPC server listening on multiple transports needs to call this function multiple times. The protocol sequence functions can optionally take a security descriptor for the ncalrpc and ncan_np protocol sequences. This security descriptor is the most effective method of restricting RPC connections to a specific group of users.

The RpcServerUseProtseqEx() functions add the capability to include a transport policy as part of the protocol registration. Including the transport policy allows developers to restrict the allocation of dynamic ports and selectively bind interfaces on multihomed computers. Although this level of specificity isn't required for many applications, certain deployment environments might necessitate it.

Up to this point, the RpcServerUseAllProtseqs() family of functions haven't been discussed. However, it's important to make note of these functions because their use generally presents an unnecessarily high security risk and should be reviewed closely when encountered. These functions bind to all available interfaces, potentially creating a dangerous exposure of the RPC server. In particular, they might bind to interfaces with insufficient access control or interfaces on hostile networks.

Note

Don't forget that protocol registration affects all RPC servers in the process. This means any servers with differing protocol security must run in different processes.


The next part of binding involves registering the endpoints for each protocol sequence. The endpoint is protocol-specific information required for contacting the RPC server. For example, the TCP protocol sequence uses a TCP port for its endpoint. Endpoints are registered with the RpcEpRegister() function, which works as shown:

RPC_STATUS RPC_ENTRY RpcEpRegister(RPC_IF_HANDLE IfSpec,         RPC_BINDING_VECTOR *BindingVector,         UUID_VECTOR *UuidVector, unsigned char *Annotation)


This function supplies the endpoint mapper with the endpoints of an RPC interface. The first parameter is RPC_IF_HANDLE, mentioned in the previous section. The next two parameters contain vectors of binding handles and UUIDs to register with the endpoint mapper.

Some utility methods simplify endpoint registration, however. The RpcServerUseProtseqEp() can be used to register the endpoint and protocol sequence in a single call. However, the easiest way to handle registration is to use the RpcServerUseProtseqIf() functions; they register all endpoints specified in the IDL file.

Listening for Requests

The only thing left in setting up the server is to listen for RPC requests by using the RpcServerListen() function. This function isn't that interesting, except it indicates that the server application is expecting requests from that point forward and potentially exposed to malicious input. All code to handle those requests is indicated in the previous steps of interface registration.

Authentication

As you would expect, the attack surface of an RPC application depends heavily on the level of authentication it requires. Windows provides several different levels of authentication, which are layered on top of each other. This means each new level of authentication performs the authentication of the previous levels and adds some requirements. The authentication levels are listed in ascending order:

  • RPC_C_AUTHN_LEVEL_DEFAULT Default level of authentication chosen by the current OS settings. (This level is not additive.)

  • RPC_C_AUTHN_LEVEL_NONE No authentication; any anonymous user can access the service

  • RPC_C_AUTHN_LEVEL_CONNECT Authentication is done only at connection establishment and not for individual calls.

  • RPC_C_AUTHN_LEVEL_CALL This level specifies that users must authenticate for each procedure call they make. It's intended primarily for use with connectionless transports.

  • RPC_C_AUTHN_LEVEL_PKT This level ensures that any data received is from the client that originally established the connection. No data validation is performed, however.

  • RPC_C_AUTHN_LEVEL_PKT_INTEGRITY This level is like RPC_C_AUTHN_LEVEL_PKT, except it also ensures that no data has been modified en route.

  • RPC_C_AUTHN_LEVEL_PKT_PRIVACY This level does the same as RPC_C_AUTHN_LEVEL_PKT_INTEGRITY and uses encryption to ensure that third parties can't read data being transmitted.

In addition to the authentication level performed on incoming packets, programmers can also select the services for authenticating clients. These authentication services include NTLM authentication and Kerberos. There's also the provision for no authentication, indicated by the RPC_C_AUTHN_NONE constant.

Each authentication service must be registered by calling RpcServerRegisterAuthInfo() with the appropriate parameters for the service. For most applications, RPC_C_AUTHN_GSS_NEGOTIATE provides the best results, as it attempts to use Kerberos authentication but can downgrade to NTLM if required. You should be wary of any application that doesn't require at least an RPC_C_AUTHN_LEVEL_CONNECT authentication, using the RPC_C_AUTHN_GSS_NEGOTIATE service or better.

Authenticating Requests

You've seen how the server can restrict interfaces and provide a basic authentication requirement, but what about authenticating the actual calls and providing authorization? RPC authorization and authentication are specific to a binding. You know that a server can provide a DACL for a binding, which should be the foundation of any RPC security. However, two routines can be used in a security callback (or in a call itself, for that matter) to provide detailed client authentication information from a binding handle. The first is as follows:

RPC_STATUS RPC_ENTRY RpcBindingInqAuthClient(         RPC_BINDING_HANDLE ClientBinding,         RPC_AUTH_HANDLE *Privs, unsigned char **ServerPrincName,         unsigned long *AuthnLevel, unsigned long *AuthnSvc,         unsigned long *AuthsSvc)


The second and third parameters of this function provide all authentication information associated with the client's binding handle. The remaining parameters cover the authentication of the client requests. When supporting the RPC_C_AUTHN_WINNT service, the final parameter is always RPC_C_AUTHZ_NONE.

The RpcBindingInqAuthClient() function is superseded in Windows XP and later by the following function:

RPCRTAPI RPC_STATUS RPC_ENTRY RpcServerInqCallAttributes(         RPC_BINDING_HANDLE ClientBinding,         void *RpcCallAttributes)


This function meets the same requirements as RpcBindingInqAuthClient() and provides additional client binding information. This information is returned in the second parameter in the RPC_CALL_ATTRIBUTES_V2 structure. In addition to the authentication level and service, it indicates whether a NULL session is used, what protocol sequence is used, whether the client is local or remote, and a multitude of other useful tidbits. Note that this function isn't supported over ncacn_dg protocols, so the return values need to be checked to make sure the function was able to obtain the correct information.

Impersonation in RPC

RPC can impersonate authenticated clients via the same basic infrastructure as named pipes. Generally, it's the most effective method for accessing secure objects safely in the calling user's context. It allows developers to use the familiar DACL structure on objects and place the burden of security enforcement on the OS. An RPC server can impersonate a client with one of two functions: RpcImpersonateClient() and RpcGetAuthorizationContextForClient(). The prototypes for these functions are explained in the following paragraphs.

The following function impersonates the client indicated by the binding handle:

RPC_STATUS RPC_ENTRY RpcImpersonateClient(         RPC_BINDING_HANDLE BindingHandle)


The BindingHandle parameter can be 0, in which case the server impersonates the context of the client currently being served by the thread. This function is the primary mechanism used for impersonation of a client.

The main purpose of the following function is to return an AUTHZ_CLIENT_CONTEXT_HANDLE structure that represents the client indicated by the first parameter:

RPC_STATUS RPC_ENTRY RpcGetAuthorizationContextForClient(         RPC_BINDING_HANDLE ClientBinding,         BOOL ImpersonateOnReturn, PVOID Reserved1,         PLARGE_INTEGER pExpirationTime, LUID Reserved2,         DWORD Reserved3, PVOID Reserved4,         PVOID *pAuthzClientContext)


Of particular interest is the ImpersonateOnReturn parameter. If it's set to true, the function impersonates the client indicated by the ClientBinding binding handle, just as though RpcImpersonateClient() has been called.

When auditing RPC applications, you need to be aware of how clients can restrict servers' capability to impersonate them. Neglecting to take this step might expose a client's credentials to a malicious server. A client application can enforce impersonation restrictions on a per-binding basis with RpcBindingSetAuthInfoEx(). This function has the following prototype:

RPC_STATUS RPC_ENTRY RpcBindingSetAuthInfoEx(         RPC_BINDING_HANDLE Binding,         unsigned char PAPI *ServerPrincName,         unsigned long AuthLevel, unsigned long AuthnSvc,         RPC_AUTH_IDENTITY_HANDLE AuthIdentity,         unsigned long AuthzSvc, RPC_SECURITY_QOS *SecurityQOS)


Note the last parameter, which points to an RPC_SECURITY_QOS structure. Although there are several variations of this structure, depending on the version, each has an ImpersonationType member that indicates what level of impersonation a server can use with the connecting client. The legal values for this member are as follows:

  • RPC_C_IMP_LEVEL_DEFAULT Use the default impersonation level.

  • RPC_C_IMP_LEVEL_ANONYMOUS Use the SecurityAnonymous impersonation level.

  • RPC_C_IMP_LEVEL_IDENTIFY Use the SecurityIdentify impersonation level.

  • RPC_C_IMP_LEVEL_IMPERSONATE Use the SecurityImpersonate impersonation level.

  • RPC_C_IMP_LEVEL_DELEGATE Use the SecurityDelegation impersonation level (cloaking).

Of these values, obviously the most dangerous are RPC_C_IMP_LEVEL_IMPERSONATE and RPC_C_IMP_LEVEL_DELEGATE. By permitting either impersonation level, the client allows the server to make use of its credentials. The delegation impersonation level extends the server's capabilities even more than typical impersonations. It allows the server to authenticate across the network on behalf of the clientthat is, the server can access anything on the network as though it's the connected client. You should inspect any code using either value to ensure that impersonation is required and being used properly.

Note

If the local RPC endpoint is used (ncalrpc), RPC_C_IMP_LEVEL_IMPERSONATE and RPC_C_IMP_LEVEL_DELEGATE are equivalent. Even if RPC_C_IMP_LEVEL_IMPERSONATE is used, the server is permitted to make network accesses on behalf of the client.


As with named pipes, failure to check return values of impersonation functions can result in an RPC request being given more privileges than it's supposed to have. In fact, this type of error is even more relevant in RPC because many factors can cause impersonation functions to fail.

Context Handles and State

Before you go any further, you need to see how RPC keeps state information about connected clients. RPC is inherently stateless, but it does provide explicit mechanisms for maintaining state. This state information might include session information retrieved from a database or information on whether a client has called procedures in the correct sequence. The typical RPC mechanism for maintaining state is the context handle, a unique token a client can supply to a server that's similar in function to a session ID stored in an HTTP cookie. From the server's point of view, the context handle is a pointer to the associated data for that client, so no special translation of the context handle is necessary. The server just refers to a context handle as though it's a void pointer. Of course, transmitting a pointer to a potentially malicious client would be extremely dangerous. Instead, the RPC runtime sends the client a unique context token and translates the token back to the original pointer value on receipt. Context handles aren't a mandatory part of RPC and aren't required to make an RPC program work. However, most RPC services require context handles to function properly and prevent disclosing any sensitive information to the client.

Context handles are useful for maintaining application state; however, they aren't intended for maintaining authentication state. A context handle could be exposed to malicious users in a variety of ways, such as by sniffing the network transport or through the actions of a malicious client. Another RPC interface might even reveal the context handle if strict context handles aren't used. This simple interface uses a context handle for security purposes:

BOOL LogonUser([out] PCONTEXT_HANDLE ctx) BOOL LogoffUser([in] PCONTEXT_HANDLE ctx) BOOL GetTableList([in] PCONTEXT_HANDLE ctx,        [out] PTABLE_DESCRIPTOR tables) BOOL JoinTable([in] PCONTEXT_HANDLE ctx, [in] int table_id) BOOL SitOut([in] PCONTEXT_HANDLE ctx) BOOL SetBack([in] PCONTEXT_HANDLE ctx) BOOL CashIn([in] PCONTEXT_HANDLE ctx,        [in] PCREDIT_CARD ccDetails) BOOL CashOut([in] PCONTEXT_HANDLE ctx,        [out] PMAIL_INFO mailInfo)


This interface represents a simple RPC poker game that uses a context handle to maintain the session. The first step in using this application is to log in. Like any well-behaved RPC service, this application determines the user's identity via native RPC authentication, but after that, it relies on the context handle. So your first consideration is whether that context handle can be exposed to anyone. For instance, most RPC interfaces don't require an encrypted channel, so attackers might be able to sniff the context handle over the network. After attackers have the context handle, they can take control of the session and steal a player's winnings.

Strict Context Handles

Generally, an RPC interface has no need to share a context handle with another interface. However, the RPC subsystem has no way of determining this implicitly. So the RPC service normally accepts any valid context handle, regardless of the originating interface. Developers can prevent this issue by using strict context handles defined by using the strict_context_handle attribute. A strict context handle is valid only for the originating interface, and the originator doesn't accept context handles from any other interface.

In the poker example, context handles are used to validate authentication. If this interface fails to use strict context handles, attackers could go to an unrelated interface and receive a valid context handle for the poker interface. A nonstrict context handle allows attackers to bypass the authentication system easily because the application checks credentials only in the logon method. If attackers provide a handle from another interface, they have implicit access to all methods of the poker interface.

Of course, the poker game probably won't do well if attackers provide a context handle from another interface. Effectively, they are just giving the application an arbitrary data structure that has no relation to what it expects. This input would probably cause a crash or throw some other error. However, what would happen if the other interface could be manipulated enough to make the arbitrary structure recognizable to the poker game? The following structure represents the context for the poker game followed by an implementation of the CashOut() function:

// Game implementation struct GAME_CONTEXT {     long iBalance;     BOOLEAN isComplete;     HAND myHand; } BOOL CashOut(PCONTEXT_HANDLE ctx, PMAIL_INFO mailInfo) {     struct GAME_CONTEXT *game = ctx;     if (game->isComplete) {         DepositWinnings(game->iBalance);         return TRUE;     }     return FALSE; } ... more game handling functions ...


Now you need to consider another interface on the same server. Assume the poker game is part of a casino application that exposes a separate RPC interface for account management. The following code is the context structure for the account management interface, along with a function to update account information:

// Account implementation struct ACCT_CONTEXT {     long birthDate;     char sName[MAX_STR];     char sAcctNum[MAX_STR]; } void UpdateAcctInfo(PCONTEXT_HANDLE ctx, long bDate,                     char *name, char *acctnum) {     struct ACCT_CONTEXT *acct = ctx;     acct->birthDate = bDate;     strncpy(acct->sName, name, MAX_STR - 1);     strncpy(acct->sAcctNum, acctnum, MAX_STR - 1); } ... more account management functions ...


This example is simple, but it should help make the vulnerability apparent. Attackers could use these interfaces to build an account structure with an extremely large balance. All that's necessary is calling the UpdateAcctInfo() function and passing a large value as the bDate parameter. Then attackers can call the CashOut() function on the poker interface. This interface pays out the amount passed as bDate in the earlier call because birthDate in ACCT_CONTEXT is at the same offset as iBalance in GAME_CONTEXT. So attackers can simply log in to the account manager interface, select how much money they want, and then cash out of the poker game. This example is contrived, but it does demonstrate the point of this attack. A real vulnerability is usually more complicated and has a more immediate impact. For example, a context handle pointing to a C++ class instance might allow attackers to overwrite vtable and function pointers, resulting in arbitrary code execution.

Note

The exact meaning and implementation of a vtable depends on the language and object model. However, for most purposes you can assume a vtable is simply a list of pointers to member functions associated with an object.


One more quirk is that the other interface need not be implemented by a single application. It might be exposed by the OS or a third-party component. Developers might be unaware of what else is occurring and, therefore, consider strict context handles unnecessary. So you need to keep an eye out for this issue if you identify an interface that isn't using strict context handles, and see what functionality other interfaces might provide.

Proprietary State Mechanisms

Some application developers choose to write their own state-handling code in lieu of the mechanisms the RPC layer provides. These mechanisms generally exist for historical reasons or compatibility with other systems. As an auditor, you need to assess state-handling mechanisms by looking for the following vulnerabilities:

  • Predictable (not cryptographically random) session identifiers

  • Short session identifiers vulnerable to brute-force attacks

  • Discoverable session identifiers (access control failure)

  • Session identifiers that leak sensitive information

Generally, you'll find that custom state mechanisms fail to address at least one of these requirements. You might be able to use this information to identify a vulnerability that allows state manipulation or bypassing authentication.

Threading in RPC

The RPC subsystem services calls via a pool of worker threads. It's an efficient way of handling calls in Windows, but it does have some drawbacks. First, an RPC call can occur on any thread in the pool, so an RPC server can't expect any thread affinity between calls. This means the call should behave the same, regardless of the thread it's executing in. Second, an RPC call can be preempted at any time, even by another instance of the same call. This behavior can lead to vulnerabilities when access to shared resources isn't synchronized properly. Threading and concurrency issues are a topic of their own, however, so they are discussed in Chapter 13, "Synchronization and State."

Auditing RPC Applications

Now that you know the basics of RPC, you can use the following checklist as a guideline for performing RPC audits:

  1. Look for any other RPC servers in the same process that might expose protocols the developer didn't expect.

  2. If the application doesn't use strict context handles, look for any other interfaces that can be leveraged for an attack.

  3. Look for any proprietary state-handling mechanisms, and see whether they can be used for spoofing or state manipulation.

  4. Check for weaknesses in the ACLs applied to the protocol sequence.

  5. Look for authentication bypasses or spoofing attacks that are possible because of weak transport security.

  6. Look for authentication bypasses in custom authentication schemes, weak use of authentication, or the absence of authentication.

  7. Check to see whether state mechanisms are being used to maintain security state. If they are, try to find ways to bypass them.

  8. Audit any impersonation to see whether a client can evade it or use it to steal the server's credentials.

  9. Pay special attention to possible race conditions and synchronization issues with shared resources (discussed in more detail in Chapter 13).

  10. Review all exposed interfaces for general implementation vulnerabilities. If the IDL isn't compiled with the /robust switch and interface parameters aren't restricted, you need to spend more time checking for memory corruption vulnerabilities.

RPC Interface Binary Audits

If you don't have the source code for an RPC service, you need to be able to locate RPC interfaces in the corresponding application binaries. This section explains a simple technique for locating all relevant methods in an RPC binary.

First, recall that an RPC server registers its interfaces by using the RpcServerRegisterIf() and RpcServerRegisterIfEx() functions. Here's the prototype of the RpcServerRegisterIfEx() function:

RPC_STATUS RPC_ENTRY RpcServerRegisterIfEx(RPC_IF_HANDLE IfSpec,         UUID *MgrTypeUuid, RPC_MGR_EPV *MgrEpv,         unsigned int Flags, unsigned int MaxCalls,         RPC_IF_CALLBACK_FN *IfCallback)


The RpcServerRegisterIf() function has a similar prototype. Servers need to use one of these functions to indicate what methods are available. These methods are specified in the RPC_IF_HANDLE structure, the first argument. This structure isn't documented very well, but you can examine it by looking at the IDL-generated C server file that creates this structure. Essentially, RPC_IF_HANDLE contains only one member, which is a pointer to a RPC_SERVER_INTERFACE structure. This structure has the following format (as noted in rpcdcep.h):

typedef struct _RPC_SERVER_INTERFACE {     unsigned int Length;     RPC_SYNTAX_IDENTIFIER InterfaceId;     RPC_SYNTAX_IDENTIFIER TransferSyntax;     PRPC_DISPATCH_TABLE DispatchTable;     unsigned int RpcProtseqEndpointCount;     PRPC_PROTSEQ_ENDPOINT RpcProtseqEndpoint;     RPC_MGR_EPV __RPC_FAR *DefaultManagerEpv;     void const __RPC_FAR *InterpreterInfo;     unsigned int Flags ; } RPC_SERVER_INTERFACE, __RPC_FAR * PRPC_SERVER_INTERFACE;


In a typical binary, this structure looks something like this:

 .text:75073BD8 dword_75073BD8  dd 44h, 300F3532h, 11D038CCh, 2000F0A3h, 0DD0A6BAFh, 20001h .text:75073BD8                                       ; DATA XREF: .text:off_75073B88o .text:75073BD8                                       ; .data:off_7508603Co .text:75073BD8                 dd 8A885D04h, 11C91CEBh, 8E89Fh, 6048102Bh, 2 ; Interface ID .text:75073C04                 dd offset DispatchTable .text:75073C08                 dd 3 dup(0)           ; RpcProtseqEndpointCount, RpcProtseqEndpoint, DefaultMgrEpv .text:75073C14                 dd offset InterpreterInfo .text:75073C18                 dd 4000001h           ; flags


Of particular interest is the InterpreterInfo field, which points to a MIDL_SERVER_INFO structure defined in rpcndr.h as the following:

typedef struct _MIDL_SERVER_INFO_     {     PMIDL_STUB_DESC            pStubDesc;     const SERVER_ROUTINE *     DispatchTable;     PFORMAT_STRING             ProcString;     const unsigned short *     FmtStringOffset;     const STUB_THUNK *         ThunkTable;     PFORMAT_STRING             LocalFormatTypes;     PFORMAT_STRING             LocalProcString;     const unsigned short *     LocalFmtStringOffset;     } MIDL_SERVER_INFO, *PMIDL_SERVER_INFO;


In a binary, the structure looks like this:

.text:75073C1C InterpreterInfo dd offset pStubDesc   ; DATA XREF: .text:75073C14o .text:75073C20                 dd offset ServerDispatchTable .text:75073C24                 dd offset ProcString .text:75073C28                 dd offset FmtStringOffset .text:75073C2C                 dd 5 dup(0)


The second member, named ServerDispatchTable in this example, contains a pointer to a table of all exposed server routines for the interface. To find RPC server routines in a binary, use the following steps:

1.

Find the import for RpcServerRegisterIf() or RpcServerRegisterIfEx() and cross-reference to find where it's used.

2.

Examine the first argument; it points to a single pointer that points to an RPC_SERVER_INTERFACE structure.

3.

Follow the InterpreterInfo structure member in the RPC_SERVER_INTERFACE structure.

4.

Follow the DispatchTable memory in the MIDL_SERVER_INFO structure to the table of server routines.

Voilà! You're done. Notice all the interesting information you pick up along the way, such as whether a callback function is passed to RpcServerRegisterIfEx(), endpoints associated with the server interface, format string information, and so on.




The Art of Software Security Assessment. Identifying and Preventing Software Vulnerabilities
The Art of Software Security Assessment: Identifying and Preventing Software Vulnerabilities
ISBN: 0321444426
EAN: 2147483647
Year: 2004
Pages: 194

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net