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

  • A+
所属分类:安全文章

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

http://scz.617.cn:8/windows/202103071208.txt

☆ 拦截DNSAPI!SyncResolverQueryRpc阻断FQDN解析

阻断指定进程的FQDN解析请求。若发往53/UDP的FQDN解析请求由进程本身发出,可用wf.msc设置出站规则进行阻断。但在Win10中,大多数进程发往53/UDP的报文实际由DNS Client Service发出,进程试图进行FQDN解析时,底层API实际向DNS ClientService发起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+'' 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能到达的极限,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

NSAPI!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 @rdx             // FQDN

WS2_32!GetAddrInfoW( FQDN, ... )

    du @rdx             // 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进程中找出实际进行页面浏览的那个进程?此番快速演示,未深究。

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的处理由DNSClient Service进行,前述断点在Opera进程空间。

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

本来要写一篇《单身理工科小伙儿约会乱谈》,但这个周末在干别的正事,就没写成。有空再说。我是被CS专业耽误了的扯淡砖家。

本文始发于微信公众号(青衣十三楼飞花堂):DNS系列(11)--研究Win10 FQDN解析

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: