Skip to content

标题: DNS系列(11)--研究Win10 FQDN解析

创建: 2021-03-07 12:08 更新: 2022-11-07 11:13 链接: https://scz.617.cn/windows/202103071208.txt


目录:

☆ 拦截DNSAPI!SyncResolverQueryRpc阻断FQDN解析
☆ 后补
    1) raw socket
    2) 取Unicode字符串的长整型值(little-endian序)
☆ 用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 Debugging (已不适用于Win10)
    1) Enabling RPC State Information
    2) 已知EndPoint反查PID
    3) 不灵的RPC调试命令
    4) 为什么Win10中rpcexts.dll不灵了
    5) !rpcexts.help
☆ Win10 FQDN解析时绕过hosts文件
☆ Win10 RPC调试中RPC Client PID的利用
☆ Windows域名解析保护机制深度分析

☆ 拦截DNSAPI!SyncResolverQueryRpc阻断FQDN解析

想阻断指定进程的FQDN解析请求。若发往53/UDP的FQDN解析请求由进程本身发出,可 用wf.msc设置出站规则进行阻断。但在Win10中,大多数进程发往53/UDP的报文实际 由DNS Client Service发出,进程试图进行FQDN解析时,底层API实际向DNS Client Service发起RPC请求,由后者进行socket通信,FQDN解析结果经RPC响应返回给普通 进程,比如ping、Firefox、Opera俱如此。如果在wf.msc中阻断ping.exe的53/UDP外 发,达不到阻断指定进程FQDN解析请求的预期效果。

Win7可以停用DNS Client Service,此时进程试图解析FQDN时由本进程发送53/UDP报 文。Win10无法停用DNS Client Service,这招不适用。

https://docs.microsoft.com/en-us/windows/win32/api/icmpapi/nf-icmpapi-icmpsendecho

顺便说一下,ping.exe外发ICMP应该也不是进程自身进行socket通信,底层API应该 是交由其他系统组件完成ICMP外发,试图在wf.msc中阻断ping.exe的ICMPv4报文外发, 达不到预期效果,除非阻断全系统的ICMPv4报文外发。bluerust说某个版本Windows 开始不支持raw socket,我好像有这个印象,但早就不关心这些事,也就模糊了,不 欲深究。

下面这段Python3代码显示Unicode字符串"www.baidu.com"前16字节的长整数形式,2 个长整数,little-endian序:


x = 'www.baidu.com' x = "".join([c+'\0' for c in x]).encode( 'latin-1' ).hex() y = x[:16] ''.join(map(str.add, y[-2::-2], y[-1::-2])) y = x[16:32] ''.join(map(str.add, y[-2::-2], y[-1::-2]))


'002e007700770077' '0064006900610062'

我只会这么矬的办法,不知更好的办法是啥?

cdb.exe -noinh -snul -hd -o ping.exe www.baidu.com

.prompt_allow +reg +ea +dis;rm 0xa

条件断点,试图解析"www.baidu.com"时断下来:

bu RPCRT4!NdrClientCall3 "r $t9=poi(@rsp+0x28);.if(@rdx==4 and @r8==0 and qwo(@$t9)==0x002e007700770077 and qwo(@$t9+8)==0x0064006900610062){du @$t9}.else{gc}"

条件断点可以弄成字符串匹配的,但那样写起来有点复杂,快速演示时就这样吧。

