Bas Groothedde

Hijacking a process with Code Injection on Windows


Taking over running processes


Introduction

Code Injection is a technique used by hackers to inject their own code into another process. Like this, they can hijack the process and inherit its privileges. Code Injection methods can be used for very malicious activities, but are used for very productive activities as well. This article is for educational purposes and in this first instalment, we will have a look at Code Injection in general and the most basic method that can be used to achieve it further on in the article.

The first part of this article will explain what Code Injection is, its use cases and possible detection methods. After this, it will become technical with code examples, explaining how you can implement basic Code Injection.

Code Injection

As per usual, I cite a Wikipedia page on the subject for a simple definition of the term. Once the APA citation system on this blog is ready, I'll add the proper sources. Citing the article on DLL injection, which is the most used form of code injection:

In computer programming, DLL injection is a technique used for running code within the address space of another process by forcing it to load a dynamic-link library. DLL injection is often used by external programs to influence the behavior of another program in a way its authors did not anticipate or intend. For example, the injected code could hook system function calls, or read the contents of password textboxes, which cannot be done the usual way. A program used to inject arbitrary code into arbitrary processes is called a DLL injector.

Code Injection allows a programmer to write code and by using one of the few injection methods, inject it into a running process (like Chrome, Internet Explorer, Outlook) and start executing it in that program space. By doing so, the programmer can have his code act like it's that program running that code. For example, when the programmer's code opens a connection to some server and sends data about your browsing history there, it will look like Chrome is doing that if he injected his code in Chrome.

This means that Windows or Anti-virus software will have a hard time determining that that data should not be sent, considering a trusted application is doing it. The firewall will also do nothing about the matter, because Chrome is allowed to open connections to the internet for browsing websites.

Malware

There are many use cases for Code Injection, but let's start with a very malicious one; malware. Malware (malicious software) is software that (as the name suggests) runs malicious code on your system that could potentially share your private information online or disrupt your normal computer operation. As the Wikipedia definition states:

Malware, short for malicious software, is any software used to disrupt computer operations, gather sensitive information, gain access to private computer systems, or display unwanted advertising.

There are plenty methods available for malware developers to achieve these goals, Code Injection is just one of them. By injecting code into trusted programs, malware can easily extract information from that program and share it online. For example, by injecting Outlook, a malware program can steal your private emails. By injecting Chrome, malware could attempt to steal your stored passwords, your cookies and your browser history. This is why it is so extremely important to have a proper up-to-date anti-virus software package running on your system.

Debugging

Another use case for Code Injection is debugging a running application. Many compilers (a program that converts code into data that can be executed by your operating system) compile a separate debug version of the program and don't need to do a lot of code injection while the program is running, however some debuggers still do. It is extremely useful for a debugger to hijack a program and obtain information about how the program is running, catch errors and log useful information for the programmer. This is a very useful application for Code Injection. Microsoft even has a Debugger API for this. We will be using another set of debug privileges and functions to do our own code injection later.

Cheating in video games

Another slightly questionable use case is cheating in video games. Now, if you want to have some help in a single player game, that's fine, however Code Injection techniques are often used in multiplayer games as well. Players often purchase or freely download a cheat program that injects the cheat into the process of the game. Like this, the cheat has access to memory of that game's process and can (for example) extract information about where players are in the level (wallhack), or even automatically aim at a players head and shoot (aimbot + triggerbot).

I do not condone this type of hacking, however it is far less questionable than Malware and usually fairly safe for everyone, merely very frustrating for people trying to play a fair game online.

Other use cases

There are many use cases for Code Injection. In any situation where you need access to a running process, Code Injection can help you out. For example; I once wrote a Code Injection hack for an old version of Internet Explorer for lobby-PC clients of a Windows domain. This hack ensured that IE was running at all times, in full screen and disabled any possibility of closing or minimizing the browser. This was to ensure that the lobby-PC was used merely for browsing the internet and nothing else.

Detection

A lot of Code Injection methods can easily be detected by anti-virus software and anti-cheat software. The basic method we're going to discuss in this article is one of the easiest methods to detect, because the AV-software can simply scan for signatures of code and functions that are often used to inject code into other processes. The same goes for anti-cheat engines.

Another method AV-software uses to detect this is by hooking the functions required for Code Injection themselves, even by using Code Injection to do so! This way, they can log every call to those functions and when a malicious call comes along they don't trust, they'll quarantine that program.

New implementations of basic Code Injections techniques aren't always detected immediately though, like the one we're discussing in this article is actually only detected by 1 virus scanner as potentially risky (not malicious) on VirusTotal.com. Scary, isn't it?

There are also Code Injection techniques, like the recent Atom Bombing technique, that are extremely hard to detect at all. If you would combine that technique with the Manual Mapping technique, things get really scary. I will write about those methods in future articles.

