CE训练教程分析及控制台等效代码
1.介绍
在学习使用CE的过程中,一开始参照着其他人的分析过程,是能够过关的,但还是不甚理解,随着深入的学习及分析后,自己尝试编写控制台代码来还原CE的训练教程软件,加深了对逆向分析的理解,在此做个记录和分享,希望能够帮助到对逆向分析感兴趣的小伙伴。
CE下载地址:https://www.cheatengine.org/downloads.php
2.打开Cheat Engine 训练教程

帮助-》Cheat Engine教程,双击打开,界面如下图,这里只演示分析32位训练程序。

CE打开训练进程

3.步骤2

3.1介绍
通过多次“打我”改变健康值,,使用【精确数值】和4字节数值类型扫描确认变量地址。实际就是是找一个变量的地址,然后改为1000即可。
3.2控制台等效代码如下
//Cheat Engine 步骤 2: 精确值扫描 (密码=090453)
//等效示例代码
#include<Windows.h>
#include<conio.h>
#include <iostream>
/// <summary>
/// 打印健康值
/// </summary>
/// <param name="value"></param>
void display(int value)
{
printf("健康:%d\n", value);
}
int main()
{
srand(GetTickCount64());
int hp = 100;//健康值
while (true)
{
display(hp);
printf("打我->回车\n");
while (!_kbhit())//检测键盘按键
{
if (hp == 1000)//检测当前健康值是否等于1000则过关
{
goto end;
}
Sleep(1);
}
getchar();
hp -= (rand() % 5 + 1);
}
end:
printf("========你过关========\n");
system("pause");
return 0;
}
3.3控制台运行界面

4.步骤3

4.1介绍
本关与步骤2类似,只不过界面不会显示具体数值,而是显示减少的数值。
4.2控制台等效代码如下
//Cheat Engine 步骤 3: 未知的初始值 (密码=419482)
//等效示例代码
//2025年3月6日14:37:54
#include<Windows.h>
#include<conio.h>
#include <iostream>
int main()
{
srand(GetTickCount64());
int hp = 200 + rand() % 300;//健康值至少为200,最大不超过500
while (true)
{
printf("打我->回车\n");
while (!_kbhit())//检测键盘按键
{
if (hp == 5000)//检测当前健康值是否等于5000则过关
{
goto end;
}
Sleep(1);
}
getchar();
int sub = (rand() % 5 + 1);
printf("减少健康值:-%d\n", sub);
hp -= sub;
}
end:
printf("========你过关========\n");
system("pause");
return 0;
}
4.3控制台运行界面

5.步骤4

5.1介绍
本关是寻找单浮点和双浮点数,这里特别需要注意的是,浮点数类型的数值不是完全精确的,扫描类型可使用“介于...两者之间”的方式扫描,并且数值类型要选择对应的“单浮点”和“双浮点”。
5.2控制台等效代码如下
//Cheat Engine 步骤 4: 浮点数 (密码=890124)
//等效示例代码
//2025年3月6日14:43:09
#include<Windows.h>
#include<conio.h>
#include <iostream>
int main()
{
srand(GetTickCount64());
float hp = 100;//健康值
double bullet = 100;//弹药
int input = 0;//输入值
while (true)
{
printf("健康:%.0f,弹药:%.1f\n", hp, bullet);
printf("健康-打我->1,弹药-开火->2\n");
printf("请输入数字:");
while (!_kbhit())//检测键盘按键
{
if (hp == 5000 && bullet == 5000)//检测健康值和弹药
{
goto end;
}
Sleep(1);
}
scanf_s("%d", &input);
if (input == 1)
{
hp -= rand() % 5;
}
else if (input == 2)
{
bullet -= (rand() % 3) * 1.3;
}
}
end:
printf("========你过关========\n");
system("pause");
return 0;
}
5.3控制台运行界面

6.0步骤5

6.1介绍
本关是找随机数值,可通过“精确数值”扫描方式,更改对应数值,找到对应的变量地址。然后通过“找出是什么改写了这个地址”的方式,将对应修改值的汇编代码替换为什么也不做,即"nop"。
6.2控制台等效代码如下
//Cheat Engine 步骤 5: 代码查找 (密码=888899)
//等效示例代码
//2025年3月6日14:43:09
#include<Windows.h>
#include<conio.h>
#include <iostream>
//_kbhit()
int main()
{
srand(GetTickCount64());
int num = rand() % 200 + 10;
static int lastNum = num;
while (true)
{
printf("数值:%d\n", num);
printf("改变数值-》回车");
getchar();
num = rand() % 500 + 100;
if (num == lastNum)//上一句赋值代码被nop后,这句会与之前相等
{
break;
}
lastNum = num;
}
end:
printf("========你过关========\n");
system("pause");
return 0;
}
6.3控制台运行界面

