Hooking rundll32.exe

Ask your beginner questions here.
Post Reply
User avatar
snx90
Posts: 2
Joined: Sun Jul 31, 2016 7:41 am
Location: Nomad

Tue Aug 30, 2016 2:44 pm

Hi all,

I am writing a DLL that hooks rundll32.exe's import table at runtime to intercept whichever LoadLibrary API variant it uses to load the DLL specified on the command line (the "target DLL"). Whenever the target DLL is loaded, the hook DLL then patches the target DLL's export table to add a new entry, named "FakedFunc", that is a function that displays a message box and returns. This means that whenever I run "rundll32.exe ___.dll,FakedFunc" (where ___.dll is any valid DLL name, e.g. kernel32), I should see a message box instead of the usual error "Missing entry: FakedFunc". I have implemented the IAT hook to intercept the LoadLibrary, and now I was wondering which is the best approach to patch the target DLL's export table on-the-fly. Any suggestion?

Thanks in advance.
User avatar
Vrtule
Posts: 465
Joined: Sat Mar 13, 2010 9:14 pm
Location: Czech Republic
Contact:

Tue Aug 30, 2016 5:17 pm

WOuldn't it be better to make an IAT hook on GetProcecAddress (I assume rundll uses this routine to find the target function) and change the result of the original call when string "FakeFunc" is entered?
User avatar
snx90
Posts: 2
Joined: Sun Jul 31, 2016 7:41 am
Location: Nomad

Tue Aug 30, 2016 5:24 pm

You might be right, but my goal is to learn and try different methods. So if possible, I would like to follow the approach described in my original post.

Thanks!
Munsta
Posts: 21
Joined: Tue Sep 30, 2014 6:52 pm

Fri Sep 16, 2016 2:30 pm

Vrtule wrote:WOuldn't it be better to make an IAT hook on GetProcecAddress (I assume rundll uses this routine to find the target function) and change the result of the original call when string "FakeFunc" is entered?
Apparently snx90 want's to call his dll with random rundll32 argument. FakeFunc is non-existing EXPORT and to be able to do so he needs to place code in DllMain() that will patch rundll32.exe memory, probably as you suggested GetProcAddress() to keep full functionality and avoid DLLHell. Target dll needs to have at least 1 real Export in order for this to work. Hooking of rundll32.exe was also in Conficker.C used for its .vmx files which were Dlls actually. Here is the source code of rundll32 that's cloned for ReactOS and has the "same" functionality:

Code: Select all

/*
 * ReactOS rundll32
 * Copyright (C) 2003-2004 ReactOS Team
 *
 * COPYRIGHT:       See COPYING in the top level directory
 * PROJECT:         ReactOS rundll32.exe
 * FILE:            base/system/rundll32/rundll32.c
 * PURPOSE:         Run a DLL as a program
 * PROGRAMMER:      ShadowFlare (blakflare@hotmail.com)
 */

// Both UNICODE and _UNICODE must be either defined or undefined
// because some headers use UNICODE and others use _UNICODE
#ifdef UNICODE
#ifndef _UNICODE
#define _UNICODE
#endif
#else
#ifdef _UNICODE
#define UNICODE
#endif
#endif

#define WIN32_NO_STATUS
#include <stdarg.h>
#include <stdlib.h>
#include <windef.h>
#include <winbase.h>
#include <winnls.h>
#include <winuser.h>
#include <tchar.h>

#include "resource.h"

typedef int (WINAPI *DllWinMainW)(
  HWND hWnd,
  HINSTANCE hInstance,
  LPWSTR lpwCmdLine,
  int nCmdShow
);
typedef int (WINAPI *DllWinMainA)(
  HWND hWnd,
  HINSTANCE hInstance,
  LPSTR lpCmdLine,
  int nCmdShow
);

