【RPC】GodPotato原理分析之RPCSS

文章目录
  1. 1. 调试
    1. 1.1. 调试准备
    2. 1.2. 触发断点
      1. 1.2.1. ClientResolveOXID 断点
      2. 1.2.2. ResolveOxid2 调用
      3. 1.2.3. 【INFO】UseProtseqIfNeeded 发起的调用
      4. 1.2.4. UseProtseqIfNeed 被调用
      5. 1.2.5. ResolveOxid2 如何确定要调用的服务端是哪个
      6. 1.2.6. 正常调用下 UsePortSeq 的返回
      7. 1.2.7. GodPotato Hook 情况下的返回
        1. 1.2.7.1. 在 GodPotato 查看发起的调用信息:
        2. 1.2.7.2. RPCSS 服务中 _ClientResolveOXID 的第一次调用
        3. 1.2.7.3. RPCSS 服务中 _ClientResolveOXID 的第二次调用
        4. 1.2.7.4. RPCSS 服务中 _ClientResolveOXID 的第三次调用
    3. 1.3. UseProtseq 函数及参数说明
      1. 1.3.1. EndPoint
      2. 1.3.2. ncacn_np
      3. 1.3.3. UseProtseq 断点
  2. 2. 获取参数个数
  3. 3. RPC结构体解析
  4. 4. GodPotato 管道建立调试

概述:在 GodPotato原理分析 文中,描述了 GodPotato 的大致逻辑,但是对核心的处理操作部分没有处理,也就是——RPCSS 的接口 Hook 部分,本文就这一部分进行记录。

调试环境

Windows Server 2016

前文:GodPotato原理分析

调试

代码部分我们已经了解了,那么调试部分就比较简单了,双机调试挂载目标进程,在 combase!_UseProtseq 处下断点即可。当然,整体的流程可以结合 IDA 反汇编的代码查看。

UseProtseq 函数原型

⚠️ 注意 UseProtseq 在不同版本下的参数可能不同

1
2
3
4
5
6
__int64 __fastcall UseProtseq(
void *hRpc,
unsigned __int16 wTowerId,
unsigned int *pdwTCPPort,
tagDUALSTRINGARRAY **ppsaNewBindings,
tagDUALSTRINGARRAY **ppsaSecurity)

调试准备

  1. 添加进程断点

    1
    2
    !gflag +ksl;
    sxe cpr:GodPotato.exe
  2. 查看被调用接口
    这个断点需要添加在 rpcss 服务中

    1
    bu RPCRT4!Invoke+0x73-2d ".echo \"RPCRT4!Invoke Call:\";u r10 l5;gc"

    或者我们也可以在被调试程序(GodPotato)中添加 RPCRT4!NdrClientCall2 断点查看 RPC 都调用了那些接口:

    1
    bu RPCRT4!NdrClientCall2 "k"
  3. 查看 GodPotato Hook 接口

    1
    bp combase!_UseProtseq

触发断点

在 GodPotato 源码中,调用 unmarshalTrigger.Trigger() 就触发相关断点了(最终调用的是 combase!CoUnmarshalInterface)。

ClientResolveOXID 断点

CoUnmarshalInterface 接口调用时,使用了正常申请的 OXID 来定位和访问服务器上的对象,这一步 RPC 操作是验证 OXIDGodPotato 发起的堆栈如下所示:

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
# Child-SP          RetAddr               Call Site
00 00000000`008fde28 00007ffb`c9a3963a RPCRT4!NdrClientCall2
01 (Inline Function) --------`-------- combase!ClientResolveOXID+0x4a [onecore\com\combase\idl\internal\daytona\objfre\amd64\lclor_c.c @ 279]
02 00000000`008fde30 00007ffb`c99db6c3 combase!CRpcResolver::ClientResolveOXID+0xca [onecore\com\combase\dcomrem\resolver.cxx @ 1207]
03 00000000`008fdec0 00007ffb`c98f3597 combase!COXIDTable::ClientResolveOXID+0x1190db [onecore\com\combase\dcomrem\ipidtbl.cxx @ 3111]
04 00000000`008fe070 00007ffb`c98efd6b combase!ReadObjRef+0x247 [onecore\com\combase\dcomrem\marshal.cxx @ 9974]
05 00000000`008fe140 00007ffb`74d6d1e7 combase!CoUnmarshalInterface+0x7b [onecore\com\combase\dcomrem\coapi.cxx @ 1962]
06 00000000`008fe5f0 00007ffb`154fa678 mscorwks!DoNDirectCall__PatchGetThreadCall+0x7b
07 00000000`008fe690 00007ffb`154fa3d8 0x00007ffb`154fa678
08 00000000`008fe7f0 00007ffb`154fa32b 0x00007ffb`154fa3d8
09 00000000`008fe840 00007ffb`154f8c78 0x00007ffb`154fa32b
0a 00000000`008fe8b0 00007ffb`154f06e4 0x00007ffb`154f8c78
0b 00000000`008feb50 00007ffb`74d6d9c2 0x00007ffb`154f06e4
0c 00000000`008fee70 00007ffb`74c75a23 mscorwks!CallDescrWorker+0x82
0d 00000000`008feec0 00007ffb`7515d231 mscorwks!CallDescrWorkerWithHandler+0xd3
0e 00000000`008fef60 00007ffb`74cc73b3 mscorwks!MethodDesc::CallDescr+0x2b1
0f 00000000`008ff1a0 00007ffb`74ce9b88 mscorwks!ClassLoader::RunMain+0x22b
10 00000000`008ff400 00007ffb`7524c6bd mscorwks!Assembly::ExecuteMainMethod+0xbc
11 00000000`008ff6f0 00007ffb`74cf6d73 mscorwks!SystemDomain::ExecuteMainMethod+0x47d
12 00000000`008ffcc0 00007ffb`74cda434 mscorwks!ExecuteEXE+0x47
13 00000000`008ffd10 00007ffb`b26ed6ea mscorwks!CorExeMain+0xac
14 00000000`008ffd70 00007ffb`b4bcac42 mscoreei!CorExeMain+0xfa
15 00000000`008ffdd0 00007ffb`c8b97374 MSCOREE!CorExeMain_Exported+0x72
16 00000000`008ffe00 00007ffb`caa7cc91 KERNEL32!BaseThreadInitThunk+0x14
17 00000000`008ffe30 00000000`00000000 ntdll!RtlUserThreadStart+0x21

