Overview:
Alright, so I'm sure there are a lot of people out there who are slow to get started or have good ideas about actual bot logic but are getting stumbled up before they even get going. Over the last few weeks I've decided that I'd like to learn a bit about this type of program, but I had no clue where to get started.
This post is an attempt to bring people up to speed quickly so that they contribute in more meaningful ways.
This is ripe for copy-pasta, but it's not super usable as I've stripped a lot of error detection and recovery for the sake of getting to the point. I encourage you to read and understand this code and modify/rewrite it to suit yourself.
The bot is called sopha, which is why you'll see that everywhere.
I'm happy to hear feedback as I'd like to learn how to do this better, however I've made some odd choices in the creation of what I have no for the purpose of exploration and learning; I'll try to detail where/why I've done this in the remainder of this post.
Architecture:
My bot has (currently) 3 projects associated with it, this is to keep some modularity. This post will detail a loader, injector and bot_framework. There's really no reason why loader/injector should be seperate for what I've done, but I plan to augment this with a part 2 that details using a c# loader/loading the runtime into wow, thus I wanted to keep the actual dll injection as a seperate library.
loader:
Produces an .exe, this where the magic starts. The loaders only goal is to open wow and use the injector dll to load the bot_framework dll into wow. This is very basic. Note: I should create the process suspended, but it's fast enough that I get the dll injected and the init function called before wow actually has it's D3D interface up and running, which is fine.
loader.cpp
Code:
#include <windows.h>
#include <stdio.h>
#define DLL "sopha_injector.dll"
#define FUNCTION "Inject"
#define INJECTDLL "sopha_framework.dll"
#define INJECTFUNCTION "Initialize"
/******************************************************************************
** Function: main
** Description: entry does the bare minimum for this example
**
******************************************************************************/
int main()
{
printf("SoPHa\nsopha_loader.exe\nby: sophant\n\n");
printf("Attempting to load DLL: %s\n",DLL);
HINSTANCE loaderDLL = LoadLibrary(DLL);
if(!loaderDLL)
{
printf("Failed to load %s.\n",DLL);
return -1;
}
printf("Loaded.\n\n");
printf("Attempting to load exported function: %s\n",FUNCTION);
FARPROC injectFunction = GetProcAddress(loaderDLL,FUNCTION);
if(!injectFunction)
{
printf("Couldn't find exported function\n");
return -1;
}
printf("Loaded.\n\n");
//Create Inject Prototype
typedef bool (_cdecl * _injectfcn)(HANDLE, const char*, const char*, bool);
_injectfcn Inject = _injectfcn(injectFunction);
//Start wow and inject into it
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
printf("Launching WoW.exe\n");
CreateProcess(NULL,(LPSTR)"C:/Program Files (x86)/World of Warcraft/Wow.exe",NULL,NULL,false,NULL,NULL,NULL,&si,&pi);
printf("Injecting %s and calling %s\n",INJECTDLL, INJECTFUNCTION);
//Be advised, you should check to make sure these exist, otherwise it will fail strangely
Inject(pi.hProcess,INJECTDLL,INJECTFUNCTION,false);
FreeLibrary(loaderDLL);
return 1;
}
injector:
This is the DLL injector library. It supports injecting a custom dll and calling an exported initialization function. It will (someday) support immediate unloading of the injected DLL, however that's not in yet.
This utilizes the CreateRemoteThread method of dll injection.
Pretty basic.
injector.c
Code:
#include <windows.h>
#include <stdio.h>
// C version of what we're doing
// HMODULE mod = LoadLibrary(dll);
// FARPROC init = GetProcAddress(mod, func);
// __asm call init
// ExitThread(0); or FreeLibraryAndExitThread(mod);
/******************************************************************************
** Function: main
** Description: entry does the bare minimum for this example
**
******************************************************************************/
#ifdef __cplusplus // If used by C++ code,
extern "C" { // we need to export the C interface
#endif
__declspec( dllexport ) bool _cdecl Inject(HANDLE proc, const char* dll, const char* func, bool unload)
{
// Load up kernel32 and the needed exported functions
HMODULE kernel32 = LoadLibrary("kernel32.dll");
FARPROC loadlibrary = GetProcAddress(kernel32, "LoadLibraryA" );
FARPROC getprocaddress = GetProcAddress(kernel32, "GetProcAddress" );
FARPROC exitprocess = GetProcAddress(kernel32, "ExitProcess" );
FARPROC exitthread = GetProcAddress(kernel32, "ExitThread" );
FARPROC freelibraryandexitthread = GetProcAddress(kernel32, "FreeLibraryAndExitThread" );
// code is built in this buffer (do we know within 2 MAX_PATHs how
// large this will be. Does it need to be allocated via
// heapalloc or would malloc be okay?
LPBYTE code = (LPBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 1024);;
DWORD codeidx = 0;
// The memory in the process we write to
LPVOID codecaveAddress = VirtualAllocEx(proc, 0, 1024, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);;
DWORD dwCodecaveAddress = PtrToUlong(codecaveAddress);;
// Placeholder addresses to use the strings
DWORD dllAddr = 0;
DWORD dllNameAddr = 0;
DWORD funcNameAddr = 0;
// Where the codecave execution should begin at
DWORD codecaveExecAddr = 0;
// Handle to the thread we create in the process
HANDLE hThread = NULL;
// Old protection on page we are writing to in the process and the bytes written
DWORD oldProtect = 0;
DWORD bytesRet = 0;
//------------------------------------------//
// Variable initialization. //
//------------------------------------------//
// This section will cause compiler warnings on VS8,
// you can upgrade the functions or ignore them
// Build names
//Allocate space for the dll module handle, dll name and init function
//name
dllAddr = codeidx + dwCodecaveAddress;
memcpy(code + codeidx, &dllAddr, 4);
codeidx += 4;
dllNameAddr = codeidx + dwCodecaveAddress;
memcpy(code + codeidx, dll, (DWORD)strlen(dll) + 1);
codeidx += (DWORD)strlen(dll) + 1;
funcNameAddr = codeidx + dwCodecaveAddress;
memcpy(code + codeidx, func, (DWORD)strlen(func) + 1);
codeidx += (DWORD)strlen(func) + 1;
//Keep a pointer to the end of the data section so we know where to start
//exectuing our thread from
codecaveExecAddr = codeidx + dwCodecaveAddress;
//actual code cave
code[codeidx++] = 0x68; //push [addr]
memcpy(code + codeidx, &dllNameAddr, 4); //[addr]
codeidx += 4;
code[codeidx++] = 0xB8; // mov eax, [addr]
memcpy(code + codeidx, &loadlibrary, 4); //[addr]
codeidx += 4;
code[codeidx++] = 0xFF; //call eax
code[codeidx++] = 0xD0;
// eax <= loadlibrary(dll) -- eax has dll handle
code[codeidx++] = 0xA3; //move [addr], eax
memcpy(code + codeidx, &dllAddr, 4); //[addr]
codeidx += 4;
code[codeidx++] = 0x68; //push [addr]
memcpy(code + codeidx, &funcNameAddr, 4); //[addr]
codeidx += 4;
code[codeidx++] = 0x50; //push eax (module handle still in eax)
code[codeidx++] = 0xB8; //move eax, [addr]
memcpy(code + codeidx, &getprocaddress, 4); //addr
codeidx += 4;
code[codeidx++] = 0xFF; //call eax
code[codeidx++] = 0xD0;
code[codeidx++] = 0xFF; //call eax
code[codeidx++] = 0xD0;
// leave her in
if (!unload)
{
code[codeidx++] = 0x6A; //push
code[codeidx++] = 0x00; // 0
code[codeidx++] = 0xB8; //mov eax [addr]
memcpy(code + codeidx, &exitthread, 4); //[addr]
codeidx += 4;
code[codeidx++] = 0xFF; //call eax
code[codeidx++] = 0xD0;
}
else
{
//not supported
//do the same thing again...
code[codeidx++] = 0x6A; //push
code[codeidx++] = 0x00; // 0
code[codeidx++] = 0xB8; //mov eax [addr]
memcpy(code + codeidx, &exitthread, 4); //[addr]
codeidx += 4;
code[codeidx++] = 0xFF; //call eax
code[codeidx++] = 0xD0;
}
VirtualProtectEx(proc, codecaveAddress, codeidx, PAGE_EXECUTE_READWRITE, &oldProtect);
WriteProcessMemory(proc, codecaveAddress, code, codeidx, &bytesRet);
VirtualProtectEx(proc, codecaveAddress, codeidx, oldProtect, &oldProtect);
FlushInstructionCache(proc, codecaveAddress, codeidx);
HeapFree(GetProcessHeap(), 0, code);
hThread = CreateRemoteThread(proc, NULL, 0, (LPTHREAD_START_ROUTINE)((void*)codecaveExecAddr), 0, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(proc, codecaveAddress, 0, MEM_RELEASE);
return true;
}
#ifdef __cplusplus // If used by C++ code,
} // we need to export the C interface
#endif
bot_framework:
This is where the magic is. This is what gets injected into the wow process and initialized by the dll injector from above.
DLLMain doesn't do much here, it simply creates a console for debugging then drops out. This abides by the best practices for a dll. The real magic is in the exported Initialization function. This creates a d3d interface object then patches the vtable (create_device) so that we can grab the WoW d3d device that's being used.
Once our detoured create_device gets called, we go ahead and call the real create_device then steal the d3ddevice that's returned. We use that d3d device to (once again) patch a vtable, this time it's end scene.
Additionally, we spawn a thread to repatch the endscene detour every 100ms to defeat simple levels of endscene fixing from the target application (I don't think wow does this).
framework.cpp
Code:
#include <windows.h>
#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <process.h>
#include "application.h"
#include <d3d9.h>
#pragma comment(lib, "d3d9.lib")
#include <d3dx9.h>
#pragma comment(lib, "d3dx9.lib")
//create device info
typedef HRESULT (WINAPI* proto_createdevice) (LPDIRECT3D9, UINT, D3DDEVTYPE, HWND, DWORD, D3DPRESENT_PARAMETERS*, LPDIRECT3DDEVICE9*);
HRESULT WINAPI sopha_createdevice (LPDIRECT3D9, UINT, D3DDEVTYPE, HWND, DWORD, D3DPRESENT_PARAMETERS*, LPDIRECT3DDEVICE9*);
proto_createdevice real_createdevice = NULL;
//endscene info
typedef HRESULT (WINAPI* proto_endscene) (LPDIRECT3DDEVICE9);
HRESULT WINAPI sopha_endscene (LPDIRECT3DDEVICE9);
proto_endscene real_endscene = NULL;
//other functions
bool sopha_vtablecreate(void);
void sopha_createconsole(void);
//repatch loop (good idea from whoever came up with it, forget where I saw this, though :-\)
DWORD WINAPI sopha_repatch(LPVOID);
//Variable
PDWORD sopha_vtable = NULL;
LPDIRECT3DDEVICE9 sopha_d3ddev = NULL;
HMODULE wow_baseAddress;
/******************************************************************************
** Function: DllMain
** Description: main entry point for dll, just create our console here
**
******************************************************************************/
BOOL WINAPI DllMain(HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
sopha_createconsole();
break;
case DLL_PROCESS_DETACH:
FreeConsole();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
/******************************************************************************
** Function: sopha_createconsole
** Description: creates the console in the wow process for printf statements
**
******************************************************************************/
void sopha_createconsole()
{
FILE *out;
FILE *in;
FILE *err;
AllocConsole();
out = _fdopen(_open_osfhandle(PtrToUlong(GetStdHandle(STD_OUTPUT_HANDLE)), _O_TEXT), "w");
in = _fdopen(_open_osfhandle(PtrToUlong(GetStdHandle(STD_INPUT_HANDLE)), _O_TEXT), "r");
err = _fdopen(_open_osfhandle(PtrToUlong(GetStdHandle(STD_ERROR_HANDLE)), _O_TEXT), "w");
*stdout = *out;
*stdin = *in;
*stderr = *err;
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}
/******************************************************************************
** Function: Initialize
** Description: main init function, must be called before d3d->createdevice
**
******************************************************************************/
#ifdef __cplusplus // If used by C++ code,
extern "C" { // we need to export the C interface
#endif
__declspec(dllexport) void __cdecl Initialize(void)
{
if(sopha_vtablecreate())
{
wow_baseAddress = GetModuleHandle(NULL);
}
else
{
}
}
#ifdef __cplusplus
}
#endif
/******************************************************************************
** Function: sopha_vtablecreate
** Description: initialize our hook by patching the d3d interface vtable
**
******************************************************************************/
bool sopha_vtablecreate(void)
{
LPDIRECT3D9 d3d = Direct3DCreate9(D3D_SDK_VERSION);
if(!d3d)
{
return false;
}
sopha_vtable = (PDWORD)*(PDWORD)d3d;
d3d->Release();
DWORD prot;
if(VirtualProtect(&sopha_vtable[16], sizeof(DWORD), PAGE_READWRITE, &prot) != 0)
{
*(PDWORD)&real_createdevice = sopha_vtable[16];
*(PDWORD)&sopha_vtable[16] = (DWORD)sopha_createdevice;
if(VirtualProtect(&sopha_vtable[16], sizeof(DWORD), prot, &prot) == 0)
{
return false;
}
}
else
{
return false;
}
return true;
}
/******************************************************************************
** Function: sopha_createdevice
** Description: our detoured create device, this intercepts the call to
** create device so we can repatch the wow device vtable
******************************************************************************/
HRESULT WINAPI sopha_createdevice(LPDIRECT3D9 p1, UINT p2, D3DDEVTYPE p3, HWND p4,
DWORD p5, D3DPRESENT_PARAMETERS* p6,
LPDIRECT3DDEVICE9* p7)
{
HRESULT res = real_createdevice(p1, p2, p3, p4, p5, p6, p7);
DWORD prot;
if(VirtualProtect(&sopha_vtable[16], sizeof(DWORD), PAGE_READWRITE, &prot) != 0)
{
*(PDWORD)&sopha_vtable[16] = *(PDWORD)&real_createdevice;
real_createdevice = NULL;
if(VirtualProtect(&sopha_vtable[16], sizeof(DWORD), prot, &prot) == 0)
{
return D3DERR_INVALIDCALL;
}
}
else
{
return D3DERR_INVALIDCALL;
}
if(res == D3D_OK)
{
sopha_d3ddev = *p7;
sopha_vtable = (PDWORD)*(PDWORD)sopha_d3ddev;
*(PDWORD)&real_endscene = (DWORD)sopha_vtable[42];
if(CreateThread(NULL, 0, sopha_repatch, NULL, 0, NULL) == NULL)
{
return D3DERR_INVALIDCALL;
}
sopha_initialize(wow_baseAddress);
}
return res;
}
/******************************************************************************
** Function: sopha_endscene
** Description: our hax0red endscene
**
******************************************************************************/
HRESULT WINAPI sopha_endscene(LPDIRECT3DDEVICE9 p1)
{
sopha_apptick();
return real_endscene(p1);
}
/******************************************************************************
** Function: sopha_repatch
** Description: i owe someone some props for this, but I can't remember who
** this just constantly repatches the hook
******************************************************************************/
DWORD WINAPI sopha_repatch(LPVOID Param)
{
(void)(Param==0);
while(1)
{
Sleep(100);
*(PDWORD)&sopha_vtable[42] = (DWORD)sopha_endscene;
}
return 42; //never reached, may as well return the answer.
}
From here, I have a simple application layer that will actually run all the bot logic. You can see the calls to Initialize and AppTick in here. This just makes it cleaner for me. Also you'll notice that the initialization takes the wow base address as it's parameter, which will facilitate setting up all the function pointers in part 2.
application.cpp
Code:
#include <stdio.h>
#include <windows.h>
#include "application.h"
/******************************************************************************
** Function: sopha_initialize
** Description: gets called once the bot is fully injected
**
******************************************************************************/
void sopha_initialize(HMODULE base)
{
printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nsopha initialized (%08x)...\n",base);
}
/******************************************************************************
** Function: sopha_apptick
** Description: get's called as often as the screen refreshes
**
******************************************************************************/
void sopha_apptick(void)
{
static unsigned int timesran = 0;
printf("sopha_apptick ran %d times.\r",timesran++);
}
application.h
Code:
#ifndef __sopha_application_h__
#define __sopha_application_h__
void sopha_initialize(HMODULE base);
void sopha_apptick(void);
#endif
Please feel free to comment/question/critique this code or the concepts. I'd also like some feedback on where you'd like to see this go next.
Sorry if this post has some typos and errors, I'll work to correct them later tonight.
Thanks for your time!
-Ev