-2

Wrote a simple Dll injector for "calculator.exe" process, with printing out some lines I confirmed that the injector did its job but the messagebox doesn't appear.

Context:

-void inject_dll(DWORD, char*) takes the ID of the process that I want to inject and the name of the dll it will be injecting.

-DWORD get_PId(const w_char_t*) is a function that returns the processID of the given argument (processname)

I have confirmed that the get_PId function works properly, so the error should be somewhere else.

Injector code:

#include "stdafx.h"
#include <windows.h>
#include <TlHelp32.h>
#include <iostream>
void inject_dll(DWORD PId, char* DllName)
{

HANDLE hProcess;
PVOID Alloc;
SIZE_T DllPathLen;
HMODULE Kernel32Base;
PVOID LoadLibAddress;

if (PId != 0 && DllName != NULL)
{
    DllPathLen = strlen(DllName);
    Kernel32Base = GetModuleHandleA("Kernel32.dll");
    if (Kernel32Base == NULL)
    {
        std::cout << "kernel32.dll not found" << std::endl;
        return;
    }

    LoadLibAddress = GetProcAddress(Kernel32Base, "LoadLibraryA");
    if (LoadLibAddress == NULL)
    {
        std::cout << "LoadLibraryA not found" << std::endl;
        return;
    }


    hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PId);
    if (hProcess == NULL)
    {
        std::cout << "hProcess handle not opened" << std::endl;
        return;
    }


    Alloc = VirtualAllocEx(hProcess, NULL, DllPathLen + 1, MEM_COMMIT, PAGE_READWRITE);
    if (Alloc == NULL)
    {
        std::cout << "no memory allocated for DllPath" << std::endl;
        return;
    }

    if (!WriteProcessMemory(hProcess, Alloc, DllName, DllPathLen + 1, NULL))
    {
        std::cout << "didn't write dll to processmemory" << std::endl;
        return;
    }

    CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibAddress, Alloc, 0, NULL);
    std::cout << "end reached" << std::endl;
}

}


DWORD get_PId( const wchar_t* ProcessName) {

HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);

if (hSnapshot != NULL)
{
    if (Process32First(hSnapshot, &pe32)) {
        do
        {
            if (!wcscmp(pe32.szExeFile, ProcessName))
                return pe32.th32ProcessID;

        } while (Process32Next(hSnapshot, &pe32));
    }
}
return 0;
}

int main(int argc, char* argv[])
{
DWORD pid = get_PId(L"Calculator.exe");
std::cout << pid << std::endl;
if (pid) {
    char dllName[] = "CORRECT PATH (dont worry about this)";
    std::cout << dllName << std::endl;
    inject_dll(pid, dllName);

}

ExitPoint:
    system("Pause");
    return 0;
}

The Dll code, when its in the memory of the injected process it should show a messagebox:

#include <windows.h>

VOID ShowMessageBox() {
MessageBoxA(NULL, "injected", "injector", MB_OK);
}

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

