物品拾取
0x01 找到函数调用地址
上期我们在找攻击的调用函数地址时候其实已经找到了这个函数 76A670
这个函数里面有一个switch case 在case 10006的时候会走到拾取逻辑,最终走到769b30这个函数中
汇编代码就如上图,咱们就在调用的时候加个断点,来看下esi和edi究竟是什么,其实esi可以根据之前的攻击函数才出来,应该是玩家实体,毕竟这里应该调用的是玩家类的成员函数,所以ecx相当于this这个指针.
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是从函数的入参传进来的,现在只能继续逆流而上查找入参是从哪里来的.
0x02: 分析参数来源
然后我们用老方法,获取调用过程如下.
76a5c6->5c72b5->7af2f9->7e8c79->5c77df->7bf179->747b18->8699b2->87b817->4be438->
从上层传入的第一个参数也是edi,再找这个edi的来源
可以看到,edi来自ecx+8
这里一顿操作,发现eax来自esi
esi又来自ecx
ecx又来自ebx
好了,这里真相了,ebx来自eax, eax又来自基址9A45B0,这个基址仔细一看跟当初的怪物列表最终的基址一模一样,应该出自同一个全局变量之类的.
废了老大功夫,找到这个edi,我把它这个逻辑写到咱们的辅助程序里,试了下,结果直接异常退出了,这时候我把获取到的参数和ce里面获取到的比较了一下,发现这么深的一个变量只是用来临时存储用的.
只能从头来过了,其实还是重复上面的操作,下面还是直接贴个结果吧,主要还是让大家了解向上追踪的一个方法.
0x03: 编写打开怪物掉落列表的窗口的代码
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才是咱们要的
编译运行后就能正确的打开掉落列表了
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值得过程.
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,[ebp+08]
007BECA6 - 89 81 981A0000 - mov [ecx+00001A98],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->
最终我们根据堆栈分析出了如下这个调用过程
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
}
用了如上代码后,大功告成