前面我们找到了使用人物背包的函数调用,但是并不完美,因为我们需要处理一些参数,比如说用背包第二个物品,就需要push 1像数组一样从0 1 2 4 5 6这样来数,这对于程序员来说没问题,但是对于使用者肯定是不好的,所以我们需要得到背包的内容,来遍历背包,再根据需求,比如说喝红药水来封装一下。所以我们就需要人物背包里的内容。
实操:
搜索地址
先用CE来搜索数据来找到使用物品的时候的一个调用:
通过筛选,比如说先搜索18,然后吃掉变17后又搜索17,这样直到唯一确定。
这里比较简单嗷,一下子就找到了。
地址:4C13D4B0 值:17
探索基址
由于pe文件的特性,这个程序肯定是会有一个写死的地址来使用的,通过这个地址我们可以利用偏移等等来得到程序运行后背包所存放的地址。
从正常开发的角度来思考,这个肯定是把人物背包的东西存放在了某一个地址,然后查看人物背包的时候又给显示出来,所以这里直接给这个地址下一个硬件访问的地址就可以知道是什么访问了这个物品的地址空间了。
一看是停在了这里:
4C13D4B0 17
00A4AFAB | 837E 70 01 | cmp dword ptr ds:[esi+70],1 | 最初访问背包第一个物品的汇编指令
然后问题就出现了,这里会一直停下来,如果你打上断点后继续运行,会继续停在这条指令,而且你想返回上一层函数,采用Ctrl+F9和F8的指令也返回不了,还是会跳转到这里,那么这里就很蹊跷了,通过我不停的查看这个 esi+70的地址,可以看到它对于的内容就是你物品栏里面的物品数量,比如说:
我这里的物品是这样的
然后断下来查看esi+70后:
翻译成10进制就是先4后20然后17,就和物品栏里面的物品数量是一一对应的。
所以这里我猜测,它是打开物品栏之后,一个循环一直读取物品里面的所有数量。
然后这里有一个跳转,就是大于1之后会跳转,我们把它修改一下看看会发生什么:
修改成直接跳转不管1了的时候可以看到是只显示了物品并没有显示物品的数量。
那么这里就很明显了,这条cmp指令就是和1作判断,如果大于1就显示有多少,等于1就不显示了。
这条指令前面有3个函数call,一个很明显的mov esi,eax,就很有可能是他们修改了esi的值,那么就从下往上观察有没有修改:
对于函数的话可以在进入函数前后通过观察esi寄存器来区别。
然后4这个mov esi,eax根本断不下来,就肯定不是这个地方了。
再往上:
可以看到有两个跳转,很有可能是跳转的原因,直接跳过了mov esi,eax。分别给这两个je打断点观察:
这两个不管那个跳转只有小跳转A4A56会跳转,而大的je A4B251不会,很可能就是拿来判断代码逻辑把
而且通过第一个je A4B254大跳转最后还是会回到小跳转je A4AF56
所有这咯从 00A4AF47到00A4AF56中间的就不用考虑了,就继续往上看:
这条指令是在访问背包时完全可以断下来的,而且是直接对esi这个寄存器进行了修改的。
mov esi,dword ptr ss:[esp+84]
前面我们提到过,跟esp有关的基本上都是函数参数,或者临时变量。这里方法就不细讲了,可以参考前面的博客。
这里的esp+84是这个函数的函数参数,call A4AE00
通过值的比对,这里应该是最后push进来的一个参数,因为正好和栈是对着的。但是奇怪的是这个函数如果就这看对应的最后一个传参是一个push 0,再往上看看就可以看到其实前面有一个jmp跳转:
而且这个跳转是生效的,那么参数就应该是前面的push edx,ecx,ebx,eax了。
eax的值也是我们想要的,那么可以肯定是这里了。就往上找eax:
通过我的分析;
这个函数会修改eax的值。
这是这段函数的逻辑:
通过这三个跳转,大致上可以分析清楚了,就是比较eax是啥,然后如果不是要的就清零后返回了。
通过观察,这个函数的参数就是从0开始然后到某一个值结尾,猜测这个值应该是数组的范围把,应该就是遍历数组然后输出的函数
那么这个函数的功能大概明白了,重新分析下这个函数:
test eax,eax 然后有个jl jl是小于跳转,就肯定不会跳转了呗,下面是jge相等跳转,应该意思是等于某个值之后就跳转了应该是数组的最大范围就直接跳出去,然后cmp [esp+8],0这个肯定是一样的,因为这个函数前面是push 0,
mov ecx,dword ptr ds:[ecx+24]
lea ecx,dword ptr ds:[ecx+eax*4]
mov eax,dword ptr ds:[ecx]
剩下的应该好理解,把ecx+24的给ecx然后ecx通过eax偏移,后赋值给ecx。所以这里我们需要往上找ecx的值。
首先可以先偷懒看看每次断在这里的ecx是不是一个固定值,或者重启游戏看这个地址会不会改变,这里我自己测试是并不是一个固定的值,所以还得往上找。那么现在的关键就变成了找ecx的值了。
因为这个函数内部没有涉及到ecx的值,所以得跳出这个函数往上找:
这里有三个,但是我们还得考虑会不会跳到这里,这里我们可以利用条件断点,或者说看代码逻辑执行下来会不会执行到背包的代码逻辑。由于前面我没有计数这个值,我就用后面一种看代码逻辑来处理了,大家可以通过前面寄存器的积累来通过条件断点判断。
通过我的测试,第一个
mov ecx,dword ptr ss:[esp+2c]
这个汇编指令是会执行的。而且这里又扯到了esp了,办法就不多说了,参考前面:
esp+2c离函数返回地址太远了,很有可能就是一个临时变量。就直接给这个esp+2C打一个写入断点,看看是怎么赋值的。
但是问题是很多地方都会修改这个值,所以为了我们的目的,我直接给函数的开头打个断点,断到函数的时候再给这个地址打一个硬件断点就可以看到在函数里面是谁修改了这个esp+2C的值:
然后就破案了:
是这里的mov dword ptr ss:[esp+34],ecx修改了esp+2C的值。可以再用一个条件断点来判断一下。
mov dword ptr ss:[esp+34],ecx
然后往上找ecx:
这里
lea ecx,dword ptr ds:[edi+8] 又修改了ecx,又往上找edi
但是往上的一些又修改edi的指令,是完全断不下来的,这个通过条件断点或者代码逻辑判断断不下来就说明在背包的情况下没有使用这个,就继续一直往上找,直到有一个:
这里是完全可以断下来:
00A4B35F | E8 2CAAA0FF | call xajh.455D90 |
00A4B364 | 8B40 08 | mov eax,dword ptr ds:[eax+8] |
00A4B367 | 8B78 14 | mov edi,dword ptr ds:[eax+14] |
而且eax这个关键寄存器的值,在这个函数 call 455D90里面有赋值为一个定值也就是所谓的基址,那么我们就是找到内容了,由于一步一步找的时候没有记录值,这样的话我们只能从这个基址往下找一直到最后到背包的地址,然后来确定通过基址如何偏移才能达到最后的背包的地址。
这个函数的所有je都不会执行。
最后我把所有关键的汇编指令整理出来:
mov eax,dword ptr ds:[1597F20]
mov eax,dword ptr ds:[eax+30]
mov eax,dword ptr ds:[eax+90]
mov eax,dword ptr ds:[eax+8]
mov edi,dword ptr ds:[eax+14]
lea ecx,dword ptr ds:[edi+8]
mov dword ptr ss:[esp+34],ecx
//[esp+34] == esp+2C 0.0
mov ecx,dword ptr ss:[esp+2C]
mov ecx,dword ptr ds:[ecx+24]
lea ecx,dword ptr ds:[ecx+eax*4]
mov eax,dword ptr ds:[ecx]
//eax == esp+84
mov esi,dword ptr ss:[esp+84]
cmp dword ptr ds:[esi+70],1
[[[[[[[1597F20]+30]+90]+8]+14]+8+24]+eax*4]+70
最后这个:[[[[[[[1597F20]+30]+90]+8]+14]+8+24]+eax*4]+70 就是通过偏移得到的背包了,这里eax可以改,比如从0-数组最大值:
这里就是背包的第一个数据
这里就是第二个。
总结
这个就比较像是找基址了,用到了很多经验相关的,比如说硬件访问写入断点,比如说esp寄存器拿来分析函数参数或者临时变量。