Basic Code Injection (DLL Injection) implementation

Things get technical now. We're going to look at the most basic form of Code Injection there is. This method will find a running process and open a handle to it. It will then allocate memory in that process that will hold a filepath to a .dll file containing our code. We'll then start a thread in that process which will execute LoadLibrary (a function to load a DLL) with the path to our DLL as parameter. That's it, our code is then loaded in an external process.

The injection code is written in C++ and the injected code in written in PureBasic, for variety. We'll start off by declaring our class of functions, which is nothing else than telling the code about the functions we'll write. The injection code is compiled with the x64 VC++ compiler in Unicode character mode.

#pragma once
#include <Windows.h>

#ifdef UNICODE
#define LOADLIBRARY "LoadLibraryW"
#define strcomparei lstrcmpi
#define strlenx lstrlen
#define soc 2
#else 
#define LOADLIBRARY "LoadLibraryA"
#define strcomparei strcmpi
#define strlenx strlen
#define soc 1
#endif 

class BasicInjector {
    public:
        DWORD GetPIDFromName(const LPCTSTR name); // find process ID from process name (i.e. notepad.exe -> 348)
        DWORD CountPIDFromName(const LPCTSTR name); // count all process IDs from process name
        DWORD GetPIDListFromName(const LPCTSTR name, DWORD list[]); // get all process IDs for all running instances of i.e. notepad.exe
        void SetDebugPrivileges(BOOL state); // adjust the current process' privileges to allow us to inject code externally

        HMODULE Inject(DWORD processId, const LPCTSTR szDllPath); // inject a DLL into another process
        void Eject(DWORD processId, HMODULE module); // Unload (eject) a DLL from another process
};

Implementation: GetPIDFromName

GetPIDFromName is very useful in our code, because it allows us to determine the process ID for a running process. We can retrieve the process ID for chrome by calling this function with "chrome.exe" as parameter.

The function uses CreateToolhelp32Snapshot to get a list of running processes on the system. It then iterates through that list until it finds a process that has the same name as the one provided to the function. It then returns the process ID of that process to the caller of the function.

DWORD BasicInjector::GetPIDFromName(const LPCTSTR name) {
    DWORD pid = 0;
    HANDLE hSnapshot;
    PROCESSENTRY32 pi;
    BOOL p32res;

    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hSnapshot != INVALID_HANDLE_VALUE) {
        pi.dwSize = sizeof(PROCESSENTRY32);

        // Fetch first process from the snapshot
        p32res = Process32First(hSnapshot, &pi);

        while (p32res) {
            if (strcomparei(pi.szExeFile, name) == 0) { // found it!
                pid = pi.th32ProcessID;
                break;
            }

            // Get the next process from the snapshot
            p32res = Process32Next(hSnapshot, &pi);
        }

        CloseHandle(hSnapshot);
    }

    return pid;
}

Our example implementation code in the end only needs to find 1 PID, so I'll spare you the implementations of CountPIDFromName and GetPIDListFromName. These are in the library so you can inject your code in all matching processes. This is particularly useful for processes like chrome that runs an instance of chrome.exe for every tab or to inject code in every notepad window for example.

Implementation: SetDebugPrivileges

The SetDebugPrivileges function is required to give the process that injects the code into another process debug privileges. These are required to be able to open another process and start a thread in it. This is an easy to detect routine by AV software and anti-cheat packages. Other Injection methods I'll write about later do not require this routine at all times.

void BasicInjector::SetDebugPrivileges(BOOL state) {
    TOKEN_PRIVILEGES tk;
    HANDLE hToken;

    // Get current process token
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_READ, &hToken))
        return;

    if (!LookupPrivilegeValue(NULL, L"SeDebugPrivilege", &tk.Privileges[0].Luid)) {
        CloseHandle(hToken);
        return;
    }

    // Modify token
    tk.PrivilegeCount = 1;
    tk.Privileges[0].Attributes = (state ? SE_PRIVILEGE_ENABLED : NULL);

    // Adjust token privileges
    AdjustTokenPrivileges(hToken, NULL, &tk, NULL, NULL, NULL);

    CloseHandle(hToken);
}

When SetDebugPrivileges(true) is called we can perform the injection after that call. After the injection, we should call SetDebugPrivileges(false) to remove the now useless privilege again, as we don't need it anymore.

Implementation: Inject

We have now arrived at the most important function in this set, the Inject method. This function will open the target process, allocate memory for the path to our DLL and execute LoadLibrary in the target process to load our code. I'll explain this method step by step, we'll start off with the function signature and local variables.

