大家好,我是山里猴,上次发了篇经验分享的帖子后,有些朋友说能不能做一个从零开始的系列. 获取怪物信息
0x01:工具及背景知识
1.cheat engine
ce查内存神器,还能调试 查看反汇编静态代码用的 查看代码或者调试都很有用 c++:用来编写辅助程序 首先找到怪物的血量地址,如下图.
承蒙大家厚爱,小弟受宠若惊,哈哈,从零开始系列来了.
说到怎么做好一个辅助啊,这我非常有经验,辅助最重要的就是,对线期一定不要跟大哥抢钱,大哥上去送的时候一定要及时加血,并用自己伟岸的身躯给大哥挡伤害.
!!!不对,这里是吾爱论坛啊,不好意思,走错片场了,下面回归正题.
本期内容先讲下怎么从目标程序中获取怪物信息.
这期内容的markdown我也会同步放到我的gayhub项目中的https://github.com/shanlihou/hack_warspear,的tutorial文件夹下面
python:用来在immunity debugger中编写插件
lua:用来在ce中编写插件
汇编:能看懂就行,不要求会写,逆向程序逻辑时候需要

还记得我们上次讲怎么用ce+lua吗.
我们编写如下脚本
代码位置:$(ROOT)/info/find_hp_base.lua
好,我们现在拿着如上代码在ce上执行.执行完后,就可以操作玩家打怪了.
在玩家攻击后会看到如下图打印

上图中红框框出来的这个打印就是一次扣血的调用过程,我们就拿着这个找基址
70e76a->70fa54->747e0e->87c579->86052b->75b247ab->75b02aac->75b044db->75b041e0->8602c6->
现在用immunity debugger打开我们的游戏并定位到位置0x570FFB

我们从上图1位置(0x570FFB)可以看出血量应该是保存在esi+0xf4这个位置,那么我们现在就要找esi的值是怎么来的
我们网上看图2位置(0x570ff2)esi是从eax赋值的,那么就要找eax怎么来的.
在网上看图3位置有一个函数调用,一般汇编的函数如果有返回值的话是保存在eax的.所以我们要跟进去看是不是这个函数返回的.