switch (ul_reason_for_call)
{
    case DLL_PROCESS_ATTACH:
        ShowMessageBox();

    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
}
return TRUE;
}
Arthur VP
  • 17
  • 5
  • attach debugger to target process, set be on `LoadLibraryA` (why not `LoadLibraryW` ?) and look what is going – RbMm Dec 05 '17 at 20:47
  • My guess is either `CreateRemoteThread()` or the injected `LoadLibraryA()` call is failing, but the code is not checking for an error in either of those calls. Assuming `CreateRemoteThread()` successfully calls `LoadLibraryA()`, I'm going to assume `LoadLibraryA()` is failing. Maybe the DLL's bitness doesn't match the calculator's bitness, for instance. Also, `inject_dll` is leaking memory and resources, as it doesn't clean up after itself at all – Remy Lebeau Dec 05 '17 at 21:51
  • You are probably right, looks like the error can't be seen by just looking at the code. I know now how to attach the visual studio 2017 IDE debugger to the target process, how would i confirm that LoadLibraryA() gets called? when i set a breakpoint at 'GetProcAddress(KernelBase, "loadlibraryA")', what should i look for? I'm pretty new to debugging @RbMm – Arthur VP Dec 06 '17 at 14:21
  • at first - are both process have same bitness ? if no (one 32 and another 64 - your code obviously will be not working). if yes - you need, how i say, set bp on `kernel32.LoadLibraryW` - or more exactly on address, which you pass to `CreateRemoteThread`. say look this value (*LoadLibAddress*) before call `CreateRemoteThread`. then attach debugger to target process. VS debugger require - break all first, before you can open disasm window and set bp to this address. then call `CreateRemoteThread`. debugging is absolute must for this task – RbMm Dec 06 '17 at 14:41
  • Yes both are 64 bit, I checked the value of LoadLibAdress = GetProcAddress(..., "loadlibraryA") and the debugger gave me 0x0007ff37...{kernel32.dll!LoadLibraryAStub}. So LoadLibAdress does get a valid adress @RbMm – Arthur VP Dec 06 '17 at 16:46
  • so set bp at this address in target process before call `CreateRemoteThread` and what be ? – RbMm Dec 06 '17 at 18:26
  • @ArthurVP does `CreateRemoteThread()` return a valid thread handle? If so, wait for the thread to exit and then call `GetExitCodeThread()` to find out if `LoadLibraryA()` returned 0 or not. But note that the value will be truncated on 64bit. If you need the actual `HMODULE`, [see this](https://stackoverflow.com/questions/27332509/createremotethread-on-loadlibrary-and-get-the-hmodule-back). – Remy Lebeau Dec 07 '17 at 17:23
  • Attached debugger to calculator.exe and debugged in visual studio. I used the LoadLibAdress value i got in visual studio to look where it points to in the debugger on calculator.exe. This adress in calculator.exe contained "jmp qword ptr: loadlibraryA" so that looks good. CreateRemoteThread however gives back a handle with value 0x90. Which isnt even a used memory adress in calculator.exe, but it doesnt have to be right? @RemyLebeau – Arthur VP Dec 08 '17 at 18:32
  • @ArthurVP: right, it is a kernel handle within the calling process, not the remote process. What is important is that it is not null, so you can use it with things like `WaitForSingleObject()`, `GetExitCodeThread()`, etc. Since you are using `LoadLibraryA()` as the remote thread procedure, the return value of `LoadLibraryA()` will be the remote thread's exit code. – Remy Lebeau Dec 08 '17 at 18:36
  • I think the solution is pretty close now, in visual studio debugger the exit code is 259; how do I see the loadlibraryA exitvalue in the calculator process debugger? I'm looking at the adress where loadlibraryA gets called but i cant see anything possibly describing 259 (i'm new to assembly language) @RemyLebeau – Arthur VP Dec 08 '17 at 19:06
  • @ArthurVP: [exit code 259 is `STILL_ACTIVE`](https://msdn.microsoft.com/en-us/library/windows/desktop/ms683190.aspx). As I said earlier, you have to wait for the remote thread to terminate (use [`WaitForSingleObject()`](https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032.aspx) for that) before you can then get the actual exit code. Also, on a 64 bit system, the return value of `LoadLibrary()` is going to be truncated to 32 bits when retrieved as a thread exit code, but you can [do some extra work](https://stackoverflow.com/questions/27332509/) to get the full value. – Remy Lebeau Dec 08 '17 at 19:40
  • Allright, when I use WaitForSingleObject() and put it to INFINITE, the debugger doesnt go past this function; so there is nothing happening with the remotethread. (exitcode is always on 259) @RemyLebeau – Arthur VP Dec 08 '17 at 21:02
  • @ArthurVP: The only way the thread could hang like that is if `LoadLibraryA()` never exits, which implies your DLL's entry point is likely deadlocking. [There are certain things you simply can't do in the entry point](https://msdn.microsoft.com/en-us/library/windows/desktop/dn633971.aspx), so make sure your DLL is doing safe things while loading. – Remy Lebeau Dec 08 '17 at 21:36
  • I've read about deadlocking now, but it doesn't seem like my DLL offends any of the "deadlocking rules", aka not using any LoadLibrary function etc. . So this means there is some module that calculator.exe wants to use that my dll is using, while my dll is waiting to use LoadLibraryA which is being used by calculator, this results in a deadlock? @RemyLebeau – Arthur VP Dec 09 '17 at 11:18
  • "*it doesn't seem like my DLL offends any of the "deadlocking rules"*" - actually, it is: "*You should never perform the following tasks from within DllMain: ... Call functions in User32.dll or Gdi32.dll. Some functions load another DLL, which may not be initialized.*" You are calling `MessageBox()`, which is a User32 function. – Remy Lebeau Dec 09 '17 at 17:23
  • Oh wow, indeed! I've changed my dll now, it changes a value at a static adress (of a global variable) in a simple program. When running the dll, the debugger shows no change on the global variable value. This time the dll doesn't use any user32/gdi32.dll functions so I'm kind off lost. If you have time, please check this out: [https://github.com/AvPelli/Dll-injecting-beginner](https://github.com/AvPelli/Dll-injecting-beginner) , thanks for the help so far! @RemyLebeau – Arthur VP Dec 09 '17 at 21:45
  • @ArthurVP you are asking a new question. If your DLL now injects OK, then this question is finished. Changing variables in memory is a different issue. And besides, you shouldn't be relying on fixed addresses anyway. Things like ASLR are designed to change addresses at runtime to thwart attackers from accessing fixed addresses – Remy Lebeau Dec 10 '17 at 00:14

2 Answers2

0

I am also not 100% sure of this but I think its because of session separation in windows Vista and above. You can read more about it here.

Since Vista onwards things have changed with the introduction of 'Session Separation'. This was one of so many defenses introduced in Vista towards securing the system. 'Session Separation' ensured that core system processes including services always run in session 0 while all user process's run in different sessions. As a result any process running in user session failed to inject DLL into system process as CreateRemoteThread did not work across session boundaries...

Injection should work fine on processes such as notepad.exe or paint.exe.

Axois
  • 1,961
  • 2
  • 11
  • 22
-2

I suspect you are running into one of the DllMain restrictions, you are not supposed to call anything in user32.dll or gdi32.dll from DllMain.

SoronelHaetir
  • 14,104
  • 1
  • 12
  • 23