会通过 rpc 使用 NdrClientCall2 调用到 rpcss 服务的 ClientResolveOXID

ClientResolveOXID 功能说明:

在 COM 分布式环境中,对象导出标识符(OXID,Object Export Identifier)用于唯一标识服务器上的一个导出对象集。ClientResolveOXID 接口的主要功能是让客户端根据给定的 OXID 解析出与之关联的服务器信息和对象信息,从而建立与服务器上特定对象的连接,以便后续调用该对象的接口方法。

ResolveOxid2 调用

当一个客户端要调用一个远程COM对象时,它需要通过 ResolveOxid2 函数来获取对象的绑定信息, 获取到的绑定信息通常会指向RPC端点映射器(\pipe\epmapper),以便客户端可以进一步查询服务的端点信息

首次 rpcss!ClientResolveOXID 被调用后,rpcss 就会通过 ResolveOxid2 来获取 EndPoint 这一步仍会通过 rpc 调用,

位于 rpcss 中的 ClientResolveOXID 接口一步步(通过 ResolveOxid2)调用到 rpcss!CProcess::UseProtseqIfNeeded,这个接口同样是调用的 rpc 接口,被调用接口的相关信息如下所示:

1
2
3
4
5
6
7
8
9
10
1: kd> !NdrClientCall2
Tue Feb 11 17:57:13.679 2025 (UTC + 8:00) NdrClientCall2() Stack
[RPC] Request Function: rpcss!CProcess::UseProtseqIfNeeded
addr_rpc_interface:0x7fff330b1080
[RPC] Caller Info:
Name:svchost.exe
PID:904
Command:C:\Windows\system32\svchost.exe -k RPCSS -p
[RPC] GUID&Procederes: {18f70770-8e64-11cf-9af1-0020af6e72f4}:1
@$NdrClientCall2()

可以看到被调用 rpc 的 GUID{18f70770-8e64-11cf-9af1-0020af6e72f4},调用接口 ID 为 1。这个 GUID 和 ID 是不是很熟悉 😂。就是 UseProtseq 接口(通过 Hook 修改的接口)。从 UseProtseqIfNeeded 调用到了 GodPotatocombase!UseProtseq

