3
votes

I have a legacy application, which contains a grid with data I need to extract.

I don't have the code for that application and it is impossible to get the data out of it with normal means (like programmatically selecting all cells and copying them into clipboard).

So I decided to use DLL injection as described in section "II. The CreateRemoteThread & LoadLibrary Technique" at

http://www.codeproject.com/Articles/4610/Three-Ways-to-Inject-Your-Code-into-Another-Proces

My plan is

  1. To load a DLL into the address space of the legacy application.
  2. Make the DLL read the data from the grid and write them out (e. g. via a named pipe).

The first step is to inject the DLL into the address space of the legacy application (step a) above).

I've written following code for that:

int  InjectDll            (HANDLE hProcess);

int _tmain(int argc, _TCHAR* argv[])
{
    printf("DllInjector\n");

    /**
     * Find out PID of the legacy application (START)
     */
    HWND windowHandle = FindWindowW(NULL, L"FORMSSSSS");
    DWORD* processID = new DWORD;
    GetWindowThreadProcessId(windowHandle, processID);

    DWORD delphiAppProcessId = *processID;
    /**
     * Find out PID of the legacy application (END)
     */

    printf("Process ID of legacy app: %lu\n", delphiAppProcessId);

    // Now we need the handle of the legacy app
    HANDLE hProcess = OpenProcess(
    PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
    FALSE, delphiAppProcessId);

    if (hProcess != NULL)
    {
        printf("Found handle, ready for injection\n");
        int result = InjectDll(hProcess);
        CloseHandle( hProcess );
        printf("Injection complete, result=%d\n", result);

    }
    else
    {
        printf("Handle not found\n");
    }

    system("pause");

    return 0;
}

int InjectDll( HANDLE hProcess )
{
    HANDLE hThread;
    const char* const szLibPath = "D:\\mycompany\\SampleDll\\Debug\\SampleDll.dll";
    void*  pLibRemote = 0;  // the address (in the remote process) where
                            // szLibPath will be copied to;
    DWORD  hLibModule = 0;  // base adress of loaded module (==HMODULE);

    HMODULE hKernel32 = ::GetModuleHandle(L"Kernel32");

    // 1. Allocate memory in the remote process for szLibPath
    // 2. Write szLibPath to the allocated memory
    pLibRemote = ::VirtualAllocEx( hProcess, NULL, sizeof(szLibPath), MEM_COMMIT, PAGE_READWRITE );
    if( pLibRemote == NULL )
        return false;
    ::WriteProcessMemory(hProcess, pLibRemote, (void*)szLibPath,sizeof(szLibPath),NULL);

    // Load "LibSpy.dll" into the remote process 
    // (via CreateRemoteThread & LoadLibrary)
    hThread = ::CreateRemoteThread( hProcess, NULL, 0,  
                    (LPTHREAD_START_ROUTINE) ::GetProcAddress(hKernel32,"LoadLibraryA"), 
                    pLibRemote, 0, NULL );
    if( hThread == NULL )
        goto JUMP;

    ::WaitForSingleObject( hThread, INFINITE );

    // Get handle of loaded module
    ::GetExitCodeThread( hThread, &hLibModule );
    ::CloseHandle( hThread );

JUMP:   
    ::VirtualFreeEx( hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE );
    if( hLibModule == NULL ) // (1)
        return false;


    // Unload "LibSpy.dll" from the remote process 
    // (via CreateRemoteThread & FreeLibrary)
    hThread = ::CreateRemoteThread( hProcess,
                NULL, 0,
                (LPTHREAD_START_ROUTINE) ::GetProcAddress(hKernel32,"FreeLibrary"),
                (void*)hLibModule,
                 0, NULL );
    if( hThread == NULL )   // failed to unload
        return false;

    ::WaitForSingleObject( hThread, INFINITE );
    ::GetExitCodeThread( hThread, &hLibModule );
    ::CloseHandle( hThread );

    // return value of remote FreeLibrary (=nonzero on success)
    return hLibModule;
}

Some comments:

  1. The legacy program has the title "FORMSSSSS".
  2. The sample DLL has following DllMain method:

-

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD  ul_reason_for_call,
LPVOID lpReserved

{
    OutputDebugStringA("DllMain called: ");
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        OutputDebugStringA("DLL_PROCESS_ATTACH\n");
    case DLL_THREAD_ATTACH:
        OutputDebugStringA("DLL_THREAD_ATTACH\n");
    case DLL_THREAD_DETACH:
        OutputDebugStringA("DLL_THREAD_DETACH\n");
    case DLL_PROCESS_DETACH:
        OutputDebugStringA("DLL_PROCESS_DETACH\n");
        break;
    }
    return TRUE;
}

When it is called, a text is written into the standard output of the application.


When I run the program above (the one with _tmain method), I expect to see the text

DllMain called: DLL_PROCESS_ATTACH

in the console output (it means that the DLL injection was successful).

But it doesn't happen.


One potential cause is that the PID of the legacy application is determined incorrectly:

HWND windowHandle = FindWindowW(NULL, L"FORMSSSSS");
DWORD* processID = new DWORD;
GetWindowThreadProcessId(windowHandle, processID);

DWORD delphiAppProcessId = *processID;

But the value delphiAppProcessId is the same as the PID displayed in the task manager, so I can exclude this potential bug.


Using the debugger I found out that the execution stops at the line with comment (1):

JUMP:   
    ::VirtualFreeEx( hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE );
    if( hLibModule == NULL ) // (1)
        return false;

What do I need to change in order for the sample DLL to be injected into the address space of the application with title "FORMSSSSS" ?

Update, 16.09.2012:

I replaced all occurrences of

sizeof(szLibPath)

by pathLength, where

const int pathLength = strlen(szLibPath)+1;

Now, in

    ::WaitForSingleObject( hThread, INFINITE );
    ::GetExitCodeThread( hThread, &hLibModule );
    ::CloseHandle( hThread );

    // return value of remote FreeLibrary (=nonzero on success)
    return hLibModule;
}

hLibModule is nonzero, which means that the injection was successful.

But I still can't see the log output of the sample DLL in the output of the program.

Update, 16.09.2012 (2):

When I

a) add a call to AllocConsole() in DllMain of the sample DLL, b) rebuild it and c) execute the injecting program,

then a console window appears, which has the same icon as the Delphi application.

When I remove AllocConsole from the DllMain function, and execute the injecting application, the console window does not appear.

So the injection might actually work.

1
Are you sure you want to 'roll your own' and not just buy MadCodeHook. It saves you tons of effort. - Jan Doggen

1 Answers

1
votes

The biggest problem that I can see is that sizeof(szLibPath) evaluates to the size of a pointer. Use strlen(szLibPath)+1 instead.

For sure that means that your injection will fail because the path that LoadLibraryA receives will be truncated. There may be other problems, but that's the place to start.