上图是进入函数0x715720后,的相关内容,我给大家翻译一下上面的内容,下面是翻译后的c++代码.
eax = *(ecx + 0x30); eax = *(eax); edx = *(ebp + 0x8); while (1): { ecx = *(eax + 0x10);// MOV ECX,DWORD PTR DS:[EAX+10] if (ecx > edx){//CMP ECX,EDX; JBE SHORT warspear.0071573C eax = *(eax + 0x4); continue; } else if (exc < edx){//JNB SHORT warspear.0071574B eax = *(eax + 0x8); continue; } else{ eax = *(eax + 0x14); break; } }有一定c/c++语言功底的朋友不难看出,上面代码是一段对数据结构二叉树的查找过程.所以我们可以确定了,这款游戏的怪物信息是使用二叉树来存储的.
把上图的eax想象成一个节点.那么eax+0x10这个位置存储的就可能是类似id的值,然后跟edx这个目标值来比较.
如果当前id > edx 则从右节点继续找(eax + 0x04)
如果当前id < edx 则从左节点继续找(eax + 0x08)
如果相等就用eax = *(eax + 0x14)然后返回.
所以从上图汇编代码我们得到三个信息:
1.a的确是返回值
2.怪物信息存在一个二叉树里
3.从第一行(MOV EAX,DWORD PTR DS:[ECX+38])看出,咱门下一个追踪目标就是这个ecx了
我们继续顺着上面汇编图的4,5,6会发现我们已经找到了基址(哈哈,很尴尬,前面的查看堆栈貌似没啥用啊,没关系,下次讲一定会把它用上的.因为我之前完成的时候已经过去很久了,写这篇文章时候是边做边写的.所以没想到没用上)
从"MOV EAX,DWORD PTR DS:[9A45B0]" 中得到基址 0x9A45B0
我们编写如下lua代码
代码位置:$(ROOT)/info/print_tree.lua
把上面代码放在ce中执行就会看到打印如下

上图中1已经把我自己的hp打印出来了.可以看出这个基址是没有问题的.
0x06:写我们的辅助程序好了现在完事具备,只欠东风,下面就开始写我们的辅助程序.

如图是用vs2019打开工程后的目录结构.
图上标号1位置这个工程是辅助逻辑相关,比如查看怪物信息,攻击怪物,捡去物品等,这个最后生成了一个静态库
图上标号2位置这个工程是注入后的主进程,最后编译为一个动态库.(实际上1和2可以合并一起,因为2在运行过程中调用gameData工程里的接口,所以把gameData静态链接进2)
图上标号3位置这个工程是用来把上面编写的动态库注入到目标程序中用的.
下面我们就其中一些重要并在本章节用到的代码讲解一下
1.注入过程代码位置:$(ROOT)/InjectDll/InjectDll.cpp
warHandle = FindWindowA(GAME_CLASS_NAME, NULL);//根据窗口名字找到目标进程的窗口句柄 if (warHandle == 0) return; printf("find handle\n"); GetWindowThreadProcessId(warHandle, &pid); // 根据目标句柄获取目标主进程pid if (pid == 0) { printf("get pid error\n"); return; } hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); // 获取目标进程句柄,并获取所有操作权限 if (hProcess == NULL) { printf("get hProcess error\n"); return; } addressDW = (LPDWORD)VirtualAllocEx(hProcess, NULL, 256, MEM_COMMIT, PAGE_READWRITE); //在目标进程内申请一段内存,主要用来存放即将被我们注入的动态库的全路径 if (addressDW == NULL) { printf("get hProcess error\n"); return; } WriteProcessMemory(hProcess, addressDW, dllPath, strlen(dllPath) + 1, &byWriteSize); //将动态库全路径保存到刚刚申请的内存里 if (byWriteSize < strlen(dllPath)) { printf("write memmory failed!"); return; } threadHandle = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryA, addressDW, NULL, NULL); //在目标进程内创建线程线程,线程会执行加载动态库的操作 WaitForSingleObject(threadHandle, 0xFFFFFFFF); //等待刚刚加载动态库操作完成,好了,到此,我们的注入操作完成. CloseHandle(threadHandle); VirtualFreeEx(hProcess, addressDW, 256, MEM_COMMIT); CloseHandle(hProcess); 2.注入后的主函数代码位置:$(ROOT)/GameData/HookGameMainThread.cpp
LRESULT CALLBACK GameWndProc(int nCode, WPARAM wParam, LPARAM lParam) { CWPSTRUCT* lpArg = (CWPSTRUCT*)lParam; if (nCode == HC_ACTION) { if (lpArg->hwnd == getGameWndHandle() && lpArg->message == g_myMsgCode) { // 注入后的ui线程会给主线程发消息,消息被钩子在这里截获,截获后,根据消息类型执行相关操作 doAction((MsgAction)lpArg->wParam); return 1; } } return CallNextHookEx(g_hhkGame, nCode, wParam, lParam); } DWORD HookMainThread() //注入后的主函数其实会实例化一个ui窗口,然后再窗口主进程里执行这个函数 { log_debug("hook ok\n"); HWND hGame = getGameWndHandle(); // 获取主线程窗口句柄,这个要通过ce实现查找主线程句柄存放基址 DWORD ndThreadId = GetWindowThreadProcessId(hGame, NULL); // 根据主线程句柄获取主线程id if (ndThreadId == NULL) { return 1; } g_hhkGame = SetWindowsHookExA(WH_CALLWNDPROC, GameWndProc, NULL, ndThreadId); // 注册一个钩子函数到主线程中,钩子函数为上面的GameWndProc return 1; }最终效果如下图所示:

大家会看到,多了一个图标和我们目标进程一样,名字叫Dialog的窗口,这个就是注入后的UI线程.
点击窗口中的test按钮,会给主线程发送一个MsgTest的消息,主线程中收到消息后会把怪物信息输出到e:\shlog\warspear.log里面(这个位置是写死的,大家可以在GameData/debug.h文件里修改)
可以看到,日志输出和我们在ce里面看到的是一样的,到此,我们的获取怪物信息就ok了.
大家有什么不明白,欢迎给我提issue.




































































































查看全部评分