调用堆栈如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
0: kd> k
# Child-SP RetAddr Call Site
00 00000016`b57fed28 00007fff`330489f0 rpcss!CProcess::UseProtseqIfNeeded
01 00000016`b57fed30 00007fff`33051a27 rpcss!CServerOxid::LazyUseProtseq+0x58
02 00000016`b57fed80 00007fff`33052943 rpcss!ResolveOxidCommon+0x1bb
03 00000016`b57fef20 00007fff`36dea2d3 rpcss!ResolveOxid2+0x173
04 00000016`b57fefb0 00007fff`36d825a7 RPCRT4!Invoke+0x73
05 00000016`b57ff030 00007fff`36dd0c3a RPCRT4!NdrStubCall2+0x3f7
06 00000016`b57ff380 00007fff`36dcb128 RPCRT4!NdrServerCall2+0x1a
07 00000016`b57ff3b0 00007fff`36da8146 RPCRT4!DispatchToStubInCNoAvrf+0x18
08 00000016`b57ff400 00007fff`36da7a98 RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1a6
09 00000016`b57ff4e0 00007fff`36da8601 RPCRT4!RPC_INTERFACE::DispatchToStub+0xf8
0a 00000016`b57ff550 00007fff`36da795f RPCRT4!OSF_SCALL::DispatchHelper+0x125
0b 00000016`b57ff670 00007fff`36da9265 RPCRT4!OSF_SCALL::DispatchRPCCall+0x7b
0c 00000016`b57ff6a0 00007fff`36da8f49 RPCRT4!OSF_SCALL::ProcessReceivedPDU+0x2b9
0d 00000016`b57ff770 00007fff`36dbc01f RPCRT4!OSF_SCALL::BeginRpcCall+0xbd
0e 00000016`b57ff7a0 00007fff`36db6dbd RPCRT4!OSF_SCONNECTION::ProcessReceiveComplete+0x5eb
0f 00000016`b57ff8a0 00007fff`36e45a04 RPCRT4!CO_ConnectionThreadPoolCallback+0xed
10 00000016`b57ff920 00007fff`37a21d19 RPCRT4!CO_RecvInlineCompletion+0x24
11 00000016`b57ff960 00007fff`379fd79a ntdll!TppSimplepExecuteCallback+0x99
12 00000016`b57ff9b0 00007fff`36847374 ntdll!TppWorkerThread+0x68a
13 00000016`b57ffcb0 00007fff`379fcc91 KERNEL32!BaseThreadInitThunk+0x14
14 00000016`b57ffce0 00000000`00000000 ntdll!RtlUserThreadStart+0x21

【INFO】UseProtseqIfNeeded 发起的调用

UseProtseqIfNeeded 发起的调用信息是通过 CProcess::GetBindingHandleCopy 返回的 RPC_BINDING_HANDLE,这个接口可以看到调用客户端的 PID

相关调试信息如下所示,可以看到目标 PID 为 GodPotato:

UseProtseqIfNeed 被调用

由于通过 Marshal.WriteIntPtr(DispatchTablePtr, Marshal.GetFunctionPointerForDelegate(useProtseqDelegate)); 写入的 Hook 直接修改了RPC调用表(DispatchTable)中 UseProtseq 的地址,这一步会直接调用到如下所示的位置,最后跳转到写入的 delegatefun 接口中

delegatefun 接口中,写入了需要被连接的服务端信息:

  1. “ncacn_np:localhost/pipe/GodPotato[\pipe\epmapper]”
  2. “ncacn_ip_tcp:Second Call![135]”

相关调试信息如下所示:

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
0:008> kvn
# Child-SP RetAddr : Args to Child : Call Site
00 00000000`1bc7eba0 00007ffb`ca73a2d3 : 00000000`00eec480 00000000`00000007 00000000`00000000 00000000`00eed790 : mscorwks!UMThunkStubAMD64+0x7a
01 00000000`1bc7ec40 00007ffb`ca6d25a7 : 00007ffb`c9b407e2 00000000`00eed5c0 00000000`00edb7a0 00000000`00000006 : RPCRT4!Invoke+0x73
02 00000000`1bc7ecb0 00007ffb`ca720c3a : 00000000`00000000 00000000`00000000 00000000`00eec5d0 00000000`1bc7f038 : RPCRT4!NdrStubCall2+0x3f7
03 00000000`1bc7f000 00007ffb`ca71b128 : 00000000`00eed400 00000000`00000001 00000000`00000000 00007ffb`ca70d84d : RPCRT4!NdrServerCall2+0x1a
04 00000000`1bc7f030 00007ffb`ca6f8146 : 00000000`1bc7f094 00000000`1bc7f090 00000000`1bc7f230 ffffffff`fffffffb : RPCRT4!DispatchToStubInCNoAvrf+0x18
05 00000000`1bc7f080 00007ffb`ca6f7a98 : 00000000`00ee0d20 00000000`00000000 00000000`00000000 00000000`00000000 : RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1a6
06 00000000`1bc7f160 00007ffb`ca7050af : 00000000`00000000 00000000`1bc7f2f8 00000000`00eec480 00000000`00ee0d20 : RPCRT4!RPC_INTERFACE::DispatchToStub+0xf8
07 00000000`1bc7f1d0 00007ffb`ca7044b8 : 00000000`0001241b 00000000`00000001 00000000`00000000 00000000`00ee3e90 : RPCRT4!LRPC_SCALL::DispatchRequest+0x31f
08 00000000`1bc7f2a0 00007ffb`ca703aa1 : 00000000`00e10000 00000000`00ee3bd0 00000000`00000000 00000000`00000000 : RPCRT4!LRPC_SCALL::HandleRequest+0x7f8
09 00000000`1bc7f3b0 00007ffb`ca70350e : 00000000`00000000 00000000`00000000 00000000`00000001 00000000`00edc6b0 : RPCRT4!LRPC_ADDRESS::HandleRequest+0x341
0a 00000000`1bc7f450 00007ffb`ca707b62 : 00000000`00eed480 00000000`00edb7a0 00000000`00edc7b8 00000000`1bc7f828 : RPCRT4!LRPC_ADDRESS::ProcessIO+0x89e
0b 00000000`1bc7f590 00007ffb`caa50330 : 00000000`00000000 00000000`1bc7f638 00000000`1bc7f828 00000000`00000000 : RPCRT4!LrpcIoComplete+0xc2
0c 00000000`1bc7f630 00007ffb`caa7d566 : 00000000`00000000 00000000`00eca100 00000000`00000000 00000000`00edb760 : ntdll!TppAlpcpExecuteCallback+0x260
0d 00000000`1bc7f6b0 00007ffb`c8b97374 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!TppWorkerThread+0x456
0e 00000000`1bc7f9b0 00007ffb`caa7cc91 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
0f 00000000`1bc7f9e0 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
0:008> dq 00000000`1bc7ec40
00000000`1bc7ec40 00000000`00eec480 00000000`00000007
00000000`1bc7ec50 00000000`00000000 00000000`00eed790
00000000`1bc7ec60 00000000`00eed7a0 00000000`00eed7b0
00000000`1bc7ec70 00000000`00000000 00007ffb`c9b406d2
00000000`1bc7ec80 00000000`00eec5d0 00000000`1bc7f038
00000000`1bc7ec90 00000000`00edb7a0 00000000`00000000
00000000`1bc7eca0 00000000`00eec5d0 00007ffb`ca6d25a7
00000000`1bc7ecb0 00007ffb`c9b407e2 00000000`00eed5c0
0:008> dq 00000000`00eed7a0 l1
00000000`00eed7a0 00000000`00eeeb00
0:008> du 00000000`00eeeb00
00000000`00eeeb00 "OMncacn_np:localhost/pipe/GodPot"
00000000`00eeeb40 "ato[\pipe\epmapper]"
0:008> du 00000000`00eeeb68
00000000`00eeeb68 "ncacn_ip_tcp:Second Call!"


