Originally Posted by
bnovc
A little background: I've been reading this forum and gamehacking.com quite a bit in regard to memory reading techniques. I am also pretty familiar with C/C++/C# (I write embedded C for work). I've done some assembly but not much and even less for x86 (although the concepts are the same).
So I have a couple questions:
1. How do people originally detect memory addresses? All of the tutorials that I see mention memory address they've already found. For something simple like finding health, I know that you can search, lower your health, etc. (gone through the Cheat Engine tutorial and done this), but how about finding the beginning of the player struct then or (more difficult?) finding the list of enemies and their information?
2. Similarly, (maybe answered by #1's answer) how do you re-detect the addresses when the game is patched / re-run? Do you have to go through the process again?
3. Are there existing joint projects to make bots? It seems like a pretty time consuming task to get reasonable AI and a good set of features. It seems like generic code for movement, combat, etc. could be setup an distribute for people to contribute to with more guarded code for the memory read (this could then be abstracted per game easily, too).
4. Is there API to detect if another process is reading your memory? e.g. Does Warden only operate by having a list of processes and then checks for memory changes (I assume it makes a hash of its memory periodically?).
Any tutorial/articles links would greatly be appreciated.
Thanks for any information & help.
Answer 1. There're many memory address people are mentioning, for example function address, object status offsets, other enums or structs. They are found in different ways. For lua functions, there's an easy way, just search the lua function names, say 'UnitAura". In ida, you'll find
Code:
.data:00A3C96C dd offset sub_69F5C0
.data:00A3C970 dd offset aUnitbuff ; "UnitBuff"
.data:00A3C974 dd offset sub_6A9990
.data:00A3C978 dd offset aUnitdebuff ; "UnitDebuff"
.data:00A3C97C dd offset sub_6A99E0
.data:00A3C980 dd offset aUnitaura ; "UnitAura"
.data:00A3C984 dd offset sub_6A9A30
For for object status offsets, you can search "OBJECT_FIELD_GUID". It can be used to build a list of descriptors struction.
Code:
void genDescriptors(){
/* storage struct:
{obj,item} for item
{obj,item, container} for container
{obj,unit} for unit
{obj,unit,player} for player
{obj,gameobject}
{obj,dynamic}
{obj,corpse}
*/
char *types[8] = {"obj","container","item","unit","player","gameobject","dynamic","corpse"};
TCHAR *datatype[6] = {L"unk00", L"int32", L"unk02", L"float32",L"GUID",L"chars"};
UINT prevSecSize = 0;
UINT baseptr = 0x105CF10; //// <<<<<<<<<<<< ptr to "OBJECT_FIELD_GUID"
for(int sec=0;sec<8;sec++){
if(ReadInt(baseptr)==0) baseptr+=4; ///// shift by one DWORD. special treatment for boundry between 'player' and 'unit'. there's an additional 0x00000000;
UINT i = 0;
UINT ptr,curoffset,cursize,other,endmark;
TCHAR str[64];
TCHAR outp[200];
char outpp[400];
FILE *pf;
fopen_s(&pf, types[sec],"w");
while(true){
ptr = ReadInt(baseptr);
curoffset = ReadInt(baseptr+0x4); //total relative index count in DWORD
cursize = ReadInt(baseptr+0x8);
other = ReadInt(baseptr+0xC);
if (other>=6)
other = 0;
endmark = ReadInt(baseptr+0x18); // the next offset , used to test whether this section has ended.
ReadUTF8((LPCVOID)ptr,str,64);
_stprintf_s(outp,L"[%08X]%s[%08X]\t=\t0x%08X[+%08X] \t ; // type: %s(%d), size: %d * DWORD\n",baseptr,str,ptr,curoffset*4,prevSecSize,datatype[other%6],other,cursize);
int ascLen = WideCharToMultiByte(CP_ACP,0,outp,-1,(LPSTR)outpp,0,NULL,NULL);
ascLen = WideCharToMultiByte(CP_ACP,0,outp,-1,(LPSTR)outpp,ascLen, NULL,NULL);
fwrite(outpp, sizeof(char),strlen(outpp), pf);
if(endmark!=curoffset+cursize) {
prevSecSize += (cursize+curoffset)*4;
baseptr+=0x14;
break;
}
baseptr+=0x14;
}
fclose(pf);
printf("descriptor %s has been dumped\n",types[sec]);
}
}
For generating the basePtr of list of currently visible units (Assuming you already opened the process),
Code:
BOOL GenBP()
{
HANDLE snaphnd = CreateToolhelp32Snapshot(0x4,0);
if (!snaphnd)
{
//errmsg(L"Can't create toolhelp32 snapshot.\n");
return false;
}
THREADENTRY32 info;
info.dwSize = 7*sizeof(UINT);
BOOL more_thread = true, found = false;
THREAD_BASIC_INFORMATION tbi;
ULONG retlong;
tbi.TebBaseAddress = 0;
playerguid.LowPart = playerguid.HighPart = basepoint = 0;
// Get a handle to the DLL module.
HINSTANCE hinstLib = LoadLibrary(TEXT("ntdll"));
pfnNtQueryInformationThread ProcAdd;
// If the handle is valid, try to get the function address.
if (hinstLib != NULL)
{
ProcAdd = (pfnNtQueryInformationThread) GetProcAddress(hinstLib, "NtQueryInformationThread");
// If the function address is valid, call the function.
if (NULL == ProcAdd)
{
printf("can't get NtQueryInformationThread function!");
}
FreeLibrary(hinstLib);
}else{
printf("can't get loadlibrary ntdll function!");
}
if (Thread32First(snaphnd, &info) && NULL != ProcAdd)
{
while (more_thread && !found)
{
if (info.th32OwnerProcessID==wowpid)
{
if ((thrdhnd = OpenThread(0x40, false, wowtid)))
{
// printf("Handle thread successfully opened: %08X\n", (UINT)thrdhnd);
if (!ProcAdd(thrdhnd,(THREADINFOCLASS )0, &tbi, 28, &retlong))
{
// hexdump(&tbi, sizeof(tbi));
// hexdump((LPDWORD)(tbi.TebBaseAddress), 0x100);
// hexdump((void*)(tbi.TebBaseAddress), 0x100);
// hexdump(&(tbi.TebBaseAddress), 0x100);
tlsoffset = ReadInt((PBYTE)(tbi.TebBaseAddress) + 0x2c);
targetslot = ReadInt((PBYTE)tlsoffset);
basepoint = ReadInt((PBYTE)(targetslot+16)); // sometimes it'll be exchanged with playerguid
playerguid = ReadGuid((PBYTE)(targetslot+8));
found = true;
// printf("****************\ntlsoffset(fs:[2c]): %08X\n\
//targetslot([tlsoff]): %08X\n\
//basepoint([tarslot+16]): %08X\n\
//guid: %08X%08X\n****************\n",tlsoffset, targetslot, basepoint,playerguid.HighPart, playerguid.LowPart);
}
else
{
printf("can't query thread information");
}
CloseHandle(thrdhnd);
}
else
{
printf("can't open thread");
}
}
more_thread = Thread32Next(snaphnd, &info);
}
if (!found)
;//errmsg(L"Wired! no thread found for wow.exe.\n");
}
else
{
//errmsg(L"Thread32First failed.\n");
}
CloseHandle(snaphnd);
return found;
}
With the basepoint (It's the start of list of all the loaded units, players, gameobjects, items), you can build a list of units by enumerating them as:
Code:
BOOL enumAllObj(WOWOBENUMPROC fn)
{
UINT currentptr = ReadInt((LPCVOID)(basepoint + 0x0C));
int cnt = 0;
int mobind;
while ((currentptr != 0) && ((currentptr &1) ==0) && (currentptr !=0x1c)){
freeObj(&all[cnt]);
updateObj(currentptr, &all[cnt]);
if (IsValidOb(&all[cnt]))
{
// if unitobj.guid==mbt.playerguid:
// playerobj = currentptr
// else:
// oblist.append(unitobj)
//if (ob.type!= W_CONTAINER && ob.type!=W_ITEM)
if(playerguid==all[cnt].guid) {
updateObj(currentptr, &player);
}else if(IsValidOb(&player) &&
szMobList.find(_bstr_t(all[cnt].name))!=std::string::npos &&
( !all[cnt].isDead()||
all[cnt].isLootable())){
for(mobind=0;mobind<MAXTRACK;mobind++){
if(!mobsnearby[mobind] ||
!IsValidOb(mobsnearby[mobind]) ||
player.distanceObj(&all[cnt])<=player.distanceObj(mobsnearby[mobind])){
mobsnearby[mobind] = &all[cnt];
break;
}
}
}
if (all[cnt].type==W_CORPSE && fn(&all[cnt]))
break;
cnt++;
}else freeObj(&all[cnt]);
currentptr = ReadInt(LPCVOID(currentptr + 0x3c));
}
}
For other functions, you can trace from the found lua functions, and if you're really good at reversing you'll find other useful functions. Of course, it's much easier to use the info dumps threads to check what other people have found. Big Thank goes to Apoc. The key is to find some signature or patterns which are kept the same no matter how wow patches. Some strange strings would be the best choices since they are easy to search in ida.
Answer 2. All these methods are the same since wow 1.x times (five or six years ago)... And I was like you searching for these information. I learnt a lot from a radar program called "bwh" loader or something like that. It's open source! Great credits to the author "bwh". That's why I want to share my source as well, to teach the next generations..
Answer 3. To me , the most challenging part is the bot's AI. I sincerely hope to have a library which allows user to write scripts(xml seems to be a good choice) to run his customized bot.
Answer 4. I've barely zero knowledge about warden. Left to be answered by Cypher or other people. But until yesterday, my bot is not detected as I'm using ReadProcessMemory only. I began to use WriteProcessMemory now to call the wow functions. Bless my bot..