/*
LPCTSTR DllNotLoaded = _T("LoadLibrary failed to load \"%s\"");
LPCTSTR MissingEntry = _T("Missing entry point:%s\nIn %s");
*/
LPCTSTR rundll32_wtitle = _T("rundll32");
LPCTSTR rundll32_wclass = _T("rundll32_window");

TCHAR ModuleFileName[MAX_PATH+1];
LPTSTR ModuleTitle;


// CommandLineToArgv converts a command-line string to argc and
// argv similar to the ones in the standard main function.
// This is a specialized version coded specifically for rundll32
// and is not intended to be used in any other program.
LPTSTR *WINAPI CommandLineToArgv(LPCTSTR lpCmdLine, int *lpArgc)
{
    LPTSTR *argv, lpSrc, lpDest, lpArg;
    int argc, nBSlash, nNames;
    BOOL bInQuotes, bFirstChar;

    // If null was passed in for lpCmdLine, there are no arguments
    if (!lpCmdLine) {
        if (lpArgc)
            *lpArgc = 0;
        return 0;
    }

    lpSrc = (LPTSTR)lpCmdLine;
    // Skip spaces at beginning
    while (*lpSrc == _T(' ') || *lpSrc == _T('\t'))
        lpSrc++;

    // If command-line starts with null, there are no arguments
    if (*lpSrc == 0) {
        if (lpArgc)
            *lpArgc = 0;
        return 0;
    }

    lpArg = lpSrc;
    argc = 0;
    nBSlash = 0;
    bInQuotes = FALSE;
    bFirstChar = TRUE;
    nNames = 0;

    // Count the number of arguments
    while (nNames < 4) {
        if (*lpSrc == 0 || (*lpSrc == _T(',') && nNames == 2) || ((*lpSrc == _T(' ') || *lpSrc == _T('\t')) && !bInQuotes)) {
            // Whitespace not enclosed in quotes signals the start of another argument
            argc++;

            // Skip whitespace between arguments
            while (*lpSrc == _T(' ') || *lpSrc == _T('\t') || (*lpSrc == _T(',') && nNames == 2))
                lpSrc++;
            if (*lpSrc == 0)
                break;
            if (nNames >= 3) {
                // Increment the count for the last argument
                argc++;
                break;
            }
            nBSlash = 0;
            bFirstChar = TRUE;
            continue;
        }
        else if (*lpSrc == _T('\\')) {
            // Count consecutive backslashes
            nBSlash++;
            bFirstChar = FALSE;
        }
        else if (*lpSrc == _T('\"') && !(nBSlash & 1)) {
            // Open or close quotes
            bInQuotes = !bInQuotes;
            nBSlash = 0;
        }
        else {
            // Some other character
            nBSlash = 0;
            if (bFirstChar && ((*lpSrc != _T('/') && nNames <= 1) || nNames > 1))
                nNames++;
            bFirstChar = FALSE;
        }
        lpSrc++;
    }

    // Allocate space for the pointers in argv and the strings in one block
    argv = (LPTSTR *)malloc(argc * sizeof(LPTSTR) + (_tcslen(lpArg) + 1) * sizeof(TCHAR));

    if (!argv) {
        // Memory allocation failed
        if (lpArgc)
            *lpArgc = 0;
        return 0;
    }

    lpSrc = lpArg;
    lpDest = lpArg = (LPTSTR)(argv + argc);
    argc = 0;
    nBSlash = 0;
    bInQuotes = FALSE;
    bFirstChar = TRUE;
    nNames = 0;

    // Fill the argument array
    while (nNames < 4) {
        if (*lpSrc == 0 || (*lpSrc == _T(',') && nNames == 2) || ((*lpSrc == _T(' ') || *lpSrc == _T('\t')) && !bInQuotes)) {
            // Whitespace not enclosed in quotes signals the start of another argument
            // Null-terminate argument
            *lpDest++ = 0;
            argv[argc++] = lpArg;

            // Skip whitespace between arguments
            while (*lpSrc == _T(' ') || *lpSrc == _T('\t') || (*lpSrc == _T(',') && nNames == 2))
                lpSrc++;
            if (*lpSrc == 0)
                break;
            lpArg = lpDest;
            if (nNames >= 3) {
                // Copy the rest of the command-line to the last argument
                argv[argc++] = lpArg;
                _tcscpy(lpArg,lpSrc);
                break;
            }
            nBSlash = 0;
            bFirstChar = TRUE;
            continue;
        }
        else if (*lpSrc == _T('\\')) {
            *lpDest++ = _T('\\');
            lpSrc++;

            // Count consecutive backslashes
            nBSlash++;
            bFirstChar = FALSE;
        }
        else if (*lpSrc == _T('\"')) {
            if (!(nBSlash & 1)) {
                // If an even number of backslashes are before the quotes,
                // the quotes don't go in the output
                lpDest -= nBSlash / 2;
                bInQuotes = !bInQuotes;
            }
            else {
                // If an odd number of backslashes are before the quotes,
                // output a quote
                lpDest -= (nBSlash + 1) / 2;
                *lpDest++ = _T('\"');
                bFirstChar = FALSE;
            }
            lpSrc++;
            nBSlash = 0;
        }
        else {
            // Copy other characters
            if (bFirstChar && ((*lpSrc != _T('/') && nNames <= 1) || nNames > 1))
                nNames++;
            *lpDest++ = *lpSrc++;
            nBSlash = 0;
            bFirstChar = FALSE;
        }
    }

    if (lpArgc)
        *lpArgc = argc;
    return argv;
}

