How to convert a plugin-based cheat into an injected cheat

On domingo, 12 de septiembre de 2010 0 comentarios

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 variables
volatile 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 ); 
Grabbing the interfaces from client.dll
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