加密与解密x64逆向——变量、if和switch、循环语句
数据结构
主要是对局部变量,全局变量,数组等的识别。
1.局部变量
局部变量是函数内定义的变量,存放的内存区域称之为栈区。生命周期就是从函数进入到返回释放。
函数在入口处申请了预留栈空间和局部变量空间,也就是sub rsp,30h。局部变量空间在高地址。在应用程序被编译成release版本的时候,需要有更高的性能,所以在取局部变量的时候选用寄存器,当不够的时候再采用栈空间。
2.全局变量
3.数组
数组是相同数据类型的组合,以线性方式连续存储在内存中。数据是从低地址到高地址顺序排列的。
数组中有4个类型为int的集合。内存大小就是
比如下面这个例子可以见得一个字符4个字节,就可以推算出来数组中的位置
①数组寻址公式
编译器采用数组寻址公式来定位一个数组元素的地址。因此,掌握数组寻址公式,进行逆向分析的时候可以快速识别编译器访问的是哪个元素。一维数组寻址公式如下。
多维数组也适用。
可以这么理解第一个int【3】是指三个为一组的一维数组要排到第二个,再从第二排第一个开始定位。
②一维数组
可以看出,编译器访问数组的代码是利用数组寻址公式去访问的。当数组下标为常量的时候,公式可以直接计算出数组相对于数组首地址的偏移。
逆向的时候如果由以上特征,就怀疑是一个数组访问。
控制语句
主要介绍的是两种识别控制语句的方法,特征识别法和图形识别法。
1.if语句
真为非0,假为0.
特征识别:有一个jxx用于向下跳转,且跳转的目的if_end中没有jmp指令。
图形识别:虚线箭头表示条件跳转jxx,实线就是无条件跳转jmp。
2.if...else
比if语句多出一个else。当if表达式为真的时候就跳过else。
如果if成立就不jnz,接着运行,不成立就jnz,再跳出。
特征识别:有一个jxx用于向下跳转,且跳转的目的else中有jmp。else代码尾部没有jmp,else的代码会执行if_else_end代码。
图形识别:会有一个向下的虚线箭头,所以虚线箭头中会有一个向下的实线箭头。
3.if...else if...else语句
其实就是if...else多嵌套一层。
特征识别:多几个if中嵌套。
图形识别:如上。
4.switch...case语句
是比较常用的多分支结构。switch语句通常比if语句有更高的效率。编译器也有多种优化方案,比如switch分支小于6的时候直接用if...else语句实现,大于等于6的时候就会进行优化。
case表如下。
当大于等于6的时候,编译器会采用case表的方式实现switch语句。这样就可以避免使用if语句来提高性能。
这里有一个值的留意的地方,为什么要把argc减去1。答案就是argc减n为了switch表空闲空间减少,如下有个例子。
当case项较多的时候,编译器直接用if语句来实现switch语句。就有另外一个优化方案-判定树。将每一个case值作为一个节点,然后再节点中找到一个值,成立一个二叉平衡树。
这里已经将整个判断过程一分为二.
5.转移指令机器码的计算.
①call/jmp direct
②call/jmp memory direct
在32位系统当中这里是绝对地址。然而在x64系统当中就是相对地址。
循环语句
1.do循环
先执行语句块,再执行表达式判断。
特征识别:有一个jxx指令用于向上跳转,且跳转的目的语句当中没有jxx指令。
图形识别:有一个向上的虚线箭头。
2.while循环
先进行表达式判断,再执行语句块
循环的特点是向低地址跳转。需要注意的是while比do多一次if判断
,循环上效率不如do。
特征识别:会先有一个jmp向上跳转的指令,然后下面有一个jxx跳转指令。
图形识别:因为有jmp向上跳转,实现箭头。还有一个向下的虚线箭头。
3.for循环
是由赋初值,循环条件,循环步长。
自增就是圈中的地方。
特征识别:多了一个jmp跳转用于自增。
图形识别:因为多了一个跳转,所以会比while循环多一个向下的箭头。
数学运算符
本节讲的是release版本中编译器优化过的四则运算。
1.整数加法和减法
①加法和减法
②常量折叠
当出现两个以上常量运算的时候,编译器可以在编译期间计算出结果。这样在程序允许中就可以不用计算。
2.整数的乘法
有有符号(imul)无符号(mul)两种。周期较长,所以经常用lea比例因子寻址优化乘法指令。
3.整数的除法
除法指令周期较长所以一般用其他指令来代替除法指令。目前本人境界实在看不懂,待我重新看看书。