windows的rpc是一个很重要的接口, 以前我对它一直不算了解, 今天以一个安全研究的角度去介绍一下它.

注册rpc服务

server 通过 RpcServerUseProtseqEp 注册服务, 可以有的类型有: ncalrpc (ALPC), ncacn_np (named pipe) or ncacn_ip_tcp (TCP socket) 参考链接
pipe类型:

1
2
3
4
5
RpcServerUseProtseqEp(
L"ncacn_np",
RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
L"\\pipe\\DEMO",
nullptr);

socket类型:

1
2
3
4
5
status = RpcServerUseProtseqEp(
reinterpret_cast<unsigned char*>("ncacn_ip_tcp"), // Use TCP/IP protocol.
RPC_C_PROTSEQ_MAX_REQS_DEFAULT, // Backlog queue length for TCP/IP.
reinterpret_cast<unsigned char*>("4747"), // TCP/IP port to use.
NULL); // No security.

alpc类型:

1
RpcServerUseProtseqEpA("ncalrpc", 10, "spoolss", SecurityDescriptor);

之后是注册rpc函数接口:

以后面示例代码Example1Server.cpp为例:

1
2
3
4
5
6
7
8
status = RpcServerRegisterIf2(
Example1_v1_0_s_ifspec, // Interface to register.
NULL, // Use the MIDL generated entry-point vector.
NULL, // Use the MIDL generated entry-point vector.
RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH, // Forces use of security callback.
RPC_C_LISTEN_MAX_CALLS_DEFAULT, // Use default number of concurrent calls.
(unsigned)-1, // Infinite max size of incoming data blocks.
SecurityCallback); // Naive security callback.

第四个参数flag, 值如下:

1
2
3
4
5
6
7
8
9
10
#define RPC_IF_AUTOLISTEN                   0x0001
#define RPC_IF_OLE 0x0002
#define RPC_IF_ALLOW_UNKNOWN_AUTHORITY 0x0004
#define RPC_IF_ALLOW_SECURE_ONLY 0x0008
#define RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH 0x0010
#define RPC_IF_ALLOW_LOCAL_ONLY 0x0020
#define RPC_IF_SEC_NO_CACHE 0x0040
#if (NTDDI_VERSION >= NTDDI_VISTA)
#define RPC_IF_SEC_CACHE_PER_PROC 0x0080
#define RPC_IF_ASYNC_CALLBACK 0x0100

RPC_IF_ALLOW_SECURE_ONLY代表接口需要认证.

示例项目里, 它会自动生成Example1_s.c文件, 里面有Example1_v1_0_s_ifspec的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
RPC_IF_HANDLE Example1_v1_0_s_ifspec = (RPC_IF_HANDLE)& Example1___RpcServerInterface;
static const RPC_SERVER_INTERFACE Example1___RpcServerInterface =
{
sizeof(RPC_SERVER_INTERFACE),
{{0x00000001,0xEAF3,0x4A7A,{0xA0,0xF2,0xBC,0xE4,0xC3,0x0D,0xA7,0x7E}},{1,0}},
{{0x8A885D04,0x1CEB,0x11C9,{0x9F,0xE8,0x08,0x00,0x2B,0x10,0x48,0x60}},{2,0}},
(RPC_DISPATCH_TABLE*)&Example1_v1_0_DispatchTable,
0,
0,
0,
&Example1_ServerInfo,
0x06000000
};
static const RPC_DISPATCH_FUNCTION Example1_table[] =
{
NdrServerCall2,
0
};
static const RPC_DISPATCH_TABLE Example1_v1_0_DispatchTable =
{
1,// 指定了有几个回调函数.
(RPC_DISPATCH_FUNCTION*)Example1_table
};

static const SERVER_ROUTINE Example1_ServerRoutineTable[] =
{
(SERVER_ROUTINE)Output// 导出函数
};

static const MIDL_SERVER_INFO Example1_ServerInfo =
{
&Example1_StubDesc,
Example1_ServerRoutineTable,// 导出函数表
Example1__MIDL_ProcFormatString.Format,
(unsigned short *) Example1_FormatStringOffsetTable,
0,
(RPC_SYNTAX_IDENTIFIER*)&_NDR64_RpcTransferSyntax_1_0,
2,
(MIDL_SYNTAX_INFO*)Example1_SyntaxInfo
};

在IDA中看Example1___RpcServerInterface:

1694054196198

其中+4位置是rpc服务对应的UUID.

再看[50h]位置的Example1_ServerInfo:

1694054233133

再看[8h]位置的SERVER_ROUTINE表:

1694054247738

client调用

client要调用服务, 必须通过RpcStringBindingCompose函数绑定, 再通过RpcBindingFromStringBinding获得RPC_BINDING_HANDLE, 下面是示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
status = RpcStringBindingCompose(
NULL, // UUID to bind to.
reinterpret_cast<unsigned char*>("ncacn_ip_tcp"), // Use TCP/IP
// protocol.
reinterpret_cast<unsigned char*>("localhost"), // TCP/IP network
// address to use.
reinterpret_cast<unsigned char*>("4747"), // TCP/IP port to use.
NULL, // Protocol dependent network options to use.
&szStringBinding); // String binding output.

status = RpcBindingFromStringBinding(
szStringBinding, // The string binding to validate.
&hExample1Binding); // Put the result in the implicit binding
// handle defined in the IDL file.

bind后, 直接调用接口代码即可Output("Hello Implicit RPC World!");, 实际上, 它真实的调用是如下:

1694054894682

通过调用NdrClientCall3函数来实现调用. 第二个参数就是目标server的函数编号, 此处就是第0号函数(即Example1_ServerRoutineTable[0]), 从第四个参数开始, 就是目标函数所需要的参数.

观察一下它的 Example1_ProxyInfo

1694055232546

Example1_StubDesc:

1694055253629

从这个结构体可以看到, +18h位置是&hExample1Binding, 指向RPC_BINDING_HANDLE.

再看Example1___RpcClientInterface对象(_RPC_CLIENT_INTERFACE结构体), +4位置是server对应的uuid

1694083472060

下面从调试角度看看它的关系:

1694056212795

继续handle对应的结构体:

1694056354014

如图, 在[F0h]偏移位置的地址, 指向的结构体存在三个指针, 这三个指针分别是RpcStringBindingCompose的参数.

