不以物喜,不以己悲

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
posted @ 2025-03-07 11:43  这种人  阅读(261)  评论(0)    收藏  举报