I have a 64 bit process and i'm trying to inject C# .Net dll and invoke one of its methods using the below "Launch" method. this code was taken from SnoopX GitHub project with some changes i made by myself. the target process is usually injected successfully but sometimes it crashes when reaching CreateRemoteThread method.
the logs in case of success contains the following:
11/24/2019 12:21:19 : Assembly Class and Method to load:S:\MyLibrary.dll|MyLibrary.InjectedClass|GOBABYGO 11/24/2019 12:21:19 : Got process handle 11/24/2019 12:21:19 : VirtualAllocEx successful 11/24/2019 12:21:19 : WriteProcessMemory successful 11/24/2019 12:21:19 : CreateRemoteThread successful 11/24/2019 12:21:19 : WaitForSingleObject successful 11/24/2019 12:21:19 : Thread finished 11/24/2019 12:21:19 : Library loaded 11/24/2019 12:21:19 : Entered If _routine case 11/24/2019 12:21:19 : VirtualAllocEx successful 11/24/2019 12:21:19 : WriteProcessMemory successful 11/24/2019 12:21:19 : CreateRemoteThread successful 11/24/2019 12:21:19 : Thread started 11/24/2019 12:21:19 : acmLocal = S:\MyLibrary.dll|MyLibrary.InjectedClass|GOBABYGO 11/24/2019 12:21:19 : About to load assembly S:\MyLibrary.dll 11/24/2019 12:21:19 : About to load type MyLibrary.InjectedClass 11/24/2019 12:21:19 : Just loaded the type MyLibrary.InjectedClass 11/24/2019 12:21:19 : About to invoke GOBABYGO on type MyLibrary.InjectedClass 11/24/2019 12:21:19 : Return value of GOBABYGO on type MyLibrary.InjectedClass is True 11/24/2019 12:21:19 : WaitForSingleObject successful 11/24/2019 12:21:19 : Thread finished 11/24/2019 12:21:19 : Completed launch function
in case of failure i can see in the log only the following(i marked above the differences in Bold):
11/24/2019 13:15:45 : Assembly Class and Method to load:S:\MyLibrary.dll|MyLibrary.InjectedClass|GOBABYGO 11/24/2019 13:15:45 : Got process handle 11/24/2019 13:15:45 : VirtualAllocEx successful 11/24/2019 13:15:45 : WriteProcessMemory successful 11/24/2019 13:15:45 : CreateRemoteThread successful 11/24/2019 13:15:45 : WaitForSingleObject successful 11/24/2019 13:15:45 : Thread finished 11/24/2019 13:15:45 : Library loaded 11/24/2019 13:15:45 : Entered If _routine case 11/24/2019 13:15:45 : VirtualAllocEx successful 11/24/2019 13:15:45 : WriteProcessMemory successful 11/24/2019 13:15:45 : CreateRemoteThread successful 11/24/2019 13:15:56 : WaitForSingleObject successful 11/24/2019 13:15:56 : Thread finished 11/24/2019 13:15:56 : Completed launch function
i have to mention that i'm experienced C# developer, but knows nothing in C++.. so i'll be glad for any detailed instructions of how to find the accurate cause of the problem.
below is the Injector.cpp code:
#include "stdafx.h"
#include "Injector.h"
#include <vcclr.h>
#include <stdio.h>
#include <conio.h>
EXTERN_C IMAGE_DOS_HEADER __ImageBase;
WCHAR DllPath[MAX_PATH] = { 0 };
#pragma data_seg(".shared")
LPTHREAD_START_ROUTINE _routine = 0;
#pragma data_seg()
using namespace System;
using namespace System::IO;
using namespace System::Diagnostics;
using namespace SnoopXInjector;
#pragma managed(push, off)
void LoadImagePath()
{
::GetModuleFileNameW((HINSTANCE)&__ImageBase, DllPath, _countof(DllPath));
}
#pragma managed(pop)
String^ GetLogPath()
{
auto applicationDataPath =
Environment::GetFolderPath(Environment::SpecialFolder::LocalApplicationData);
if (!Directory::Exists(applicationDataPath))
{
Directory::CreateDirectory(applicationDataPath);
}
auto logPath = applicationDataPath + "\\ManagedInjector.log";
return logPath;
}
void Injector::Initialize()
{
try
{
File::Delete(GetLogPath());
}
catch (const FileNotFoundException^)
{
// ignored
}
}
void LogMessage(String^ message)
{
try
{
File::AppendAllText(GetLogPath(), DateTime::Now.ToString("MM/dd/yyyy HH:mm:ss") + " : "
+ message + Environment::NewLine);
}
catch (Exception^ ex)
{
Console::WriteLine(ex->Message); // Print the error message.
Console::WriteLine(ex->StackTrace); //String that contains the stack trace for this
exception.
}
}
void Injector::LogMessage(String^ message)
{
::LogMessage(message);
}
DWORD StartThread(HANDLE hProcess, LPTHREAD_START_ROUTINE function, wchar_t * data)
{
try
{
auto buffLen = (wcslen(data) + 1) * sizeof(wchar_t);
void* remoteData = ::VirtualAllocEx(hProcess, NULL, buffLen, MEM_COMMIT,
PAGE_READWRITE);
if (remoteData)
{
LogMessage("VirtualAllocEx successful");
::WriteProcessMemory(hProcess, remoteData, data, buffLen, NULL);
LogMessage("WriteProcessMemory successful");
auto hThread = ::CreateRemoteThread(hProcess, NULL, 0,
function, remoteData, 0, NULL);
LogMessage("CreateRemoteThread successful");
if (!hThread)
{
LogMessage("CreateRemoteThread(): failed");
getch();
return -1;
}
::WaitForSingleObject(hThread, INFINITE);
LogMessage("WaitForSingleObject successful");
LogMessage("Thread finished");
DWORD exitCode;
::GetExitCodeThread(hThread, &exitCode);
::CloseHandle(hThread);
::VirtualFreeEx(hProcess, remoteData, 0, MEM_RELEASE);
return exitCode;
}
}
catch (Exception^ ex)
{
LogMessage(ex->Message); // Print the error message.
LogMessage(ex->StackTrace); //String that contains the stack trace for this exception.
return 0;
}
return 0;
}
bool^ Injector::Launch(Int32 processId, String^ assembly, String^ className, String^ methodName)
{
try
{
auto assemblyClassAndMethod = assembly + "|" + className + "|" + methodName;
LogMessage("Assembly Class and Method to load:" + assemblyClassAndMethod);
pin_ptr<const wchar_t> acmLocal = PtrToStringChars(assemblyClassAndMethod);
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
if (!hProcess)
{
LogMessage("failed to get process handle");
return false;
}
LogMessage("Got process handle");
LoadImagePath();
auto kernel = ::GetModuleHandle(L"kernel32");
(HMODULE)StartThread(hProcess, (LPTHREAD_START_ROUTINE)::GetProcAddress(kernel,
"LoadLibraryW"), DllPath);
LogMessage("Library loaded");
if (_routine)
{
LogMessage("Entered If _routine case");
StartThread(hProcess, _routine, (wchar_t*)acmLocal);
}
::CloseHandle(hProcess);
LogMessage("Completed launch function");
return true;
}
catch (Exception^ ex)
{
LogMessage(ex->Message); // Print the error message.
LogMessage(ex->StackTrace); //String that contains the stack trace for this exception.
return false;
}
}
//__declspec(dllexport)
DWORD WINAPI ThreadStart(void* param)
{
try
{
LogMessage("Thread started");
//CoInitialize(NULL);
String^ acmLocal = gcnew String((wchar_t *)param);
LogMessage(String::Format("acmLocal = {0}", acmLocal));
cli::array<String^>^ acmSplit = acmLocal->Split('|');
LogMessage(String::Format("About to load assembly {0}", acmSplit[0]));
auto assembly = Reflection::Assembly::LoadFile(acmSplit[0]);
if (assembly == nullptr)
{
LogMessage("Assembly loading returned null");
return 0;
}
LogMessage(String::Format("About to load type {0}", acmSplit[1]));
auto type = assembly->GetType(acmSplit[1]);
if (type == nullptr)
{
LogMessage("Getting type returned null");
return 0;
}
LogMessage(String::Format("Just loaded the type {0}", acmSplit[1]));
auto methodInfo = type->GetMethod(acmSplit[2], Reflection::BindingFlags::Static |
Reflection::BindingFlags::Public);
if (methodInfo == nullptr)
{
LogMessage("Getting method returned null");
return 0;
}
LogMessage(String::Format("About to invoke {0} on type {1}", methodInfo->Name,
acmSplit[1]));
auto returnValue = methodInfo->Invoke(nullptr, nullptr);
if (returnValue == nullptr)
{
returnValue = "NULL";
}
LogMessage(String::Format("Return value of {0} on type {1} is {2}", methodInfo->Name,
acmSplit[1], returnValue));
}
catch (Exception^ e)
{
LogMessage(e->Message); // Print the error message.
LogMessage(e->StackTrace); //String that contains the stack trace for this exception.
}
return 0;
}
#pragma managed(push, off)
BOOLEAN WINAPI DllMain(IN HINSTANCE hDllHandle,
IN DWORD nReason,
IN LPVOID Reserved)
{
_routine = (LPTHREAD_START_ROUTINE)&ThreadStart;
return true;
}
#pragma managed(pop)
Thanks in advance for your assistance, have a wonderful day!