How to convert a plugin-based cheat into an injected cheat
It's pretty easy. First let's set up our DllMain:in dllmain.cpp
PHP Code:
// dllmain.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"
#include "windows.h"
#include "phantom.hpp"
// We initialize ourselves in another thread since we have to wait for modules to be initialized
// If we wait in DLL_PROCESS_ATTACH we risk a deadlock ( waiting for modules that will be loaded after us )DWORD WINAPI InitThread( LPVOID param )
{
bool success = false;
try
{ // Get the path of the cheat folder
GetModuleFileNameA( hmCheat, g_path, MAX_PATH );
// Find the last backslash
char* path_end = NULL;
for ( char* ptr = g_path; *ptr; ++ptr )
{
if ( *ptr=='\\' ) path_end = ptr+1;
}
*path_end = '\0';
// Actually load everything
LoadTool();
success = true;
}
catch ( const std::exception& err )
{
log_error( err.what() );
if ( hGameWindow )
{
std::string str( "Fatal error:\n" );
str.append( err.what() );
MessageBoxA( hGameWindow, str.c_str(), "ERROR", MB_OK|MB_ICONWARNING );
}
success = false;
}
catch (...)
{
log_error( "Fatal error." );
if ( hGameWindow ) MessageBoxA( hGameWindow, "Fatal error.", "ERROR", MB_OK|MB_ICONWARNING );
success = false;
}
// Now wait untill we get an exit signal or ctrl&delete are pressed
if ( success )
{
while ( !g_signal_exit )
{
Sleep( 50 );
if ( GetAsyncKeyState( VK_DELETE ) && GetAsyncKeyState( VK_CONTROL ) ) SignalExit();
}
}
if ( g_loaded_as_plugin ) return 0;
else FreeLibraryAndExitThread( hmCheat, 0 );
}
BOOL APIENTRY DllMain( HMODULE hModule, DWORD dwReason, LPVOID lpReserved )
{
if ( dwReason==DLL_PROCESS_ATTACH )
{
// Save our module handle
hmCheat = hModule;
// No need to call us anymore
DisableThreadLibraryCalls( hModule );
// Just create the initializer thread with default stuff
hThread = CreateThread( NULL, 0, &InitThread, NULL, 0, NULL );
}
return TRUE;
}
What we do here is pretty easy, as DllMain is called when we're attached to the game process we create a new thread to initialize everything. As the comments explain I did this to prevent deadlocks when waiting for client.dll etc to get loaded.
In the thread I've set up a basic try/catch ( if an error happens it's gracefully handled and the error is displayed ).
I also have a dummy plugin interface in my cheat so people that prefer can load my cheat as a plugin ( unloading is handled a bit differently, I'll get to that later ).
SignalExit() function will unhook/close all cheats and set the g_signal_exit variable to true.
I have a phantom.hpp and phantom.cpp that deal with loading/unloading my cheat, pretty straightforward stuff.
phantom.hpp:
PHP Code:
#ifndef _CHEAT_PHANTOM_HGUARD
#define _CHEAT_PHANTOM_HGUARD
// ----------------------------------------------------------------
// Deals with basic cheat framework
// ----------------------------------------------------------------
// Note: include windows.h when used.
#include
// If we're loaded as a plugin, we cannot unload the old way. extern volatile bool g_loaded_as_plugin;// Notify our thread that it can shut down. CALL SignalExit() TO STOP THE CHEAT.extern volatile bool g_signal_exit;// Path of the cheat folder.extern char g_path[ MAX_PATH ];
// Some windows stuff.extern HANDLE hThread;extern HMODULE hmCheat;extern HWND hGameWindow;
// Get a file relative to the Tool folderinline std::string get_path( const char* file )
{
std::string path( g_path );
path.append( file );
return path;
}
// Unhook and unload the cheat.void SignalExit();
// Initialize the cheat.void LoadTool();
#endif // _CHEAT_PHANTOM_HGUARD
and phantom.cpp
PHP Code:
#include "stdafx.h"
#include "phantom.cpp"
// More includes...
// Global variablesvolatile bool g_loaded_as_plugin = false;volatile bool g_signal_exit = false;char g_path[ MAX_PATH ];HANDLE hThread = NULL;HMODULE hmCheat = NULL;HWND hGameWindow = NULL;// ----
void SignalExit()
{
// Use plugin_unload if loaded as a plugin
if ( g_loaded_as_plugin )
{
log_error( "SignalExit: The Tool was loaded as a plugin! Please use the plugin_unload command." );
return;
}
// ==== PUT YOUR PLUGIN UNLOAD CODE HERE ====
// Signal exit
g_signal_exit = true;
}
void LoadTool()
{
// ==== GRAB THE INTERFACES HERE ====
// Or find the appSystemFactory function
// ==== PUT YOUR PLUGIN LOAD CODE HERE ====
// JUST THROW AN std::runtime_error IN CASE OF AN ERROR}
This is nice and all but this doesn't give you access to the interfaceFactory ( aka appSystemFactory ) you're given when loaded as a plugin.
You can go 3 ways here, either get the interfaces directly from the correct .dll or find the appSystemFactory yourself.
Getting the interfaces manually
This does mean you have to know in which .dll the interface you want is located. (hint: the ICvar interface is created in vstdlib.dll)
PHP Code:
HMODULE hmEngine = GetModuleHandleA( "engine.dll" );CreateInterfaceFn pfnEngine = ( CreateInterfaceFn ) GetProcAddress( hmEngine, "CreateInterface" );IVEngineClient* pEngine = ( IVEngineClient* ) pfnEngine( "VEngineClient013", NULL );
client.dll gets the interfaces it needs within the CHLClient::Init function ( aka IBaseClientDLL ). Easy grabbing them from there.
Find the appSystemFactory function
I think I found it. I looked at the plugin_load code because it had to pass the appSystemFactory pointer as an argument. Open engine.dll in IDA and search for the referenced text string "ISERVERPLUGINCALLBACKS003".
Right after it got a pointer to your IServerPluginCallbacks it calls your Load function. I'll verify this later.
That's it basically.
Google for any random dll injector (or use one of the many posted here).
GL.
Adding plugin support on top of that is very easy. What happens is the cheat gets initialized trough DllMain but when the Load function is called, g_loaded_as_plugins is set to true. This means that SignalExit will just print an error and that unloading should be done trough plugin_unload ( if you unload anyway we'll get crashes because the game still thinks the dll is loaded ).
A call to Unload will change that variable, unload, wait for our thread to finish and return.
PHP Code:
// ================================================================ plugin.hpp
#ifndef _CHEAT_PLUGIN_HGUARD
#define _CHEAT_PLUGIN_HGUARD
// ----------------------------------------------------------------
// Plugin interface
// ----------------------------------------------------------------
// Allows one to inject the Tool as a plugin using 'plugin_load' in the game
#include "sdk.h"
#include "phantom.hpp"
namespace cheat{
// Required if the cheat is loaded as a plugin
DLL_EXPORT void* CreateInterface( const char* name, int* something );
// Server plugin
// This is just a dummy, loading is done through DLL_PROCESS_ATTACH
class CPlugin : public IServerPluginCallbacks
{
public:
// The global instance
static CPlugin g;
CPlugin() : m_cmdindex(0) { }
virtual bool Load( CreateInterfaceFn pfnFactory, CreateInterfaceFn pfnServer );
virtual void Unload( void );
virtual void Pause( void );
virtual void UnPause( void );
virtual const char* GetPluginDescription( void );
virtual void LevelInit( char const *pMapName );
virtual void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax );
virtual void GameFrame( bool simulating );
virtual void LevelShutdown( void );
virtual void ClientActive( edict_t *pEntity );
virtual void ClientDisconnect( edict_t *pEntity );
virtual void ClientPutInServer( edict_t *pEntity, char const *playername );
virtual void SetCommandClient( int index );
virtual void ClientSettingsChanged( edict_t *pEdict );
virtual PLUGIN_RESULT ClientConnect( bool *bAllowConnect, edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen );
virtual PLUGIN_RESULT ClientCommand( edict_t *pEntity, const CCommand &args );
virtual PLUGIN_RESULT NetworkIDValidated( const char *pszUserName, const char *pszNetworkID );
virtual void OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue );
virtual void OnEdictAllocated( edict_t *edict );
virtual void OnEdictFreed( const edict_t *edict );
private:
int m_cmdindex;
};
};
#endif // _CHEAT_PLUGIN_HGUARD
// ================================================================ plugin.cpp
#include "stdafx.h"
#include "plugin.hpp"
namespace cheat{
void* CreateInterface( const char* name, int* something )
{
if ( !strcmp( name, "ISERVERPLUGINCALLBACKS003" ) ) return &CPlugin::g;
else return NULL;
}
CPlugin CPlugin::g;
bool CPlugin::Load( CreateInterfaceFn pfnFactory, CreateInterfaceFn pfnServer )
{
// Loading is done trough DLL_PROCESS_ATTACH
g_loaded_as_plugin = true;
return true;
}
void CPlugin::Unload()
{
g_loaded_as_plugin = false; // Otherwise SignalExit() will fail
SignalExit();
// Wait to give the thread time to exit
WaitForSingleObject( hThread, 0 );
}
void CPlugin::Pause() { }
void CPlugin::UnPause() { }
const char* CPlugin::GetPluginDescription( void )
{
return "L33t plugin description";
}
void CPlugin::LevelInit( char const *pMapName ) { }
void CPlugin::ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) { }
void CPlugin::GameFrame( bool simulating ) { }
void CPlugin::LevelShutdown( void ) { }
void CPlugin::ClientActive( edict_t *pEntity ) { }
void CPlugin::ClientDisconnect( edict_t *pEntity ) { }
void CPlugin::ClientPutInServer( edict_t *pEntity, char const *playername ) { }
void CPlugin::SetCommandClient( int index ) { m_cmdindex = index; }
void CPlugin::ClientSettingsChanged( edict_t *pEdict ) { }
PLUGIN_RESULT CPlugin::ClientConnect( bool *bAllowConnect, edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ) { return PLUGIN_CONTINUE; }
PLUGIN_RESULT CPlugin::ClientCommand( edict_t *pEntity, const CCommand &args ) { return PLUGIN_CONTINUE; }
PLUGIN_RESULT CPlugin::NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ) { return PLUGIN_CONTINUE; }
void CPlugin::OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ) { }
void CPlugin::OnEdictAllocated( edict_t *edict ) { }
void CPlugin::OnEdictFreed( const edict_t *edict ) { }
};
Code:
loc_10160EC6: ; . mov eax, dword_103AF2D8 ; HMODULE hmServer push eax ; hModule call sub_102228E0 ; Get its CreateInterface function mov ecx, [esi+84h] mov edx, [ecx] ; vtable mov edx, [edx] ; 1st vfunc add esp, 4 push eax ; Pass gameServerFactory as the 2nd argument mov eax, dword_103AF2CC ; appSystemFactory :) push eax ; 1st argument call edx ; Call IServerPluginCallbacks::Load test al, al ; check if Load returned false in which case it failed jnz short loc_10160EF3
Pseudocode:
PHP Code:
CreateInterfaceFn pfnAppSystem = *reinterpret_cast<CreateInterfaceFn**>( FindPattern( [ "A1???? 50 E8???? 8B8E???? 8B11 8B12 83C404 50 A1????" in engine.dll ] ) + 26 );
0 comentarios:
Publicar un comentario