简介

使用CE手动查找基址时,经常遇到搜索到其中某一层的时候突然没有结果了,或者在某几层之间不断循环,无法找到最终的静态地址的情况。本文通过一个简单的Demo,以查找Player数组基址为例,浅析导致上述原因的原因,并给出解决方案

视频教程

B站同名账号:KrnlsYs 有本文内容对应的视频,还有完整的CE入门教程以及其他编程话题的教程等,推荐有学习需求的读者关注

示例代码

注意:不同版本不同编译器对该代码编译生成的汇编指令都会不同,你有可能无法复现本章内容

#include

#include

struct Player

{

int id;

char padding[0x300];

int health;

};

struct PlayerManager

{

char padding[0x20];

std::vector players;

} PlayerMgr;

int main()

{

for (size_t i = 0; i < 10; i++)

{

auto player = new Player;

player->id = i;

player->health = 100;

PlayerMgr.players.push_back(player);

}

int id = -1;

std::cin >> id;

while (true)

{

std::cin.get();

PlayerMgr.players[id]->health -= 4;

std::cout << "Player " << id << " health: " << PlayerMgr.players[id]->health << '\n';

}

return 0;

}

操作流程

定位血量地址

运行程序后用CE附加,搜索100,之后随便输入一个索引,然后回车使其血量变化,搜索96,很容易找到血量地址

定位player地址

对改地址右键查找访问,然后回到demo,回车

易知第三条的rcx是players[6]的结构体地址

查找数组地址

搜索上述players[6]的结构体地址,发现有两个结果

对两个地址都查找访问,然后回到demo再次使血量变化

发现上边一条是rbp,也就是堆栈相关的指令,所以不管它

所以使用第二个地址查找访问的结果进行寻址

坑点1

注意这两条指令

前后寄存器是一致的,含义是把右边的rax当做地址取值,然后赋值给rax寄存器本身。问题来了:我们要追的地址也存储在rax中,而且是在这条指令执行之前的rax。然而CE给我们显示出的rax是执行后的结果

因此直接搜索这个rax是错误的方法

同时容易发现,此时的rax正是我们之前搜索的players[6]的结构体地址,也就是说我们陷入了某种诡异的循环

解决的方式是:在反汇编中对这条指令下断点,然后回到demo使血量发生变化,CE会把程序断在该指令执行之前,此时获取的是正确的rax。搜索这个rax有可能找到正确的地址

断下后得到正确的rax

坑点2

复制该rax,取消断点继续运行,然后搜素该rax

这个方法可以解决一部分问题,但遗憾的是,在这个例子中仍然没有结果

既然常规的查找访问无法找到正确基址,我们就手动追查基址

以第一条为例,来到这条反汇编的上方,看看rax是从哪里来

稍有基础的人都会知道,此处的rax来自上方call的返回值,所以我们进call看看返回值从何而来

找到ret附近,看看最后一次对rax的赋值是什么指令

通过lea rax,[rax+rcx*8]对rax进行了最后一次赋值,不难看出此处又嵌套了第一个坑点,即我们要追查的rax已被执行后的结果覆盖

那么,为什么在坑点1中下断获取到执行前的rax也搜不到任何结果?通过上图可以知道,即使是执行前的rax,它也仅仅是寄存器中的一个数值,从未出现在内存中,自然无法通过内存扫描得到

问题解决

所以我们对这条lea rax,[rax+rcx*8]下断,获取此处执行前的rax

取消断点,运行,搜索

成功找到数组地址