00000273c13026fc "www.baidu.com" RPCRT4!NdrClientCall3: 00007ff8477346e0 4c89442418 mov qword ptr [rsp+18h],r8 ss:000000ef4176cc10=000000ef4176d9a0 0:000> kpn # Child-SP RetAddr Call Site 00 000000ef4176cbf8 00007ff8452e82e1 RPCRT4!NdrClientCall3 01 000000ef4176cc00 00007ff8452e7fe3 DNSAPI!SyncResolverQueryRpc+0x171 02 000000ef4176ce00 00007ff8452ebcb7 DNSAPI!Rpc_ResolverQuery+0xc3 03 000000ef4176ced0 00007ff8452eb466 DNSAPI!Query_PrivateExW+0x7a7 04 000000ef4176d700 00007ff8455bb163 DNSAPI!DnsQueryEx+0x166 05 000000ef4176d970 00007ff8455baf34 mswsock!SaBlob_Query+0xcb 06 000000ef4176da40 00007ff8455ba60e mswsock!Rnr_DoDnsLookup+0x1ac 07 000000ef4176dae0 00007ff84792ba88 mswsock!Dns_NSPLookupServiceNext+0x1de 08 000000ef4176def0 00007ff84792bc9c WS2_32!NSPROVIDER::NSPLookupServiceNext+0x78 09 000000ef4176dfc0 00007ff84792bbbd WS2_32!NSQUERY::LookupServiceNext+0xa0 0a 000000ef4176e040 00007ff84792bf7a WS2_32!WSALookupServiceNextW+0xdd 0b 000000ef4176e090 00007ff84792c316 WS2_32!QueryDnsForFamily+0x1ae 0c 000000ef4176ea40 00007ff8479234da WS2_32!QueryDns+0x172 0d 000000ef4176eb00 00007ff847925eac WS2_32!LookupAddressForName+0x122 0e 000000ef4176ec10 00007ff7bc5e11dc WS2_32!GetAddrInfoW+0x38c 0f 000000ef4176edb0 00007ff7bc5e1fa3 ping!ResolveTarget+0xe0 10 000000ef4176ee40 00007ff7bc5e356d ping!wmain+0x447 11 000000ef4176f960 00007ff8484d7034 ping!__wmainCRTStartup+0x14d 12 000000ef4176f9a0 00007ff8487a2651 KERNEL32!BaseThreadInitThunk+0x14 13 000000ef4176f9d0 00000000`00000000 ntdll!RtlUserThreadStart+0x21

上述调用栈回溯已是ping能到达的极限,RPCRT4!NdrClientCall3()是向DNS Client Service发起RPC请求。

下面给出各函数FQDN形参位置:


RPCRT4!NdrClientCall3( rcx, rdx, r8, r9, FQDN, ... )

r rcx,rdx,r8,r9
du poi(@rsp+0x28)   // FQDN

DNSAPI!SyncResolverQueryRpc( FQDN, ... )

du @rcx             // FQDN

DNSAPI!Rpc_ResolverQuery( rcx, FQDN, ... )

du @rdx             // FQDN

DNSAPI!Query_PrivateExW( FQDN, QueryType, ... )

du @rcx             // FQDN
                    // QueryName
@rdx                // QueryType

DNSAPI!DnsQueryEx(PDNS_QUERY_REQUEST pQueryRequest, PDNS_QUERY_RESULT pQueryResults, PDNS_QUERY_CANCEL pCancelHandle)

du poi(@rcx+8)      // FQDN
                    // pQueryRequest->QueryName

mswsock!SaBlob_Query( FQDN, ... )

du @rcx             // FQDN

WS2_32!GetAddrInfoW( FQDN, ... )

du @rcx             // FQDN

我是怎么找到前述调用栈回溯的呢?如果已经对DNSAPI.dll很熟,可以关注:

DNSAPI!DnsQueryEx DNSAPI!DnsQuery_A DNSAPI!DnsQuery_W DNSAPI!DnsQuery_UTF8 DNSAPI!DnsQueryExA DNSAPI!DnsQueryExW DNSAPI!DnsQueryExUTF8 DNSAPI!SyncResolverQueryRpc // 同步查询 DNSAPI!AsyncResolverQueryRPC // 异步查询

最初我简单断了一下DNSAPI!DnsQuery_A(),没有命中。

假设之前完全没有积累,不知流程最后会去DNSAPI!SyncResolverQueryRpc(),怎么 摸过来?大概说一下,不保证最优解。

首先IDA看ping.exe,大概找个位置在处理命令行上指定的FQDN。然后cdb调ping.exe, 在前一步找到的位置设断,同时Wireshark抓53/UDP的包。接下来一路pc (Step to Next Call),看哪个call会触发53/UDP通信。假设某个call会触发53/UDP 通信,重新用cdb调ping.exe,直接断在这个call上,t进去,继续pc,继续Wireshark。 重复这个过程,直至DNSAPI!SyncResolverQueryRpc()。有些call可能是第N次经过时 才触发53/UDP通信,这种得把前N-1次命中略过,我碰上的情形N最多等于2。

