-2

I want to inject a function, create(), into a target process (in this case, notepad.exe). Here is my code:

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <TlHelp32.h>

void create(wchar_t wRemoteBuffer[2][60])                  //the function to be injected to the target proccess (notepad.exe)
{
    LPCWSTR lpFileName = wRemoteBuffer[0];
    //wchar_t *lpFileName = L"C:\\CodeInjectTest.txt";

    HANDLE hFile = CreateFile(lpFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, NULL, NULL);

    //BYTE bBuffer[] = "if you see this file,then the CodeInjectTest has succeed\n";
    LPCWSTR lpBuffer = wRemoteBuffer[1];

    DWORD dNumberOfByteToWrite;
    WriteFile(hFile, lpBuffer, sizeof(lpBuffer), &dNumberOfByteToWrite, NULL);
}

int GetTargetProcessId()       
{
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(pe32);

    DWORD ProcessId;

    HANDLE hHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hHandle == INVALID_HANDLE_VALUE)
    {
        printf("fail to call\n");
        exit(-1);
    }

    int TargetProcessId;
    const wchar_t *target = L"notepad.exe";

    BOOL bMore = ::Process32First(hHandle, &pe32);
    while(bMore)
    {
        printf("the name is %ws ,the id is %d\n", pe32.szExeFile, pe32.th32ProcessID);

        if (wcscmp(pe32.szExeFile, target) == 0)
        {
            TargetProcessId = pe32.th32ProcessID;
            return TargetProcessId;
        }

        bMore = ::Process32Next(hHandle, &pe32);
    }

    return -1;
}

