全局和局部变量
全局变量可以被一个程序中的任意函数访问和使用
局部变量只能在它被定义的函数中访问
在反汇编代码中:
全局变量通过内存地址引用
局部变量通过栈地址引用
代码清单6-1:两个全局变量
#include<stdio.h>
int x = 1;
int y = 2;
void main() {
x = x + y;
printf("total=%d", x);
}
代码清单6-2:两个局部变量
#include<stdio.h>
void main(){
int x = 1;
int y = 2;
x = x+y;
printf("total=%d", x);
}
使用gcc编译为64位程序,使用IDA打开(也可以使用gcc -S 查看汇编代码)
![](https://img.haomeiwen.com/i17790459/817d9b2042e669d3.png)
此部分代码为未经标记的汇编代码。
![](https://img.haomeiwen.com/i17790459/9ebdec4cb7a395a5.png)
这部分代码是经过IDA标记的代码。
![](https://img.haomeiwen.com/i17790459/2e6b67dfccc87e4a.png)
反汇编算数操作
以下的C代码进行了加发,减法,自增自减,去模五种运算
C语言代码
![](https://img.haomeiwen.com/i17790459/db1ca5e5a75cbe8a.jpg)
汇编代码
![](https://img.haomeiwen.com/i17790459/485e207d0a064d38.jpg)
这里需要解释一下cdq的作用,cdq其实多出现在除法运算之前,这里使用除法来进行取模运算。
CDQ 是一个让很多人感到困惑的指令。这个指令把 EAX 的第 31 bit 复制到 EDX 的每一个 bit 上。 它大多出现在除法运算之前。它实际的作用只是把EDX的所有位都设成EAX最高位的值。也就是说,当EAX <80000000, EDX 为00000000;当EAX >= 80000000, EDX 则为FFFFFFFF。
例如 :
假设 EAX 是 FFFFFFFB (-5) ,它的第 31 bit (最左边) 是 1,
执行 CDQ 后, CDQ 把第 31 bit 复制至 EDX 所有 bit
EDX 变成 FFFFFFFF
这时候, EDX:EAX 变成 FFFFFFFF FFFFFFFB ,它是一个 64 bit 的大型数字,数值依旧是 -5。
备注:
EDX:EAX,这里表示EDX,EAX连用表示64位数
在使用div
或者idiv
指令时,是在用edx:eax除操作数并将结果保存在eax中,余数保存在edx中,最后把余数赋值给b。
void main(){
int a = 0;
int b = 1;
a = a + 11;
a = a - b;
a--;
b++;
b = a % 3;
}
![](https://img.haomeiwen.com/i17790459/be10ea0a73b81021.png)
识别if语句
对于一个if语句必定有一个条件跳转
不是所有的条件跳转都对应一个if语句
C语言代码
![](https://img.haomeiwen.com/i17790459/3f7d56ea7b9ff2c6.jpg)
汇编代码
![](https://img.haomeiwen.com/i17790459/f821dae63a9d9f16.jpg)
基于cmp的结果进行判断是否继续执行还是跳转到40102B
用IDA Pro图像化分析函数
![](https://img.haomeiwen.com/i17790459/10f83b0bce5665bf.jpg)
![](https://img.haomeiwen.com/i17790459/e4fd6451e071e135.png)
no为红色箭头
yes为绿色箭头
蓝色表示下一个执行块
识别嵌套的if语句
C语言代码
![](https://img.haomeiwen.com/i17790459/cd644da3ec4dd72c.jpg)
汇编代码
![](https://img.haomeiwen.com/i17790459/76b77dca66970d7b.jpg)
![](https://img.haomeiwen.com/i17790459/b4f9501ea653ba86.jpg)
1,2,3处发生了三次跳转。
![](https://img.haomeiwen.com/i17790459/3f53fcd227dc661c.png)
这类多重if嵌套还是直接看IDA图像比较方便。
识别循环
找到for循环
for循环的四个组件
- 初始化
- 比较条件‘
- 执行代码
- 递增递减
C语言代码
# include<stdio.h>
void main(){
int i;
for(i=0;i<100;i++){
printf("i equals %d.\n", i);
}
}
汇编
![](https://img.haomeiwen.com/i17790459/bc053a3a0c43a0ae.jpg)
IDA Pro图形化
![](https://img.haomeiwen.com/i17790459/61fa4920cbddd8d1.jpg)
![](https://img.haomeiwen.com/i17790459/8b377d8299de8e4f.png)
当比较语句为false的时候,执行循环四步,有一个很明显的闭环。红的箭头部分是跳出循环,可以进行下一步操作,本示例程序中,没有进行下一步动作,仅清理栈并返回。
找到while循环
C语言代码
![](https://img.haomeiwen.com/i17790459/68e50ce2fdf4464f.jpg)
汇编代码
![](https://img.haomeiwen.com/i17790459/d6d20c44cfba79cf.jpg)
停止代码的时候应该关注1处的条件跳转。
理解函数的调用约定
函数调用在汇编代码中的表现可能不一样,调用约定决定了函数调用发生的方式。这些约定包含了参数被放在栈上或者寄存器中的顺序,以及是由调用者或者被调函数负责在函数执行完毕后清理栈。
一个函数调用的伪代码
![](https://img.haomeiwen.com/i17790459/de00950cd10f09f8.jpg)
最常见的三个调用约定:cdecl,stdcall,fastcall,下面讨论他们的关键区别。
1. cdecl
![](https://img.haomeiwen.com/i17790459/c65bc7ecc8e5f891.jpg)
在cdecl约定中:
参数从右到左按序被压入栈
当函数完成时由调用者清理栈。
将返回值保存在EAX中。
2.stdcall
stdcall主要约定了栈的清理是由被调函数来执行的。stdcall是Windows API的标准调用约定。任何调用这些API的代码都不需要清理栈,清理栈由实现API函数代码的DLL程序所承担
3.fastcall
在fastcall中,前面的一些参数被传到寄存器(典型的是前两个),备用的寄存器是EDX和ECX。如果需要,剩下的参数再以从右到左的次序被加载到栈上。
使用fastcall比其他约定更高效,因为代码不需要涉及过多的栈操作
4.压栈与移动
C代码
![](https://img.haomeiwen.com/i17790459/3cd81a3edcbbfbae.jpg)
adder函数汇编代码
![](https://img.haomeiwen.com/i17790459/36cad232280eb51c.jpg)
即使是同一种编译器,在调用约定方面也可能存在差异性,这依赖于各种选项和设置
![](https://img.haomeiwen.com/i17790459/c639923fc9783624.jpg)
分析switch语句
if语句通常以两种方式被编译:
使用if样式和跳转表
IF样式
C代码
![](https://img.haomeiwen.com/i17790459/a4f0557e09dff163.jpg)
反汇编代码
![](https://img.haomeiwen.com/i17790459/950dff52d56d881c.jpg)
![](https://img.haomeiwen.com/i17790459/69c98dfda27c03d9.jpg)
采用三对比较跳转,最后一句无条件跳转
IDA Pro图形化
![](https://img.haomeiwen.com/i17790459/e5f2b322bc77925b.jpg)
整个图线看上去就像是多层嵌套的IF,在if判断为False的情况下执行下一个判断。
![](https://img.haomeiwen.com/i17790459/23bcd56e4ad8754a.png)
跳转表
上面三个case的switch会被编译器编译为if,else结构,但是如果case的数量线性增加,或者刚好我们需要的case在最后一个,那么算法的时间复杂度不就变成了O(n)吗,其实并不是这样,当case的数量增加,编译器会使用跳转表优化代码,降低算法的时间复杂度。
C语言代码
#include<stdio.h>
void main(){
int i=2;
switch(i)
{
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
case 4:
printf("4");
break;
case 5:
printf("5");
break;
default:
break;
}
}
![](https://img.haomeiwen.com/i17790459/a2a402dc225587d7.jpg)
![](https://img.haomeiwen.com/i17790459/7aedd88d28ab75f2.jpg)
![](https://img.haomeiwen.com/i17790459/9ed9fa41548ff54d.jpg)
![](https://img.haomeiwen.com/i17790459/483b00df7cd53efa.png)
[rbp+var_4]存储的是case 一共有六种case(从0到5)所以是和case-1做比较
![](https://img.haomeiwen.com/i17790459/51e2ebeaf154d737.png)
lea rdx, ds:0[rax*4]
这个指令中是rax*4加上跳转表的基址来确定要跳转到哪一个case块,乘以四是因为跳转表中每一项是一个4字节大小的地址。
![](https://img.haomeiwen.com/i17790459/1493dd98ed8011c8.png)
IDA Pro上有标记这是跳转表。
反汇编数组
数组a是局部定义的,数组b是全局定义的
C代码
![](https://img.haomeiwen.com/i17790459/ce4a06d428b6dc0d.jpg)
汇编代码
![](https://img.haomeiwen.com/i17790459/4ac007365b6a1108.jpg)
![](https://img.haomeiwen.com/i17790459/2199837da3298297.png)
eax是索 引,每个元素的大小是4,数据的基址加上偏移来访问正确的数组元素。
![](https://img.haomeiwen.com/i17790459/0520aa82e88f0003.png)
识别结构体
C代码
![](https://img.haomeiwen.com/i17790459/cab2d93a4cfff2a6.jpg)
main函数反汇编代码
![](https://img.haomeiwen.com/i17790459/5712baf7951d7806.jpg)
test函数反汇编代码
![](https://img.haomeiwen.com/i17790459/3da4569425742bc7.jpg)
![](https://img.haomeiwen.com/i17790459/41f9694bfce5bfe5.jpg)
分析链表遍历
C代码
![](https://img.haomeiwen.com/i17790459/9e4678b4c7e21c45.jpg)
![](https://img.haomeiwen.com/i17790459/f7c5b9a461782ccd.jpg)
汇编代码
![](https://img.haomeiwen.com/i17790459/2ceebe3f58d6bdd6.jpg)
![](https://img.haomeiwen.com/i17790459/708d54f3ab616c2b.jpg)
小结
- 从细节中抽象
- 不要过度死磕汇编,从全局角度看待问题
网友评论