若碰上通过_guard_dispatch_icall_fptr/ntdll!LdrpDispatchUserCallTarget调用 目标函数,用"u @rax l 1"检查目标函数。ntdll!LdrpDispatchUserCallTarget通过 "jmp rax"转移到目标函数,中间没有call指令,使用pc时如果发现已经离开原函数, 不一定是跑飞了。

cdb.exe -noinh -snul -hd -o ping.exe www.baidu.com

就ping.exe而言,如下断点将阻断所有FQDN解析:

bu DNSAPI!SyncResolverQueryRpc+0x16a "r $t9=poi(@rsp+0x20);du @$t9;ezu @$t9 \".\";gc"

这是RPCRT4!NdrClientCall3()的主调位置,将所有FQDN替换成"."。

Opera开起来后台有十几个同名进程,先在一个标签页中访问www.baidu.com,用 Tcpview通过TCP连接找出对应的PID。有啥别的办法从十几个同名Opera进程中找出实 际进行页面浏览的那个进程?Message Analyzer、Network Monitor、ETW都太重型, 后来找了些快速办法回答此问题。

wmic process where name="opera.exe" get CommandLine,Name,ProcessId | findstr network.mojom.NetworkService

cdb.exe -noinh -snul -hd -o -p 16036 bu DNSAPI!SyncResolverQueryRpc+0x16a "r $t9=poi(@rsp+0x20);du @$t9;ezu @$t9 \".\";gc"

之后Opera无法访问任何FQDN;即使有/etc/hosts加持也不行,对hosts的处理由DNS Client Service进行,前述断点在Opera进程空间。

本文未从工程角度解决实际问题,仅为探索性质。

☆ 后补

1) raw socket

2021-03-07 huyuguang

huyuguang指出,Win10仍然支持raw socket,ping不用的原因也许是不想需要管理员 权限才能运行。关于不支持raw socket的传闻大概来自从某个版本起,限制了源IP不 是本机的raw socket通信。有很多协议仍然需要raw socket,例如pptp(gre)等等。

2021-03-08 scz

huyuguang这个说法合理。我去查了一下自己的历史文档:

《12.3 XP SP2对raw socket所做的改动》

《12.7 去除XP SP2/SP3对raw socket的一些限制》 https://scz.617.cn/windows/200701091750.txt

历史上陆续出现的针对raw socket的限制有:

a) 不能通过raw socket发送TCP报文。做此尝试时会得到10004号错误。 b) 不能通过raw socket发送伪造源IP的UDP报文。 c) 不能通过raw socket发送IP碎片。做此尝试时会得到10004号错误。 d) 不能通过raw socket发送全部IP碎片,只有第一个碎片可被发送出去。试图发送 后续碎片时会得到10004号错误。 e) 不允许用raw socket发送IP-ENCAP、IPv6报文

这是十几年前的事儿,现在的情形可能有变。

2) 取Unicode字符串的长整型值(little-endian序)

2020-03-07 bluerust

Python3


import struct

x = "www.baidu.com" x = bytes( x, encoding='utf-16' )[2:] [hex(struct.unpack( "<Q", x[i:i+8] )[0]) for i in range(0 ,len(x)//8*8, 8)]


['0x2e007700770077', '0x64006900610062', '0x6f0063002e0075']


import hexdump

x = "www.baidu.com" x = bytes( x, encoding='utf-16' )[2:] ['0x' + ''.join(map(str.add, y[-2::-2], y[-1::-2])) for y in hexdump.dump( x, size=16, sep=',' ).split( ',' )]


import hexdump

x = "www.baidu.com" x = bytes( x, encoding='utf-16' )[2:] ['0x' + ''.join(map(str.add, y[-2::-2], y[-1::-2])) for y in hexdump.dump( x, size=16 ).split()]


['0x002E007700770077', '0x0064006900610062', '0x006F0063002E0075', '0x006D']

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

https://scz.617.cn/windows/202111161210.txt

☆ RPC Debugging (已不适用于Win10)

https://scz.617.cn/windows/202111152036.txt

☆ Win10 FQDN解析时绕过hosts文件

https://scz.617.cn/windows/202111181224.txt

☆ Win10 RPC调试中RPC Client PID的利用

https://scz.617.cn/windows/202111190952.txt

☆ Windows域名解析保护机制深度分析

https://scz.617.cn/windows/202111241129.txt