软件逆向基础-CE篇
- 软件逆向基础
2.1 综述计算机中关于数符的表示方式,并举例说明。
2.1.1数字的表示方式
在计算机中,数字的表示方式主要包括整数,浮点数等,每种表达方式都有其特定的编码规则和存储方式。
- 整数
①无符号整数:表示非负整数。例如,一个8位的无符号整数可以表示的范围是0到255(00H到FFH)。
②有符号整数:可以表示正整数、负整数或零。在计算机中,有符号整数的最高位是符号位,0表示正数,1表示负数。其余位表示数值的大小。例如,一个8位的有符号整数可以表示的范围是-128到+127(80H到7FH)。
原码:
原码分为符号位和数值位。最高位用于表示符号,0表示正数,1表示负数,其余位则表示数值的大小。例如,8位二进制原码的表示范围为:
- 正数:0xxxxxxx(如:00000001表示+1)
负数:1xxxxxxx(如:10000001表示-1的绝对值,但符号为负)
优点:原码表示简单,直观地将最高位作为符号位。
缺点:
原码的运算复杂,特别是减法运算时必须额外处理符号位和数值位的计算。
存在两个0(+0和-0),导致零的表示不唯一,计算中容易引发混淆。
反码:
反码是通过对原码中的每一位取反(即0变为1,1变为0)来表示负数的。正数的反码与原码相同,负数则通过取反得到。例如:
正数5的8位二进制原码是00000101,反码也是00000101。
负数-5的8位二进制原码是10000101,反码则是11111010。
优点:反码比原码在计算上有所改进,是在加法和减法运算中引入了符号的统一处理。
缺点:反码仍然存在两个0(+0和-0),即零的双重表示问题。
补码:
补码是对反码进行进一步改进,使负数的表示更加简洁有效。具体来说,正数的补码与原码相同,负数的补码则是将其反码加1。例如:
正数5的补码是00000101。
负数-5的反码是11111010,补码则是11111010+1=11111011。
优点:
补码解决了零的双重表示问题,只存在一个0(00000000)。补码系统使加法和减法运算更加统一,可以通过加法来处理减法运算,无需额外的符号位处理逻辑。这使得计算机中的加减运算电路更简单高效。
缺点:补码对负数的表示并不直观,不像原码那样一眼就能分辨数值的大小。
移码:
移码通常用于浮点数的指数部分。移码通过在原数值的基础上加上一个固定的偏移量来表示数值,避免了符号位的使用。常用的偏移量是127(对于8位移码)。
例如:
假设偏移量为127,数值+5的移码表示为127+5=132(二进制为10000100)。
数值-5的移码表示为127-5=122(二进制为01111010)。
优点:移码可以避免处理符号位的问题,特别是在浮点数运算中非常有用。
缺点:移码仅适用于特定场景,如浮点数的指数部分,并不适合通用的整数运算。
(2)浮点数
浮点数在计算机中用于表示带有小数部分的数值。浮点数的表示方式包括符号位、阶码和尾数三部分。
①符号位:用于表示浮点数的正负。0表示正数,1表示负数。
②阶码:用于表示浮点数的指数部分,通常采用补码表示。阶码的大小决定了浮点数的表示范围。
③尾数:用于表示浮点数的有效数字部分,即小数点后面的数字。尾数的精度决定了浮点数的表示精度。
例如,将十进制数8.125转化为单精度浮点数表示,其二进制表示为1000.001,科学计数法为1.000001×2^3。对于该数,阶码位3,偏移之后的值为3+127=130,130转为二进制数为10000010。尾数部分为000001,首位符号位为0,得到其二进制表示为0 10000010 00000100000000000000000(尾数位不足23位则在后面补零,阶码不足8位则在前面补零)。
2.1.2 字符的表示方式
在计算机中,字符通过特定的编码方式来表示。常见的字符编码方式包括ASCII码和Unicode码。
(1)ASCII码
ASCII码(American Standard Code for Information Interchange,美国标准信息交换码)是一种用于表示英文字符和控制字符的编码方式。ASCII码使用7位或8位二进制数表示128个或256个不同的字符。ASCII码表中包括了大小写英文字母、数字、标点符号、空格等常见字符。
例如,在ASCII码表中,大写字母A的编码是65(十进制),对应的二进制表示是01000001。小写字母a的编码是97(十进制),对应的二进制表示是01100001。
(2)Unicode码
Unicode码(Universal Multiple-Octet Coded Character Set,统一码)是一种旨在涵盖全世界各种语言的字符编码方式。Unicode码使用多个字节表示不同语言和符号的字符,从而支持更广泛的字符集。
Unicode码包括多个版本,每个版本都增加了新的字符和符号。在Unicode码中,每个字符都有一个唯一的编码值,这个编码值可以用于在计算机中唯一地标识该字符。
例如,在Unicode码中,汉字“中”的编码是20013(十进制),对应的UTF-8编码是E4B8AD(十六进制)。
2.2撰写 Cheat Engine(CE)的详细使用指南
2.2.1 下载与安装
我们可以在CE的官网(https://www.cheatengine.org/)下载cheat Engine的安装包,下载完成后找到文件所在的位置,进入文件夹运行.Exe文件,随后可以进入CE的安装界面,设置好安装路径我们即可安装成功,或者直接下载压缩包,解压即可。
图2.1 成功安装CE
2.2.2 Cheat Engine简介
Cheat Engine(一般简称CE)是一款开放源代码、免费且功能强大的内存修改编辑工具,它允许用户修改游戏或软件的内存数据,以实现一些额外的功能或作弊效果。它能够扫描正在运行的游戏或软件的内存,查找特定的数值或数据。Cheat Engine内置了十六进制编辑器,允许用户以十六进制形式查看和编辑文件或内存区域的内容,除此之外,Cheat Engine提供了动态地址扫描工具,使得即使是复杂的动态地址也能相对简单地被找到。
2.2.3 系统自带的教程详解
进入教程我们有两种方法,第一种从文件夹进入,第二种是从CE软件中的帮助栏里打开,如下图所示。
图2.2 文件夹进入教程
图2.3 CE软件进入教程
图2.4 CE使用教程
2.2.4 核心功能说明
(1)精确值扫描
首先我们用CE打开这个教程
图2.5 CE打开教程
这个功能精确值扫描,简单来说就是我们打开教程后,窗口显示“健康”,他的默认值是100,通过“打我”这个键可以相应的减少,我们的目的是找到他的数值位置,改成1000。可以先扫描精确值,再扫描减少的数值或者变化的数值。
①首先,我们将进程附加后,我们先精确搜索100
图 2.6 精确值扫描
②点击打我后,搜索新的健康值
图2.7 再次扫描
③这时我们发现已经找到了数值对应的地址,双击这个数值,改成1000,这个功能就完成了。
- 未知的初始值
①搜索未知的初始值
图2.9 未知初始值搜索
②点击“打我”,搜索“减少的数值”或者通过变动的数值去搜索数值减少了多少
图2.10 减少的数值
③以此类推,找到健康值,并改成要求的5000
图2.11 找到健康值
- 浮点数
我们可以在这关通过改变单精度浮点数的健康值和双精度浮点数的弹药值,改变两者的值为5000或者更大,不断扫描,最终找到目标内存并且改写,不同的是本关需要将搜索的数值类型改成浮点数。
①搜索单浮点数
图2.12 搜索单浮点
图2.13 找到单浮点值
②搜索双浮点值
图2.14 搜索双浮点值
③修改值
图2.15 修改值
- 代码查找
本关我们会发现健康值的存储位置会发生改变,先找到该地址,然后再查看是什么查找改写了该地址,并且对健康值做一次改变,找到改变找到地址的汇编代码,并将其替换为“nop”,即可通关。
①找到健康值的内存地址
图2.16 健康值的内存地址
②找到是什么改写了该地址,随后我们改变代码
图2.17 改变代码
③先点击“改变数值”,然后我们点击右上角的替换
图2.18 替换
④成功修改成nop,更改代码后,点击停止,关闭。
图2.19 完成
- 指针
首先找到数值的地址,然后再查找是什么改写了这个地址。再次改变数值,CE 便可以列出找到的汇编代码。 双击一行汇编代码(或选择它并点击"详细信息")并打开"详细信息"窗口以显示详细的信息,用来告诉你当这个指令运行时发生了什么事情。接着找到偏移量,手动添加地址,设置偏移量,找到基址。
①先找到数值地址
图2.20 数值地址
图2.21 找到
②查找是什么改写了这个地址,并且改变数值
图2.22 改变数值
图2.23 找到指令
③找到指令地址并查找,绿色的地址就是基址
图2.24 找到指针数值
图2.25 绿色基址
④添加指针
图2.26 添加指针
图2.27 添加完成
- 代码注入
代码注入是将一小段你写出的代码注入到目标进程中并执行它的技巧。查找这个地址,然后看看是什么在改写它("找出是什么改写了这个地址")。
当你看到那条减少数值的汇编代码后,选择"显示反汇编程序",然后打开"自动汇编窗口"(菜单-工具->自动汇编 或 按下快捷键 Ctrl+a ),选择"模板"中的"代码注入"。CE 将自动生成一部分汇编代码并为你输入指令做好准备(如果 CE 没有给出正确的地址,你也可以手工输入它)
接着,更改汇编指令
步骤:
①找到该健康值的地址
图2.28 健康值
图2.29 找到
图2.30 改写
图2.31 找到指针数值
图2.32 显示反汇编程序
图2.34 模版点击代码注入
图2.35 把sub改成add 后面的01改成02
图2.36 代码注入成功
- 多级指针
在这关里面,我们是四级指针,我们要把每一级的偏移量记录下来。
①查找数值的内存
图2.37 查找数值内存
图2.38 找到
图2.39 第一次偏移
图2.40 第二次偏移
图2.41 第三次偏移
图2.42 第四次偏移
图2.43 找到绿色基址
图2.44 添加地址
- 注入++
我们要找到四个玩家的血量内存地址,修改血量,查看是什么改写了该地址,找到与血量相关的代码。
①找到血量
图2.45 查找血量
图2.46 查找成功
②什么改写了这个值
图2.47 指针数值
图2.48 变动的数值
③右击什么访问了这个地址全选之后右键,点击 ‘ 打开选中地址的分析数据 ’ 可以看到四个玩家的血量,ID,标识符。
图2.49 查看
④我们发现友军的标识为1,敌军的是2,由这点我们可以进行反汇编进行代码注入,工具 进入自动汇编,模板里面的代码注入。
原代码如下:
图2.50 原代码
图2.51 修改后的代码
具体来说,cmp [ebx + 10], 1 这条指令用于比较 [ebx + 10]`位置处的值与数字 `1`。这里的 [ebx + 10] 实际是指向内存中某个地址的指针,该地址存储了一个敌友标识符。如果这个标识符的值等于 1,那么 je(Jump if Equal)指令将使程序跳转到标号 m:所指向的位置,这意味着当标识符确实为 `1` 时,程序将不会执行接下来的 mov指令,即不会进行扣血操作。
标识符值为 `0010`,这表明当前对象不是敌人(假设 `1` 表示敌人,`0` 或其他值表示非敌人)。因此,在这种情况下,`cmp [ebx + 10], 1` 指令会发现 `[ebx + 10]` 的值并不等于 `1`,`je` 指令不会触发跳转,程序将继续执行下一条指令,即执行扣血操作的 `mov` 指令。
为了实现预期的行为——即只有当对象是敌人时才执行扣血操作,可以通过修改或注入适当的代码,确保 `cmp [ebx + 10], 1` 和随后的 `je` 指令正确地根据敌友标识符来决定是否跳过扣血操作。完成这样的修改或注入之后,重新运行程序,就能确保只有真正的敌人才会被扣除生命值,而友方单位或中立对象则不会受到影响。