int main()
{
    wchar_t wBuffer[2][60] = {L"C:\\CodeInjectTest.txt", L"if you see this file,then the CodeInjectTest has succeed\n"};

    HANDLE hTargetHandle;
    int TargetProcessId = GetTargetProcessId();

    hTargetHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, TargetProcessId);
    if (hTargetHandle == INVALID_HANDLE_VALUE)
    {
        DWORD d = GetLastError();
        printf("openprocess fail\n");
        printf("the TargetProcessId is: %d\n", TargetProcessId);
        printf("the result of getlast is: %d\n", d);
        system("PAUSE");
        exit(-1);
    }

    DWORD dwBufferSize = (DWORD)GetTargetProcessId - (DWORD)create;

    LPVOID lpProcBaseAddress = VirtualAllocEx(hTargetHandle, NULL, dwBufferSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (lpProcBaseAddress == NULL)
    {
        DWORD d = GetLastError();
        printf("virtualallocex has fail\n");
        printf("the last error is:%d\n", d);
        system("PAUSE");
        exit(-1);
    }

    BOOL a = WriteProcessMemory(hTargetHandle, lpProcBaseAddress, create, dwBufferSize, NULL);                   //write the function to be injected to the target process
    if (a == 0)
    {
        DWORD d = GetLastError();
        printf("writeprocessmemory has fail\n");
        printf("the last error is %d\n", d);
        system("PAUSE");
        exit(-1);
    }

    LPVOID lpRemoteBuffer = ::VirtualAllocEx(hTargetHandle, NULL, sizeof(wBuffer[0]) + sizeof(wBuffer[1]), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    if (lpRemoteBuffer == NULL)
    {
        DWORD d = GetLastError();
        printf("buffer virtualallocex has fail\n");
        printf("the last error is:%d\n", d);
        system("PAUSE");
        exit(-1);
    }

    BOOL b = ::WriteProcessMemory(hTargetHandle, lpRemoteBuffer, wBuffer, sizeof(wBuffer[0]) + sizeof(wBuffer[1]), NULL);        //write the parameter the create function needs to the target process
    if (b == 0)
    {
        DWORD d = GetLastError();
        printf("buffer writeprocessmemory has fail\n");
        printf("the last error is:%d\n", d);
        system("PAUSE");
        exit(-1);
    }

    DWORD dwThreadId;
    HANDLE hRemoteThreadHandle = CreateRemoteThread(hTargetHandle, NULL, NULL, (LPTHREAD_START_ROUTINE)lpProcBaseAddress, lpRemoteBuffer, 0, &dwThreadId);
    if (hRemoteThreadHandle != NULL)
    {
        WaitForSingleObject(hRemoteThreadHandle, 5000);
        printf("succeed\n");
        system("PAUSE");
        return 0;
    }

    system("PAUSE");
    return 1;
}

However, the target process crashes when CreateRemoteThread() is called, and the create() function is not called. But the return value of CreateRemoteThread() is not NULL.

I run this program on Windows XP Pro SP3. At first, when running this program, it displays a dialog box, showing DEP has close notepad.exe to protect the system, and notepad.exe crashes. But I have passed PAGE_EXECUTE_READWRITE to the last parameter of VirtualAllocEx(). Then I close the DEP dialog, and run the program again. This time it does not show the DEP dialog, but notepad.exe still crashes.

Can anyone tell me where the problem is?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
freedomwings
  • 85
  • 10
  • One more thing,the return value of **CreateRemoteThread** function is not null . But it is truly when calling the **CreateRemoteThread** function the target process notepad.exe crashes . I have debugged it in Ollydbg . – freedomwings Sep 01 '16 at 01:42
  • When you copy function code into a memory buffer, you should allocate the buffer with `PAGE_READWRITE` first, then do the copy, then use `VirtualProtect/Ex()` to make the buffer read-only and and enable execution. Also make sure to call `FlushInstructionCache()` afterwards before actually executing the code. – Remy Lebeau Sep 01 '16 at 03:55
  • Inconsistent formatting is a safe sign for poor code. Please fix your code formatting. It's not going to improve code quality, but at least it makes it easier to read. – IInspectable Sep 01 '16 at 09:10

3 Answers3

3

This is a VERY BAD way to implement code injection.

For one thing, your calculation of the create() function's byte size is not guaranteed to work the way you are expecting. It relies on GetTargetProcessId() being located in memory immediately after create(), but that is not guaranteed by the compiler/linker. They may be in reverse order, or they may have other code in between them.

Second, even if you could copy the raw bytes of the entire function into the remote process like you are attempting, the create() function will still likely fail because:

  1. it is declared wrong to begin with. It does not match the signature that LPTHREAD_START_ROUTINE is expecting. In particular, its return value and calling convention are both wrong, which will cause mismanagement of the call stack.

  2. create() calls CreateFileW() and WriteFile() statically, so it relies on the loaded memory address of kernel32.dll within YOUR PROCESS's address space, which may be different than the loaded address within the TARGET PROCESS's address space. This is especially important if Address Space Layout Randomization is enabled.

There are much cleaner and safer ways to implement code injection, like moving your create() code into a separate DLL and then using CreateRemoteThread() to load that DLL into the target process. When the DLL is loaded into the target process, its DLL_PROCESS_ATTACH handler can then call its create() code normally without using ugly hacks.

You inject a DLL by using LoadLibrary() as the remote thread procedure, and a (remote-allocated) pointer to the DLL filename as the thread parameter.

Use GetProcAddress() within your own process to get a pointer to LoadLibrary() within your process. If ASLR is not enabled, and kernel32.dll is not re-based in the target process, you can pass that LoadLibrary() pointer directly to CreateRemoteThread() as the thread procedure, since the load address of kernel32.dll, and thus the address of LoadLibrary(), will be the same in both processes.

However, if ALSR or rebasing is involved, then you will have to adjust the LoadLibrary() pointer before passing it to CreateRemoteThread(). You can use GetModuleHandle() to get the load address of kernel32.dll within your own process. To get the load address of kernel32.dll within the target process, use CreateToolhelp32Snapshot(TH32CS_SNAPMODULE)/Module32First()/Module32Next(), or EnumProcessModules() or EnumProcessModulesEx(). If the two addresses differ, adjust the LoadLibrary() pointer by the difference before passing it to CreateRemoteThread().

Using LoadLibrary() in this manner works because the signature of LoadLibrary() is compatible with LPTHREAD_START_ROUTINE. This also has the added benefit that the return value of LoadLibrary() becomes the thread's exit code, so your app can call CreateRemoteThread(), wait for the thread to terminate when LoadLibrary() exits, and then grab the exit code to determine if the injection was successful or not (but you cannot access the actual error code if LoadLibrary() fails).

Try something like this:

DLL:

BOOL create()
{
    HANDLE hFile = CreateFileW(L"C:\\CodeInjectTest.txt", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
        return FALSE;

    const char *lpBuffer = "if you see this file, then the CodeInjectTest has succeed\n";
    DWORD dNumberOfByteToWrite;
    WriteFile(hFile, lpBuffer, strlen(lpBuffer), &dNumberOfByteToWrite, NULL);
    CloseHandle(hFile);

    return TRUE;
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
    if (fdwReason == DLL_PROCESS_ATTACH)
    {
        DisableThreadLibraryCalls(hinstDLL);

        // your injected code here...
        return create();
    }

    return TRUE;
}

EXE:

#include <windows.h>

#include <stdio.h>
#include <stdlib.h>

#include <tlhelp32.h>
#include <psapi.h>
#include <shlwapi.h>

DWORD GetTargetProcessId(const wchar_t *target)
{
    DWORD TargetProcessId = 0;

    PROCESSENTRY32 pe32 = {0};
    pe32.dwSize = sizeof(pe32);

    HANDLE hHandle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hHandle == INVALID_HANDLE_VALUE)
    {
        printf("CreateToolhelp32Snapshot failed with error %u\n", GetLastError());
        return 0;
    }

    if (!Process32First(hHandle, &pe32))
    {
        if (GetLastError() != ERROR_NO_MORE_FILES)
            printf("Process32First failed with error %u\n", GetLastError());
    }
    else
    {
        do
        {
            printf("[%u] %ws\n", pe32.th32ProcessID, pe32.szExeFile);
            if (wcscmp(pe32.szExeFile, target) == 0)
            {
                TargetProcessId = pe32.th32ProcessID;
                break;
            }

            if (!Process32Next(hHandle, &pe32))
            {
                if (GetLastError() != ERROR_NO_MORE_FILES)
                    printf("Process32Next failed with error %u\n", GetLastError());
                break;
            }
        }
        while (true);
    }

    CloseHandle(hHandle);

    return TargetProcessId;
}

LPVOID GetTargetProcAddress(HANDLE hProcess, const wchar_t *szWantedModule, const char *szProcName)
{
    // note, there is a very interesting gotcha in a comment to this answer:
    //
    // Would ASLR cause friction for the address with DLL injection?
    // http://stackoverflow.com/a/8569008/65863
    //  
    // "The address of the module may not change but that does not make
    // what the OP is doing safe! Consider the case where your app is
    // running with shims enabled (something which you do not control!)
    // or even the case where some other pieces of software which also
    // performs EAT hooks is running in your process (again, not something
    // you control). In that case, GetProcAddress could return a pointer
    // to a function in another module to what you're expecting/asking,
    // including one which is not loaded in the process which you're going
    // to call CreateRemoteThread on, in that case the target will crash."
    //
    // you probably won't run into this very often, if ever, but you should
    // be aware of it nonetheless!

    HANDLE hLocalMod = GetModuleHandleW(szWantedModule);
    LPVOID lpProcAddress = GetProcAddress(hLocalMod, szProcName);
    if (!lpProcAddress)
    {
        printf("GetProcAddress failed with error %u\n", GetLastError());
        return NULL;
    }

    // return lpProcAddress;

    HANDLE hRemoteMod = NULL;
    wchar_t szModName[MAX_PATH];

    HMODULE hMods[1024];
    DWORD cbNeeded;

    if (!EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded))
    {
        printf("EnumProcessModules failed with error %u\n", GetLastError());
        return NULL;
    }

    cbNeeded /= sizeof(HMODULE);
    for (DWORD i = 0; i < cbNeeded; ++i)
    {
        if (GetModuleFileNameEx(hProcess, hMods[i], szModName, sizeof(szModName) / sizeof(wchar_t)))
        {
            if (wcscmp(PathFindFileNameW(szModName), szWantedModule) == 0)
            {
                hRemoteMod = hMods[i];
                break;
            }
        }
    }

    if (!hRemoteMod)
    {
        printf("Cannot find %ws in remote process\n", szWantedModule);
        return NULL;
    }

    if (hLocalMod != hRemoteMod)
        lpProcAddress = (LPVOID)((INT_PTR)hRemoteMod - (INT_PTR)hLocalMod);

    return lpProcAddress;
}

