BEFORE anyone asks, there is no ill intent here. This project is merely for educational and personal use, and, at the most, is designed to be a "cheat engine" or possible future anti-cheat mechanism. There is no intention of using this in any malicious manner.
I have a solution with the following 3 projects:
- 32-bit MFC application which allows the user to choose a process to inject
- 32-bit Win32 DLL to inject into the target process via VirtualAlloc + WriteProcessMemory + CreateRemoteThread + LoadLibrary technique
- 32-bit Win32 Console application to test when a local injection has occurred
In the DLL, I have created the following set of functions:
////////////////
// Deceiver.h //
////////////////
#ifdef DECEIVED_EXPORTS
# define DECEIVED_API __declspec(dllexport)
#else
# define DECEIVED_API __declspec(dllimport)
#endif
volatile class DECEIVED_API CDeceived
{
public:
CDeceived(void);
virtual HANDLE WINAPI GetRunningProcess();
virtual DWORD WINAPI GetRunningProcessId();
virtual HANDLE WINAPI GetRunningThread();
virtual DWORD WINAPI GetRunningThreadId();
virtual LPVOID WINAPI Allocate(DWORD size);
virtual BOOL WINAPI Deallocate(LPVOID address, DWORD size);
virtual BOOL WINAPI Read(LPVOID address, LPVOID buffer, DWORD size);
virtual BOOL WINAPI Write(LPVOID address, LPVOID buffer, DWORD size);
virtual BOOL WINAPI ReadEx(HANDLE hProcess, LPVOID address, LPVOID buffer, DWORD size);
virtual BOOL WINAPI WriteEx(HANDLE hProcess, LPVOID address, LPVOID buffer, DWORD size);
WCHAR m_signature[10];
};
extern DECEIVED_API CDeceived* deceiver;
LPVOID DECEIVED_API WINAPI RemoteInitialize();
//////////////////
// Deceiver.cpp //
//////////////////
#include "stdafx.h"
#include "Deceived.h"
DECEIVED_API CDeceived* deceiver = NULL;
CDeceived::CDeceived()
{
memcpy(&m_signature[0], L"Deceived?\0", 10);
}
HANDLE WINAPI CDeceived::GetRunningProcess()
{
return GetCurrentProcess();
}
DWORD WINAPI CDeceived::GetRunningProcessId()
{
return GetCurrentProcessId();
}
HANDLE WINAPI CDeceived::GetRunningThread()
{
return GetCurrentThread();
}
DWORD WINAPI CDeceived::GetRunningThreadId()
{
return GetCurrentThreadId();
}
LPVOID WINAPI CDeceived::Allocate(DWORD size)
{
return VirtualAlloc(NULL, size, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE);
}
BOOL WINAPI CDeceived::Deallocate(LPVOID address, DWORD size)
{
return VirtualFree(address, size, MEM_RELEASE);
}
BOOL WINAPI CDeceived::Read(LPVOID address, LPVOID buffer, DWORD size)
{
DWORD dwBytesRead = 0;
BOOL bRet = ReadProcessMemory(GetCurrentProcess(), address, buffer, size, &dwBytesRead);
return bRet && (dwBytesRead > 0);
}
BOOL WINAPI CDeceived::Write(LPVOID address, LPVOID buffer, DWORD size)
{
DWORD dwBytesWritten = 0;
BOOL bRet = WriteProcessMemory(GetCurrentProcess(), address, buffer, size, &dwBytesWritten);
return bRet && (dwBytesWritten > 0);
}
BOOL WINAPI CDeceived::ReadEx(HANDLE hProcess, LPVOID address, LPVOID buffer, DWORD size)
{
DWORD dwBytesRead = 0;
BOOL bRet = ReadProcessMemory(hProcess, address, buffer, size, &dwBytesRead);
return bRet && (dwBytesRead > 0);
}
BOOL WINAPI CDeceived::WriteEx(HANDLE hProcess, LPVOID address, LPVOID buffer, DWORD size)
{
DWORD dwBytesWritten = 0;
BOOL bRet = WriteProcessMemory(hProcess, address, buffer, size, &dwBytesWritten);
return bRet && (dwBytesWritten > 0);
}
LPVOID DECEIVED_API WINAPI RemoteInitialize()
{
#ifdef _DEBUG
MessageBoxA(NULL, "Please attach a debugger", "Deceived::RemoteInitialize", MB_ICONINFORMATION);
#endif
if(deceiver != NULL) delete deceiver;
deceiver = new CDeceived();
LPVOID lpReturn = deceiver->Allocate(sizeof(deceiver));
if(lpReturn) {
deceiver->Write(lpReturn, &deceiver, sizeof(deceiver));
return lpReturn;
}
return NULL;
}
After the MFC application injects the DLL into the Console test project...
It calls RemoteInitialize() to initialize the remote class and return an address in virtual memory space to the caller, where it should then be localized into a shared instance of the CDeceived class. Here's how I am handling this:
BOOL CDeceiverHook::Validate(LPVOID lpDeceivedAddress)
{
CDeceived *deceiver = new CDeceived();
BOOL bRet = deceiver->ReadEx(hProcess, lpDeceivedAddress, &m_deceived, sizeof(m_deceived));
int cmp = _wcsicmp(m_deceived->m_signature, L"Deceived?");
return bRet && (cmp == 0);
}
...but the localized class pointer doesn't seem to point to the remote one, and instead holds several NULL pointers in it's virtual table which cause access violations if you try to execute any of them.
I should probably note that I have successfully given the MFC application the proper debugging rights via OpenThreadToken, ImpersonateSelf and SetPrivilege. Would I also have to somehow lock the class's address in memory, perhaps? Is the volatile keyword not enough, or incorrectly used here? What do I need to do to retrieve the very same pointer allocated by the DLL?
Thanks in advance! Upvotes will be given for any valid advice.