NdrClientCall3找rpc接口

一个方法就是通过上述poi(poi(poi(poi(@rcx)+18))+f0)偏移去找字符串

某些情况下, 可能RPC_BINDING_HANDLE指针不存在(即poi(poi(@rcx)+18)为空), 这种情况下, 一般第四个参数(即函数调用的第一个参数), 是一个和binding_handle 有关的结构体. 在同一个dll里, 通过查找它的引用, 大概率找得到声明位置.

以下以sspi接口中sspicli.dll!SspipProcessSecurityContext调用的rpc来示例如何寻找:

方法1: 通过逆向代码查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  LODWORD(v19.Pointer) = IsOkayToExec(&v58);
if ( SLODWORD(v19.Simple) >= 0 )
{
v79 = v58[3];
......
v19.Pointer = NdrClientCall3((MIDL_STUBLESS_PROXY_INFO *)&sspirpc_ProxyInfo, 6u, 0i64, v79, &v90, v61, v59).Pointer;// 1. 关注第四个参数, 来自行1的v58

NTSTATUS __fastcall IsOkayToExec(_QWORD *a1)// 2. 深入函数实现
{
if ( (DllState & 0x40000000) != 0 )
{
if ( a1 )
{
if ( !SecDllClient )
return -1073741502;
*a1 = SecDllClient;// 3. 找到赋值
}
return 0;

通过查找SecDllClient的引用, 来到函数InitState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
__int64 InitState(void)
{
......
RpcConnection = CreateRpcConnection(0, 2, (unsigned int)&v4, (unsigned int)&SecLsaPackageCount, (__int64)&v3);
......
SecDllClient = LocalAlloc(0x40u, 0x30ui64);
if ( SecDllClient )
{
*((_QWORD *)SecDllClient + 3) = v4;// 4. +8位置赋值了v4

__int64 __fastcall CreateRpcConnection(__int64 a1, int a2, __int64 a3, __int64 a4, __int64 a5)
{
result = SecpGetRpcBinding(&Binding);
if ( (int)result >= 0 )
{
v14.Simple = 0i64;
v12 = a2;
v10.Pointer = NdrClientCall3((MIDL_STUBLESS_PROXY_INFO *)&sspirpc_ProxyInfo, 0, 0i64, Binding, a1, v12, a5, a4, a3).Pointer;// 5. v4即a3, a3来自于rpc调用的赋值. 关注到第四个参数binding

int __fastcall SecpGetRpcBinding(RPC_BINDING_HANDLE *a1)
{
Binding = 0i64;
v2 = RpcStringBindingComposeW(
0i64,
(RPC_WSTR)L"ncalrpc",
0i64,
(RPC_WSTR)L"lsasspirpc",
word_180030B10,
&StringBinding);
v2 = RpcBindingFromStringBindingW(StringBinding, &Binding);// 6. 可以看到, binding就是我们要找的rpc_binding_handle

因为名字有isass, 估计是进程isass.exe, 通过rpcview查看:

1694059325724

rpcview不会自动识别端口来自哪个dll的rpc接口, 因此需要我们猜一下.

因为是sspi组件, 所以查看左下方所有的dll, 猜测应该是sspisrv.dll对应的服务是目标lsasspirpc, 右下方就是rpc的调用表.

rpcview的Flags栏, 鼠标放上去, 可以看到它的flag是什么意思.

1721958980682

如果想有符号, 通过下列方式添加(目录不要包含空格):

1694059416499

1694059427500

类似于windbg. 添加后重启rpcview.

它不会自动下载符号, 需要把符号先下载到对应目录才行, 下面是示例下载符号到C:\symbols目录的方法, 如果只需要一个dll的符号, 可以只下载那个dll, 不用全部下载.

1
2
cmd> cd "C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\"
cmd> .\symchk /s srv*c:\SYMBOLS*https://msdl.microsoft.com/download/symbols C:\Windows\System32\*.dll

另外, 在选中dll后, 点击右键, 选择decompile, 可以得到接口对应的idl.

在sspisvc.dll里, 我们查找RpcServerRegisterIf3引用, 找到:

1694059702812

跟踪dword_180006380:

1694059793138

查看[50h]:

1694060073435

查看[8h]:

1694060911071

自此我们找到了它的调用接口.

一般rpc server的调用栈回溯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SspiSrv!SspirProcessSecurityContext
RPCRT4!Invoke+0x73
RPCRT4!Ndr64StubWorker+0xb98
RPCRT4!NdrServerCallAll+0x3c
RPCRT4!DispatchToStubInCNoAvrf+0x17
RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1a8
RPCRT4!RPC_INTERFACE::DispatchToStub+0xf1
RPCRT4!LRPC_SCALL::DispatchRequest+0x14d
RPCRT4!LRPC_SCALL::HandleRequest+0xd5a
RPCRT4!LRPC_SASSOCIATION::HandleRequest+0x2c3
RPCRT4!LRPC_ADDRESS::HandleRequest+0x183
RPCRT4!LRPC_ADDRESS::ProcessIO+0x939
RPCRT4!LrpcIoComplete+0xfe
ntdll!TppAlpcpExecuteCallback+0x20c
ntdll!TppWorkerThread+0x4b3
KERNEL32!BaseThreadInitThunk+0x17
ntdll!RtlUserThreadStart+0x20

方法2: 通过调试找到uuid

在调用NdrClientCall3断下, 依次查看结构体:

1694084123706

和rpcview一致.

com接口的rpc NdrpClientCall3

com接口一般在进程内调用rpc时就用的 RPCRT4!NdrpClientCall3接口. 声明如下:

1
2
3
4
5
6
7
8
9
10
11
CLIENT_CALL_RETURN RPC_ENTRY
NdrpClientCall3
(
void * pThis, // rcx
MIDL_STUBLESS_PROXY_INFO *pProxyInfo, // rdx
ulong nProcNum, // r8
void *pReturnValue, // r9
NDR_PROC_CONTEXT *pContext, // poi(@rsp+0x28)
uchar *StartofStack // poi(@rsp+0x30)

)

第二个参数就是MIDL_STUBLESS_PROXY_INFO 结构体指针, 第三个是函数序号. 如果确认是进程内的调用, 可以直接在当前线程的RPCRT4!Invoke下断点, 直接找到相关处理函数.

示例堆栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
07 00000053`8d2fd8e0 00007ffe`ef6d7de3     eapahost!EapHost::HostAuthenticatorApis::EapHostAuthenticatorReceivePacket+0xde // 实际调用的函数

08 00000053`8d2fd960 00007ffe`ef73bc6d RPCRT4!Invoke+0x73
09 00000053`8d2fd9d0 00007ffe`ef6668b9 RPCRT4!Ndr64StubWorker+0xbfd
0a 00000053`8d2fe0a0 00007ffe`ef802209 RPCRT4!NdrStubCall3+0xc9
0b 00000053`8d2fe100 00007ffe`ef66a92b combase!CStdStubBuffer_Invoke+0x59 [d:\rs1\onecore\com\combase\ndr\ndrole\stub.cxx @ 1527]
0c 00000053`8d2fe140 00007ffe`ef84de3c RPCRT4!CStdStubBuffer_Invoke+0x3b
0d (Inline Function) --------`-------- combase!InvokeStubWithExceptionPolicyAndTracing::__l6::<lambda_76d9e92c799d246a4afbe64a2bf5673d>::operator()+0x24 [d:\rs1\onecore\com\combase\dcomrem\channelb.cxx @ 1824]
0e 00000053`8d2fe170 00007ffe`ef84e482 combase!ObjectMethodExceptionHandlingAction<<lambda_76d9e92c799d246a4afbe64a2bf5673d> >+0x4c [d:\rs1\onecore\com\combase\dcomrem\excepn.hxx @ 91]
0f (Inline Function) --------`-------- combase!InvokeStubWithExceptionPolicyAndTracing+0x8d [d:\rs1\onecore\com\combase\dcomrem\channelb.cxx @ 1822]
10 00000053`8d2fe1d0 00007ffe`ef81fab1 combase!DefaultStubInvoke+0x222 [d:\rs1\onecore\com\combase\dcomrem\channelb.cxx @ 1891]
11 00000053`8d2fe3f0 00007ffe`ef8054c0 combase!CCtxChnl::SendReceive+0x2b1 [d:\rs1\onecore\com\combase\dcomrem\crossctx.cxx @ 4138]
12 00000053`8d2fe660 00007ffe`ef737aed combase!NdrExtpProxySendReceive+0x1c0 [d:\rs1\onecore\com\combase\ndr\ndrole\proxy.cxx @ 1965]
13 00000053`8d2fe6d0 00007ffe`ef8014f4 RPCRT4!NdrpClientCall3+0x46d
14 00000053`8d2feae0 00007ffe`ef90cbb2 combase!ObjectStublessClient+0x144 [d:\rs1\onecore\com\combase\ndr\ndrole\amd64\stblsclt.cxx @ 371]
15 00000053`8d2fee70 00007ffe`e27a4808 combase!ObjectStubless+0x42 [d:\rs1\onecore\com\combase\ndr\ndrole\amd64\stubless.asm @ 176]

eapahost!ObjectStublessClient6(直接调用的 combase!ObjectStublessClient6)// 进入com接口调用

16 00000053`8d2feec0 00007ffe`e27a25d9 iassam!EAPSession::processEAPPacket+0x64

RPC接口的序列化

当我们构造rpc的请求时, 需要知道server端需要什么样的参数, 如果是自定义的结构体, 如果rpcview无法解析出结构体, 就需要我们逆向server的结构体格式化描述, 逆向出结构体. 本节将介绍server端如何构造的结构体格式化描述, 通过ida比对, 来逆向server的结构体.

假如我们的idl定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[
uuid(3d267954-eeb7-11d1-b94e-00c04fa3080d),
version(1.0),
]
interface Example1
{

error_status_t Proc0_TLSRpcGetVersion(
[in][context_handle] void* arg_0,
[in][out]long* arg_1);

error_status_t Proc1_TLSRpcConnect(
[out][context_handle] void** arg_1);
struct uknow1 {
int f1;
int f2;
int f3;
int f4;
[size_is(f3)] unsigned char* buff;
int f18;
int f1c;
[size_is(f18)] unsigned char* buff2;
};

struct uknow2 {
int f1;
int f2;
char* buff;
int f10;
int f14;
int f18;
int f1c;
};
error_status_t Proc44_TLSRpcChallengeServer(
[in][context_handle] void* arg_0,
[in]long arg_1,
[in]struct uknow1* arg_2,
[out][ref]struct uknow2** arg_3,
[out][ref]struct uknow1** arg_4,
[in][out]long* arg_5);
]

在本文示例的server中, 添加如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
error_status_t Proc0_TLSRpcGetVersion(void *handle, long *ver) {
return 0;
}

error_status_t Proc1_TLSRpcConnect(void** handle) {
return 2;
}

error_status_t Proc44_TLSRpcChallengeServer(
void* arg_0,
long arg_1,
struct uknow1* arg_2,
struct uknow2** arg_3,
struct uknow1** arg_4,
long* arg_5) {
return 1;
}

这里, 我们一共定义了三个接口函数. 并声明了两种特别的结构体ukonw1, uknow2.

在自动生成的.c文件里, 就可以看到如下信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
static const RPC_SERVER_INTERFACE Example1___RpcServerInterface =
{
sizeof(RPC_SERVER_INTERFACE),
{{0x3d267954,0xeeb7,0x11d1,{0xb9,0x4e,0x00,0xc0,0x4f,0xa3,0x08,0x0d}},{1,0}},
{{0x8A885D04,0x1CEB,0x11C9,{0x9F,0xE8,0x08,0x00,0x2B,0x10,0x48,0x60}},{2,0}},
(RPC_DISPATCH_TABLE*)&Example1_v1_0_DispatchTable,
0,
0,
0,
&Example1_ServerInfo,
0x06000000
};

static const MIDL_SERVER_INFO Example1_ServerInfo =
{
&Example1_StubDesc,
Example1_ServerRoutineTable,
Example1__MIDL_ProcFormatString.Format,
(unsigned short *) Example1_FormatStringOffsetTable,
0,
(RPC_SYNTAX_IDENTIFIER*)&_NDR64_RpcTransferSyntax_1_0,
2,
(MIDL_SYNTAX_INFO*)Example1_SyntaxInfo
};

static const MIDL_SYNTAX_INFO Example1_SyntaxInfo [ 2 ] =
{
{
{{0x8A885D04,0x1CEB,0x11C9,{0x9F,0xE8,0x08,0x00,0x2B,0x10,0x48,0x60}},{2,0}},
(RPC_DISPATCH_TABLE*)&Example1_v1_0_DispatchTable,
Example1__MIDL_ProcFormatString.Format,
Example1_FormatStringOffsetTable,
Example1__MIDL_TypeFormatString.Format,
0,
0,
0
}
,{
{{0x71710533,0xbeba,0x4937,{0x83,0x19,0xb5,0xdb,0xef,0x9c,0xcc,0x36}},{1,0}},
(RPC_DISPATCH_TABLE*)&Example1_NDR64__v1_0_DispatchTable,
0 ,
(unsigned short *) Example1_Ndr64ProcTable,
0,
0,
0,
0
}
};

其中序列化有关的就是 Example1_SyntaxInfo结构体.

在ida中如下:

1722224226542

以下是Example1__MIDL_ProcFormatString结构体的部分内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
static const Example1_MIDL_PROC_FORMAT_STRING Example1__MIDL_ProcFormatString =
{
0,
{
......

/* Procedure Proc44_TLSRpcChallengeServer */

/* 88 */ 0x0, /* 0 */
0x48, /* Old Flags: */
/* 90 */ NdrFcLong( 0x0 ), /* 0 */
/* 94 */ NdrFcShort( 0x2 ), /* 2 */
/* 96 */ NdrFcShort( 0x38 ), /* X64 Stack size/offset = 56 */
/* 98 */ 0x30, /* FC_BIND_CONTEXT */
0x40, /* Ctxt flags: in, */
/* 100 */ NdrFcShort( 0x0 ), /* X64 Stack size/offset = 0 */
/* 102 */ 0x0, /* 0 */
0x0, /* 0 */
/* 104 */ NdrFcShort( 0x48 ), /* 72 */
/* 106 */ NdrFcShort( 0x24 ), /* 36 */
/* 108 */ 0x47, /* Oi2 Flags: srv must size, clt must size, has return, has ext, */
0x7, /* 7 */
/* 110 */ 0xa, /* 10 */
0x7, /* Ext Flags: new corr desc, clt corr check, srv corr check, */
/* 112 */ NdrFcShort( 0x1 ), /* 1 */
/* 114 */ NdrFcShort( 0x1 ), /* 1 */
/* 116 */ NdrFcShort( 0x0 ), /* 0 */
/* 118 */ NdrFcShort( 0x0 ), /* 0 */

/* Parameter arg_0 */

/* 120 */ NdrFcShort( 0x8 ), /* Flags: in, */
/* 122 */ NdrFcShort( 0x0 ), /* X64 Stack size/offset = 0 */
/* 124 */ NdrFcShort( 0x2 ), /* Type Offset=2 */

/* Parameter arg_1 */

/* 126 */ NdrFcShort( 0x48 ), /* Flags: in, base type, */
/* 128 */ NdrFcShort( 0x8 ), /* X64 Stack size/offset = 8 */
/* 130 */ 0x8, /* FC_LONG */
0x0, /* 0 */

/* Parameter arg_2 */

/* 132 */ NdrFcShort( 0x10b ), /* Flags: must size, must free, in, simple ref, */
/* 134 */ NdrFcShort( 0x10 ), /* X64 Stack size/offset = 16 */
/* 136 */ NdrFcShort( 0x2e ), /* Type Offset=46 */

/* Parameter arg_3 */

/* 138 */ NdrFcShort( 0x2013 ), /* Flags: must size, must free, out, srv alloc size=8 */
/* 140 */ NdrFcShort( 0x18 ), /* X64 Stack size/offset = 24 */
/* 142 */ NdrFcShort( 0x48 ), /* Type Offset=72 */

/* Parameter arg_4 */

/* 144 */ NdrFcShort( 0x2013 ), /* Flags: must size, must free, out, srv alloc size=8 */
/* 146 */ NdrFcShort( 0x20 ), /* X64 Stack size/offset = 32 */
/* 148 */ NdrFcShort( 0x64 ), /* Type Offset=100 */

/* Parameter arg_5 */

/* 150 */ NdrFcShort( 0x158 ), /* Flags: in, out, base type, simple ref, */
/* 152 */ NdrFcShort( 0x28 ), /* X64 Stack size/offset = 40 */
/* 154 */ 0x8, /* FC_LONG */
0x0, /* 0 */

/* Return value */

/* 156 */ NdrFcShort( 0x70 ), /* Flags: out, return, base type, */
/* 158 */ NdrFcShort( 0x30 ), /* X64 Stack size/offset = 48 */
/* 160 */ 0x10, /* FC_ERROR_STATUS_T */
0x0, /* 0 */

0x0
}
};

static const unsigned short Example1_FormatStringOffsetTable[] =
{
0,
50,
88
};

Example1_FormatStringOffsetTable 结构体里的值就是每一个rpc接口函数在Example1__MIDL_ProcFormatString.Format内存里的偏移, 因为我们声明了三个函数, 所以有三个值. 因为是从Format字段开始算的, 需要偏移2字节.

在ida中反汇编server, 在偏移88+2 即 0x5a位置, 就是0, 0x48, ...

1722224655345

这里的2 dup(0)是指有两个重复的0. 偏移0x60+0x58 = 0xb8, 0x140006cb8 位置的值为 10h, 0, 0, 48h, 0, 0, 0, 0, 2....

Example1__MIDL_ProcFormatString 结构体会具象化到 Example1_Ndr64ProcTable 里.

让我们看一下Example1_Ndr64ProcTable结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
static const FormatInfoRef Example1_Ndr64ProcTable[] = // 这是一个描述符的数组.
{
&__midl_frag2,
&__midl_frag7,
&__midl_frag11
};

static const __midl_frag11_t __midl_frag11 =
{
/* Proc44_TLSRpcChallengeServer */
{
/* Proc44_TLSRpcChallengeServer */ /* procedure Proc44_TLSRpcChallengeServer */
(NDR64_UINT32) 23986240 /* 0x16e0040 */, /* explicit handle */ /* IsIntrepreted, ServerMustSize, ClientMustSize, HasReturn, ServerCorrelation, ClientCorrelation, HasExtensions */
(NDR64_UINT32) 56 /* 0x38 */ , /* Stack size */
(NDR64_UINT32) 76 /* 0x4c */,
(NDR64_UINT32) 157 /* 0x9d */,
(NDR64_UINT16) 0 /* 0x0 */,
(NDR64_UINT16) 0 /* 0x0 */,
(NDR64_UINT16) 7 /* 0x7 */,
(NDR64_UINT16) 8 /* 0x8 */
},
{
/* struct _NDR64_BIND_AND_NOTIFY_EXTENSION */
{
/* struct _NDR64_BIND_AND_NOTIFY_EXTENSION */
0x70, /* FC64_BIND_CONTEXT */
(NDR64_UINT8) 64 /* 0x40 */,
0 /* 0x0 */, /* Stack offset */
(NDR64_UINT8) 0 /* 0x0 */,
(NDR64_UINT8) 0 /* 0x0 */
},
(NDR64_UINT16) 0 /* 0x0 */ /* Notify index */
},
{
/* arg_0 */ /* parameter arg_0 */
&__midl_frag12, // _NDR64_PARAM_FORMAT.Type
{ // _NDR64_PARAM_FORMAT.Attribute
/* arg_0 */
0,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
0,
(NDR64_UINT16) 0 /* 0x0 */,
0
}, /* [in] */
(NDR64_UINT16) 0 /* 0x0 */,
0 /* 0x0 */, // _NDR64_PARAM_FORMAT.StackOffset
},
{
/* arg_1 */ /* parameter arg_1 */
&__midl_frag13,
{
/* arg_1 */
0,
0,
0,
1,
0,
0,
1,
1,
0,
0,
0,
0,
0,
(NDR64_UINT16) 0 /* 0x0 */,
0
}, /* [in], Basetype, ByValue */
(NDR64_UINT16) 0 /* 0x0 */,
8 /* 0x8 */, /* Stack offset */
},
{
/* arg_2 */ /* parameter arg_2 */
&__midl_frag15,
{
/* arg_2 */
1,
1,
0,
1,
0,
0,
0,
0,
1,
0,
0,
0,
0,
(NDR64_UINT16) 0 /* 0x0 */,
0
}, /* MustSize, MustFree, [in], SimpleRef */
(NDR64_UINT16) 0 /* 0x0 */,
16 /* 0x10 */, /* Stack offset */
},
{
/* arg_3 */ /* parameter arg_3 */
&__midl_frag22,
{
/* arg_3 */
0,
1,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
(NDR64_UINT16) 0 /* 0x0 */,
1
}, /* MustFree, [out], UseCache */
(NDR64_UINT16) 0 /* 0x0 */,
24 /* 0x18 */, /* Stack offset */
},
{
/* arg_4 */ /* parameter arg_4 */
&__midl_frag26,
{
/* arg_4 */
1,
1,
0,
0,
1,
0,
0,
0,
0,
0,
0,
0,
0,
(NDR64_UINT16) 0 /* 0x0 */,
1
}, /* MustSize, MustFree, [out], UseCache */
(NDR64_UINT16) 0 /* 0x0 */,
32 /* 0x20 */, /* Stack offset */
},
{
/* arg_5 */ /* parameter arg_5 */
&__midl_frag29,
{
/* arg_5 */
0,
0,
0,
1,
1,
0,
1,
0,
1,
0,
0,
0,
0,
(NDR64_UINT16) 0 /* 0x0 */,
0
}, /* [in], [out], Basetype, SimpleRef */
(NDR64_UINT16) 0 /* 0x0 */,
40 /* 0x28 */, /* Stack offset */
},
{
/* error_status_t */ /* parameter error_status_t */
&__midl_frag30,
{
/* error_status_t */
0,
0,
0,
0,
1,
1,
1,
1,
0,
0,
0,
0,
0,
(NDR64_UINT16) 0 /* 0x0 */,
0
}, /* [out], IsReturn, Basetype, ByValue */
(NDR64_UINT16) 0 /* 0x0 */,
48 /* 0x30 */, /* Stack offset */
}
};

这里可以看到, 每一个参数都有描述符.

在IDA中如下:

1722225347730

我们查看一下__midl_frag11_t结构体的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
typedef 
struct
{
struct _NDR64_PROC_FORMAT frag1;
struct _NDR64_BIND_AND_NOTIFY_EXTENSION frag2;
struct _NDR64_PARAM_FORMAT frag3;
struct _NDR64_PARAM_FORMAT frag4;
struct _NDR64_PARAM_FORMAT frag5;
struct _NDR64_PARAM_FORMAT frag6;
struct _NDR64_PARAM_FORMAT frag7;
struct _NDR64_PARAM_FORMAT frag8;
struct _NDR64_PARAM_FORMAT frag9;
}
__midl_frag11_t;
typedef struct _NDR64_PARAM_FORMAT
{
PNDR64_FORMAT Type;
NDR64_PARAM_FLAGS Attributes;
NDR64_UINT16 Reserved;
NDR64_UINT32 StackOffset;
} NDR64_PARAM_FORMAT, *PNDR64_PARAM_FORMAT;
typedef struct _NDR64_PARAM_FLAGS
{
NDR64_UINT16 MustSize : 1;
NDR64_UINT16 MustFree : 1;
NDR64_UINT16 IsPipe : 1;
NDR64_UINT16 IsIn : 1;
NDR64_UINT16 IsOut : 1;// 4
NDR64_UINT16 IsReturn : 1;
NDR64_UINT16 IsBasetype : 1;
NDR64_UINT16 IsByValue : 1;
NDR64_UINT16 IsSimpleRef : 1;// 8
NDR64_UINT16 IsDontCallFreeInst : 1;
NDR64_UINT16 SaveForAsyncFinish : 1;
NDR64_UINT16 IsPartialIgnore : 1;
NDR64_UINT16 IsForceAllocate : 1;// 12
NDR64_UINT16 Reserved : 2;
NDR64_UINT16 UseCache : 1;
} NDR64_PARAM_FLAGS;

以第二个参数的描述符举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{ 
/* arg_2 */ /* parameter arg_2 */
&__midl_frag15,
{
/* arg_2 */
1,//MustSize
1,//MustFree
0,
1,//IsIn
0,
0,
0,
0,
1,//IsSimpleRef
0,
0,
0,
0,
(NDR64_UINT16) 0 /* 0x0 */,
0
}, /* MustSize, MustFree, [in], SimpleRef */
(NDR64_UINT16) 0 /* 0x0 */,
16 /* 0x10 */, /* Stack offset */
},

它的 Attribute为 10Bh, 即IsSimpleRef, IsIn, MustFree, MustSize.

查看__midl_frag15:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
typedef 
struct
{
struct _NDR64_STRUCTURE_HEADER_FORMAT{
NDR64_FORMAT_CHAR FormatCode;
NDR64_ALIGNMENT Alignment;
NDR64_STRUCTURE_FLAGS Flags;
NDR64_UINT8 Reserve;
NDR64_UINT32 MemorySize;
}
{
/* uknow1 */
0x31, /* FC64_PSTRUCT */ // 说明是个自定义结构体. 格式-值 对应表见 NDR64_FORMAT_CHARATER 章节
(NDR64_UINT8) 7 /* 0x7 */, // 8字节对齐
{
/* uknow1 */
1,
0,
0,
0,
0,
0,
0,
0
},
(NDR64_UINT8) 0 /* 0x0 */,
(NDR64_UINT32) 40 /* 0x28 */ // 说明结构体需要的字节为0x28.
},
struct
{
struct _NDR64_NO_REPEAT_FORMAT {
NDR64_FORMAT_CHAR FormatCode;
NDR64_UINT8 Flags;
NDR64_UINT16 Reserved1;
NDR64_UINT32 Reserved2;
}
{
/* struct _NDR64_NO_REPEAT_FORMAT */
0x80, /* FC64_NO_REPEAT */
(NDR64_UINT8) 0 /* 0x0 */,
(NDR64_UINT16) 0 /* 0x0 */,
(NDR64_UINT32) 0 /* 0x0 */
},
struct _NDR64_POINTER_INSTANCE_HEADER_FORMAT {
NDR64_UINT32 Offset;
NDR64_UINT32 Reserved;
}
{
/* struct _NDR64_POINTER_INSTANCE_HEADER_FORMAT */
(NDR64_UINT32) 16 /* 0x10 */, // 说明0x10偏移处是一个指针
(NDR64_UINT32) 0 /* 0x0 */
},
struct _NDR64_POINTER_FORMAT {
NDR64_FORMAT_CHAR FormatCode;
NDR64_UINT8 Flags;
NDR64_UINT16 Reserved;
PNDR64_FORMAT Pointee;
}
{
/* *char */
0x21, /* FC64_UP */
(NDR64_UINT8) 32 /* 0x20 */,
(NDR64_UINT16) 0 /* 0x0 */,
&__midl_frag16
},
struct _NDR64_NO_REPEAT_FORMAT
{
/* struct _NDR64_NO_REPEAT_FORMAT */
0x80, /* FC64_NO_REPEAT */
(NDR64_UINT8) 0 /* 0x0 */,
(NDR64_UINT16) 0 /* 0x0 */,
(NDR64_UINT32) 0 /* 0x0 */
},
struct _NDR64_POINTER_INSTANCE_HEADER_FORMAT
{
/* struct _NDR64_POINTER_INSTANCE_HEADER_FORMAT */
(NDR64_UINT32) 32 /* 0x20 */,
(NDR64_UINT32) 0 /* 0x0 */
},
struct _NDR64_POINTER_FORMAT
{
/* *char */
0x21, /* FC64_UP */
(NDR64_UINT8) 32 /* 0x20 */,
(NDR64_UINT16) 0 /* 0x0 */,
&__midl_frag19
},
NDR64_FORMAT_CHAR frag7;
0x93 /* FC64_END */
} frag2;
}
__midl_frag15_t;


这里我把数值和结构体声明放到了一起, 方便理解. 通过这个结构体信息, 可以知道, 结构体大小为0x28. 有两个指针, 分别在结构体偏移 0x10, 0x20处.

查看PNDR64_FORMAT __midl_frag16:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
static const __midl_frag16_t __midl_frag16 =
{
typedef struct _NDR64_CONF_ARRAY_HEADER_FORMAT
{
NDR64_FORMAT_CHAR FormatCode;
NDR64_ALIGNMENT Alignment;
NDR64_ARRAY_FLAGS Flags;
NDR64_UINT8 Reserved;
NDR64_UINT32 ElementSize;
PNDR64_FORMAT ConfDescriptor;
} NDR64_CONF_ARRAY_HEADER_FORMAT;
/* *char */
{
/* *char */
0x41, /* FC64_CONF_ARRAY */ // 说明是一个数组
(NDR64_UINT8) 0 /* 0x0 */,
{
/* *char */
0,
0,
0,
0,
0,
0,
0,
0
},
(NDR64_UINT8) 0 /* 0x0 */,
(NDR64_UINT32) 1 /* 0x1 */,
&__midl_frag17
},
typedef struct _NDR64_ARRAY_ELEMENT_INFO
{
NDR64_UINT32 ElementMemSize;
PNDR64_FORMAT Element;
} NDR64_ARRAY_ELEMENT_INFO;
{
/* struct _NDR64_ARRAY_ELEMENT_INFO */
(NDR64_UINT32) 1 /* 0x1 */,
&__midl_frag21
}
};
static const __midl_frag21_t __midl_frag21 =
0x10 /* FC64_CHAR */; // 说明数组成员是 CHAR 类型

static const __midl_frag17_t __midl_frag17 =
{
/* */
(NDR64_UINT32) 1 /* 0x1 */,
typedef struct _NDR64_EXPR_VAR
{
NDR64_FORMAT_CHAR ExprType;
NDR64_FORMAT_CHAR VarType;
NDR64_UINT16 Reserved;
NDR64_UINT32 Offset;
} NDR64_EXPR_VAR;
{
/* struct _NDR64_EXPR_VAR */
0x3, /* FC_EXPR_VAR */
0x5, /* FC64_INT32 */
(NDR64_UINT16) 0 /* 0x0 */,
(NDR64_UINT32) 8 /* 0x8 */ // 说明数组size取决于偏移为8的字段.
}
};

通过__midl_frag16我们可以知道, 数组的size取决于偏移为8字段的值, 数组成员为char类型.

Rpcview 获取 RPC 接口 IDL

通过rpcview的decompile操作, 可以得到接口的idl.

如果遇到rpcview解析idl报错, 比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[
uuid(3d267954-eeb7-11d1-b94e-00c04fa3080d),
version(1.0),
]
interface DefaultIfName
{
[ERROR] unable to get list of all types sorted
......
error_status_t Proc8_TLSRpcRequestNewLicense(
[in][context_handle] void* arg_0,
[in]long arg_1,
[in]struct Struct_302_t* arg_2,
[in][string] wchar_t* arg_3,
[in][string] wchar_t* arg_4,
[in][range(0,16384)] long arg_5,
[in][ref][size_is(arg_5)]/*[range(0,16384)]*/ byte *arg_6,
[in]long arg_7,
[out]long *arg_8,
[out][ref][size_is(, *arg_8)]/*[range(0,0)]*/ byte **arg_9,
[in][out]long *arg_10);
....

[size_is(, *arg_8)] 表示arg_9的堆大小取决于*arg_8的值

因为解析错误, 导致我们需要逆向struct Struct_302_t结构体. 有两个方法可以逆向结构体.

方法一:

找到client端, 然后查看client是怎么调用的. 找client的方法如下, 用powershell安装 NtObjectManager

1
2
3
PS> Install-Module -Name NtObjectManager -RequiredVersion 1.1.33
PS> $rpc = ls c:\windows\system32\*.dll | Get-RpcServer -ParseClients
PS> $rpc | ? {$_.Client -and $_.InterfaceId -eq '44d1520b-6133-41f0-8a66-d37305ecc357'} | Select FilePath

原理就是遍历dll, 找到调用了rpc的操作, 并找出其中调用了目标接口的dll. 根据dll(或者dll的引用方), 看client是怎么操作的. 这个方法不一定好用, 比如一些size_is标签是看不出来的. 此处感谢k0shl师父的分享.

方法二 :

逆向server的 MIDL_SYNTAX_INFO结构体.

示例lserver.dll:

1722229193029

1722229211301

1722229305175

这里, 我关心的是第45个rpc函数的结构体描述符, 即1800A29B0:

1722229640593

查看arg2对应的描述符:

1722230026923

可以看到, 结构体大小为0x28, 字段大致如下:

1
2
3
4
5
6
7
8
9
10
struct{
int f_0;
int f_4;
int f_8;
int f_Ch;//pad
void *f_10h;
int f_18h;
int f_1Ch;
void *f_20h;
};

跟入unk_1800A22C0:

1722235196142

查看第一个指针的描述符:

1722235471598

1722236037118

1722236108087

可以知道, 结构体是一个数组, 类型为char, size偏移为8.

1
2
3
4
5
6
7
8
9
10
struct{
int f_0;
int f_4;
int f_8;
int f_Ch;//pad
[size_is(f_8)]char *f_10h;
int f_18h;
int f_1Ch;
void *f_20h;
};

f_20h的分析:

1722236463119

所以, arg2对应的结构体如下:

1
2
3
4
5
6
7
8
9
10
struct{
int f_0;
int f_4;
int f_8;
int f_Ch;//pad
[size_is(f_8)]char *f_10h;
int f_18h;
int f_1Ch;
[size_is(f_18h)]char *f_20h;
};

RPC项目示例

来自 示例项目
代码里的”Example1.h”是根据idl自动生成的.

Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// File Example1Server.cpp
#include <iostream>
#include "Example1.h"

// Server function.
void Output(const char* szOutput)
{
std::cout << szOutput << std::endl;
}

// Naive security callback.
RPC_STATUS CALLBACK SecurityCallback(RPC_IF_HANDLE /*hInterface*/, void* /*pBindingHandle*/)
{
return RPC_S_OK; // Always allow anyone.
}

int main()
{
RPC_STATUS status;

// Uses the protocol combined with the endpoint for receiving
// remote procedure calls.
status = RpcServerUseProtseqEp(
reinterpret_cast<unsigned char*>("ncacn_ip_tcp"), // Use TCP/IP protocol.
RPC_C_PROTSEQ_MAX_REQS_DEFAULT, // Backlog queue length for TCP/IP.
reinterpret_cast<unsigned char*>("4747"), // TCP/IP port to use.
NULL); // No security.

if (status)
exit(status);

// Registers the Example1 interface.
status = RpcServerRegisterIf2(
Example1_v1_0_s_ifspec, // Interface to register.
NULL, // Use the MIDL generated entry-point vector.
NULL, // Use the MIDL generated entry-point vector.
RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH, // Forces use of security callback.
RPC_C_LISTEN_MAX_CALLS_DEFAULT, // Use default number of concurrent calls.
(unsigned)-1, // Infinite max size of incoming data blocks.
SecurityCallback); // Naive security callback.

if (status)
exit(status);

// Start to listen for remote procedure
// calls for all registered interfaces.
// This call will not return until
// RpcMgmtStopServerListening is called.
status = RpcServerListen(
1, // Recommended minimum number of threads.
RPC_C_LISTEN_MAX_CALLS_DEFAULT, // Recommended maximum number of threads.
FALSE); // Start listening now.

if (status)
exit(status);
}

// Memory allocation function for RPC.
// The runtime uses these two functions for allocating/deallocating
// enough memory to pass the string to the server.
void* __RPC_USER midl_user_allocate(size_t size)
{
return malloc(size);
}

// Memory deallocation function for RPC.
void __RPC_USER midl_user_free(void* p)
{
free(p);
}

idl:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// File Example1.idl
[
// A unique identifier that distinguishes this interface from other interfaces.
uuid(00000001-EAF3-4A7A-A0F2-BCE4C30DA77E),

// This is version 1.0 of this interface.
version(1.0)
]
interface Example1 // The interface is named Example1
{
// A function that takes a zero-terminated string.
void Output(
[in, string] const char* szOutput);
}

Client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// File Example1Client.cpp
#include <iostream>
#include "../Example1/Example1.h"

int main()
{
RPC_STATUS status;
unsigned char* szStringBinding = NULL;

// Creates a string binding handle.
// This function is nothing more than a printf.
// Connection is not done here.
status = RpcStringBindingCompose(
NULL, // UUID to bind to.
reinterpret_cast<unsigned char*>("ncacn_ip_tcp"), // Use TCP/IP
// protocol.
reinterpret_cast<unsigned char*>("localhost"), // TCP/IP network
// address to use.
reinterpret_cast<unsigned char*>("4747"), // TCP/IP port to use.
NULL, // Protocol dependent network options to use.
&szStringBinding); // String binding output.

if (status)
exit(status);

// Validates the format of the string binding handle and converts
// it to a binding handle.
// Connection is not done here either.
status = RpcBindingFromStringBinding(
szStringBinding, // The string binding to validate.
&hExample1Binding); // Put the result in the implicit binding
// handle defined in the IDL file.

if (status)
exit(status);

RpcTryExcept
{
// Calls the RPC function. The hExample1Binding binding handle
// is used implicitly.
// Connection is done here.
Output("Hello Implicit RPC World!");
}
RpcExcept(1)
{
std::cerr << "Runtime reported exception " << RpcExceptionCode()
<< std::endl;
}
RpcEndExcept

// Free the memory allocated by a string.
status = RpcStringFree(
&szStringBinding); // String to be freed.

if (status)
exit(status);

// Releases binding handle resources and disconnects from the server.
status = RpcBindingFree(
&hExample1Binding); // Frees the implicit binding handle defined in
// the IDL file.

if (status)
exit(status);
}

// Memory allocation function for RPC.
// The runtime uses these two functions for allocating/deallocating
// enough memory to pass the string to the server.
void* __RPC_USER midl_user_allocate(size_t size)
{
return malloc(size);
}

// Memory deallocation function for RPC.
void __RPC_USER midl_user_free(void* p)
{
free(p);
}

NDR64_FORMAT_CHARACTER

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
FC64_ZERO = 0 0x0
FC64_UINT8 = 1 0x1
FC64_INT8 = 2 0x2
FC64_UINT16 = 3 0x3
FC64_INT16 = 4 0x4
FC64_INT32 = 5 0x5
FC64_UINT32 = 6 0x6
FC64_INT64 = 7 0x7
FC64_UINT64 = 8 0x8
FC64_INT128 = 9 0x9
FC64_UINT128 = 10 0xa
FC64_FLOAT32 = 11 0xb
FC64_FLOAT64 = 12 0xc
FC64_FLOAT80 = 13 0xd
FC64_FLOAT128 = 14 0xe
FC64_CHAR = 16 0x10
FC64_WCHAR = 17 0x11
FC64_IGNORE = 18 0x12
FC64_ERROR_STATUS_T = 19 0x13
FC64_POINTER = 20 0x14
FC64_RP = 32 0x20
FC64_UP = 33 0x21 //表示这个字段是个指针
FC64_OP = 34 0x22
FC64_FP = 35 0x23
FC64_IP = 36 0x24
FC64_STRUCT = 48 0x30
FC64_PSTRUCT = 49 0x31
FC64_CONF_STRUCT = 50 0x32
FC64_CONF_PSTRUCT = 51 0x33
FC64_BOGUS_STRUCT = 52 0x34
FC64_FORCED_BOGUS_STRUCT = 53 0x35
FC64_CONF_BOGUS_STRUCT = 54 0x36
FC64_FORCED_CONF_BOGUS_STRUCT = 55 0x37
FC64_SYSTEM_HANDLE = 60 0x3c
FC64_FIX_ARRAY = 64 0x40
FC64_CONF_ARRAY = 65 0x41
FC64_VAR_ARRAY = 66 0x42
FC64_CONFVAR_ARRAY = 67 0x43
FC64_FIX_FORCED_BOGUS_ARRAY = 68 0x44
FC64_FIX_BOGUS_ARRAY = 69 0x45
FC64_FORCED_BOGUS_ARRAY = 70 0x46
FC64_BOGUS_ARRAY = 71 0x47
FC64_ENCAPSULATED_UNION = 80 0x50
FC64_NON_ENCAPSULATED_UNION = 81 0x51
FC64_CHAR_STRING = 96 0x60
FC64_WCHAR_STRING = 97 0x61
FC64_STRUCT_STRING = 98 0x62
FC64_CONF_CHAR_STRING = 99 0x63
FC64_CONF_WCHAR_STRING = 100 0x64
FC64_CONF_STRUCT_STRING = 101 0x65
FC64_BIND_CONTEXT = 112 0x70
FC64_BIND_GENERIC = 113 0x71
FC64_BIND_PRIMITIVE = 114 0x72
FC64_AUTO_HANDLE = 115 0x73
FC64_CALLBACK_HANDLE = 116 0x74
FC64_SUPPLEMENT = 117 0x75
FC64_NO_REPEAT = 128 0x80
FC64_FIXED_REPEAT = 129 0x81
FC64_VARIABLE_REPEAT = 130 0x82
FC64_FIXED_OFFSET = 131 0x83
FC64_VARIABLE_OFFSET = 132 0x84
FC64_STRUCTPADN = 144 0x90
FC64_EMBEDDED_COMPLEX = 145 0x91
FC64_BUFFER_ALIGN = 146 0x92
FC64_END = 147 0x93
FC64_TRANSMIT_AS = 160 0xa0
FC64_REPRESENT_AS = 161 0xa1
FC64_USER_MARSHAL = 162 0xa2
FC64_PIPE = 163 0xa3
FC64_RANGE = 164 0xa4
FC64_PAD = 165 0xa5

通过以下方式获取:1722226870055

参考来源: Building More Windows RPC Tooling for Security Research - James Forshaw

RPC 反序列化流程(不完整)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Ndr64StubWorker
Ndr64pServerUnMarshal
check func arg tag; IsBasetype.....
Ndr64pPointerUnmarshall
or
Ndr64ConformantStringUnmarshall
or
Ndr64ComplexStructUnmarshall // 针对结构体指针类型
Ndr64ComplexStructMemorySize // 检查结构体描述符, 计算结构体长度
解码数据....
Ndr64SimpleTypeUnmarshall
Ndr64EmbeddedPointerUnmarshall
Ndr64ConformantArrayUnmarshall
Ndr64pEarlyCheckCorrelation
EvaluateExpr
Ndr64pGetAllocateAllNodesContext
Ndr64ConformantArrayMemorySize
a1->pfnAllocate()// 申请数组的堆
.....
or
Ndr64UnmarshallHandle // 针对 context_handle 类型
.....
Invoke
target rpc func

参考

https://www.tiraniddo.dev/2021/08/how-to-secure-windows-rpc-server-and.html 

https://www.codeproject.com/Articles/4837/Introduction-to-RPC-Part-1#Implicitandexplicithandles17

致谢

感谢 k0shl, @XiaoWei___ 两位大佬帮我理解这个rpc.