int main()
{
    DWORD TargetProcessId = GetTargetProcessId(L"notepad.exe");
    if (TargetProcessId == 0)
    {
        system("PAUSE");
        return -1;
    }    

    printf("TargetProcessId: %u\n", TargetProcessId);

    HANDLE hTargetHandle = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, TargetProcessId);
    if (!hTargetHandle)
    {
        printf("OpenProcess failed with error %u\n", GetLastError());
        system("PAUSE");
        return -1;
    }

    LPVOID lpRemoteProcAddress = GetTargetProcAddress(hTargetHandle, L"kernel32.dll", "LoadLibraryW");
    if (!lpRemoteProcAddress)
    {
        CloseHandle(hTargetHandle);
        system("PAUSE");
        return -1;
    }    

    const wchar_t lpFilename[] = L"C:\\CodeInjectTest.dll";
    DWORD dwBufferSize = sizeof(lpFilename);

    LPVOID lpRemoteBuffer = VirtualAllocEx(hTargetHandle, NULL, dwBufferSize, MEM_COMMIT, PAGE_READWRITE);
    if (!lpRemoteBuffer)
    {
        printf("VirtualAllocEx failed with error %u\n", GetLastError());
        CloseHandle(hTargetHandle);
        system("PAUSE");
        return -1;
    }

    if (!WriteProcessMemory(hTargetHandle, lpRemoteBuffer, lpFilename, dwBufferSize, NULL);
    {
        printf("WriteProcessMemory failed with error %u\n", GetLastError());
        VirtualFreeEx(hTargetHandle, lpRemoteBuffer, 0, MEM_RELEASE);
        CloseHandle(hTargetHandle);
        system("PAUSE");
        return -1;
    }

    DWORD dwThreadId;
    HANDLE hRemoteThreadHandle = CreateRemoteThread(hTargetHandle, NULL, NULL, lpRemoteProcAddress, lpRemoteBuffer, 0, &dwThreadId);
    if (!hRemoteThreadHandle)
    {
        printf("CreateRemoteThread failed with error %u\n", GetLastError());
        VirtualFreeEx(hTargetHandle, lpRemoteBuffer, 0, MEM_RELEASE);
        CloseHandle(hTargetHandle);
        system("PAUSE");
        return -1;
    }

    WaitForSingleObject(hRemoteThreadHandle, INFINITE);

    DWORD dwExitCode;
    GetExitCodeThread(hRemoteThreadHandle, &dwExitCode);
    CloseHandle(hRemoteThreadHandle);

    VirtualFreeEx(hTargetHandle, lpRemoteBuffer, 0, MEM_RELEASE);

    if (dwExitCode == 0)
    {
        printf("Remote LoadLibrary failed\n");
        CloseHandle(hTargetHandle);
        system("PAUSE");
        return -1;
    }

    /* optional, depending on your DLL's needs...

    lpRemoteProcAddress = GetTargetProcAddress(hTargetHandle, L"kernel32.dll", "FreeLibrary");
    if (lpRemoteProcAddress)
    {
        hRemoteThreadHandle = CreateRemoteThread(hTargetHandle, NULL, NULL, lpRemoteProcAddress, (void*)dwExitCode, 0, &dwThreadId);
        if (hRemoteThreadHandle)
        {
            WaitForSingleObject(hRemoteThreadHandle, INFINITE);
            CloseHandle(hRemoteThreadHandle);
        }
    }
    */

    CloseHandle(hTargetHandle);

    printf("success\n");
    system("PAUSE");

    return 0;
}

