Wednesday, 9 May 2012

Forwarded DLL Exports and an Interesting Loader Behaviour

Before we begin this really doesn't have any security impact other than potential uses in obfuscation when doing static analysis. However it was relatively interesting to us, hence a quick post.

Forwarded Exports
As a really quick primer Windows DLLs can support forwarded exports. Example:

C:\>dumpbin /exports c:\windows\system32\shimeng.dll
Microsoft (R) COFF/PE Dumper Version 10.00.40219.01
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file c:\windows\system32\shimeng.dll

File Type: DLL

  Section contains the following exports for ShimEng.dll

    00000000 characteristics
    4A5BC16F time date stamp Tue Jul 14 00:21:19 2009
        0.00 version
           1 ordinal base
          11 number of functions
          11 number of names

    ordinal hint RVA      name

          1    0          SE_DllLoaded (forwarded to APPHELP.SE_DllLoaded)
          2    1          SE_DllUnloaded (forwarded to APPHELP.SE_DllUnloaded)
          3    2          SE_DynamicShim (forwarded to APPHELP.SE_DynamicShim)
          4    3          SE_GetHookAPIs (forwarded to APPHELP.SE_GetHookAPIs)
          5    4          SE_GetMaxShimCount (forwarded to APPHELP.SE_GetMaxShim

Interesting Loader Behaviour
We were playing around with the pragma to define these forwarded exports i.e.:
#pragma comment(linker, "/export:ExportedFunction=SomeRandomDLL.Export") 

If we then look at the exports from the file we see:
1    1          ExportedFunction (forwarded to SomeRandomDLL.Export)

All well and good. So while playing around with malformed DLL names:
#pragma comment(linker, "/export:SafeFunction=..\\..\\..\\..\\Exe-N-DLL.Export") 

Which looks OK with dumpbin:
1    1          SafeFunction (forwarded to ..\..\..\..\Exe-N-DLL.Export)

We noticed an interesting loader behaviour (note this was observed in WinDbg after turning on 'Show loader snaps' in 'Global Flags' (gflags.exe):
1fa48:245e0 @ -2023778443 - LdrGetProcedureAddressEx - INFO: Locating procedure "Export" by name
1fa48:245e0 @ -2023778443 - LdrGetProcedureAddressEx - INFO: Locating procedure "SafeFunction" by name
1fa48:245e0 @ -2023778443 - LdrpLoadDll - ENTER: DLL name:  DLL path: 
1fa48:245e0 @ -2023778443 - LdrpFindOrMapDll - ENTER: DLL name: .DLL DLL path: 
1fa48:245e0 @ -2023778443 - LdrpFindKnownDll - ENTER: DLL name: .DLL
1fa48:245e0 @ -2023778443 - LdrpFindKnownDll - RETURN: Status: 0xc0000135
1fa48:245e0 @ -2023778427 - LdrpSearchPath - ENTER: DLL name: .DLL DLL path: 
1fa48:245e0 @ -2023778427 - LdrpResolveFileName - ENTER: DLL name: C:\Data\Recx.Code\OllieSlop\Exe-n-DLL\Debug\.DLL
1fa48:245e0 @ -2023778427 - LdrpResolveFileName - RETURN: Status: 0x00000000
1fa48:245e0 @ -2023778427 - LdrpResolveDllName - ENTER: DLL name: C:\Data\Recx.Code\OllieSlop\Exe-n-DLL\Debug\.DLL
1fa48:245e0 @ -2023778427 - LdrpResolveDllName - RETURN: Status: 0x00000000
1fa48:245e0 @ -2023778427 - LdrpSearchPath - RETURN: Status: 0x00000000
1fa48:245e0 @ -2023778427 - LdrpMapViewOfSection - ENTER: DLL name: C:\Data\Recx.Code\OllieSlop\Exe-n-DLL\Debug\.DLL
ModLoad: 013b0000 013cb000   C:\Data\Recx.Code\OllieSlop\Exe-n-DLL\Debug\.DLL
1fa48:245e0 @ -2023778427 - LdrpMapViewOfSection - RETURN: Status: 0x00000000
1fa48:245e0 @ -2023778427 - LdrpFindOrMapDll - RETURN: Status: 0x00000000
1fa48:245e0 @ -2023778427 - LdrpLoadDll - RETURN: Status: 0x00000000
1fa48:245e0 @ -2023778427 - LdrGetProcedureAddressEx - INFO: Locating procedure ".\..\..\..\Exe-N-DLL.Export" by name
1fa48:245e0 @ -2023778427 - LdrpUnloadDll - INFO: Unmapping DLL "C:\Data\Recx.Code\OllieSlop\Exe-n-DLL\Debug\.DLL"

What we see in the above output is that:

  • It tries to load a DLL simply called .DLL
  • It then tries to resolve a function by the name of .\..\..\..\Exe-N-DLL.Export

If we compare this with a well formed pragma:
#pragma comment(linker, "/export:SafeFunction=MyDLL.Export") 

We then see the correct behaviour i.e.:
1c338:1dc18 @ -2022703643 - LdrGetProcedureAddressEx - INFO: Locating procedure "Export" by name

So the root cause looks like a simple a parsing error where dots exist in the exported function name.

Yes It Does Do What You Would Expect
No doubt by now some of you will be asking what happens if you have circular references i.e.:
#pragma comment(linker, "/export:Export2=MyDLL.SafeFunction") 
#pragma comment(linker, "/export:SafeFunction=MyDLL.Export2") 

... Yes the loader does blow up in LdrpLoadDll in NTDLL.DLL after a while...


No comments:

Post a Comment