Okay, so here is a small snippet that you can use for injecting a DLL on an application via “Thread Hijacking”. It’s much safer than injecting with common methods such as CreateRemoteThread. This uses GetThreadContext and SetThreadContext to poison the registers to execute our stub that is allocated via VirtualAllocEx which contains a code that will execute LoadLibraryA that will load our DLL. But this snippet alone is not enough to make your dll injection safe, you can do cleaning of your traces upon injection and other methods. Thanks to thelastpenguin for this awesome base.
FULL CODE
#include <fstream>
#include <iostream>
#include <stdio.h>
#include <Windows.h>
#include <TlHelp32.h>
#include <direct.h> // _getcwd
#include <string>
#include <iomanip>
#include <sstream>
#include <process.h>
#include <unordered_set>
#include "makesyscall.h"
#pragma comment(lib,"ntdll.lib")
using namespace std;
DWORD FindProcessId(const std::wstring&);
long InjectProcess(DWORD, const char*);
void dotdotdot(int count, int delay = 250);
void cls();
int main_scanner();
int main_injector();
string GetExeFileName();
string GetExePath();
BOOL IsAppRunningAsAdminMode();
void ElevateApplication();
__declspec(naked) void stub()
{
__asm
{
// Save registers
pushad
pushfd
call start // Get the delta offset
start :
pop ecx
sub ecx, 7
lea eax, [ecx + 32] // 32 = Code length + 11 int3 + 1
push eax
call dword ptr[ecx - 4] // LoadLibraryA address is stored before the shellcode
// Restore registers
popfd
popad
ret
// 11 int3 instructions here
}
}
// this way we can difference the addresses of the instructions in memory
DWORD WINAPI stub_end()
{
return 0;
}
//
int main(int argc, char* argv) {
main_injector();
main_scanner();
}
BOOL IsAppRunningAsAdminMode()
{
BOOL fIsRunAsAdmin = FALSE;
DWORD dwError = ERROR_SUCCESS;
PSID pAdministratorsGroup = NULL;
// Allocate and initialize a SID of the administrators group.
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
if (!AllocateAndInitializeSid(
&NtAuthority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&pAdministratorsGroup))
{
dwError = GetLastError();
goto Cleanup;
}
// Determine whether the SID of administrators group is enabled in
// the primary access token of the process.
if (!CheckTokenMembership(NULL, pAdministratorsGroup, &fIsRunAsAdmin))
{
dwError = GetLastError();
goto Cleanup;
}
Cleanup:
// Centralized cleanup for all allocated resources.
if (pAdministratorsGroup)
{
FreeSid(pAdministratorsGroup);
pAdministratorsGroup = NULL;
}
// Throw the error if something failed in the function.
if (ERROR_SUCCESS != dwError)
{
throw dwError;
}
return fIsRunAsAdmin;
}
//
void ElevateApplication(){
wchar_t szPath[MAX_PATH];
if (GetModuleFileName(NULL, szPath, ARRAYSIZE(szPath)))
{
// Launch itself as admin
SHELLEXECUTEINFO sei = { sizeof(sei) };
sei.lpVerb = L"runas";
sei.lpFile = szPath;
sei.hwnd = NULL;
sei.nShow = SW_NORMAL;
if (!ShellExecuteEx(&sei))
{
DWORD dwError = GetLastError();
if (dwError == ERROR_CANCELLED)
{
// The user refused to allow privileges elevation.
std::cout << "User did not allow elevation" << std::endl;
}
}
else
{
_exit(1); // Quit itself
}
}
}
string GetExeFileName()
{
char buffer[MAX_PATH];
GetModuleFileNameA(NULL, buffer, MAX_PATH);
return std::string(buffer);
}
string GetExePath()
{
std::string f = GetExeFileName();
return f.substr(0, f.find_last_of("\\/"));
}
int main_scanner() {
std::cout << "Loading";
dotdotdot(4);
std::cout << endl;
cls();
string processName = "Game.exe";
string payloadPath = GetExePath() + "\\" + "hack.dll";
cls();
std::cout << "\tProcess Name: " << processName << endl;
std::cout << "\tRelative Path: " << payloadPath << endl;
std::wstring fatProcessName(processName.begin(), processName.end());
std::unordered_set<DWORD> injectedProcesses;
while (true) {
std::cout << "Scanning";
while (true) {
dotdotdot(4);
DWORD processId = FindProcessId(fatProcessName);
if (processId && injectedProcesses.find(processId) == injectedProcesses.end()) {
std::cout << "\n====================\n";
std::cout << "Found a process to inject!" << endl;
std::cout << "Process ID: " << processId << endl;
std::cout << "Injecting Process: " << endl;
if (InjectProcess(processId, payloadPath.c_str()) == 0) {
std::cout << "Success!" << endl;
injectedProcesses.insert(processId);
}
else {
std::cout << "Error!" << endl;
}
std::cout << "====================\n";
break;
}
}
}
}
int main_injector() {
cls();
if (IsAppRunningAsAdminMode())
return 1;
else
ElevateApplication();
}
void dotdotdot(int count, int delay) {
int width = count;
for (int dots = 0; dots <= count; ++dots) {
std::cout << std::left << std::setw(width) << std::string(dots, '.');
Sleep(delay);
std::cout << std::string(width, '\b');
}
}
void cls() {
std::system("cls");
std::cout <<
" -------------------------------\n"
" Thread Hijacking Injector \n"
" -------------------------------\n";
}
DWORD FindProcessId(const std::wstring& processName) {
PROCESSENTRY32 processInfo;
processInfo.dwSize = sizeof(processInfo);
HANDLE processesSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (processesSnapshot == INVALID_HANDLE_VALUE)
return 0;
Process32First(processesSnapshot, &processInfo);
if (!processName.compare(processInfo.szExeFile))
{
CloseHandle(processesSnapshot);
return processInfo.th32ProcessID;
}
while (Process32Next(processesSnapshot, &processInfo))
{
if (!processName.compare(processInfo.szExeFile))
{
CloseHandle(processesSnapshot);
return processInfo.th32ProcessID;
}
}
CloseHandle(processesSnapshot);
return 0;
}
long InjectProcess(DWORD ProcessId, const char* dllPath) {
HANDLE hProcess, hThread, hSnap;
DWORD stublen;
PVOID LoadLibraryA_Addr, mem;
THREADENTRY32 te32;
CONTEXT ctx;
// determine the size of the stub that we will insert
stublen = (DWORD)stub_end - (DWORD)stub;
cout << "Calculated the stub size to be: " << stublen << endl;
// opening target process
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
if (!hProcess) {
cout << "Failed to load hProcess with id " << ProcessId << endl;
Sleep(10000);
return 0;
}
// todo: identify purpose of this code
te32.dwSize = sizeof(te32);
hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
Thread32First(hSnap, &te32);
cout << "Identifying a thread to hijack" << endl;
while (Thread32Next(hSnap, &te32))
{
if (te32.th32OwnerProcessID == ProcessId)
{
cout << "Target thread found. TID: " << te32.th32ThreadID << endl;
CloseHandle(hSnap);
break;
}
}
// opening a handle to the thread that we will be hijacking
hThread = OpenThread(THREAD_ALL_ACCESS, false, te32.th32ThreadID);
if (!hThread) {
cout << "Failed to open a handle to the thread " << te32.th32ThreadID << endl;
Sleep(10000);
return 0;
}
// now we suspend it.
ctx.ContextFlags = CONTEXT_FULL;
SuspendThread(hThread);
cout << "Getting the thread context" << endl;
if (!GetThreadContext(hThread, &ctx)) // Get the thread context
{
cout << "Unable to get the thread context of the target thread " << GetLastError() << endl;
ResumeThread(hThread);
Sleep(10000);
return -1;
}
cout << "Current EIP: " << ctx.Eip << endl;
cout << "Current ESP: " << ctx.Esp << endl;
cout << "Allocating memory in target process." << endl;
mem = VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!mem) {
cout << "Unable to reserve memory in the target process." << endl;
ResumeThread(hThread);
Sleep(10000);
return -1;
}
cout << "Memory allocated at " << mem << endl;
LoadLibraryA_Addr = LoadLibraryA;
cout << "Writing shell code, LoadLibraryA address, and DLL path into target process" << endl;
cout << "Writing out path buffer " << dllPath << endl;
size_t dllPathLen = strlen(dllPath);
WriteProcessMemory(hProcess, mem, &LoadLibraryA_Addr, sizeof(PVOID), NULL); // Write the address of LoadLibraryA into target process
WriteProcessMemory(hProcess, (PVOID)((LPBYTE)mem + 4), stub, stublen, NULL); // Write the shellcode into target process
WriteProcessMemory(hProcess, (PVOID)((LPBYTE)mem + 4 + stublen), dllPath, dllPathLen, NULL); // Write the DLL path into target process
ctx.Esp -= 4; // Decrement esp to simulate a push instruction. Without this the target process will crash when the shellcode returns!
WriteProcessMemory(hProcess, (PVOID)ctx.Esp, &ctx.Eip, sizeof(PVOID), NULL); // Write orginal eip into target thread's stack
ctx.Eip = (DWORD)((LPBYTE)mem + 4); // Set eip to the injected shellcode
cout << "new eip value: " << ctx.Eip << endl;
cout << "new esp value: " << ctx.Esp << endl;
cout << "Setting the thread context " << endl;
if (!SetThreadContext(hThread, &ctx)) // Hijack the thread
{
cout << "Unable to SetThreadContext" << endl;
VirtualFreeEx(hProcess, mem, 0, MEM_RELEASE);
ResumeThread(hThread);
Sleep(10000);
return -1;
}
ResumeThread(hThread);
cout << "Done." << endl;
return 0;
}
PoC
I think that’s all for this writeup. With that being said, this could be my last writeup for now as I am going very very busy for the next couple of months.
Thank you so much, and I hope you enjoyed this writeup!
root@sh3n:~/$ see_ya_again_soon_!