void GetModuleTitle(void)
{
    LPTSTR lpStr;

    GetModuleFileName(0,ModuleFileName,MAX_PATH);
    ModuleTitle = ModuleFileName;

    for (lpStr = ModuleFileName;*lpStr;lpStr++) {
        if (*lpStr == _T('\\'))
            ModuleTitle = lpStr+1;
    }

    for (lpStr = ModuleTitle;*lpStr;lpStr++) {
        if (_tcsicmp(lpStr,_T(".exe"))==0)
            break;
    }

    *lpStr = 0;
}

// The macro ConvertToWideChar takes a tstring parameter and returns
// a pointer to a unicode string.  A conversion is performed if
// neccessary.  FreeConvertedWideChar string should be used on the
// return value of ConvertToWideChar when the string is no longer
// needed.  The original string or the string that is returned
// should not be modified until FreeConvertedWideChar has been called.
#ifdef UNICODE
#define ConvertToWideChar(lptString) (lptString)
#define FreeConvertedWideChar(lpwString)
#else

LPWSTR ConvertToWideChar(LPCSTR lpString)
{
    LPWSTR lpwString;
    size_t nStrLen;

    nStrLen = strlen(lpString) + 1;

    lpwString = (LPWSTR)malloc(nStrLen * sizeof(WCHAR));
    MultiByteToWideChar(0,0,lpString,nStrLen,lpwString,nStrLen);

    return lpwString;
}

#define FreeConvertedWideChar(lpwString) free(lpwString)
#endif

// The macro ConvertToMultiByte takes a tstring parameter and returns
// a pointer to an ansi string.  A conversion is performed if
// neccessary.  FreeConvertedMultiByte string should be used on the
// return value of ConvertToMultiByte when the string is no longer
// needed.  The original string or the string that is returned
// should not be modified until FreeConvertedMultiByte has been called.
#ifdef UNICODE
#define ConvertToMultiByte(lptString) DuplicateToMultiByte(lptString,0)
#define FreeConvertedMultiByte(lpaString) free(lpaString)
#else
#define ConvertToMultiByte(lptString) (lptString)
#define FreeConvertedMultiByte(lpaString)
#endif

