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 InitThreadLPVOID param )
{  
    
bool success false;
    try
    {    
// Get the path of the cheat folder
        
GetModuleFileNameAhmCheatg_pathMAX_PATH );
        
// Find the last backslash
        
charpath_end NULL;
        for ( 
charptr g_path; *ptr; ++ptr )
        {
            if ( *
ptr=='\\' path_end ptr+1;
        }
        *
path_end '\0';

        
// Actually load everything
        
LoadTool();
        
success true;
    }
    catch ( const 
std::exceptionerr )
    {
        
log_errorerr.what() );
        if ( 
hGameWindow )
        {
            
std::string str"Fatal error:\n" );
            
str.appenderr.what() );
            
MessageBoxAhGameWindowstr.c_str(), "ERROR"MB_OK|MB_ICONWARNING );
        }
        
success false;
    }
    catch (...)
    {
        
log_error"Fatal error." );
        if ( 
hGameWindow MessageBoxAhGameWindow"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 )
        {
            
Sleep50 );
            if ( 
GetAsyncKeyStateVK_DELETE ) && GetAsyncKeyStateVK_CONTROL ) ) SignalExit();
        }
    }

    if ( 
g_loaded_as_plugin ) return 0;
    else 
FreeLibraryAndExitThreadhmCheat);
}
BOOL APIENTRY DllMainHMODULE hModuleDWORD dwReasonLPVOID lpReserved )
{
    if ( 
dwReason==DLL_PROCESS_ATTACH )
    {  
        
// Save our module handle
        
hmCheat hModule;
        
// No need to call us anymore
        
DisableThreadLibraryCallshModule );
        
// Just create the initializer thread with default stuff
        
hThread CreateThreadNULL0, &InitThreadNULL0NULL );
    }
    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_pathMAX_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 charfile )
{
    
std::string pathg_path );
    
path.appendfile );
    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_pathMAX_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 GetProcAddresshmEngine"CreateInterface" );IVEngineClientpEngine = ( 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 voidCreateInterface( const charnameintsomething );

    
// 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            LoadCreateInterfaceFn pfnFactoryCreateInterfaceFn pfnServer );
        
virtual void            Unloadvoid );
        
virtual void            Pausevoid );
        
virtual void            UnPausevoid );
        
virtual const char*        GetPluginDescriptionvoid );    
        
virtual void            LevelInitchar const *pMapName );
        
virtual void            ServerActivateedict_t *pEdictListint edictCountint clientMax );
        
virtual void            GameFramebool simulating );
        
virtual void            LevelShutdownvoid );
        
virtual void            ClientActiveedict_t *pEntity );
        
virtual void            ClientDisconnectedict_t *pEntity );
        
virtual void            ClientPutInServeredict_t *pEntitychar const *playername );
        
virtual void            SetCommandClientint index );
        
virtual void            ClientSettingsChangededict_t *pEdict );
        
virtual PLUGIN_RESULT    ClientConnectbool *bAllowConnectedict_t *pEntity, const char *pszName, const char *pszAddresschar *rejectint maxrejectlen );
        
virtual PLUGIN_RESULT    ClientCommandedict_t *pEntity, const CCommand &args );
        
virtual PLUGIN_RESULT    NetworkIDValidated( const char *pszUserName, const char *pszNetworkID );
        
virtual void            OnQueryCvarValueFinishedQueryCvarCookie_t iCookieedict_t *pPlayerEntityEQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue );
        
virtual void            OnEdictAllocatededict_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{

    
voidCreateInterface( const charnameintsomething )
    {
        if ( !
strcmpname"ISERVERPLUGINCALLBACKS003" ) ) return &CPlugin::g;
        else return 
NULL;
    }


    
CPlugin CPlugin::g;

    
bool CPlugin::LoadCreateInterfaceFn pfnFactoryCreateInterfaceFn 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
        
WaitForSingleObjecthThread);
    }
    
void CPlugin::Pause() { }
    
void CPlugin::UnPause() { }
    const 
charCPlugin::GetPluginDescriptionvoid )
    {
        return 
"L33t plugin description";
    }
    
void CPlugin::LevelInitchar const *pMapName ) { }
    
void CPlugin::ServerActivateedict_t *pEdictListint edictCountint clientMax ) { }
    
void CPlugin::GameFramebool simulating ) { }
    
void CPlugin::LevelShutdownvoid ) { }
    
void CPlugin::ClientActiveedict_t *pEntity ) { }
    
void CPlugin::ClientDisconnectedict_t *pEntity ) { }
    
void CPlugin::ClientPutInServeredict_t *pEntitychar const *playername ) { }
    
void CPlugin::SetCommandClientint index ) { m_cmdindex index; }
    
void CPlugin::ClientSettingsChangededict_t *pEdict ) { }
    
PLUGIN_RESULT CPlugin::ClientConnectbool *bAllowConnectedict_t *pEntity, const char *pszName, const char *pszAddresschar *rejectint maxrejectlen ) { return PLUGIN_CONTINUE; }
    
PLUGIN_RESULT CPlugin::ClientCommandedict_t *pEntity, const CCommand &args ) { return PLUGIN_CONTINUE; }
    
PLUGIN_RESULT CPlugin::NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ) { return PLUGIN_CONTINUE; }
    
void CPlugin::OnQueryCvarValueFinishedQueryCvarCookie_t iCookieedict_t *pPlayerEntityEQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ) { }
    
void CPlugin::OnEdictAllocatededict_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