创建: 2021-11-16 12:10
http://scz.617.cn:8/windows/202111161210.txt复制
目录
☆ 用RPC/ALPC调试手段分析Win10 FQDN解析过程
1) 背景介绍
2) Inside WS2_32!GetAddrInfoW
3) 拦载RPCRT4!NdrpClientCall3获取RPC Server的IID/远程过程号
4) 用RpcView定位RPC Server的PID及远程过程
5) dnsrslvr!R_ResolverQuery调用栈回溯
6) 拦截RPCRT4!DispatchToStubInCNoAvrf获取IID/远程过程号/远程过程
7) 内核态获取ALPC目标进程
8) 利用ALPCLogger获取ALPC目标进程
☆ 后记
☆ 参考资源复制
☆ 用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相关基础知识,不做科普。
2) Inside WS2_32!GetAddrInfoW
cdb -noinh -snul -hd -o ping.exe www.baidu.com
bp WS2_32!GetAddrInfoW "kpn;du @rcx"复制
会有两次命中WS2_32!GetAddrInfoW
# Child-SP RetAddr Call Site
00 00000034`8ab5ed08 00007ff7`1daa1154 WS2_32!GetAddrInfoW
01 00000034`8ab5ed10 00007ff7`1daa1ead ping!ResolveTarget+0x60
02 00000034`8ab5eda0 00007ff7`1daa32fd ping!wmain+0x439
03 00000034`8ab5f8d0 00007fff`885b84d4 ping!NlsFPutMsgW+0x2f5
04 00000034`8ab5f910 00007fff`88d41791 KERNEL32!BaseThreadInitThunk+0x14
05 00000034`8ab5f940 00000000`00000000 ntdll!RtlUserThreadStart+0x21
# Child-SP RetAddr Call Site
00 00000034`8ab5ed08 00007ff7`1daa11bf WS2_32!GetAddrInfoW
01 00000034`8ab5ed10 00007ff7`1daa1ead ping!ResolveTarget+0xcb
02 00000034`8ab5eda0 00007ff7`1daa32fd ping!wmain+0x439
03 00000034`8ab5f8d0 00007fff`885b84d4 ping!NlsFPutMsgW+0x2f5
04 00000034`8ab5f910 00007fff`88d41791 KERNEL32!BaseThreadInitThunk+0x14
05 00000034`8ab5f940 00000000`00000000 ntdll!RtlUserThreadStart+0x21复制
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)
可以快速查看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
RPCRT4!NdrpClientCall3+0xf71 // 远程过程调用
RPCRT4!LRPC_BASE_CCALL::SendReceive
RPCRT4!LRPC_BASE_CCALL::SendReceive+0x128
ntdll!NtAlpcSendWaitReceivePort复制
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中
cdb -noinh -snul -hd -o ping.exe www.baidu.com
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 000000d8`fd3fedf8 00007fff`8817a583 dnsrslvr!R_ResolverQuery
01 000000d8`fd3fee00 00007fff`881d6162 RPCRT4!Invoke+0x73
02 000000d8`fd3fee90 00007fff`8814a284 RPCRT4!Ndr64AsyncServerWorker+0x392
03 000000d8`fd3fefb0 00007fff`8814919d RPCRT4!DispatchToStubInCNoAvrf+0x24
04 000000d8`fd3ff000 00007fff`88149a4b RPCRT4!RPC_INTERFACE::DispatchToStubWorker+0x1bd
05 000000d8`fd3ff0d0 00007fff`881310ac RPCRT4!RPC_INTERFACE::DispatchToStub+0xcb
06 000000d8`fd3ff130 00007fff`8813152c RPCRT4!LRPC_SCALL::DispatchRequest+0x34c
07 000000d8`fd3ff210 00007fff`8811ae1c RPCRT4!LRPC_SCALL::HandleRequest+0x2bc
08 000000d8`fd3ff330 00007fff`8811c67b RPCRT4!LRPC_ADDRESS::HandleRequest+0x36c
09 000000d8`fd3ff3e0 00007fff`88143a2a RPCRT4!LRPC_ADDRESS::ProcessIO+0x91b
0a 000000d8`fd3ff520 00007fff`88d0d35e RPCRT4!LrpcIoComplete+0xaa
0b 000000d8`fd3ff5c0 00007fff`88d0ecc9 ntdll!TppAlpcpExecuteCallback+0x25e
0c 000000d8`fd3ff670 00007fff`885b84d4 ntdll!TppWorkerThread+0x8d9
0d 000000d8`fd3ffa70 00007fff`88d41791 KERNEL32!BaseThreadInitThunk+0x14
0e 000000d8`fd3ffaa0 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
"<path>\livekd.exe" -k "<path>\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: <Data Not Accessible>
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复制