// DuplicateToMultiByte takes a tstring parameter and always returns
// a pointer to a duplicate ansi string.  If nBufferSize is zero,
// the buffer length is the exact size of the string plus the
// terminating null.  If nBufferSize is nonzero, the buffer length
// is equal to nBufferSize.  As with strdup, free should be called
// for the returned string when it is no longer needed.
LPSTR DuplicateToMultiByte(LPCTSTR lptString, size_t nBufferSize)
{
    LPSTR lpString;
    size_t nStrLen;

    nStrLen = _tcslen(lptString) + 1;
    if (nBufferSize == 0) nBufferSize = nStrLen;

    lpString = (LPSTR)malloc(nBufferSize);
#ifdef UNICODE
    WideCharToMultiByte(0,0,lptString,nStrLen,lpString,nBufferSize,0,0);
#else
    strncpy(lpString,lptString,nBufferSize);
#endif

    return lpString;
}

LRESULT CALLBACK EmptyWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}

// Registers a minimal window class for passing to the dll function
BOOL RegisterBlankClass(HINSTANCE hInstance, HINSTANCE hPrevInstance)
{
    WNDCLASSEX wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style         = 0;
    wcex.lpfnWndProc   = EmptyWindowProc;
    wcex.cbClsExtra    = 0;
    wcex.cbWndExtra    = 0;
    wcex.hInstance     = hInstance;
    wcex.hIcon         = 0;
    wcex.hCursor       = 0;
    wcex.hbrBackground = 0;
    wcex.lpszMenuName  = 0;
    wcex.lpszClassName = rundll32_wclass;
    wcex.hIconSm       = 0;

    return (RegisterClassEx(&wcex) != (ATOM)0);
}