7.步骤6

7.1介绍
本关通过找到数值的地址,再找到基址,这一关只是一级基址。
7.2控制台等效代码如下
//Cheat Engine 步骤 6: 指针: (密码=098712)
//等效示例代码
//2025年3月7日20:14:48
#include<Windows.h>
#include<conio.h>
#include <iostream>
//_kbhit()
struct Person
{
int* m_age;
};
Person per;
int main()
{
srand(GetTickCount64());
int input = 0;
while (true)
{
per.m_age = new int(rand() % 500 + 10);
while (true)
{
printf("改变数值:%d\n", *per.m_age);
printf("改变数值-》1,改变指针-》2\n");
printf("请输入数字:");
scanf_s("%d", &input);
if (input == 1)//改变数值
{
*per.m_age = rand() % 500 + 100;
}
else if (input == 2)//改变指针
{
if (*per.m_age == 5000)//当前值是5000了
{
per.m_age = new int(0);//改变一个地址
Sleep(3000);//等待一段时间
if (*per.m_age == 5000)//等待后再次查看该地址的值是否被锁定为5000
{
goto end;
}
}
break;
}
}
}
end:
printf("========你过关========\n");
system("pause");
return 0;
}
7.3控制台运行界面

8.步骤7

8.1介绍
本关是找到变量后,使用“找出是什么改写地址”,找到改写该地址的汇编代码,然后使用工具-》自动汇编-》模版-》代码注入。
示例如下:
alloc(newmem,2048)
label(returnhere)
label(originalcode)
label(exit)
newmem: //this is allocated memory, you have read,write,execute access
//place your code here
add dword ptr [ebx+4a8],3 //加3,底下是减1,刚好是加2
originalcode:
sub dword ptr [ebx+000004A8],01
exit:
jmp returnhere
"Tutorial-i386.exe"+28A78:
jmp newmem
nop 2
returnhere:
8.2控制台等效代码如下
//Cheat Engine 步骤 7: 代码注入: (密码=013370)
//等效示例代码
//2025年3月6日16:10:40
#include<Windows.h>
#include<conio.h>
#include <iostream>
//_kbhit()
int main()
{
int num = 100;
while (true)
{
printf("数值:%d\n", num);
printf("打我-》回车\n");
getchar();
int temp = num;
num -= 1;
if (num == temp + 2)//判定是加2了,过关
{
goto end;
}
}
end:
printf("========你过关========\n");
system("pause");
return 0;
}
8.3控制台运行界面

9.步骤8