Another thing to be careful of is 32bit vs 64bit. If your app is running on a 64bit system, you will have to compile separate versions of the DLL, and then detect the target process's actual bitness (you can use IsWow64Process() for that) so you can decide which version of the DLL to inject.

BOOL TargetIs32Bit(HANDLE hProcess)
{
    BOOL bResult;

    #ifdef _WIN64

    bResult = FALSE;
    IsWow64Process(hTargetHandle, &bResult);

    #else

    typedef void (WINAPI *LPFN_GSI)(SYSTEM_INFO*);

    LPFN_GSI lpGetSystemInfo = GetProcAddress(GetModuleHandleW(L"kernel32"), "GetNativeSystemInfo");
    if (!lpGetSystemInfo) lpGetSystemInfo = &GetSystemInfo;

    SYSTEM_INFO sysInfo = {0};
    lpGetSystemInfo(&sysInfo);

    switch (sysInfo.wProcessorArchitecture)
    {
        case PROCESSOR_ARCHITECTURE_AMD64:
        case PROCESSOR_ARCHITECTURE_IA64:
        {
            bResult = FALSE;
            IsWow64Process(hTargetHandle, &bTargetIs32Bit);
            break;
        }

        default:
            bResult = TRUE;
            break;
    }

    #endif

    return bResult;
}

...

wchar_t lpFilename[MAX_PATH];
if (TargetIs32Bit(hTargetHandle))
    lstrcpy(lpFilename, L"C:\\CodeInjectTest32.dll");
else
    lstrcpy(lpFilename, L"C:\\CodeInjectTest64.dll");
