Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pinning a DLL in memory (increase reference count)

I am trying to run an application, but the application exits due to an access violation. Running the application in the debugger I can see that this is caused by an unloaded library. I can not wait for the next release of the application, so I'm trying to workaround the problem.

I wonder whether WinDbg provides a way of increasing the reference count of a loaded module, similar to the C++ LoadLibrary() call. I could then break on module loads and increase the reference count on the affected DLL to see if I can use the application then.

I have already looked for commands starting with .load, !load, .lock, !lock, .mod and !mod in WinDbg help. .load will load the DLL as an extension into the debugger process, not into the target process.

Update

Forgot to mention that I have no source code, so I can't simply implement a LoadLibrary() call as a workaround and recompile.

The comment by Hans Passant leads me to .call and I tried to use it like

.call /v kernel32!LoadLibraryA("....dll")

but it gives the error message

Symbol not a function in '.call /v kernel32!LoadLibraryA("....dll")'

Update 2

Probably the string for the file name in .call should be a pointer to some memory in the target process instead of a string which resides in WinDbg.exe where I type the command. That again means I would probably mean to allocate some memory to store the string inside, so this might become more complex.

like image 512
Thomas Weller Avatar asked Dec 15 '22 21:12

Thomas Weller


2 Answers

Using .call in windbg as always been finicky to me. I believe you are having trouble with it because kernel32 only has public symbols so the debugger doesn't know what it's arguments look like.

So let's look at some alternatives...

The easy way

You can go grab a tool like Process Hacker, which I think is a wonderful addition to any debugger's tool chest. It has an option to inject a DLL into a process.

Process Hacker Inject Dll

Behind the scenes, it calls CreateRemoteThread to spawn a thread in the target process which calls LoadLibrary on the chosen DLL. With any luck, this will increase the module reference count. You can verify that the LoadCount has been increased in windbg by running the !dlls command before and after the dll injection.

The hard way

You can also dig into the internal data structures Windows uses to keep track of a process's loaded modules and play with the LoadCount. This changes between versions of Windows and is a serious no-no. But, we're debugging, so, what the hell? Let's do this.

Start by getting a list of loaded modules with !dlls. Suppose we care about your.dll; we might see something like:

0x002772a8: C:\path\to\your.dll
      Base   0x06b80000  EntryPoint  0x06b81000  Size        0x000cb000    DdagNode     0x002b3a10
      Flags  0x800822cc  TlsIndex    0x00000000  LoadCount   0x00000001    NodeRefCount 0x00000001

We can see that the load count is currently 1. To modify it, we could use the address printed before the module path. It is the address of the the ntdll!_LDR_DATA_TABLE_ENTRY the process holds for that module.

r? @$t0 = (ntdll!_LDR_DATA_TABLE_ENTRY*) 0x002772a8

And, now you can change the LoadCount member to something larger as so:

?? @$t0->LoadCount = 2

But, as I said, this stuff changes with new versions of Windows. On Windows 8, the LoadCount member was moved out of _LDR_DATA_TABLE_ENTRY and into a new ntdll!_LDR_DDAG_NODE structure. In place of it, there is now an ObsoleteNodeCount which is not what we want.

On Windows 8, we would run the following command instead:

?? @$t0->DdagNode->LoadCount = 2

And, time to check our work...

0x002772a8: C:\path\to\your.dll
      Base   0x06b80000  EntryPoint  0x06b81000  Size        0x000cb000    DdagNode     0x002b3a10
      Flags  0x800822cc  TlsIndex    0x00000000  LoadCount   0x00000002    NodeRefCount 0x00000001

Awesome. It's 2 now. That'll teach FreeLibrary a lesson about unloading our DLLs before we say it can.

The takeaway

Try the easy way first. If that doesn't work, you can start looking at the internal data structures Windows uses to keep track of this kind of stuff. I don't provide the hard way hoping you'll actually try it, but that it might make you more comfortable around the !dlls command and those data structures in the future.

Still, all modifying the LoadCount will afford you is confirmation that you are seeing a DLL get unloaded before it should have. If the problem goes away after artificially increasing the LoadCount, meaning that you've confirmed your theory, you'll have to take a different approach to debugging it -- figuring out when and why it got unloaded.

