@lniwn
2017-09-08T12:43:04.000000Z
字数 5049
阅读 1210
Windows DNS
关于DNS劫持的概念,网上很多介绍,这里就不细说了。最近正好在做这方面的预研,把过程记录下来。
DNS查询协议走的53端口,默认是UDP协议,可以自己按照MSDN描述来构造协议包,而Windows系统已经提供了相关的API,用于进行DNS查询。
std::pair<std::wstring, std::string> CDNSHelper::QueryIpAndCanonname(const wchar_t* cname){std::pair<std::wstring, std::string> result;std::wstring host_name = cname;PDNS_RECORD pDnsRecord = NULL;DNS_STATUS dnsStatus = ::DnsQuery(host_name.c_str(),DNS_TYPE_A,DNS_QUERY_NO_HOSTS_FILE|DNS_QUERY_BYPASS_CACHE|DNS_QUERY_TREAT_AS_FQDN,pSrvList_,&pDnsRecord,NULL);//if (DNS_ERROR_RCODE_NAME_ERROR == dnsStatus)//{// // #TODO// // DNS name does not exist.//}if (ERROR_SUCCESS != dnsStatus){return result;}bool needBreak = false;for (PDNS_RECORD cursor = pDnsRecord; cursor != NULL && !needBreak; cursor = cursor->pNext){if (cursor->Flags.S.Section != DnsSectionAnswer){continue;}if (::DnsNameCompare(cursor->pName, host_name.c_str())){switch(cursor->wType){case DNS_TYPE_CNAME:{host_name = cursor->Data.CNAME.pNameHost;}break;case DNS_TYPE_A:{IN_ADDR ipaddr;ipaddr.S_un.S_addr = cursor->Data.A.IpAddress;result = std::make_pair(host_name, ::inet_ntoa(ipaddr));needBreak = true;}break;default:break;}}}::DnsRecordListFree(pDnsRecord, DnsFreeRecordListDeep);return result;}
DnsQuery的第1个参数为要查询的目标域名;第2个参数为查询类型,这里我们要查询A记录,所以使用了DNS_TYPE_A;第3个参数为查询选项,DNS_QUERY_NO_HOSTS_FILE标识是否使用本地host文件,在实际产品中,可以使用检测条件(比如环境变量)来决定是否使用本地host文件,DNS_QUERY_BYPASS_CACHE标识是否使用本地缓存,因为windows系统会缓存查询过的域名,可以通过ipconfig /displaydns来查看所有已经缓存的记录;第4个参数为PIP4_ARRAY数组,可以通过下面方式初始化
bool CDNSHelper::Init(const char** dns_array, unsigned int length){if (length < 1){return false;}pSrvList_ = static_cast<PIP4_ARRAY>(::LocalAlloc(LPTR, sizeof(IP4_ARRAY) + sizeof(IP4_ADDRESS) * (length-1)));pSrvList_->AddrCount = length;for (unsigned int index = 0; index < length; ++index){pSrvList_->AddrArray[index] = ::inet_addr(dns_array[index]);}HINSTANCE hNtdll = ::LoadLibrary(L"Ntdll.dll");if (!hNtdll){return false;}return true;}
AddrArray字段为C语言常见的可变数组用法,不明白的可以自行Google。
如果查询的目标域名有CDN服务器,那么DnsQuery查询的结果就是一个链表,与实际CDN跳转顺序一致,所以需要遍历这个链表,找到Type为DNS_TYPE_A的节点,才是最终目标。
经过上面的步骤,已经拿到了真实IP,但是应用程序在访问域名时,并不会主动使用这个IP,所以我们接下来需要让上层程序使用我们已经查询到的IP,以IE控件为例,当你调用Navigate进行URL导航时,浏览器会按照这样的顺序来查找IP地址
因此我们要做的就是把IP更新到本地缓存,这样就可以让应用程序无缝接入我们的防劫持逻辑,遗憾的是,暂时还没有找到方式,在不提权的情况下更新本地缓存,所以采用Hook的方式,最终Hook了GetAddrInfoW函数,直接返回对应的IP,不在进行查询。
int WSAAPI MyGetAddrInfoExW(_In_opt_ PCTSTR pName,_In_opt_ PCTSTR pServiceName,_In_ DWORD dwNameSpace,_In_opt_ LPGUID lpNspId,_In_opt_ const ADDRINFOEX *pHints,_Out_ PADDRINFOEX *ppResult,_In_opt_ struct timeval *timeout,_In_opt_ LPOVERLAPPED lpOverlapped,_In_opt_ LPLOOKUPSERVICE_COMPLETION_ROUTINE lpCompletionRoutine,_Out_opt_ LPHANDLE lpNameHandle){int result = NO_ERROR;std::map<std::wstring, std::pair<std::wstring, unsigned long> >::iterator itFind = gDnsCache_->find(pName);if (itFind != gDnsCache_->end()&& NS_DNS == dwNameSpace){//const wchar_t* ai_canonname = L"optonly1st.webcache.ourwebpic.com";PADDRINFOEX pResult = (PADDRINFOEX)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(ADDRINFOEX));pResult->ai_family = AF_INET;pResult->ai_socktype = pHints->ai_socktype;pResult->ai_protocol = pHints->ai_protocol;pResult->ai_flags = 0; // 1 << 31;const size_t canonname_len = itFind->second.first.size() + 1;pResult->ai_canonname = (PWSTR)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, canonname_len * sizeof(wchar_t));::wcscpy_s(pResult->ai_canonname, canonname_len, itFind->second.first.c_str());pResult->ai_addrlen = sizeof(SOCKADDR_IN);pResult->ai_addr = (sockaddr*)::HeapAlloc(::GetProcessHeap(), HEAP_ZERO_MEMORY, pResult->ai_addrlen);reinterpret_cast<PSOCKADDR_IN>(pResult->ai_addr)->sin_family = pResult->ai_family;reinterpret_cast<PSOCKADDR_IN>(pResult->ai_addr)->sin_port = 0;reinterpret_cast<PSOCKADDR_IN>(pResult->ai_addr)->sin_addr.S_un.S_addr = itFind->second.second;*ppResult = pResult;}else{result = oldGetAddrInfoExW(pName, pServiceName, dwNameSpace, lpNspId, pHints, ppResult, timeout, lpOverlapped, lpCompletionRoutine, lpNameHandle);}return result;}
GetAddrInfoW这个API就是获取ip地址的,被我们hook掉之后,如果应用程序查询的域名是已经缓存的,则填充完ADDRINFOEX这个结构体之后直接返回,否则调用原来的处理逻辑。
上面的代码并不能完全防止DNS劫持,还有两个待完成项:①使用自定义协议进行DNS查询,这就需要自建DNS服务器了;②更新系统的DNS缓存,而不使用hook的方式。