HMODULE BasicInjector::Inject(DWORD processId, const LPCTSTR szDllPath) {
    HMODULE hLibModule = NULL; // a reference to the loaded DLL when it is loaded
    HANDLE hThread, hProcess; // handles to the process and created thread for the external process
    LPVOID pLibraryRemote; // a buffer which contains the path of the DLL to load.

Preparations are almost done, we still need that debug privilege to allow us to open a process with all privileges, which is easy with our SetDebugPrivileges function. We simply call it with true as parameter:

SetDebugPrivileges(true);

We now have debug privileges, so we can open the process with all the privileges we need:

hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, processId);

If that goes well (see complete implementation below for error checking), we can allocate memory in the target process that will hold the path to our DLL with VirtualAllocEx. We'll store a reference to the memory in our pLibraryRemote variable:

pLibraryRemote = VirtualAllocEx(hProcess, NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE);

If that goes well, we write the path of the DLL to that memory so that we can have the target process load our DLL with LoadLibrary shortly after:

WriteProcessMemory(hProcess, pLibraryRemote, szDllPath, (strlenx(szDllPath) + 1) * soc, NULL);

If that goes well as well, we'll simply start a remote thread (we execute a function in the target process). Specifically, we'll tell CreateRemoteThread that the thread's start routine (its entry point) is LoadLibrary and its parameter is the pLibraryRemote buffer. Like this, Windows will create a thread in the target process that only executes that function, so we can wait for it to finish and get its result. We'll store the reference to the thread in our hThread variable.

hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32"), LOADLIBRARY), pLibraryRemote, 0, NULL);

If the thread was successfully created, we'll wait for it to finish and obtain its exit code (in this case, the return value of LoadLibrary). This is a reference to the loaded module and will allow us to determine if the injection was successful. If the result is not equal to zero, it means the library was loaded.

if (hThread != NULL) {
    WaitForSingleObject(hThread, INFINITE);
    GetExitCodeThread(hThread, (LPDWORD)&hLibModule);
    CloseHandle(hThread);
}

We clean up, free resources, close handles, remove debug privileges and return the result in the last part of the function. The complete function will look like this:

HMODULE BasicInjector::Inject(DWORD processId, const LPCTSTR szDllPath) {
    HMODULE hLibModule = NULL;
    HANDLE hThread, hProcess;
    LPVOID pLibraryRemote;

    SetDebugPrivileges(true);

    hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, processId);
    if (hProcess != NULL) {
        pLibraryRemote = VirtualAllocEx(hProcess, NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE);
        if (pLibraryRemote) {
            if (WriteProcessMemory(hProcess, pLibraryRemote, szDllPath, (strlenx(szDllPath) + 1) * soc, NULL)) {
                hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32"), LOADLIBRARY), pLibraryRemote, 0, NULL);
                if (hThread != NULL) {
                    WaitForSingleObject(hThread, INFINITE);
                    GetExitCodeThread(hThread, (LPDWORD)&hLibModule);
                    CloseHandle(hThread);
                }
            }

            VirtualFreeEx(hProcess, pLibraryRemote, MAX_PATH, MEM_RELEASE);
        }

        CloseHandle(hProcess);
    }

    // No need for these anymore
    SetDebugPrivileges(false);

    // Return the library module reference
    return hLibModule;
}

Implementation: Eject

We still have one function in our class, which allows us to eject our injected code from the target process by calling FreeLibrary instead of LoadLibrary and with the reference to the library module as parameter. This function is less complex than the Inject function, because it does not require us to allocate memory in the target process:

void BasicInjector::Eject(DWORD processId, HMODULE module) {
    HANDLE hThread, hProcess;

    // We still need debug privileges for the PROCESS_ALL_ACCESS privilege
    SetDebugPrivileges(true);

    hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, processId);
    if (hProcess) {
        hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32"), "FreeLibrary"), module, 0, NULL);
        if (hThread) {
            WaitForSingleObject(hThread, INFINITE);
            CloseHandle(hThread);
        }

        CloseHandle(hProcess);
    }

    // Remove debug privileges again
    SetDebugPrivileges(false);
}

The injected code

Before I show you how you can use this class to inject a DLL into another process, we'll write a DLL that will be injected into notepad.exe for demonstration. This DLL will be injected into the first occurrence we find of notepad.exe and hijack it by writing text to the editor and modify the title. We'll do the injected code in the PureBasic language instead of C++, for variety.

This code also implemented PureBasic's AttachProcess and DetachProcess procedures, which are called by the automatically generated entry point for the DLL. These functions are comparable with an implementation of DllMain. Because AttachProcess (or DllMain) is called automatically when the DLL loads, we can execute our desired code there.

The DLL is compiled as x64 thread safe unicode DLL in PureBasic 5.40 LTS.

Structure SEARCH_DATA
  processId.l
  handle.i
EndStructure

