暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

用RPC/ALPC调试手段分析Win10 FQDN解析过程

青衣十三楼飞花堂 2021-11-17
1245
创建: 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
0000000d8`fd3ff520 00007fff`88d0d35e RPCRT4!LrpcIoComplete+0xaa
0000000d8`fd3ff5c0 00007fff`88d0ecc9 ntdll!TppAlpcpExecuteCallback+0x25e
0000000d8`fd3ff670 00007fff`885b84d4 ntdll!TppWorkerThread+0x8d9
0d 000000d8`fd3ffa70 00007fff`88d41791 KERNEL32!BaseThreadInitThunk+0x14
0000000d8`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

复制


文章转载自青衣十三楼飞花堂,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论