I. Project background
Xueersi network school live classroom online installation program, is an independent application, to provide the installation function of the student side, in order to reduce the volume of the installation package, avoid the introduction of third-party network library, the use of the operating system WinInet network library. To optimize the network, improve the success rate of network connection, and avoid domain name hijacking caused by Local DNS, HttpDNS is used to resolve domain names.
Why use HttpDNS
HttpDNS has the following major advantages over traditional DNS:
1. Domain name anti-hijacking:
Domain name resolution requests are directly sent to the HttpDNS server using the Http (Https) protocol, bypassing carrier’s Local DNS and avoiding domain name hijacking.
2. Accurate scheduling:
Due to the diversity of carrier policies, the Local DNS resolution result may not be the nearest and optimal node. The HttpDNS can directly obtain the client IP address and obtain the most accurate resolution result based on the client IP address, enabling the client to access the nearest service node.
3. Real-time effect:
The domain name resolution with a low resolution delay in milliseconds is implemented in combination with on-end policies such as pre-resolution of hotspot domain names, cached DNS resolution results, and lazy update of resolution results.
HttpDNS implementation scheme
There are two common ways to use HttpDNS:
If the domain name is an IP address, then the domain name is an IP address. If the domain name is an IP address, then the domain name is an IP address.
1. Virtual host problems
Starting with HTTP /1.1, the header supports the Host field, which is used to access virtual hosts. An appropriate Host must be configured in the HTTP request header to correctly access the desired service. By default, the Host field is the domain name in the request address. If the requested domain name is directly replaced with an IP address, the corresponding service cannot be accessed correctly. Therefore, the network library must support the user-defined Host field. WinInet is a Windows system library and does not support changing the Host field. Therefore, you cannot simply replace the domain name with the resolved IP address to initiate a request. In the HTTPS protocol, virtual hosts also cause SNI problems, that is, during the TLS handshake phase, appropriate Host information needs to be specified to ensure that the server can return the correct certificate. Otherwise, the SSL handshake fails.
2. Https certificate verification is incorrect
Another problem with replacing a domain name with an IP address is certificate verification during an SSL/TLS handshake. The main cause is that the server certificate is inconsistent with the peer name of the client. A simple solution would be to ignore SSL certificate validation failures, but this would make the HTTPS request an insecure one.
Solution 2: If a third-party network library provides a callback for domain name resolution, you can customize domain name resolution or implement HttpDNS. This paper adopts this scheme, using the Windows API Hook mechanism, the domain name resolution GetAddrInfoEx interface Hook, to achieve custom DNS resolution, failure to go to the default DNS resolution.
Common network libraries provide solutions:
-
Qt5Network library: For example, in QT 5.15, connectToHostEncrypted interface, which provides the peer name parameter to realize the peer name needed to be verified in SSL handshake phase to solve the problem of certificate authentication domain name mismatch;
-
Libcurl: curl_easy_setopt CURLOPT_RESOLVE provides custom hostname to IP address resolution, that is, custom domain name resolution.
The solution of this paper:
Because our project needs to use the WinInet network library of Windows system only, this library does not support modifying the Host header, nor does it provide the callback of domain name resolution. If we Hook these apis to implement the HttpDNS resolution process, if it fails, then go to the default domain name resolution process, This will implement the HttpDNS function.
4. Principle and implementation of Windows Hook
1. Classification of HOOK
Hooks are divided into application layer (Ring3) Hook and kernel layer (Ring0) Hook. Application layer Hook is applicable to x86 and X64, while kernel layer Hook is generally only applicable to x86 platform. Patch Guard technology introduced with Windows Vista version 64 greatly limits the use of Windows X64 kernel hooks. In this project, we use Inline Hook, which is implemented by Microsoft Detours library. As shown in Figure 1:
FIG. 1
2. Technical principle of Inline Hook
Inline hooks directly modify the code of any function in memory, hijacking it to the Hook API. It can be used more widely because it can Hook any function that is in memory.
The ZwQuerySystemInformation() code is normal when the procexp. Exe process calls the ZwQuerySystemInformation() function. Figure 3 shows the state after Hook. The first 5 bytes of the ZwQuerySystemInformation() function have been modified to JMP 0x10001120, the address of our injection code, before we can start our custom operation. 0x1000116A We unhook ZwQuerySystemInformation() to restore the code. The purpose of a Hook is to hijack the flow of execution of a process when a function is called. Now that we have hijacked the process’s execution flow, we can restore the ZwQuerySystemInformation() code so that our injected code can call ZwQuerySystemInformation() normally. After executing the injection code, hook again to monitor the function.
Figure 2
FIG. 3
3. Code implementation of Inline Hook
// Code Hook function BOOL hookByCode(LPCWSTR szDllName,LPCSTR szFuncName,PROC pfnNew,PBYTE pOrgBytes) {FARPROC pfnOrg {0}; DWORD dwOldProtect{ 0 }; DWORD dwAddress { 0 }; BYTE pBuf[5] {0xE9,0,}; PBYTE pByte { nullptr }; / / get NTDLL. ZwQuerySystemInformation function address pfnOrg = (FARPROC) GetProcAddress call (GetModuleHandle (szDllName), szFuncName); pByte = (PBYTE)pfnOrg; If (pByte[0] == 0xE9) {return FALSE; } // To modify 5 bytes, add the write property VirtualProtect((LPVOID)pfnOrg, 5, PAGE_EXECUTE_READWRITE, &dwOldProtect) to memory; // Back up the original code (5 bytes) memcpy(pOrgBytes, pfnOrg, 5); DwAddress = (DWORD)pfnNew - (DWORD) pfnorg-5; memcpy(&pBuf[1], &dwAddress, 4); // "hook" : modify 5 bytes (JMP XXXXXXXX) memcpy(pfnOrg, pBuf, 5); // Restore memory properties VirtualProtect((LPVOID)pfnOrg, 5, dwOldProtect, &dwOldProtect); return TRUE; }Copy the code
First, obtain the API address and save it in pfnOrg, then modify the memory segment attribute to RWX, back up the original code (the subsequent code has been restored), calculate the relative offset of JMP in real time, and finally modify the first 5 bytes of API code to restore the memory attribute.
4. Use detours library to implement Hook
Detours library is a widely used library for API Hook provided by Microsoft. It encapsulates the implementation details of Hook and is very convenient to use. Such as: Old_GetAddrInfoEx; New_GetAddrInfoEx; Old_GetAddrInfoEx; The application calls StartHook/StopHook to Hook the corresponding API when appropriate.
INT (WSAAPI* Old_GetAddrInfoEx)( __in_opt PCWSTR pName, __in_opt PCWSTR pServiceName, __in DWORD dwNameSpace, __in_opt LPGUID lpNspId, __in_opt const ADDRINFOEX* hints, __deref_out PADDRINFOEXW* ppResult, __in_opt struct timeval* timeout, __in_opt LPOVERLAPPED lpOverlapped, __in_opt LPLOOKUPSERVICE_COMPLETION_ROUTINE lpCompletionRoutine, __out_opt LPHANDLE lpHandle) = GetAddrInfoEx; INT WSAAPI New_GetAddrInfoEx( __in_opt PCWSTR pName, __in_opt PCWSTR pServiceName, __in DWORD dwNameSpace, __in_opt LPGUID lpNspId, __in_opt const ADDRINFOEX* hints, __deref_out PADDRINFOEXW* ppResult, __in_opt struct timeval* timeout, __in_opt LPOVERLAPPED lpOverlapped, __in_opt LPLOOKUPSERVICE_COMPLETION_ROUTINE lpCompletionRoutine, __out_opt LPHANDLE LPHANDLE) { // If the custom resolution fails, Return Old_GetAddrInfoEx(pName, pServiceName, dwNameSpace, lpNspId, Hints, ppResult, timeout, lpOverlapped, lpCompletionRoutine, lpHandle); } bool StartHook() { DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourAttach(&(PVOID&)Old_GetAddrInfoEx, New_GetAddrInfoEx); LONG ret = DetourTransactionCommit(); return ret == NO_ERROR; } bool StopHook() { DetourTransactionBegin(); DetourUpdateThread(GetCurrentThread()); DetourDetach(&(PVOID&)Old_GetAddrInfoEx, New_GetAddrInfoEx); LONG ret = DetourTransactionCommit(); return ret == NO_ERROR; }Copy the code
Hook process
The general process of WinInet network request is shown in Figure 4 below. When the HttpSendRequest request is sent, the domain name resolution function GetAddrInfoEx is called to complete domain name resolution. Hook GetAddrInfoEx function during domain name resolution. The WinInet network request process after Hook is shown in Figure 5 below. After the Hook domain name resolution function GetAddrInfoEx succeeds, the original domain name resolution function GetAddrInfoEx will not be called, but the custom domain name resolution function will be called. If the custom resolution function fails, there is a backstop strategy, and the original resolution function GetAddrInfoEx is also called. The following is the custom domain name resolution function New_GetAddrInfoEx.
FIG. 4
Figure 5
The custom domain name resolution function is as follows:
Static void my_addressInfo_alloc (__in_opt PCWSTR pServiceName, __in DWORD dwNameSpace, __in_opt LPGUID lpNspId, __in_opt const ADDRINFOEX* hints, __deref_out PADDRINFOEXW* ppResult, __in_opt struct timeval* timeout, __in_opt LPOVERLAPPED lpOverlapped, __in_opt LPLOOKUPSERVICE_COMPLETION_ROUTINE lpCompletionRoutine, __out_opt LPHANDLE lpHandle) { ADDRINFOEX my_hints = *hints; my_hints.ai_family = AF_INET; my_hints.ai_flags ^= (AI_CANONNAME | AI_FQDN); Old_GetAddrInfoEx(L"localhost", pServiceName, dwNameSpace, lpNspId, &my_hints, ppResult, timeout, lpOverlapped, lpCompletionRoutine, lpHandle); } INT WSAAPI New_GetAddrInfoEx( __in_opt PCWSTR pName, __in_opt PCWSTR pServiceName, __in DWORD dwNameSpace, __in_opt LPGUID lpNspId, __in_opt const ADDRINFOEX* hints, __deref_out PADDRINFOEXW* ppResult, __in_opt struct timeval* timeout, __in_opt LPOVERLAPPED lpOverlapped, __in_opt LPLOOKUPSERVICE_COMPLETION_ROUTINE lpCompletionRoutine, __out_opt LPHANDLE lpHandle) { do { struct in_addr addr; / / IP and localhost don't need HTTPDNS if (pName = = nullptr | | hints = = nullptr | | InetPtonW (AF_INET, pName, (void*)&addr) || wcscmp(pName, L"localhost") == 0) { break; HttpDNS::IpList IpList = HttpDNS::instance()->getHostByName(pName); if (ipList.size() == 0) { break; } // The new object cannot be freed properly due to the memory allocated on the private heap when GetAddrInfoEx is called // blog: http://www.youngroe.com/2018/12/01/Windows/windows_client_dns_over_https/ ADDRINFOEX* pTarget = nullptr; For (auto& IP: ipList) {// Allocate ADDRINFOEX* pTemp = NULlptr; my_addressinfo_alloc(pServiceName, dwNameSpace, lpNspId, hints, &pTemp, timeout, lpOverlapped, lpCompletionRoutine, lpHandle); if (pTemp == nullptr) { continue; } if (*ppResult == nullptr) { *ppResult = pTemp; pTarget = *ppResult; } else { assert(pTarget); pTarget->ai_next = pTemp; pTarget = pTarget->ai_next; } std::string ipa = CStringUtil::wstring2string(ip); struct sockaddr_in* mysock = (struct sockaddr_in*)pTemp->ai_addr; mysock->sin_addr.S_un.S_addr = inet_addr(ipa.c_str()); } if (*ppResult == nullptr) { break; } return NO_ERROR; } while (false); return Old_GetAddrInfoEx(pName, pServiceName, dwNameSpace, lpNspId, hints, ppResult, timeout, lpOverlapped, lpCompletionRoutine, lpHandle); }Copy the code
There was a small problem with the implementation of the Hook GetAddrInfoEx function. The addrinfoexW memory allocation in the result returned by GetAddrInfoEx was not correct. Normally, the addrinfoexW in the returned result is allocated by GetAddrInfoEx on its private heap, and then freed by FreeAddrInfoEx after the caller uses the result. However, when we implement it ourselves, it is difficult to get the private heap handle. There is no way to allocate memory for addrinfoexW. Using new to allocate memory will cause problems when FreeAddrInfoEx is freed. A simple and crude way to do this is to parse localHost by calling the original GetAddrInfoEx and then directly use the addrinfoexW in the result. Since GetAddrInfoEx is assigned, FreeAddrInfoEx is also free.
Attention issues:
-
We only implemented HttpDNS for IPv4
-
Filter out parses such as localhost
-
Filter out non-domain class resolution
Six, summarized
-
Advantages: Using API Hook technology WinInet network library HttpDNS can play the role of degradation, save a whole process of DNS resolution; Using this technique, the business layer is completely transparent and does not require any changes to the business layer code.
-
Disadvantages: Hook GetAddrInfoEx adds redundant localhost parsing, which has a certain performance impact.
7. References
-
HTTPS (including SNI) Service scenario Direct IP connection solution description
-
HTTPS IP direct connection problem summary
-
How do I use DNS-over-HTTPS transparently on a Windows client
-
The TLS SNI problem
-
Microsoft official API HOOK library
-
Principle and implementation of Windows Hook
Author: xinjian CAI, senior c++ client engineer of good future
For more information about good future technology, please: wechat scan code follow ** “Good future Technology” ** wechat public number