# 写入的内容如下所示:
00000000`00a462c0 4f 00 4d 00 6e 00 63 00-61 00 63 00 6e 00 5f 00 O.M.n.c.a.c.n._.
00000000`00a462d0 6e 00 70 00 3a 00 6c 00-6f 00 63 00 61 00 6c 00 n.p.:.l.o.c.a.l.
00000000`00a462e0 68 00 6f 00 73 00 74 00-2f 00 70 00 69 00 70 00 h.o.s.t./.p.i.p.
00000000`00a462f0 65 00 2f 00 47 00 6f 00-64 00 50 00 6f 00 74 00 e./.G.o.d.P.o.t.
00000000`00a46300 61 00 74 00 6f 00 5b 00-5c 00 70 00 69 00 70 00 a.t.o.[.\.p.i.p.
00000000`00a46310 65 00 5c 00 65 00 70 00-6d 00 61 00 70 00 70 00 e.\.e.p.m.a.p.p.
00000000`00a46320 65 00 72 00 5d 00 00 00-6e 00 63 00 61 00 63 00 e.r.]...n.c.a.c.
00000000`00a46330 6e 00 5f 00 69 00 70 00-5f 00 74 00 63 00 70 00 n._.i.p._.t.c.p.
00000000`00a46340 3a 00 53 00 65 00 63 00-6f 00 6e 00 64 00 20 00 :.S.e.c.o.n.d. .
00000000`00a46350 43 00 61 00 6c 00 6c 00-21 00 00 00 00 00 00 00 C.a.l.l.!.......

ResolveOxid2 如何确定要调用的服务端是哪个

在 UseProtseq 调用返回后,如下所示的堆栈,在 ResolveOxidCommon+0x180 位置调用了 rpcss!CServerOxid::GetRemoteInfo ,其中 r8 结构体为 __MIDL_ILocalObjectExporter_0007,查看 r8 就可以看到

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
00 000000c7`0f8fe8d0 00007ffb`c60d2943     rpcss!ResolveOxidCommon+0x180
01 000000c7`0f8fea70 00007ffb`ca73a2d3 rpcss!ResolveOxid2+0x173
02 000000c7`0f8feb00 00007ffb`ca6d25a7 RPCRT4!Invoke+0x73