; Determine if hWnd is the main window (has no parent and is visible)
Procedure isMainWindow(hWnd.i)
  ProcedureReturn Bool(GetWindow_(hWnd, #GW_OWNER) = 0 And IsWindowVisible_(hWnd))
EndProcedure

; A callback function for EnumWindows, called for each window in the list. 
; This callback function determines if the found window is the probable main
; window of the process we want.
Procedure.i enumCallback(hWnd.i, lParam.i)
  Protected *sd.SEARCH_DATA = lParam 
  Protected processId.l = 0
  GetWindowThreadProcessId_(hWnd, @processId)
  If(*sd\processId <> processId Or Not isMainWindow(hWnd))
    ProcedureReturn #True 
  EndIf 

  *sd\handle = hWnd
  ProcedureReturn #False 
EndProcedure

; A procedure to find the main window of a process
Procedure.i findMainWindow(pid.l)
  Protected sd.SEARCH_DATA
  sd\processId = pid
  sd\handle    = 0
  EnumWindows_(@enumCallback(), @sd)
  ProcedureReturn sd\handle
EndProcedure

; A thread that prints text to the notepad editor every second.
Procedure thrHijacked(lParam)
  Protected length

  SetWindowText_(lParam, "Hello World!! ")

  Repeat 
    length = GetWindowTextLength_(lParam)
    SetFocus_(lParam)

    ; Append text to the end of the notepad editor
    SendMessage_(lParam, #EM_SETSEL, length, length)
    SendMessage_(lParam, #EM_REPLACESEL, 0, "Hijacked!!! ")

    Delay(1000)
  Until Not IsWindow_(lParam) 
EndProcedure

; Keep track of a reference to our text filling thread
Global hThread

; AttachProcess is called automatically when the DLL is loaded.
ProcedureDLL AttachProcess(hInstance)
  ; Find the edit window
  Protected currentProcess = GetCurrentProcessId_()
  Protected mainWindow     = findMainWindow(currentProcess)
  Protected editWindow     = FindWindowEx_(mainWindow, #Null, "Edit", 0)

  ; Change the notepad window title and start filling the editor with useless text.
  If(IsWindow_(editWindow)) 
    SetWindowText_(mainWindow, "This process is mine now, /hook.dll!")
    hThread = CreateThread(@thrHijacked(), editWindow)
  EndIf 

EndProcedure

; When the DLL is unloaded, kill our text writing thread
ProcedureDLL DetachProcess(hInstance)
  If(IsThread(hThread))
    KillThread(hThread)
  EndIf 
EndProcedure

Compile this code as x64 thread safe unicode DLL as hook.dll in the same directory where you'll be running the BasicInjection.exe from. We'll now continue with the implementation of the injector.

Implementation: BasicInjection main()

We'll now use the library we just made (class BasicInjector) to easily inject the DLL we just made (hook.dll) into an instance of notepad.exe we'll find. The code for this is painfully simple. As you can see, with a little code it is very easy to inject code into another process and take over its memory and pretend that your code is not malicious. To the system, you're just notepad.exe!

#include "stdafx.h"
#include <Windows.h>
#include "BasicInjector.h"

int main()
{
    // Reserve MAX_PATH space for the full path of hook.dll
    wchar_t path[MAX_PATH];

    // Create an instance of the BasicInjector
    BasicInjector bi = BasicInjector();

    // Obtain the full path from the relative path to hook.dll
    if (GetFullPathName(L"hook.dll", MAX_PATH, path, NULL)) {

        // Find the first PID for notepad.exe
        DWORD pid = bi.GetPIDFromName(L"notepad.exe");
        printf("notepad.exe pid: %d\r\n", pid);
        if (pid) {
            // Inject our DLL into that PID
            HMODULE library = bi.Inject(pid, path);
            printf("library handle: %d\r\n", library);
            if (!library) { // if library != 0 then our code is now being run in notepad.exe's process!
                printf("injection failed\r\n");
            }
        }
    }

    return 0;
}

Video of above code

Here's a video of the code you read about, if you're worried about the implications on your system. As you can see, the code hardly does anything special. It merely takes over a notepad.exe process if it exists and writes to its editor.

GitHub repository / download code

This code is also added to GitHub for you to clone and play around with. Be careful though, see disclaimer below.

Conclusion

This is just the first article on this subject I'll be writing, but the purpose of this article was to show you how easy it can be for malicious software, debuggers or cheat software to hijack another process and have the code pretend to be something it is not. Once more, this code is rather easy to detect by AV software and anti-cheat systems, use with care. I take absolutely no responsibility if something goes wrong. You're free to use the code, but on your own responsibility.

Anti-cheat systems like VAC or BattleEye will probably detect this sooner or later, perhaps even immediately, so it might get you banned if you use this in conjunction with a game for cheating.

The next article will be on another Injection method, like Manual Map, Atom Bombing or QueueUserAPC techniques. These techniques are a little bit harder to detect when used right, especially the Atom Bombing method.


Related articles