标题: 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}" 条件断点可以弄成字符串匹配的,但那样写起来有点复杂,快速演示时就这样吧。 00000273`c13026fc "www.baidu.com" RPCRT4!NdrClientCall3: 00007ff8`477346e0 4c89442418 mov qword ptr [rsp+18h],r8 ss:000000ef`4176cc10=000000ef4176d9a0 0:000> kpn # Child-SP RetAddr Call Site 00 000000ef`4176cbf8 00007ff8`452e82e1 RPCRT4!NdrClientCall3 01 000000ef`4176cc00 00007ff8`452e7fe3 DNSAPI!SyncResolverQueryRpc+0x171 02 000000ef`4176ce00 00007ff8`452ebcb7 DNSAPI!Rpc_ResolverQuery+0xc3 03 000000ef`4176ced0 00007ff8`452eb466 DNSAPI!Query_PrivateExW+0x7a7 04 000000ef`4176d700 00007ff8`455bb163 DNSAPI!DnsQueryEx+0x166 05 000000ef`4176d970 00007ff8`455baf34 mswsock!SaBlob_Query+0xcb 06 000000ef`4176da40 00007ff8`455ba60e mswsock!Rnr_DoDnsLookup+0x1ac 07 000000ef`4176dae0 00007ff8`4792ba88 mswsock!Dns_NSPLookupServiceNext+0x1de 08 000000ef`4176def0 00007ff8`4792bc9c WS2_32!NSPROVIDER::NSPLookupServiceNext+0x78 09 000000ef`4176dfc0 00007ff8`4792bbbd WS2_32!NSQUERY::LookupServiceNext+0xa0 0a 000000ef`4176e040 00007ff8`4792bf7a WS2_32!WSALookupServiceNextW+0xdd 0b 000000ef`4176e090 00007ff8`4792c316 WS2_32!QueryDnsForFamily+0x1ae 0c 000000ef`4176ea40 00007ff8`479234da WS2_32!QueryDns+0x172 0d 000000ef`4176eb00 00007ff8`47925eac WS2_32!LookupAddressForName+0x122 0e 000000ef`4176ec10 00007ff7`bc5e11dc WS2_32!GetAddrInfoW+0x38c 0f 000000ef`4176edb0 00007ff7`bc5e1fa3 ping!ResolveTarget+0xe0 10 000000ef`4176ee40 00007ff7`bc5e356d ping!wmain+0x447 11 000000ef`4176f960 00007ff8`484d7034 ping!__wmainCRTStartup+0x14d 12 000000ef`4176f9a0 00007ff8`487a2651 KERNEL32!BaseThreadInitThunk+0x14 13 000000ef`4176f9d0 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( "