9.1介绍
本关是一个4级指针,按照寻找一级指针的方式,一直找,直到找到基址为止(基址显示为绿色)
[01A35C78]=[esi+18]
[01A35C60+18]
[[019CC9D0+0]+18]
[[[01A448A0+14]]+18]
[[[[01A24F60+0c]+14]+18]

9.2控制台等效代码如下
//Cheat Engine 步骤 8: 多级指针: (密码=525927)
//等效示例代码
//2025年3月6日16:22:47
#include<Windows.h>
#include<conio.h>
#include <iostream>
//_kbhit()
struct OneStruct
{
char m_name[0x18];
int m_age;
int m_c;
};
struct TwoStruct
{
OneStruct* m_one;
};
struct ThreeStruct
{
char m_name[0x14];
TwoStruct* m_two;
};
struct FourStruct
{
char m_name[0xc];
ThreeStruct* m_three;
};
FourStruct* pMain = nullptr;
void init()
{
if (pMain)
{
delete pMain;
pMain = nullptr;
}
pMain = new FourStruct;
pMain->m_three = new ThreeStruct;
pMain->m_three->m_two = new TwoStruct;
pMain->m_three->m_two->m_one = new OneStruct;
pMain->m_three->m_two->m_one->m_age = rand() % 500;
}
int main()
{
srand(GetTickCount64());
int input = 0;
while (true)
{
init();
while (true)
{
printf("数值:%d\n", pMain->m_three->m_two->m_one->m_age);
printf("改变数值-》1,更改指针-》2\n");
printf("请输入数字:");
scanf_s("%d", &input);
if (input == 1)
{
pMain->m_three->m_two->m_one->m_age = rand() % 500;
}
else if(input == 2)
{
if (pMain->m_three->m_two->m_one->m_age == 5000)//检测是否有锁定
{
init();
Sleep(3000);
if (pMain->m_three->m_two->m_one->m_age == 5000)//再次检测是否锁定
{
goto end;
}
}
break;
}
}
}
end:
printf("========你过关========\n");
system("pause");
return 0;
}
等效代码找指针过程
[010A5080]=[[010A5068+18]]
[[[010A6658+0]+18]]
[[[[010A5020+14]+0]+18]]
[[[[[010A6618+0c]+14]+0]+18]
9.3控制台运行界面

10.步骤9

10.1介绍
本关先通过扫描找到玩家的“健康”地址,然后用结构分析表查看差异,找到区分敌我阵营的数据,这里找到为与“健康”偏移为0C的地址,然后再“找出是什么修改地址”,定位到修改值的汇编代码,使用工具-》自动汇编-》模版-》CT表框架代码-》代码注入,在其中进行判定是否为己方阵营,然后进行对应的操作。
10.2结构分析表

10.3玩家1基址

10.4玩家2基址

10.5玩家3基址

10.6玩家4基址

10.7源汇编修改代码

10.8注入代码-》一击必杀
[ENABLE]
//code from here to '[DISABLE]' will be used to enable the cheat
alloc(newmem,2048)
label(returnhere)
label(originalcode)
label(exit)
newmem: //this is allocated memory, you have read,write,execute access
//place your code here
cmp [ebx+04+0c],1
je originalcode
mov [ebx+04],0 //一击必杀
fldz
jmp exit
originalcode:
mov [ebx+04],eax
fldz
exit:
jmp returnhere
"Tutorial-i386.exe"+29D8D:
jmp newmem
returnhere:
[DISABLE]
//code from here till the end of the code will be used to disable the cheat
dealloc(newmem)
"Tutorial-i386.exe"+29D8D:
db 89 43 04 D9 EE
//mov [ebx+04],eax
//fldz
10.9控制台等效代码如下
//Cheat Engine 步骤 9: 注入++: (密码=31337157)
//等效示例代码
//2025年3月7日10:42:32
#include<Windows.h>
#include<conio.h>
#include <iostream>
//_kbhit()
/// <summary>
/// 名字长度
/// </summary>
const int NAMELENGTH = 5;
/// <summary>
/// 格式符号
/// </summary>
const char* FORMATSTR = "==========================";
const char* FORMATSTREND = "==============================================================================";
struct Player
{
int m_test0 = 0;//填补
/// <summary>
/// 健康值
/// </summary>
float m_hp;
int m_test1[2] = {};//填补
/// <summary>
/// 阵营,1-我方,2-敌方
/// </summary>
int m_camp;
byte m_sex;//填补
/// <summary>
/// 名字
/// </summary>
char m_name[NAMELENGTH] = {};
};
struct GamePlayer
{
char g_full[0x4fc] = {};//填充使用
Player* pDavePlayer = nullptr;
Player* pEricPlayer = nullptr;
Player* pHalPlayer = nullptr;
Player* pKittPlayer = nullptr;
};
GamePlayer* player = nullptr;
/// <summary>
/// 攻击
/// </summary>
/// <param name="player"></param>
/// <param name="lossHp"></param>
void Attack(Player* const player, int lossHp)
{
player->m_hp = player->m_hp - lossHp;
}
/// <summary>
/// 重启游戏
/// </summary>
void RestartGame()
{
player = new GamePlayer;
printf("%s开始游戏%s\n", FORMATSTR, FORMATSTR);
player->pDavePlayer = new Player;
player->pDavePlayer->m_hp = 100;
player->pDavePlayer->m_camp = 1;
memcpy_s(player->pDavePlayer->m_name, NAMELENGTH, "Dave", 4);
player->pEricPlayer = new Player;
player->pEricPlayer->m_hp = 100;
player->pEricPlayer->m_camp = 1;
memcpy_s(player->pEricPlayer->m_name, NAMELENGTH, "Eric", 4);
player->pHalPlayer = new Player;
player->pHalPlayer->m_hp = 500;
player->pHalPlayer->m_camp = 2;
memcpy_s(player->pHalPlayer->m_name, NAMELENGTH, "Hal", 3);
player->pKittPlayer = new Player;
player->pKittPlayer->m_hp = 500;
player->pKittPlayer->m_camp = 2;
memcpy_s(player->pKittPlayer->m_name, NAMELENGTH, "Kitt", 4);
}
/// <summary>
/// 重启游戏并自动执行
/// </summary>
void RestartGameAutoRun()
{
RestartGame();
while (true)
{
int weLoss = rand() % 5 + 2;
int enemyLoss = 1;
Attack(player->pDavePlayer, weLoss);
Attack(player->pEricPlayer, weLoss);
Attack(player->pHalPlayer, enemyLoss);
Attack(player->pKittPlayer, enemyLoss);
if (player->pDavePlayer->m_hp <= 0 || player->pEricPlayer->m_hp <= 0)
{
MessageBox(nullptr, L"失败了,你的团队阵亡", L"提示", MB_OK);
printf("失败了,你的团队阵亡,重新开始游戏\n");
RestartGame();
break;
}
if (player->pHalPlayer->m_hp <= 0 || player->pKittPlayer->m_hp <= 0)
{
MessageBox(nullptr, L"你过关!!!", L"提示", MB_OK);
break;
}
printf("执行中...\n");
}
}
/// <summary>
/// 玩法介绍
/// </summary>
void GameInfo()
{
system("cls");//清屏
printf("%s玩法介绍%s\n", FORMATSTR, FORMATSTR);
printf("攻击玩家1-》1,攻击玩家2-》2,攻击玩家3-》3,攻击玩家4-》4\n");
printf("重新开始游戏-》8,重新开始游戏并自动执行-》9,游戏介绍-》0\n");
printf("%s\n\n", FORMATSTREND);
}
/// <summary>
/// 玩家健康值
/// </summary>
void PlayerHPInfo()
{
printf("%s玩家健康值%s\n", FORMATSTR, FORMATSTR);
printf("玩家1:Dave->%.0f 玩家2:Eric->%.0f C.玩家3:HAL->%.0f C.玩家4:KITT->%.0f\n",
player->pDavePlayer->m_hp, player->pEricPlayer->m_hp, player->pHalPlayer->m_hp, player->pKittPlayer->m_hp);
printf("%s\n", FORMATSTREND);
}
int main()
{
srand(GetTickCount64());
int input = 0;
GameInfo();
RestartGame();
int lossHp = 0;
#if _DEBUG //调试使用,跳过找地址
printf("玩家1-》%p\n", &player->pDavePlayer->m_hp);
printf("玩家3-》%p\n", &player->pHalPlayer->m_hp);
#endif
while (true)
{
PlayerHPInfo();
printf("请输入数字:");
int ret = scanf_s("%d", &input);
if (ret == 0)//输入不正确的格式
{
//清空输入缓冲区
int c;
while ((c = getchar()) != '\n' && c != EOF);
printf("输入格式不正确!!!\n");
continue;
}
switch (input)
{
case 0:
GameInfo();
break;
case 1:
{
lossHp = rand() % 5 + 1;
printf("攻击玩家1:-%d\n", lossHp);
Attack(player->pDavePlayer, lossHp);
}
break;
case 2:
{
lossHp = rand() % 5 + 1;
printf("攻击玩家2:-%d\n", lossHp);
Attack(player->pEricPlayer, lossHp);
}
break;
case 3:
{
printf("攻击玩家3:-1\n");
Attack(player->pHalPlayer, 1);
}
break;
case 4:
{
printf("攻击玩家4:-1\n");
Attack(player->pKittPlayer, 1);
}
break;
case 8://重新启动游戏
{
printf("%s重新启动游戏%s\n", FORMATSTR, FORMATSTR);
RestartGame();
}
break;
case 9://重新启动游戏并自动执行
{
printf("%s重新启动游戏并自动执行%s\n", FORMATSTR, FORMATSTR);
RestartGameAutoRun();
}
break;
}
}
printf("%s你过关%s\n", FORMATSTR, FORMATSTR);
system("pause");
return 0;
}
10.10控制台运行界面

10.11控制台等效代码,注入代码-》一击必杀

自己的控制台等效代码这里生成的修改代码为:movss [ecx+04],xmm1,这里不能简单的修改为“movss [ecx+04],0”,具体原因请自行研读"movss"指令的操作。这里选择使用"pxor xmm1,xmm1"用异或方式,将xmm1赋值为0,然后执行"movss [ecx+04],xmm1"的时候,就能赋值为0了。
[ENABLE]
//code from here to '[DISABLE]' will be used to enable the cheat
alloc(newmem,2048)
label(returnhere)
label(originalcode)
label(exit)
newmem: //this is allocated memory, you have read,write,execute access
//place your code here
cmp [ecx+04+0c],1
je exit
pxor xmm1,xmm1 //异或为0,一击必杀
originalcode:
movss [ecx+04],xmm1
exit:
jmp returnhere
"ConsoleApplication2.exe"+11A66:
jmp newmem
returnhere:
[DISABLE]
//code from here till the end of the code will be used to disable the cheat
dealloc(newmem)
"ConsoleApplication2.exe"+11A66:
db F3 0F 11 49 04
//movss [ecx+04],xmm1

浙公网安备 33010602011771号