// inject DLL ...
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I think that `kernel32.dll` is always at the same place in every process (of a given bitness) because ASLR randomizes the address *per boot* rather than per process. I don't think it is likely to get rebased either, isn't it pretty much the first thing to be loaded into each new process? (One other problem you don't mention is that there's no particular reason to expect the compiler to generate position-independent code, unless I'm missing something?) – Harry Johnston Sep 01 '16 at 04:06
  • According to my book , although ASLR is enabled , but the base address of system DLL is the same in every process in the system . And , code injection can hide deeper than DLL injection , my book said. – freedomwings Sep 01 '16 at 04:25
  • @HarryJohnston Can you explain the second problem you mentiond detailedly? – freedomwings Sep 01 '16 at 04:30
  • @HarryJohnston: kernel32.dll is one of the first, but not the very first, DLL loaded into each process (ntdll is loaded before kernel32, for instance). AFAIK, kernel32 can be rebased in different processes, albeit that is *very* rare. It probably doesn't hurt to be prepare for it, just in case. – Remy Lebeau Sep 01 '16 at 05:59
  • Sounds reasonable. :-) Freedomwings, most x86/amd64 code only runs properly if it is located at the same address in virtual memory that it was compiled for. In Windows, executable files (including DLLs) contain all the information necessary to modify the code so that it will work at an arbitrary address, this is called "relocation" and is part of what the loader does. You're copying code to a different address without relocating it, that won't work in general. See [Position-independent code](https://en.wikipedia.org/wiki/Position-independent_code) on Wikipedia. – Harry Johnston Sep 01 '16 at 21:32
  • Remy's answer avoids that problem by using LoadLibrary() and a DLL; the DLL contains all the necessary information to relocate the code, and LoadLibrary knows how to do so. – Harry Johnston Sep 01 '16 at 21:36
  • Shouldn't it be "lpProcAddress += (LPVOID)((INT_PTR)hRemoteMod - (INT_PTR)hLocalMod);" with a += instead of = ? – jesses Dec 09 '21 at 17:50
  • @jesses you can't add `void*` pointers using `+=`, so it would have to be more like this: `lpProcAddress = (LPVOID)((INT_PTR)lpProcAddress + ((INT_PTR)hRemoteMod - (INT_PTR)hLocalMod));`, or perhaps something more like this: `lpProcAddress = (LPVOID)((INT_PTR)hRemoteMod + ((INT_PTR)lpProcAddress - (INT_PTR)hLocalMod))` – Remy Lebeau Dec 09 '21 at 20:05
0

First, there is not an ALSR problem, and almost all processes have the same base address of kernel32.dll.We can confirm this by attempting the method which Remy Lebeau said, which use CreateRemoteThread() to create a remote thread called LoadLibrary.

Second, If you want to use CreateRemoteThread() in that way, you should be sure that there's no function of other modules(like dlls) in your create() function.But why?

There are some details about calling a function in Windows.If you debug your .exe file using Olly, you may notice that when you call a function, the Olly may display some codes like this:

binary codes | assembly instruction

  1. 6A XX ----push XX
  2. E8 XXXXXXXX ----call create -->second row ....
  3. FF25 XXXXXXXX ----jmp XXXXXXXX -->third row

So, because you copied your create() function in the target process, and in other words, you copied the binary codes in the target process, there may be an access violation when operating the second row which jumps to the third row that was not copied into target process, however.Maybe there's where the target process corrupted.

there's a picture depicting this situation:

corruption situatioin

There's also one more thing we should pay attention to, and that's the import table in the .exe file.We know when we call a function exported by a DLL, system will look up the function address in import table in .idata section.But every process may have a different address of import table.So in the above situation, when Olly jumps to 0x0040100C, the system will look up the target's import table using the address of import table of the original process, which may also wreak havoc.

So, If you want to use CreateRemoteThread(), you can use the technic of DLL injection like what Remy Lebeau said or directly uses the method what you did, but be sure not to call functions exported by other DLLs in create() function.

coneco
  • 1
  • 2
-2

I don't know what you think:

DWORD dwBufferSize=(DWORD)GetTargetProcessId-(DWORD)create;

is going to do, but it probably won't. a) A process ID is not a pointer; b) If you have compiled a 64 bit application, pointers won't fit in a DWORD anyway.

Also, you look like you are trying to copy the executable of the create function - but what about the data segment? (things like the address of CreateFile).

  • The OP is *expecting* `GetTargetProcessId()` to immediately follow `create()` in memory, thus subtracting the starting memory addresses of the two functions would produce the byte size of the `create()` function. In practice, this is **not guaranteed**. – Remy Lebeau Sep 01 '16 at 03:13
  • `(DWORD)GetTargetProcessId` is not a process ID, it is the address of the `GetTargetProcessId` function. – user207421 Sep 01 '16 at 04:12
  • 1
    Oops. Good catch guys. I'll leave this answer up for a day or so, then delete it (there's no way to edit it into a good answer - it's just completely wrong, and @RemyLebeau has already written an excellent answer. – Martin Bonner supports Monica Sep 01 '16 at 08:10