int WINAPI _tWinMain(
  HINSTANCE hInstance,
  HINSTANCE hPrevInstance,
  LPTSTR lpCmdLine,
  int nCmdShow
)
{
    int argc;
    TCHAR szMsg[RC_STRING_MAX_SIZE];

    LPTSTR *argv;
    LPTSTR lptCmdLine,lptDllName,lptFuncName,lptMsgBuffer;
    LPSTR lpFuncName,lpaCmdLine;
    LPWSTR lpwCmdLine;
    HMODULE hDll;
    DllWinMainW fnDllWinMainW;
    DllWinMainA fnDllWinMainA;
    HWND hWindow;
    int i;
    size_t nStrLen;

    // Get command-line in argc-argv format
    argv = CommandLineToArgv(GetCommandLine(),&argc);

    // Skip all beginning arguments starting with a slash (/)
    for (i = 1; i < argc; i++)
        if (*argv[i] != _T('/')) break;

    // If no dll was specified, there is nothing to do
    if (i >= argc) {
        if (argv) free(argv);
        return 0;
    }

    lptDllName = argv[i++];

    // The next argument, which specifies the name of the dll function,
    // can either have a comma between it and the dll filename or a space.
    // Using a comma here is the preferred method
    if (i < argc)
        lptFuncName = argv[i++];
    else
        lptFuncName = _T("");

    // If no function name was specified, nothing needs to be done
    if (!*lptFuncName) {
        if (argv) free(argv);
        return 0;
    }

    // The rest of the arguments will be passed to dll function
    if (i < argc)
        lptCmdLine = argv[i];
    else
        lptCmdLine = _T("");

    // Everything is all setup, so load the dll now
    hDll = LoadLibrary(lptDllName);
    if (hDll) {
        nStrLen = _tcslen(lptFuncName);
        // Make a non-unicode version of the function name,
        // since that is all GetProcAddress accepts
        lpFuncName = DuplicateToMultiByte(lptFuncName,nStrLen + 2);

#ifdef UNICODE
        lpFuncName[nStrLen] = 'W';
        lpFuncName[nStrLen+1] = 0;
        // Get address of unicode version of the dll function if it exists
        fnDllWinMainW = (DllWinMainW)GetProcAddress(hDll,lpFuncName);
        fnDllWinMainA = 0;
        if (!fnDllWinMainW) {
            // If no unicode function was found, get the address of the non-unicode function
            lpFuncName[nStrLen] = 'A';
            fnDllWinMainA = (DllWinMainA)GetProcAddress(hDll,lpFuncName);
            if (!fnDllWinMainA) {
                // If first non-unicode function was not found, get the address
                // of the other non-unicode function
                lpFuncName[nStrLen] = 0;
                fnDllWinMainA = (DllWinMainA)GetProcAddress(hDll,lpFuncName);
            }
        }
#else
        // Get address of non-unicode version of the dll function if it exists
        fnDllWinMainA = (DllWinMainA)GetProcAddress(hDll,lpFuncName);
        fnDllWinMainW = 0;
        if (!fnDllWinMainA) {
            // If first non-unicode function was not found, get the address
            // of the other non-unicode function
            lpFuncName[nStrLen] = 'A';
            lpFuncName[nStrLen+1] = 0;
            fnDllWinMainA = (DllWinMainA)GetProcAddress(hDll,lpFuncName);
            if (!fnDllWinMainA) {
                // If non-unicode function was not found, get the address of the unicode function
                lpFuncName[nStrLen] = 'W';
                fnDllWinMainW = (DllWinMainW)GetProcAddress(hDll,lpFuncName);
            }
        }
#endif

        free(lpFuncName);

        if (!RegisterBlankClass(hInstance, hPrevInstance))
        {
            FreeLibrary(hDll);
            return 0;
        }
        // Create a window so we can pass a window handle to
        // the dll function; this is required
        hWindow = CreateWindowEx(0,rundll32_wclass,rundll32_wtitle,0,CW_USEDEFAULT,0,CW_USEDEFAULT,0,0,0,hInstance,0);

        if (fnDllWinMainW) {
            // Convert the command-line string to unicode and call the dll function
            lpwCmdLine = ConvertToWideChar(lptCmdLine);
            fnDllWinMainW(hWindow,hInstance,lpwCmdLine,nCmdShow);
            FreeConvertedWideChar(lpwCmdLine);
        }
        else if (fnDllWinMainA) {
            // Convert the command-line string to ansi and call the dll function
            lpaCmdLine = ConvertToMultiByte(lptCmdLine);
            fnDllWinMainA(hWindow,hInstance,lpaCmdLine,nCmdShow);
            FreeConvertedMultiByte(lpaCmdLine);
        }
        else {
            // The specified dll function was not found; display an error message
            GetModuleTitle();
            LoadString( GetModuleHandle(NULL), IDS_MissingEntry, (LPTSTR) szMsg,RC_STRING_MAX_SIZE);

            lptMsgBuffer = (LPTSTR)malloc((_tcslen(szMsg) - 4 + _tcslen(lptFuncName) + _tcslen(lptDllName) + 1) * sizeof(TCHAR));
            _stprintf(lptMsgBuffer,szMsg,lptFuncName,lptDllName);
            MessageBox(0,lptMsgBuffer,ModuleTitle,MB_ICONERROR);
            free(lptMsgBuffer);
        }

        DestroyWindow(hWindow);
        UnregisterClass(rundll32_wclass,hInstance);

        // The dll function has finished executing, so unload it
        FreeLibrary(hDll);
    }
    else {
        // The dll could not be loaded; display an error message
        GetModuleTitle();
        LoadString( GetModuleHandle(NULL), IDS_DllNotLoaded, (LPTSTR) szMsg,RC_STRING_MAX_SIZE);

        lptMsgBuffer = (LPTSTR)malloc((_tcslen(szMsg) - 2 + _tcslen(lptDllName) + 1) * sizeof(TCHAR));
        _stprintf(lptMsgBuffer,szMsg,lptDllName);

        MessageBox(0,lptMsgBuffer,ModuleTitle,MB_ICONERROR);
        free(lptMsgBuffer);
    }

    if (argv) free(argv);
    return 0; /* rundll32 always returns 0! */
}
Post Reply