一、用RPC/ALPC调试手段分析Win10 FQDN解析过程
1) 背景介绍
参看
《DNS系列(11)–研究Win10 FQDN解析》
http://scz.617.cn:8/windows/202103071208.txt
假设用ping、Opera触发FQDN解析,最终通过RPC交由其他进程完成真正的53/UDP通信;
单就Win10 FQDN解析而言,这个RPC Server是Dnscache(DNS Client)服务。
一个通用问题,如何普适地利用调试手段找到RPC Client所对应的RPC Server,如何
在RPC Server中对RPC Client进行识别、过滤?本文给出部分解答,某些技术手段属
于Hacking,不绝对可靠,但大多数时候可行。假设读者了解RPC/ALPC相关基础知识,
不做科普。
Guest环境如下
win10
Win10企业版2016 LTSB 1607(OS Build 14393.4704)
ping.exe
10.0.14393.0 (rs1_release.160715-1616)
dnsapi.dll
10.0.14393.4350 (rs1_release.210407-2154)
dnsrslvr.dll
10.0.14393.4350 (rs1_release.210407-2154)
ntdll.dll
10.0.14393.4704 (rs1_release.211004-1917)
rpcrt4.dll
10.0.14393.4704 (rs1_release.211004-1917)
2) Inside WS2_32!GetAddrInfoW
在Guest中
“C:\temp\cdb.exe” -noinh -snul -hd -o ping.exe www.baidu.com
.prompt_allow +reg +ea +dis
bc *
bp WS2_32!GetAddrInfoW “kpn;du @rcx”
会有两次命中WS2_32!GetAddrInfoW
# Child-SP RetAddr Call Site
00 000000348ab5ed08 00007ff7
1daa1154 WS2_32!GetAddrInfoW
01 000000348ab5ed10 00007ff7
1daa1ead ping!ResolveTarget+0x60
02 000000348ab5eda0 00007ff7
1daa32fd ping!wmain+0x439
03 000000348ab5f8d0 00007fff
885b84d4 ping!NlsFPutMsgW+0x2f5
04 000000348ab5f910 00007fff
88d41791 KERNEL32!BaseThreadInitThunk+0x14
05 000000348ab5f940 00000000
00000000 ntdll!RtlUserThreadStart+0x21
# Child-SP RetAddr Call Site
00 000000348ab5ed08 00007ff7
1daa11bf WS2_32!GetAddrInfoW
01 000000348ab5ed10 00007ff7
1daa1ead ping!ResolveTarget+0xcb
02 000000348ab5eda0 00007ff7
1daa32fd ping!wmain+0x439
03 000000348ab5f8d0 00007fff
885b84d4 ping!NlsFPutMsgW+0x2f5
04 000000348ab5f910 00007fff
88d41791 KERNEL32!BaseThreadInitThunk+0x14
05 000000348ab5f940 00000000
00000000 ntdll!RtlUserThreadStart+0x21
参看
https://docs.microsoft.com/en-us/windows/win32/api/ws2tcpip/nf-ws2tcpip-getaddrinfow
https://docs.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-addrinfow
WS2_32!GetAddrInfoW函数原型如下
INT WSAAPI GetAddrInfoW
(
[in,optional] PCWSTR pNodeName, // rcx
[in,optional] PCWSTR pServiceName, // rdx
[in,optional] ADDRINFOW *pHints, // r8
[out] PADDRINFOW *ppResult // r9
);
WS2_32!GetAddrInfoW第3形参类型是ADDRINFOW结构
typedef struct addrinfoW
{
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
PWSTR ai_canonname;
struct sockaddr *ai_addr;
struct addrinfoW *ai_next;
} ADDRINFOW, *PADDRINFOW;
addrinfoW结构的第一个成员ai_flags会影响GetAddrInfoW()的行为。ping调用
GetAddrInfoW()时,pHints.ai_flags第一次传值4,第二次传值2,它们的含义是
AI_CANONNAME(2)
The canonical name is returned in the first ai_canonname member.
AI_NUMERICHOST(4)
pNodeName以点分十进制形式指定
“www.baidu.com”是FQDN,不是点分十进制IP地址,故第一次WS2_32!GetAddrInfoW调
用失败,返回值WSAHOST_NOT_FOUND(11001),参看
https://docs.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2
可以快速查看11001的意义
$ net helpmsg 11001
No such host is known.
我们关心第二次WS2_32!GetAddrInfoW调用,对应FQDN解析。
Win10上WS2_32!GetAddrInfoW内部用到”RPC Over ALPC”。注意RPC和ALPC是两个独立
的概念,二者不具有必然的捆绑关系,换句话说,存在其他ALPC,但与RPC无关。
单次WS2_32!GetAddrInfoW内部会两次调用ntdll!NtAlpcSendWaitReceivePort,第一
次对应RPC的BIND操作,第二次才是真正的远程过程调用。
bp ntdll!NtAlpcSendWaitReceivePort “kpn;r rcx”
断点命中时rcx是ClientCommunicationPortHandle,调用栈回溯整理如下
DNSAPI!Rpc_ResolverQuery+0xf4
RPCRT4!NdrClientCall3
RPCRT4!NdrClientCall3+0xed
RPCRT4!NdrpClientCall3
RPCRT4!NdrpClientCall3+0x4e9 // BIND
RPCRT4!GenericHandleMgr
RPCRT4!GenericHandleMgr+0x92
ntdll!LdrpDispatchUserCallTarget
DNSAPI!DNS_RPC_HANDLE_bind
DNSAPI!DNS_RPC_HANDLE_bind+0xd0
RPCRT4!RpcBindingBind
RPCRT4!RpcBindingBind+0x55
RPCRT4!LRPC_FAST_BINDING_HANDLE::Bind
RPCRT4!LRPC_FAST_BINDING_HANDLE::Bind+0x192
RPCRT4!LRPC_CASSOCIATION::Bind
RPCRT4!LRPC_CASSOCIATION::Bind+0x6ef
ntdll!NtAlpcSendWaitReceivePort // 对应RPC BIND
RPCRT4!NdrpClientCall3+0xf71 // 远程过程调用
// call qword ptr [RPCRT4!_guard_dispatch_icall_fptr]
ntdll!LdrpDispatchUserCallTarget // “u @rax l 1″检查目标函数
// “jmp rax”进行流程转移
RPCRT4!LRPC_BASE_CCALL::SendReceive
RPCRT4!LRPC_BASE_CCALL::SendReceive+0x128
ntdll!NtAlpcSendWaitReceivePort // 对应RPC远程过程调用
3) 拦载RPCRT4!NdrpClientCall3获取RPC Server的IID/远程过程号
从前一小节调用栈看出,WS2_32!GetAddrInfoW内部会过RPCRT4!NdrpClientCall3,
拦截后者可以得到RPC Server的IID、远程过程号。若对RPC本身不太了解,参[1]。
RPCRT4!NdrpClientCall3函数原型如下
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)
)
第3形参nProcNum就是远程过程号。IID可以通过第2形参pProxyInfo间接获取。
在Guest中
“C:\temp\cdb.exe” -noinh -snul -hd -o ping.exe www.baidu.com
.prompt_allow +reg +ea +dis
bc *
bp WS2_32!GetAddrInfoW 2 “kpn;du @rcx”
断点命中后设置新断点
bp RPCRT4!NdrpClientCall3 “kpn;dt -io ntdll!_GUID poi(poi(@rdx))+4;r r8”
新断点命中时依次显示IID、远程过程号
IID 45776b01-5956-4485-9f80-f428f7d60129
ProcNum 4
4) 用RpcView定位RPC Server的PID及远程过程
参看
《RpcView简介》
http://scz.617.cn:8/windows/202110270957.txt
在Guest中启动RpcView
RpcView.exe /f
假设已用第3小节的办法获取RPC Server的IID、远程过程号,在RpcView中搜IID
RpcView
Filter
Interfaces
45776b01-5956-4485-9f80-f428f7d60129
上述操作直接过滤出RPC Server的PID及远程过程,比如
svchost.exe(564)
dnsrslvr.dll
R_ResolverQuery(4)
检查svchost(564)
$ tasklist /svc /fi “services eq dnscache”
$ tasklist /svc /fi “pid eq 564”
Image Name PID Services
========================= ======== ============================================
svchost.exe 564 CryptSvc, Dnscache, LanmanWorkstation,
NlaSvc, TermService
svchost(564)中有好几个服务,RDP服务也在其中。
5) dnsrslvr!R_ResolverQuery调用栈回溯
dnsrslvr!R_ResolverQuery第3形参对应FQDN
dnsrslvr!R_ResolverQuery
(
x,
x,
FQDN, // @r8
…
)
调试svchost(564),设置条件断点
bp dnsrslvr!R_ResolverQuery “.if(qwo(@r8)==0x2e007700770077 and qwo(@r8+8)==0x64006900610062 and qwo(@r8+0x10)==0x6f0063002e0075 and wo(@r8+0x18)==0x6d){kpn}.else{du @r8;gc}”
FQDN是”www.baidu.com”时断下,否则显示FQDN后继续。dnsrslvr!R_ResolverQuery
会被频繁命中,若不设过滤条件,无法有效调试。
用”ping www.baidu.com”触发上述断点,调用栈回溯如下
# Child-SP RetAddr Call Site
00 000000d8fd3fedf8 00007fff
8817a583 dnsrslvr!R_ResolverQuery
01 000000d8fd3fee00 00007fff
881d6162 RPCRT4!Invoke+0x73
02 000000d8fd3fee90 00007fff
8814a284 RPCRT4!Ndr64AsyncServerWorker+0x392
03 000000d8fd3fefb0 00007fff
8814919d RPCRT4!DispatchToStubInCNoAvrf+0x24
04 000000d8fd3ff000 00007fff
88149a4b RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1bd
05 000000d8fd3ff0d0 00007fff
881310ac RPCRT4!RPC_INTERFACE::DispatchToStub+0xcb
06 000000d8fd3ff130 00007fff
8813152c RPCRT4!LRPC_SCALL::DispatchRequest+0x34c
07 000000d8fd3ff210 00007fff
8811ae1c RPCRT4!LRPC_SCALL::HandleRequest+0x2bc
08 000000d8fd3ff330 00007fff
8811c67b RPCRT4!LRPC_ADDRESS::HandleRequest+0x36c
09 000000d8fd3ff3e0 00007fff
88143a2a RPCRT4!LRPC_ADDRESS::ProcessIO+0x91b
0a 000000d8fd3ff520 00007fff
88d0d35e RPCRT4!LrpcIoComplete+0xaa
0b 000000d8fd3ff5c0 00007fff
88d0ecc9 ntdll!TppAlpcpExecuteCallback+0x25e
0c 000000d8fd3ff670 00007fff
885b84d4 ntdll!TppWorkerThread+0x8d9
0d 000000d8fd3ffa70 00007fff
88d41791 KERNEL32!BaseThreadInitThunk+0x14
0e 000000d8fd3ffaa0 00000000
00000000 ntdll!RtlUserThreadStart+0x21
6) 拦截RPCRT4!DispatchToStubInCNoAvrf获取IID/远程过程号/远程过程
第4小节用RpcView快速定位了RPC Server的远程过程。假设没有RpcView可用,又该
如何?本小节介绍一种偏Hacking的方案。
不建议在RPC Server中拦截这些函数
RPCRT4!Ndr64StubWorker
RPCRT4!NdrServerCallAll
RPCRT4!Ndr64AsyncServerWorker
RPCRT4!RPC_INTERFACE::DispatchToStubWorker
RPCRT4!LRPC_SCALL::DispatchRequest
RPCRT4!LRPC_SCALL::HandleRequest
推荐拦截
RPCRT4!DispatchToStubInCNoAvrf
调试svchost(564),设置条件断点
r $t9=0x1088
bp RPCRT4!DispatchToStubInCNoAvrf “r $t0=poi(@rdx+0x68);.if((@$t0&0xffffffff00000000)==(@rdx&0xffffffff00000000)){.if(qwo(@$t0+8)==@$t9){}.else{gc}}.else{gc}”
给$t9指定RPC Client的PID,上述断点对RPC Client的PID进行检查,匹配时才断下。
windbg条件断点.if语句不支持短路求值?只好.if中嵌套.if。
断点命中时用如下命令显示IID、远程过程号、远程过程
dt ntdll!_GUID poi(@rdx+0x28)+4
? dwo(@rdx+0x1c)
dqs poi(poi(poi(@rdx+0x28)+0x50)+8)+dwo(@rdx+0x1c)*8 l 1
显然,此处不只可以过滤PID,也可过滤IID、远程过程号,从而定位远程过程。当
RPC Client是ping这种很简单的进程时,过滤PID足矣。
7) 内核态获取ALPC目标进程
第4小节用RpcView快速定位了RPC Server的PID。假设没有RpcView可用,又该如何?
本小节介绍一种偏Hacking的方案。
参[2],本小节技术方案用到sysinternals的livekd。在Guest中这样启动livekd
“\livekd.exe” -k “\kd.exe”
livekd必须与kd相配合,不能单独使用,但不要求Guest进入”Test Mode”,对于只读
型内核数据访问,livekd非常方便。
在RPC Client(比如ping)中拦截ntdll!NtAlpcSendWaitReceivePort,断点命中时rcx
是ClientCommunicationPortHandle,假设其值为0xcc。
接下来在livekd中获取句柄0xcc对应的对象地址,这步需要指定ping的PID,可在用
户态”? @$tpid”,也可在内核态”!process 0 0 ping.exe”,假设ping的PID是5376。
kd> !handle 0xcc 0 0n5376
…
00cc: Object: ffffda88435ace20 GrantedAccess: 001f0001 (Protected) (Inherit) (Audit)
获取ClientCommunicationPort(0xffffda88435ace20)
用!alpc根据ClientCommunicationPort获取ConnectionPort
kd> !alpc /p 0xffffda88435ace20
Port ffffda88435ace20
Type : ALPC_CLIENT_COMMUNICATION_PORT
CommunicationInfo : ffffa08d1a1953c0
ConnectionPort : ffffda88402ebe20 (DNSResolver)
ClientCommunicationPort : ffffda88435ace20
ServerCommunicationPort : ffffda8843123590
OwnerProcess : ffffda884345b080 (PING.EXE)
…
获取ConnectionPort(0xffffda88402ebe20)
用!alpc查看ConnectionPort所属进程
kd> !alpc /p 0xffffda88402ebe20
Port ffffda88402ebe20
Type : ALPC_CONNECTION_PORT
CommunicationInfo : ffffa08d187183a0
ConnectionPort : ffffda88402ebe20 (DNSResolver)
ClientCommunicationPort : 0000000000000000
ServerCommunicationPort : 0000000000000000
OwnerProcess : ffffda883ff6a800 (svchost.exe)
…
kd> !process 0xffffda883ff6a800 0
PROCESS ffffda883ff6a800
SessionId: 0 Cid: 0234 Peb: d8fa688000 ParentCid: 030c
DirBase: 649e2000 ObjectTable: ffffa08d18408340 HandleCount:
Image: svchost.exe
至此已知WS2_32!GetAddrInfoW触发的”RPC Over ALPC”目标进程svchost(564)。
用”.detach”退出livekd。
8) 利用ALPCLogger获取ALPC目标进程
一般而言,若RPCRT4!NdrpClientCall3的底层走ALPC,即”RPC Over ALPC”,可用
ALPCLogger获取ALPC目标进程。
参[3],ALPCLogger是一款C#开发的工具,记录ALPC的Client、Server,作者是Pavel
Yosifovich,《Windows Internals, 7th Edition》的作者之一。
ALPCLogger用了ETW技术,调用栈回溯中有内核态的地址。但是,调用栈回溯没有符
号信息,只有绝对地址,只能在调试器中查看到底是什么。调用栈回溯所用控件无法
一次性全选、复制,只能单条选中地址并复制。ALPCLogger开源,但我没兴趣改它。
ALPCLogger算是PoC,用起来非常不便,但确实能用。曾小结过如何使用ALPCLogger,
但由于RpcView、livekd非常顺手,不在此啰嗦。
与RpcView、livekd相比,ALPCLogger相当鸡肋,不推荐,此间仅备忘。
二、后记
第6小节的技术方案非常Hacking化,莫在生产环境中使用。
RPC Client不是都调RPCRT4!NdrpClientCall3。若碰上RPCRT4!NdrpClientCall2,其
形参不直接提供远程过程号,需要从StartofStack中析取,本文没有举例说明,有刚
需的读者自行练习。
参考资源
[1] Offensive Windows IPC Internals 2 – [2021-02-21]
https://csandker.io/2021/02/21/Offensive-Windows-IPC-2-RPC.html
[2] livekd
https://docs.microsoft.com/en-us/sysinternals/downloads/livekd
[3] ALPCLogger – Pavel Yosifovich
https://github.com/zodiacon/ALPCLogger
(调用栈回溯不支持符号)
版权声明
本站“技术博客”所有内容的版权持有者为绿盟科技集团股份有限公司(“绿盟科技”)。作为分享技术资讯的平台,绿盟科技期待与广大用户互动交流,并欢迎在标明出处(绿盟科技-技术博客)及网址的情形下,全文转发。
上述情形之外的任何使用形式,均需提前向绿盟科技(010-68438880-5462)申请版权授权。如擅自使用,绿盟科技保留追责权利。同时,如因擅自使用博客内容引发法律纠纷,由使用者自行承担全部法律责任,与绿盟科技无关。