# 查看调用的服务端
1: kd> dt __MIDL_ILocalObjectExporter_0007 @r8
combase!__MIDL_ILocalObjectExporter_0007
+0x000 dwTid : 0xffffffff
+0x004 dwPid : 0xbd4
+0x008 dwAuthnHint : 5
+0x00c dcomVersion : tagCOMVERSION
+0x010 containerVersion : CONTAINERVERSION
+0x028 ipidRemUnknown : _GUID {0000fc01-0bd4-ffff-204e-8c9f56b06ce7}
+0x038 dwFlags : 0x2000000
+0x040 psa : (null)
+0x048 guidProcessIdentifier : _GUID {25897763-3dc2-4432-bd48-9d5ae6f0c61c}
+0x058 processHostId : 0
+0x060 clientDependencyBehavior : 0 ( OR_OXID_CLIENT_DEPENDENCY_NONE )
+0x068 packageFullName : (null)
+0x070 userSid : (null)
+0x078 appcontainerSid : (null)
+0x080 primaryOxid : 0xb0a1aef8`d63b5b01
+0x088 primaryIpidRemUnknown : _GUID {0000fc01-0bd4-ffff-204e-8c9f56b06ce7}
1: kd> !process 0xbd4 0
Searching for Process with Cid == bd4
PROCESS ffffd1817db76080
SessionId: 1 Cid: 0bd4 Peb: 0071e000 ParentCid: 1aa4
DirBase: 104e66000 ObjectTable: ffffe18381c45700 HandleCount: 237.
Image: GodPotato.exe

正常调用下 UsePortSeq 的返回

在不Hook情况下调用时,UseProtSeq 中通过 CopyDsaWithSecurityBindings 获取绑定的信息,如下所示:

返回内容如下所示:

GodPotato Hook 情况下的返回

ClientResolveOXID 在启用 Hook 情况下的调试。

在 GodPotato 查看发起的调用信息:

如上图所示,从 GodPotato 的 combase!ClientResolveOXID 开始,可以看到此时发起的请求信息,所请求的 rpc 接口为:{e60c73e6-88f9-11cf-9af1-0020af6e72f4}:5,该接口如下所示:

RPCSS 服务中 _ClientResolveOXID 的第一次调用

第一次调用时为了绑定 rpc ,让 rpcss 可以调用到 rpcss!ResolveOxid2,相关信息如下所示:

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
Protocol:00007ffb`c6132400  "ncacn_ip_tcp"
NetworkAddr:0000026a`9d33d476 "127.0.0.1"
Endpoint:00007ffb`c613c5a8 "135"
rax=000000c70f8fe3f0 rbx=0000026a9d535720 rcx=0000000000000000
rdx=00007ffbc6132400 rsi=00007ffbc6132400 rdi=00007ffbc613c5a8
rip=00007ffbc6106cd7 rsp=000000c70f8fe370 rbp=0000026a9d33d476
r8=0000026a9d33d476 r9=00007ffbc613c5a8 r10=0000000000000000
r11=0000000000000246 r12=0000000000000000 r13=0000026a9cd0d570
r14=000000c70f8fe530 r15=843a9ba89403af3c
iopl=0 nv up ei pl zr na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
rpcss!CreateRemoteBindingToOr+0x6b:
0033:00007ffb`c6106cd7 48ff151a660200 call qword ptr [rpcss!_imp_RpcStringBindingComposeW (00007ffb`c612d2f8)] ds:002b:00007ffb`c612d2f8={RPCRT4!RpcStringBindingComposeW (00007ffb`ca6e38d0)}
0: kd> k
# Child-SP RetAddr Call Site
00 000000c7`0f8fe370 00007ffb`c60d65a8 rpcss!CreateRemoteBindingToOr+0x6b
01 000000c7`0f8fe3e0 00007ffb`c6072d65 rpcss!CMid::GetBinding+0x488
02 000000c7`0f8fe4c0 00007ffb`c606e451 rpcss!ResolveClientOXID+0xa05
03 000000c7`0f8fe9d0 00007ffb`ca73a2d3 rpcss!_ClientResolveOXID+0xe1
04 000000c7`0f8fea80 00007ffb`ca6d25a7 RPCRT4!Invoke+0x73
05 000000c7`0f8feb10 00007ffb`ca720c3a RPCRT4!NdrStubCall2+0x3f7
06 000000c7`0f8fee60 00007ffb`ca71b128 RPCRT4!NdrServerCall2+0x1a
07 000000c7`0f8fee90 00007ffb`ca6f8146 RPCRT4!DispatchToStubInCNoAvrf+0x18
08 000000c7`0f8feee0 00007ffb`ca6f7a98 RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1a6
09 000000c7`0f8fefc0 00007ffb`ca7050af RPCRT4!RPC_INTERFACE::DispatchToStub+0xf8
0a 000000c7`0f8ff030 00007ffb`ca7044b8 RPCRT4!LRPC_SCALL::DispatchRequest+0x31f
0b 000000c7`0f8ff100 00007ffb`ca703aa1 RPCRT4!LRPC_SCALL::HandleRequest+0x7f8
0c 000000c7`0f8ff210 00007ffb`ca70350e RPCRT4!LRPC_ADDRESS::HandleRequest+0x341
0d 000000c7`0f8ff2b0 00007ffb`ca707b62 RPCRT4!LRPC_ADDRESS::ProcessIO+0x89e
0e 000000c7`0f8ff3f0 00007ffb`caa50330 RPCRT4!LrpcIoComplete+0xc2
0f 000000c7`0f8ff490 00007ffb`caa7d566 ntdll!TppAlpcpExecuteCallback+0x260
10 000000c7`0f8ff510 00007ffb`c8b97374 ntdll!TppWorkerThread+0x456
11 000000c7`0f8ff810 00007ffb`caa7cc91 KERNEL32!BaseThreadInitThunk+0x14
12 000000c7`0f8ff840 00000000`00000000 ntdll!RtlUserThreadStart+0x21
RPCSS 服务中 _ClientResolveOXID 的第二次调用
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
Protocol:00007ffb`c613c5d8  "ncacn_np"
NetworkAddr:000000c7`0f8fe262 "localhost/pipe/GodPotato"
Endpoint:00007ffb`c613c5f0 "\pipe\epmapper"
rax=000000c70f8fe250 rbx=000000c70f8fe260 rcx=0000000000000000
rdx=00007ffbc613c5d8 rsi=00007ffbc613c5d8 rdi=00007ffbc613c5f0
rip=00007ffbc6106cd7 rsp=000000c70f8fe1d0 rbp=000000c70f8fe262
r8=000000c70f8fe262 r9=00007ffbc613c5f0 r10=000000007fffffd4
r11=0000000080070057 r12=0000000000000000 r13=0000000000000001
r14=0000026a9d2bf940 r15=0000000000000000
iopl=0 nv up ei pl zr na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
rpcss!CreateRemoteBindingToOr+0x6b:
0033:00007ffb`c6106cd7 48ff151a660200 call qword ptr [rpcss!_imp_RpcStringBindingComposeW (00007ffb`c612d2f8)] ds:002b:00007ffb`c612d2f8={RPCRT4!RpcStringBindingComposeW (00007ffb`ca6e38d0)}
0: kd> k
# Child-SP RetAddr Call Site
00 000000c7`0f8fe1d0 00007ffb`c60d60ea rpcss!CreateRemoteBindingToOr+0x6b
01 000000c7`0f8fe240 00007ffb`c60c9d39 rpcss!TestBindingGetHandle+0x18a
02 000000c7`0f8fe2f0 00007ffb`c60d5938 rpcss!CTestBindingPPing::NextCall+0x29
03 000000c7`0f8fe340 00007ffb`c60b8b26 rpcss!CParallelPing::Ping+0x1b8
04 000000c7`0f8fe3b0 00007ffb`c6073a73 rpcss!CClientOxid::GetInfo+0x449e6
05 000000c7`0f8fe4c0 00007ffb`c606e451 rpcss!ResolveClientOXID+0x1713
06 000000c7`0f8fe9d0 00007ffb`ca73a2d3 rpcss!_ClientResolveOXID+0xe1
07 000000c7`0f8fea80 00007ffb`ca6d25a7 RPCRT4!Invoke+0x73
08 000000c7`0f8feb10 00007ffb`ca720c3a RPCRT4!NdrStubCall2+0x3f7
09 000000c7`0f8fee60 00007ffb`ca71b128 RPCRT4!NdrServerCall2+0x1a
0a 000000c7`0f8fee90 00007ffb`ca6f8146 RPCRT4!DispatchToStubInCNoAvrf+0x18
0b 000000c7`0f8feee0 00007ffb`ca6f7a98 RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1a6
0c 000000c7`0f8fefc0 00007ffb`ca7050af RPCRT4!RPC_INTERFACE::DispatchToStub+0xf8
0d 000000c7`0f8ff030 00007ffb`ca7044b8 RPCRT4!LRPC_SCALL::DispatchRequest+0x31f
0e 000000c7`0f8ff100 00007ffb`ca703aa1 RPCRT4!LRPC_SCALL::HandleRequest+0x7f8
0f 000000c7`0f8ff210 00007ffb`ca70350e RPCRT4!LRPC_ADDRESS::HandleRequest+0x341
10 000000c7`0f8ff2b0 00007ffb`ca707b62 RPCRT4!LRPC_ADDRESS::ProcessIO+0x89e
11 000000c7`0f8ff3f0 00007ffb`caa50330 RPCRT4!LrpcIoComplete+0xc2
12 000000c7`0f8ff490 00007ffb`caa7d566 ntdll!TppAlpcpExecuteCallback+0x260
13 000000c7`0f8ff510 00007ffb`c8b97374 ntdll!TppWorkerThread+0x456
14 000000c7`0f8ff810 00007ffb`caa7cc91 KERNEL32!BaseThreadInitThunk+0x14
15 000000c7`0f8ff840 00000000`00000000 ntdll!RtlUserThreadStart+0x21

与第一次调用的区别

RPCSS 服务中 _ClientResolveOXID 的第三次调用

验证了下只有 GodPotato 内写入两条 EndPoint 时,才能触发成功,后续补充下为什么写一个 EndPoint 提权失败的情况

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
# 两次调用的堆栈是一样的
1: kd> k
# Child-SP RetAddr Call Site
00 00000016`b5afe5d0 00007fff`330560ea rpcss!CreateRemoteBindingToOr+0x6b
01 00000016`b5afe640 00007fff`33049d39 rpcss!TestBindingGetHandle+0x18a
02 00000016`b5afe6b0 00007fff`33055938 rpcss!CTestBindingPPing::NextCall+0x29
03 00000016`b5afe700 00007fff`33038b26 rpcss!CParallelPing::Ping+0x1b8
04 00000016`b5afe770 00007fff`32ff3a73 rpcss!CClientOxid::GetInfo+0x449e6
05 00000016`b5afe880 00007fff`32fee451 rpcss!ResolveClientOXID+0x1713
06 00000016`b5afed90 00007fff`36dea2d3 rpcss!_ClientResolveOXID+0xe1
07 00000016`b5afee40 00007fff`36d825a7 RPCRT4!Invoke+0x73
08 00000016`b5afeed0 00007fff`36dd0c3a RPCRT4!NdrStubCall2+0x3f7
09 00000016`b5aff220 00007fff`36dcb128 RPCRT4!NdrServerCall2+0x1a
0a 00000016`b5aff250 00007fff`36da8146 RPCRT4!DispatchToStubInCNoAvrf+0x18
0b 00000016`b5aff2a0 00007fff`36da7a98 RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1a6
0c 00000016`b5aff380 00007fff`36db50af RPCRT4!RPC_INTERFACE::DispatchToStub+0xf8
0d 00000016`b5aff3f0 00007fff`36db44b8 RPCRT4!LRPC_SCALL::DispatchRequest+0x31f
0e 00000016`b5aff4c0 00007fff`36db3aa1 RPCRT4!LRPC_SCALL::HandleRequest+0x7f8
0f 00000016`b5aff5d0 00007fff`36db350e RPCRT4!LRPC_ADDRESS::HandleRequest+0x341
10 00000016`b5aff670 00007fff`36db7b62 RPCRT4!LRPC_ADDRESS::ProcessIO+0x89e
11 00000016`b5aff7b0 00007fff`379d0330 RPCRT4!LrpcIoComplete+0xc2
12 00000016`b5aff850 00007fff`379fd566 ntdll!TppAlpcpExecuteCallback+0x260
13 00000016`b5aff8d0 00007fff`36847374 ntdll!TppWorkerThread+0x456
14 00000016`b5affbd0 00007fff`379fcc91 KERNEL32!BaseThreadInitThunk+0x14
15 00000016`b5affc00 00000000`00000000 ntdll!RtlUserThreadStart+0x21



# RPC EndPoint 信息如下所示:
Protocol:00007fff`330b2400 "ncacn_ip_tcp"
NetworkAddr:00000016`b5afe662 "Second Call!"
Endpoint:00007fff`330bc5a8 "135"

与 hook 写入的第一个 endpoint 对比如下:

UseProtseq 函数及参数说明

相关参考:

  1. 字符串绑定 - Win32 apps | Microsoft Learn
    https://learn.microsoft.com/zh-cn/windows/win32/rpc/string-binding
  2. ncacn_np 属性 - Win32 apps | Microsoft Learn
    https://learn.microsoft.com/zh-cn/windows/win32/midl/ncacn-np
  3. 终结点属性 - Win32 apps | Microsoft Learn
    https://learn.microsoft.com/zh-cn/windows/win32/midl/endpoint

在 Hook 的 UseProtseq 函数中,主要修改了 ppdsaNewBindings 参数,写入了如下所示的两条 EndPoint 的相关信息:

  1. “ncacn_np:localhost/pipe/GodPotato[\pipe\epmapper]”
  2. “ncacn_ip_tcp:Second Call![135]”

EndPoint

[endpoint] 属性指定一个或多个已知端口, (通信终结点) 接口的服务器侦听呼叫。

1
endpoint(``"protocol-sequence:[endpoint-port]"` `[ , ...] )

ncacn_np

ncacn_np 关键字 (keyword) 将命名管道标识为终结点的协议系列。

1
endpoint(``"ncacn_np:server-name[\\pipe\\pipe-name]"``)

对于 “ncacn_np:localhost/pipe/GodPotato[\pipe\epmapper]” 形式的 EndPoint 来说,解析可以分为三个部分。分别是:

  • 协议:ncacn_np
  • 服务器名称:localhost/pipe/GodPotato
  • 管道名:\pipe\pipe-name

为什么服务器名称是 localhost/pipe/GodPotato ?

因为要连接的目标对象是本地(localhost)的服务端管道,填写本地IP也行,但是不能为空。

1
public` `readonly string serverPipe = $``"\\\\.\\pipe\\{"``GodPotato``"}\\pipe\\epmapper"``;

通过这个管道,GodPotato 才能通过模拟来获取高权限令牌。

\pipe\pipe-name 为管道信息

总体流程如下所示:

flowchart TD
	A[GodPotato:CoUnmarshalInterface] --> B
	B[GodPotato:ClientResolveOXID2] -- RPC调用 --> C
	C[rpcss服务中的rpcss!ClientResolveOXID2] -- "绑定 ncacn_ip_tcp:127.0.0.1[135]" --> D
	D[rpcss!ResolveOxid2] --> E
	E[rpcss!CProcess::UseProtseqIfNeeded] -- 获取 EndPoint 信息 --> F
	F[GodPotato!HookFun->combase!UseProtseq] -- 返回自己创建的 EndPoint --> G
	G[rpcss:ClientResolveOXID2] -- 解析返回的第一个 EndPoint --> H
	H[rpcss:ClientResolveOXID2] -- 解析返回的第二个 EndPoint --> I
	I[连接到 GodPotato 中创建的服务端]

UseProtseq 断点

1
2
3
dt /v
# 或者
dx -r1 Debugger.State.Scripts.CodeFlow.Contents.host.currentThread.Stack.Frames[0]

主要关注参数 ppsaNewBindings 就行

代码中对 ppsaNewBindings 的使用就只有 CopyStringArray

call combase!CopyStringArray 处添加断点,然后看执行完成后的内容就行。单步跳过,查看写入的内容

可以看到其写入的内容由三部分,WNameEntrieswSecurityOffsetaStringArray

  • WNameEntries: aStringArray 的长度
  • wSecurityOffsetUseProtseq 的最后一个参数的长度

其中 aStringArray 的内容为 ncalrpc:[OLE9EC507FE2C47565F9B30D6BC6C29]。而 GodPotato 则修改了这一部分逻辑,写入的 ncacn_np:localhost/pipe/GodPotato[\pipe\epmapper]

获取参数个数

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
typedef struct
{
unsigned char FullPtrUsed : 1;
unsigned char RpcSsAllocUsed : 1;
unsigned char ObjectProc : 1;
unsigned char HasRpcFlags : 1;
unsigned char IgnoreObjectException : 1;
unsigned char HasCommOrFault : 1;
unsigned char UseNewInitRoutines : 1;
unsigned char Unused : 1;
} INTERPRETER_FLAGS, * PINTERPRETER_FLAGS;

typedef struct
{
unsigned char ServerMustSize : 1;
unsigned char ClientMustSize : 1;
unsigned char HasReturn : 1;
unsigned char HasPipes : 1;
unsigned char Unused : 1;
unsigned char HasAsyncUuid : 1;
unsigned char HasExtensions : 1;
unsigned char HasAsyncHandle : 1;
} INTERPRETER_OPT_FLAGS, * PINTERPRETER_OPT_FLAGS;

typedef struct _NDR_DCOM_OI2_PROC_HEADER
{
unsigned char HandleType; // The Oi header
INTERPRETER_FLAGS OldOiFlags; //
unsigned short RpcFlagsLow; //
unsigned short RpcFlagsHi; //
unsigned short ProcNum; //
unsigned short StackSize; //
// expl handle descr is never generated //
unsigned short ClientBufferSize; // The Oi2 header
unsigned short ServerBufferSize; //
INTERPRETER_OPT_FLAGS Oi2Flags; //
unsigned char NumberParams; //
} NDR_DCOM_OI2_PROC_HEADER, * PNDR_DCOM_OI2_PROC_HEADER;

RPC结构体解析

这里我参考 GodPotato 的 C# 版本实现了 C++ 版本的解析。同时尽可能的完善了部分细节以便于学习了解相关结构体。

链接:holdyounger/DecodeRPCStruct: Decode RPC_SERVER_INTERFACE Struct,take combase.dll as an example

对于 ProcString 字段的解析参考了 rpcrt4!MulNdrpInitializeContextFromProc 函数。

对应代码的 PE 文件可以使用 https://github.com/holdyounger/DecodeRPCStruct/actions/runs/12923652850

GodPotato 管道建立调试

记录下管道连接过程

  1. 通过 NMP_CreateFile 连接 GodPotato 的管道

    1
    bp RPCRT4!NMP_CreateFile "du @rcx"
  2. 调用 IopCreateFile

    1
    2
    3
    4
    5
    6
    bp nt!IopCreateFile "dt _OBJECT_ATTRIBUTES @r8;.thread"


    #调用了两次
    1. \??\UNC\localhost\pipe\GodPotato\pipe\epmapper·
    2. \Device\NamedPipe\GodPotato\pipe\epmapper