like image 165
Sean Cline Avatar answered Jan 02 '23 02:01

Sean Cline


A dll that is linked while compiling will normally have a LoadCount of -1 (0xffff) and it is not Unloadable Via FreeLibrary

so you can utilize the loadModule Event to break on a Dynamically Loaded Module and increase the LoadCount during the Event

Blink of InLoadOrderModuleList (last dll Loaded in the process) when on initial break at ntdll!Dbgbreak() xp-sp3 for an arbitrary console app which uses a dll

0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY FullDllName LoadCount @@((( @$peb)->Ldr)->InLoadOrderModuleList.Blink)
   +0x024 FullDllName : _UNICODE_STRING "C:\WINDOWS\system32\GDI32.dll"
   +0x038 LoadCount   : 0xffff  <----------- not unloadable via FreeLibrary

setting up break on Specific Module Load

0:000> sxe ld skeleton
0:000> g
ModLoad: 10000000 10005000   C:\skeleton.dll
ntdll!KiFastSystemCallRet:
7c90e514 c3              ret

the LoadModule Breaks on MapSection so Ldr isnt yet updated

0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY FullDllName LoadCount @@((( @$peb)->Ldr)->InLoadOrderModuleList.Blink)
   +0x024 FullDllName : _UNICODE_STRING "C:\WINDOWS\system32\GDI32.dll"
   +0x038 LoadCount   : 0xffff

go up until the Ldr is updated

0:000> gu;gu;gu

ntdll!LdrpLoadDll+0x1e9:
7c91626a 8985c4fdffff    mov     dword ptr [ebp-23Ch],eax ss:0023:0013fa3c=00000000

blink showing the last loaded Module notice loadCount 0 not updated yet

0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY FullDllName LoadCount @@((( @$peb)->Ldr)->InLoadOrderModuleList.Blink)
   +0x024 FullDllName : _UNICODE_STRING "C:\skeleton.dll"
   +0x038 LoadCount   : 0

dump the LoadEntry of the module

0:000> !dlls -c skeleton
Dump dll containing 0x10000000:

**0x00252840:** C:\skeleton.dll
      Base   0x10000000  EntryPoint  0x10001000  Size        0x00005000
      Flags  0x00000004  LoadCount   0x00000000  TlsIndex    0x00000000
             LDRP_IMAGE_DLL

increase load count arbitrarily and redump (process attach hasnt been called yet)

0:000> ed 0x252840+0x38  4
0:000> !dlls -c skeleton
Dump dll containing 0x10000000:

0x00252840: C:\skeleton.dll
      Base   0x10000000  EntryPoint  0x10001000  Size        0x00005000
      Flags  0x00000004  LoadCount   0x00000004  TlsIndex    0x00000000
             LDRP_IMAGE_DLL

run the binary

0:000> g

dll is loaded into the process break with ctrl+break

Break-in sent, waiting 30 seconds... (aa0.77c): Break instruction exception - code 80000003 (first chance)

ntdll!DbgBreakPoint: 7c90120e cc int 3

dump and see system has updated the loadcount to our count+1 also process attach has been called

0:001> !dlls -c skeleton
Dump dll containing 0x10000000:

0x00252840: C:\skeleton.dll
      Base   0x10000000  EntryPoint  0x10001000  Size        0x00005000
      Flags  0x80084004  LoadCount   0x00000005  TlsIndex    0x00000000
             LDRP_IMAGE_DLL
             LDRP_ENTRY_PROCESSED
             LDRP_PROCESS_ATTACH_CALLED

btw use ken johnsons (skywing) sdbgext !remotecall instead of .call it doesnt require Private Symbols

.load sdbgext !remotecall kernel32!LoadLibraryA 0 "c:\skeleton.dll" ; g

should load the dll in the process

or use

    !loaddll "c:\\skeleton.dll" from the same extension     
kernel32!LoadLibraryA() will be run when execution is resumed
0:002> g
kernel32!LoadLibraryA() [conv=0 argc=4 argv=00AC0488]
kernel32!LoadLibraryA() returned 10000000
like image 41
blabb Avatar answered Jan 02 '23 02:01

blabb