从零开始教你做辅助--拾取篇(三 完结篇)
大家好,我是山里猴.哈哈,好久不见不知道还有人记得我吗,最近是真的忙,距离上一次更新估计有一两个月了.
这次更新也是这个系列最后一篇,主要是本系列后面更新重复内容会逐渐增多,新的可以和大家分享的内容就不多了.
我本人主要是做开发的,所以在做逆向的过程中,比较喜欢通过自己做一些插件等方式来提升逆向的效率.
其实最初选调试器的时候也用过OD,但是OD编写扩展相对其他调试器缺乏官方支持,后来也编译过x64Dbg,不过当时使用过程中有bug,可能是我编译问题.
后来发现immunity debugger这个调试器原生支持python,相信大家都听说过"人生苦短,我用python",python这语言开发效率确实高,很适合用来做插件
再后来发现CheatEngine 这个神器不止能查找内存,修改内存,也有调试功能.
而且我看论坛之前好像对于CheatEngine + lua方式,还有immunity debugger这方面的帖子也不多,所以就做了这个系列.
自己做辅助的过程也让自己又提升不少,为了分析方便,最近又学了下IDA pro的插件开发,做了个根据调用堆栈辅助分析基址的插件,之后把这个也分享下.
这是这个IDA Pro插件工程地址,用c++开发的 https://github.com/shanlihou/ida_get_base
用起来就如下图这样,下图显示的就是我用来查拾取函数参数基址时候的效果
下面是前两篇的导航
从零开始教你做辅助一
从零开始教你做辅助二
---------------------------------------------- 废话分割线 -----------------------------------------------------
# 物品拾取
## 0x01 找到函数调用地址
上期我们在找攻击的调用函数地址时候其实已经找到了这个函数 76A670
这个函数里面有一个switch case 在case 10006的时候会走到拾取逻辑,最终走到769b30这个函数中
!(https://gitee.com/shanlihou/image_bed/raw/master/warspear_03/open_monster_bag.png)
汇编代码就如上图,咱们就在调用的时候加个断点,来看下esi和edi究竟是什么,其实esi可以根据之前的攻击函数才出来,应该是玩家实体,毕竟这里应该调用的是玩家类的成员函数,所以ecx相当于this这个指针.
```lua
function clear_debug()
local tbl = debug_getBreakpointList()
if tbl == nil
then
return
end
for i,v in ipairs(tbl) do
print(string.format("%4d 0x%X",i,v))
debug_removeBreakpoint(v)
end
end
function debugger_onBreakpoint()
if EIP == 0x76a7a5
then
print(string.format("enter in 0x76a7a5, esi:0x%X, edi:0x%X", ESI, EDI))
else
print(2)
end
return 1
end
clear_debug()
print(1)
debug_setBreakpoint(0x76a7a5)
```
打印这两个值得代码其实如上,前面已经展示很多遍了,其实这种逻辑比较简单的,大家加断点,在断下来之后直接查看寄存器,也可以,只不过我个人不太喜欢让游戏停下来,因为大部分游戏,停下来之后再运行可能要重新走下登录流程,很麻烦.
最后打印出如下
enter in 0x76a7a5, esi:0xEF4F4C8, edi:0xD623270
这个esi经过我和玩家实体指针对比,确实是玩家没错.这个edi并不是之前的怪物实体指针,需要在确认下具体是什么.
这个貌似没什么捷径,只能一层一层往上找,网上看到下图位置,看到edi是从函数的入参传进来的,现在只能继续逆流而上查找入参是从哪里来的.
!(https://gitee.com/shanlihou/image_bed/raw/master/warspear_03/modify_edi_1.png)
## 0x02: 分析参数来源
然后我们用老方法,获取调用过程如下.
76a5c6->5c72b5->7af2f9->7e8c79->5c77df->7bf179->747b18->8699b2->87b817->4be438->
!(https://gitee.com/shanlihou/image_bed/raw/master/warspear_03/modify_edi_4.png)
从上层传入的第一个参数也是edi,再找这个edi的来源
!(https://gitee.com/shanlihou/image_bed/raw/master/warspear_03/modify_edi_2.png)
可以看到,edi来自ecx+8
!(https://gitee.com/shanlihou/image_bed/raw/master/warspear_03/modify_edi_3.png)
这里一顿操作,发现eax来自esi
!(https://gitee.com/shanlihou/image_bed/raw/master/warspear_03/modify_edi_5.png)
esi又来自ecx
!(https://gitee.com/shanlihou/image_bed/raw/master/warspear_03/modify_edi_6.png)
ecx又来自ebx
!(https://gitee.com/shanlihou/image_bed/raw/master/warspear_03/modify_edi_7.png)
好了,这里真相了,ebx来自eax, eax又来自基址9A45B0,这个基址仔细一看跟当初的怪物列表最终的基址一模一样,应该出自同一个全局变量之类的.
废了老大功夫,找到这个edi,我把它这个逻辑写到咱们的辅助程序里,试了下,结果直接异常退出了,这时候我把获取到的参数和ce里面获取到的比较了一下,发现这么深的一个变量只是用来临时存储用的.
只能从头来过了,其实还是重复上面的操作,下面还是直接贴个结果吧,主要还是让大家了解向上追踪的一个方法.
## 0x03: 编写打开怪物掉落列表的窗口的代码
```cpp
void EntityBase::searchDead()
{
try
{
DWORD tmp_ptr = *(DWORD*)(BASE_MONSTER_LIST_ROOT);
log_debug("before get ptr :%p\n", tmp_ptr);
tmp_ptr = *(DWORD*)(tmp_ptr + 0x10);
log_debug("before get ptr :%p\n", tmp_ptr);
tmp_ptr = *(DWORD*)(tmp_ptr + 0x3c);
log_debug("before get ptr :%p\n", tmp_ptr);
tmp_ptr = *(DWORD*)(tmp_ptr + 0x38c);
log_debug("before get ptr :%p\n", tmp_ptr);
tmp_ptr = *(DWORD*)(tmp_ptr + 0xecc);
DWORD tmp2 = tmp_ptr;
//下面这些是根据咱们第一次向上追踪写出来的一个获取参数的逻辑
//log_debug("before get ptr :%p\n", tmp_ptr);
//tmp_ptr = *(DWORD*)(tmp_ptr + 0x78);
//log_debug("before get ptr :%p\n", tmp_ptr);
//tmp_ptr = *(DWORD*)(tmp_ptr + 0x0);
//log_debug("before get ptr :%p\n", tmp_ptr);
//tmp_ptr = *(DWORD*)(tmp_ptr + 0x8);
//log_debug("after get tmp %p\n", tmp_ptr);
//如下是tmp2是咱们在第一次失败后第二次追踪后写出来的
tmp2 = *(DWORD*)(tmp2 + 0x48);
tmp2 = *(DWORD*)(tmp2 + 0x44);
tmp2 = *(DWORD*)(tmp2 + 0x0);
DWORD node = tmp2;
DWORD next_node = *(DWORD*)(node + 0x4);
while (next_node) { //这边应该是获取了列表里最后一个节点,但是这个列表存了什么暂时还不清楚
node = next_node;
DWORD next_node = *(DWORD*)(node + 0x4);
}
tmp2 = *(DWORD*)(node + 0x14);
if (tmp2 == 0)
{
log_debug("search but tmp_ptr:0\n");
return;
}
DWORD selfPtr = this->basePtr;
log_debug("will searchDead tmp2 %p %p\n", selfPtr, tmp2);
DWORD searchDropCall = SEARCH_DEAD_DROP;
__asm
{
mov eax, tmp2
push eax //怪物的地址
mov ecx, selfPtr //自己的地址
mov eax, searchDropCall
CALL eax
}
}
catch (...)
{
}
}
```
上面的代码流程中吧第一次找的地址tmp_ptr这个也包含了进来,失败了就给注释掉了,tmp2才是咱们要的
编译运行后就能正确的打开掉落列表了
!(https://gitee.com/shanlihou/image_bed/raw/master/warspear_03/drop_list.png)
## 0x04: 寻找拾取的代码地址(就是点击如上下一页)
为了找到拾取代码的地址,只能用老方法,通过send函数调用堆栈来找,这里跟上一章碰到的问题也一样,send函数只是从消息队列中取出消息来发送,要想找到拾取代码位置,还是需要找到插入消息队列的函数地址.
分析后发现如下三个插入到消息队列的操作,调用堆栈如下,在看这三个堆栈都经过这个6f0e25地址.
并且这个地址只会经过一次,所以到这里已经得到拾取函数的地址了,就是这个地址6F00B0.
eax:0x8
50dd22->7a714e->7a44ca->6eea24->6f0110->6f0e25->7e8b65->7a7222->7af33c->7a73e0->7bf0a7->747b18->8699b2->87b817->4be438->860521->76b647ab->76b42aac->
eax:0x14
50dd1c->7a714e->6eeb25->6f0110->6f0e25->7e8b65->7a7222->7af33c->7a73e0->7bf0a7->747b18->8699b2->87b817->4be438->860521->76b647ab->76b42aac->76b444db->
eax:0x14
50dd1c->7a714e->6eeeb8->6f0fa2->7e8e48->6f0110->6f0e25->7e8b65->7a7222->7af33c->7a73e0->7bf0a7->747b18->8699b2->87b817->4be438->860521->76b647ab->
## 0x05: 分析拾取代码的调用参数
现在函数已经有了,下面就是传参了,首先在6f0e20这里下断,观察发现,入参eax一直为0,剩下的就是ecx的获取了.
根据上面已经获取的堆栈分析后,得到如下获取ecx值得过程.
```cpp
DWORD a = 0x9A5798;
a = *(DWORD*)(a + 0xc);
a = *(DWORD*)(a + 0x0);
a = *(DWORD*)(a + 0x7c);
a = *(DWORD*)(a + 0x40);
a += 0x550c;
a = *(DWORD*)(a + 0x1A98);
a = *(DWORD*)(a + 0x18);
__asm
{
mov ecx, 0x0
push ecx
mov ecx, a
mov eax, 0x6f00b0
CALL eax
}
```
可惜编写了如上代码后,发现这个最终获取到的a并不是我们想要的,看来这个值是在另一个流程里赋值进去的.
现在再用ce对如上获取到a这个内存地址查是谁改写了这个值,得到如下这个地址,可以看到这里把值放进了1a98,那应该就是这个地址了.
007BECA1 - 8B EC- mov ebp,esp
007BECA3 - 8B 45 08- mov eax,
007BECA6 - 89 81 981A0000- mov ,eax <<
007BECAC - 5D - pop ebp
007BECAD - C2 0400 - ret 0004
EAX=0A1F0AF8
EBX=00000003
ECX=00CD0614
EDX=000000DA
ESI=0A1F0AF8
EDI=0EDE24F0
ESP=0019F85C
EBP=0019F85C
EIP=007BECAC
再对如上地址加断点获取到如下堆栈.
7a74f8->7af2f9->7a73e0->7ae053->7af2a3->7e8c79->6f0c50->7bf179->747b18->8699b2->87b817->4be438->
最终我们根据堆栈分析出了如下这个调用过程
```cpp
DWORD a = 0x9A5798;
a = *(DWORD*)(a + 0xc);
a = *(DWORD*)(a + 0x0);
a = *(DWORD*)(a + 0x7c);
a = *(DWORD*)(a + 0x40);
a += 0x550c;
a = *(DWORD*)(a + 0x10);
a = *(DWORD*)(a + 0x8);
a += 8;
a = *(DWORD*)(a + 0x8);
a = *(DWORD*)(a + 20);
a = *(DWORD*)(a + 0x18);
__asm
{
mov ecx, 0x0
push ecx
mov ecx, a
mov eax, 0x6f00b0
CALL eax
}
```
用了如上代码后,大功告成 完全看不懂啊,大佬,我想学习写辅助,主要是能够遍历角色周围地上的物品,就传奇那类的游戏,不知道要学习哪方面的知识,望指点一下,谢谢了 scghjl 发表于 2021-3-13 22:10
完全看不懂啊,大佬,我想学习写辅助,主要是能够遍历角色周围地上的物品,就传奇那类的游戏,不知道要学习 ...
写辅助推荐用c++吧,也有人用易语言
遍历物品要找到基址,需要cheatengine先分析下 前来给大佬提鞋{:301_975:}
评分用完了,明天补上{:301_978:} 我就喜欢这样的帖子 先感谢下 学习一下,谢谢了 学习一下6666666666 大佬 厉害赶紧学习 不错楼主,支持下 不明觉厉,进来跟高手学习了 萌新来给大佬打call了。现在分享技术的这么详细的真不多。