全局和局部变量
全局变量可以被一个程序中的任意函数访问和使用
局部变量只能在它被定义的函数中访问
在反汇编代码中:
全局变量通过内存地址引用
局部变量通过栈地址引用
代码清单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 查看汇编代码)
demo1.png
此部分代码为未经标记的汇编代码。
demo22.png
这部分代码是经过IDA标记的代码。
demo2.png反汇编算数操作
以下的C代码进行了加发,减法,自增自减,去模五种运算
C语言代码
suanshu1.jpg汇编代码
suanshu2.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;
}
suanshu22.png
识别if语句
对于一个if语句必定有一个条件跳转
不是所有的条件跳转都对应一个if语句
C语言代码
if1.jpg汇编代码
if2.jpg基于cmp的结果进行判断是否继续执行还是跳转到40102B
用IDA Pro图像化分析函数
if3.jpg if4.pngno为红色箭头
yes为绿色箭头
蓝色表示下一个执行块
识别嵌套的if语句
C语言代码
if5.jpg汇编代码
if6.jpg if7.jpg
1,2,3处发生了三次跳转。
doubleif.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);
}
}
汇编
for1.jpgIDA Pro图形化
for2.jpg for3.png
当比较语句为false的时候,执行循环四步,有一个很明显的闭环。红的箭头部分是跳出循环,可以进行下一步操作,本示例程序中,没有进行下一步动作,仅清理栈并返回。
找到while循环
C语言代码
while1.jpg汇编代码
while2.jpg
停止代码的时候应该关注1处的条件跳转。
理解函数的调用约定
函数调用在汇编代码中的表现可能不一样,调用约定决定了函数调用发生的方式。这些约定包含了参数被放在栈上或者寄存器中的顺序,以及是由调用者或者被调函数负责在函数执行完毕后清理栈。
一个函数调用的伪代码
call1.jpg
最常见的三个调用约定:cdecl,stdcall,fastcall,下面讨论他们的关键区别。
1. cdecl
call2.jpg在cdecl约定中:
参数从右到左按序被压入栈
当函数完成时由调用者清理栈。
将返回值保存在EAX中。
2.stdcall
stdcall主要约定了栈的清理是由被调函数来执行的。stdcall是Windows API的标准调用约定。任何调用这些API的代码都不需要清理栈,清理栈由实现API函数代码的DLL程序所承担
3.fastcall
在fastcall中,前面的一些参数被传到寄存器(典型的是前两个),备用的寄存器是EDX和ECX。如果需要,剩下的参数再以从右到左的次序被加载到栈上。
使用fastcall比其他约定更高效,因为代码不需要涉及过多的栈操作
4.压栈与移动
C代码
mov1.jpgadder函数汇编代码
move2.jpg即使是同一种编译器,在调用约定方面也可能存在差异性,这依赖于各种选项和设置
move3.jpg
分析switch语句
if语句通常以两种方式被编译:
使用if样式和跳转表
IF样式
C代码
switch1.jpg反汇编代码
switch2.jpgswitch3.jpg
采用三对比较跳转,最后一句无条件跳转
IDA Pro图形化
switch4.jpg整个图线看上去就像是多层嵌套的IF,在if判断为False的情况下执行下一个判断。
switch5.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;
}
}
jump0.jpg
jump1.jpg jump2.jpg
jump3-1.png
[rbp+var_4]存储的是case 一共有六种case(从0到5)所以是和case-1做比较
jump3-2.pnglea rdx, ds:0[rax*4]
这个指令中是rax*4加上跳转表的基址来确定要跳转到哪一个case块,乘以四是因为跳转表中每一项是一个4字节大小的地址。
IDA Pro上有标记这是跳转表。
反汇编数组
数组a是局部定义的,数组b是全局定义的
C代码
string1.jpg汇编代码
string2.jpg string3.pngeax是索 引,每个元素的大小是4,数据的基址加上偏移来访问正确的数组元素。
string4.png识别结构体
C代码
struct1.jpgmain函数反汇编代码
struct2.jpgtest函数反汇编代码
struct3.jpg struct4.jpg分析链表遍历
C代码
list1.jpg list2.jpg汇编代码
list3.jpg list4.jpg小结
- 从细节中抽象
- 不要过度死磕汇编,从全局角度看待问题
网友评论