新剑侠情缘——编写无限内力辅助
工具:Exeinfo PE、CheatEngine、OllyDbg、VS2019
游戏:新剑侠情缘之梦里回眸2.0(https://tieba.baidu.com/p/2715359831?red_tag=1716562167剑侠情缘吧内下载)
-
拖入PEiD等工具查看是什么语言编写,是否带壳
-
查看导入表,查看到一个Sword.dll,看函数英文名字,像是绘图、读档存档、跑步等函数,猜测攻击等函数可能也在里面
1 内力
1.1 CE附加查找内力有关地址
-
先随意释放技能,内力会慢慢回复(看到这个界面是不是很亲切,玩过的亲们)
-
下面需要注意的是,使用CE查看是谁访问了这个地址时是附加查看的(坑1,必须回到游戏中运行一下,才有显示),再使用OD附加时需要用CE再打开一次才可以用附加(坑2,游戏不能窗口化,每次都要重新调分辨率)
-
OD中单步分析,发现疑似 ‘内力恢复’;
- 其中 [ECX] 应该是一个全局变量,所以通过一个全局变量,最终会找到 ‘当前内力值地址’;猜测下面可能还会比较 最大内力值,用以判断是否继续恢复内力值
- 单步跟出,发现只是在恢复内力,去下一个地址里查看,发现几乎同样的操作;又看见SendMessage字样,猜测可能是在给窗口发消息?
- 好吧,最后试了下,没找到,剩下的两个地址是把恢复的内力值更新显示到人物属性面板上
1.2 寻找消耗内力的地址
- 上面的步骤没有找到消耗内力的地址,基本全是恢复内力的操作(因为站着不动,会慢慢恢复内力);这次手快一点,按了下《洗髓经》马上转出,看截图,好像找到了消耗内力的地址,比上图多出两个未知的地址:
- OD附加,在0042EEF6处下断,恢复游戏运行并回到游戏中,没断下!!!因为之前下的断,都是恢复内力的,一回到游戏中就断下了,所以这次感觉终于找到地方了,释放个技能试试会不会断下
- hhhh,终于断下了,如上图,这个可恶的分辨率问题(坑2)。。。OD中分析吧
1.2.1 分析内力消耗函数
- 直接先分析断点处,找到了释放技能时消耗位置
- (因为更改这个游戏很简单,直接更改游戏内的配置文件即可:一些独孤剑本来不会的技能就是更改配置文件学的,比如洗髓经。所以在释放技能时肯定会读取技能的配置文件)向上层函数回溯,确实发现寄存器中有读取的文件名
1.2.2 编写辅助dll
- 晚上睡觉前想了下:游戏修改起来很简单,如果不通过逆向,仅修改 .ini 文件里的内容即可
-
其实从游戏的存档读档功能就可以联想到:读取文档中的内容到游戏;
- 所以在技能释放,也是先读取如上图中的 .ini文件,获取该技能的内力消耗值 [edi+0x174],扯远了,回到主题
-
所以要怎么写一个辅助呢 → 写一个怎样的dll注入呢 → 什么条件触发我写的辅助呢?
- 游戏中释放技能是按 鼠标右键,切换技能是 键盘按键A~G;所以是不是可以更改它的窗口回调函数,在触发这两类消息时,把当前内力值设置为最大内力值(为了省事,选择鼠标事件吧)
-
用VS内的工具Spy++,查找剑侠情缘的窗口标题和类名
-
VS -> 选择 MFC 动态链接库 -> 在DLL类型中选择 ‘ 静态链接到 MFC 的常规DLL ’
- 主要逻辑:自己写的回调函数里响应鼠标右键(释放技能)消息
// 唯一的 CSwordCheatApp 对象
CSwordCheatApp theApp;
// --------------------------------------------------------------------------- //
HWND g_hWnd; // 接收剑侠情缘窗口句柄
WNDPROC g_OldProc; // 旧的窗口回调函数
PDWORD g_MagicNow = (PDWORD)0x004F1A40; // 当前内力值
PDWORD g_MagicMax = (PDWORD)0x004F1A44; // 最大内力值
// 自己的回调函数
LRESULT CALLBACK MyWindowProc(
_In_ HWND hWnd,
_In_ UINT Msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam)
{
// 1. 判断消息类型
if (Msg == WM_RBUTTONDOWN)
{
OutputDebugString(L"释放技能");
// 通过一个全局变量找到内力值相关的地址
// 0042E007 | > \8 > mov eax, dword ptr ds : [ecx] eax = 1
// 0042E009 | . 8 > mov edx, eax edx = 1
// 0042E00B | .C > shl edx, 0x5 edx = 32
// 0042E00E | . 0 > add edx, eax edx = 33
// 0042E010 | . 8 > lea edx, dword ptr ds : [eax + edx * 4] edx = 1+33*4(133)
// 0042E013 | . 8 > lea edx, dword ptr ds : [eax + edx * 4] edx = 1+133*4(533)
// 0042E016 | . 8 > lea eax, dword ptr ds : [eax + edx * 2] eax = 1+533*2(1067)
// 0042E019 | .C > shl eax, 0x4 eax = 1067*16(17072)
// 0042E01C | . 8 > mov edx, dword ptr ds : [eax + 0x4ED794] ; edx 是最大内力值 004F1A44
// 0042E022 | . 8 > mov edi, dword ptr ds : [eax + 0x4ED790] ; edi 当前内力值 004F1A40
* g_MagicNow = *g_MagicMax;
}
// 执行之前的窗口回调函数
return CallWindowProc(g_OldProc, hWnd, Msg, wParam, lParam);
}
// CSwordCheatApp 初始化 <----- 这里当作主函数
BOOL CSwordCheatApp::InitInstance()
{
CWinApp::InitInstance();
// 1. 通过查找窗口,获取窗口句柄
g_hWnd = ::FindWindow(L"Sword Class", L"Sword Window");
if (NULL == g_hWnd)
{
OutputDebugString(L"没有找到剑侠情缘窗口!");
return FALSE;
}
// 2. 设置窗口回调函数(SetWindowLong设置新的窗口回调,返回值是老的窗口回调函数)
g_OldProc = (WNDPROC)SetWindowLong(g_hWnd, GWL_WNDPROC, (LONG)MyWindowProc);
if (NULL == g_OldProc)
{
OutputDebugString(L"设置窗口回调函数失败!");
return FALSE;
}
return TRUE;
}
- 效果gif
总结:童年不再有,,,这个15年前的游戏逆向起来还是比较简单的,比较适合我这样的新手分析;dll编写思路大家可以尽情发挥,做技术没有标准答案;兴趣和坚持才能一直走下去,共勉!