今天要介紹的是 Remote Procedure Call (RPC),也是 Windows 中常見的 Inter Process Communication (IPC) 機制之一。
在註冊 PRC Server 的階段可以用下圖表示
接下來,透過介紹上述的 API,分享有哪些設定是非常重要,一旦設定錯誤很有可能被攻擊者濫用。
這邊以 RpcServerUserProtseqEp
為例
RPC_STATUS RpcServerUseProtseqEp(
RPC_CSTR Protseq,
unsigned int MaxCalls,
RPC_CSTR Endpoint,
void *SecurityDescriptor
);
Protseq
用來指定 RPC 的連線模式。根據 csandker 的文章,可以以前綴作為 protocol 的區分:
只有 NCACN 可以接受 remote 連接,其他都是 local
比較常見的 protocol 有以下幾種:
ncacn_ip_tcp
ncacn_http
ncacn_np
ncadg_ip_udp
ncalrpc
SecurityDescriptor
只適用於 ncalrpc
和 ncacn_np
,其他 protocol 則不適用。 (不確定像是 ncacn_ip_tcp
是不是因為 TCP socket 沒有辦法綁定 SecurityDescriptor)
另外,MSDN 也特別說明不推薦在 endpoint 綁定 SecurityDescriptor
。但是這點在 James Forshaw 的文章有不同的看法。
除了 RpcServerUserProtseqEp
,也可以使用 RpcServerUserProtseq
指定 Dynamic Endpoints,也就是由系統指派。
由 RPC Endpoint Mapper service 負責管理 Dynamic Endpoints。
使用 Dynamic Endpoints 時, server 還需要使用 RpcServerInqBindings
和 RpcEpRegister
向 RPC Endpoint Mapper 註冊。
這邊以 RpcServerRegisterIf3
為例
RPC_STATUS RpcServerRegisterIf3(
[in] RPC_IF_HANDLE IfSpec,
[in, optional] UUID *MgrTypeUuid,
[in, optional] RPC_MGR_EPV *MgrEpv,
[in] unsigned int Flags,
[in] unsigned int MaxCalls,
[in] unsigned int MaxRpcSize,
[in, optional] RPC_IF_CALLBACK_FN *IfCallback,
[in, optional] void *SecurityDescriptor
);
RPC Interface 設定檔 IDL 在經過 midl.exe 編譯後,會生成 MIDL 結構,註冊時需要將 MIDL 結構放在參數 IfSpec
。
MIDL 結構是基於 RPC NDR Engine。在 client 使用 RPC call 的時候,會將參數 marshalling 傳遞。
根據 csandker 的 example,下面是個簡單的 IDL:
[
// UUID: A unique identifier that distinguishes this
// interface from other interfaces.
uuid(9510b60a-2eac-43fc-8077-aaefbdf3752b),
// This is version 1.0 of this interface.
version(1.0),
// Using an implicit handle here named hImplicitBinding:
implicit_handle(handle_t hImplicitBinding)
]
interface Example1 // The interface is named Example1
{
// A function that takes a zero-terminated string.
int Output(
[in, string] const char* pszOutput);
void Shutdown();
}
RPC server 和 client 雙方都必須知道這個 IDL,client 才能夠以這個 IDL 和 server 互動。
SecurityDescriptor
是用來指定 RPC Interface 的 Security Descriptor。預設會是
Flags
RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH
RPC_IF_ALLOW_LOCAL_ONLY
ncacn_np
才可以使用ncalrpc
可以使用RPC_IF_AUTOLISTEN
RPC_IF_OLE
RPC_IF_ALLOW_UNKNOWN_AUTHORITY
RPC_IF_ALLOW_SECURE_ONLY
RPC_C_AUTHN_LEVEL_NONE
RPC_IF_SEC_NO_CACHE
IfCallback
也就是所謂的 Security Callback,每當 client 連接時,在檢查完 Security Descriptor 後會先觸發 Security Callback。如果需要身分驗證,通常會在 Security Callback 中實作身分驗證的檢查,因為 RPC server 沒辦法指定驗證的 level。
Security Callback 回傳 RPC_S_OK
表示可以執行後續的操作。
RPC_STATUS RpcBindingInqAuthClient(
RPC_BINDING_HANDLE ClientBinding,
RPC_AUTHZ_HANDLE *Privs,
RPC_WSTR *ServerPrincName,
unsigned long *AuthnLevel,
unsigned long *AuthnSvc,
unsigned long *AuthzSvc
);
在 Security Callback 中 RpcBindingInqAuthClient
使用 xxx 可以依據 Authenticaiton level 有不同的要求:
RPC_C_AUTHN_LEVEL_NONE
RPC_C_AUTHN_LEVEL_CONNECT
RPC_C_AUTHN_LEVEL_PKT_INTEGRITY
RPC_C_AUTHN_LEVEL_PKT_PRIVACY
RPC_STATUS RpcServerRegisterAuthInfo(
RPC_CSTR ServerPrincName,
unsigned long AuthnSvc,
RPC_AUTH_KEY_RETRIEVAL_FN GetKeyFn,
void *Arg
);
和 Named Pipe 不同,RPC 可以額外設定 AuthnSvc
選擇 Authentication Service Provider 進行身分驗證:
RPC_C_AUTHN_WINNT
RPC_C_AUTHN_GSS_KERBEROS
其他的 Auth Service Provider 可以參考 MSDN,基本上和 SSPI 是一樣的,
RPC_STATUS RpcServerInqBindings(
RPC_BINDING_VECTOR **BindingVector
);
回傳 binding handle
RPC_STATUS RpcEpRegister(
RPC_IF_HANDLE IfSpec,
RPC_BINDING_VECTOR *BindingVector,
UUID_VECTOR *UuidVector,
RPC_CSTR Annotation
);
使用 Dynamic Endpoints 時,使用此 API 向 RPC Endpoint Mapper 註冊。
RPC_STATUS RpcServerListen(
unsigned int MinimumCallThreads,
unsigned int MaxCalls,
unsigned int DontWait
);
RPC Server 開始 Listen。
在看別人的 writeups 都覺得 RPC Client 好像很簡單,實際寫的時候會發現超多坑,但時間不夠就不贅述了。
RPC_STATUS RpcStringBindingCompose(
RPC_CSTR ObjUuid,
RPC_CSTR ProtSeq,
RPC_CSTR NetworkAddr,
RPC_CSTR Endpoint,
RPC_CSTR Options,
RPC_CSTR *StringBinding
);
建立 string binding handle
RPC_STATUS RpcBindingFromStringBinding(
RPC_CSTR StringBinding,
RPC_BINDING_HANDLE *Binding
);
回傳 binding handle
RPC_STATUS RpcBindingSetAuthInfo(
RPC_BINDING_HANDLE Binding,
RPC_CSTR ServerPrincName,
unsigned long AuthnLevel,
unsigned long AuthnSvc,
RPC_AUTH_IDENTITY_HANDLE AuthIdentity,
unsigned long AuthzSvc
);
Client 未必要使用此 API 進行 Authentication Binding,即使 Server 有設定 Authentication Binding,也有可能成功連接 RPC Interface。以下有兩種情境:
RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH
RPC_IF_ALLOW_SECURE_ONLY
RPC_C_AUTHN_LEVEL_NONE
另外在 IDL 當中還分為 implicit binding, explicit binding 和 automatic binding 3種:
最後,Client 在完成 RPC 連接後,就可以根據 IDL 發起 RPC call。