上一篇:【逆向工程】C/C++的反汇编表示详解(1)函数调用,栈平衡,变量与参数的内存布局
下一篇:
整数类型
整数类型在计算机中以补码的形式保存
int x=-1 //0xFFFFFFFF 1111 1111 1111 1111 1111 1111 1111 1111
char x=-1 //0xFF 1111 1111
char x=1 //0x01 0000 0001
有符号数和无符号数存储范围
signed char x=1 // 0~127 -128~-1
unsigned char x=1 //0~255
有符号数和无符号数在内存中存储是一样的 例
unsigned char=-1 //0xFFFFFFFF
signed char=-1 //0xFFFFFFFF
有符号数和无符号数只在扩展和比较时才有区别
#include <stdio.h>
int main()
{
int x=-1; //0xFFFFFFFF 4,294,967,295
printf("%d\n",x); //有符号打印
printf("%u\n",x); //无符号打印
return 0;
}
/*
这里用的是有符号扩展转移指令
扩展位以符号位为填充符
*/
signed char x=-1; //0xFF 1111 1111
int y=x; //11111111 11111111 11111111 11111111
unsigned char x=1 //0x01 0000 0001
int y=x; //00000000 00000000 00000000 00000001
在比较时都看作有符号数
unsigned char x=-1; //0xFF
unsigned char y=1; //0X01
if(x>y){
printf("x>y\n");
}
明明x=-1,y=1确为什么输出的是x>y呢?这是因为x,y都为无符号数,上面已经说过了,有符号数和无符号数只在扩展和比较时才有区别,在储存在内存中都是
字符与字符串
字符
源代码
#include <stdio.h>
int main()
{
int ='A';
putchar(0x41);
}
反汇编
/*
可以看出char型并不是字符型 而是整形 其储存的其实还是数值
*/
0040D4B8 mov dword ptr [ebp-4],41h
字符串
源码
#include <stdio.h>
int main()
{
printf("Hello world!");
return 0;
}
反汇编
0040D4B8 push offset string "Hello world" (00422fa0)
0040D4BD call printf (0040d760)
0040D4C2 add esp,4
中文字符
重写了EASCII表 如果每种文字都重写 那么会出现乱码
源代码
#include <stdio.h>
int main()
{
char buffer[30]="Hello world";
char bufferC[20]="中国";
return 0;
}
反汇编
4: char buffer[30]="Hello world";
/*
依次将字符存放在内存中
*/
00401028 mov eax,[string "Hello world" (00422024)]
0040102D mov dword ptr [ebp-20h],eax
00401030 mov ecx,dword ptr [string "Hello world"+4 (00422028)]
00401036 mov dword ptr [ebp-1Ch],ecx
00401039 mov edx,dword ptr [string "Hello world"+8 (0042202c)]
0040103F mov dword ptr [ebp-18h],edx
/*
buffer[30]剩下的空白区域填充0x00
*/
00401042 xor eax,eax //等于mov EAX,0
00401044 mov dword ptr [ebp-14h],eax
00401047 mov dword ptr [ebp-10h],eax
0040104A mov dword ptr [ebp-0Ch],eax
0040104D mov dword ptr [ebp-8],eax
00401050 mov word ptr [ebp-4],ax
/*
每两个字符代表一个中文汉字
如0xD6 0XD0 表示 ‘中’
0xD9 0XFA 表示 ‘国’
*/
5: char bufferC[20]="中国";
00401054 mov ecx,dword ptr [string "\xd6\xd0\xb9\xfa" (0042201c)]
0040105A mov dword ptr [ebp-34h],ecx
0040105D mov dl,byte ptr [string "\xd6\xd0\xb9\xfa"+4 (00422020)]
00401063 mov byte ptr [ebp-30h],dl
00401066 xor eax,eax
00401068 mov dword ptr [ebp-2Fh],eax
0040106B mov dword ptr [ebp-2Bh],eax
0040106E mov dword ptr [ebp-27h],eax
00401071 mov word ptr [ebp-23h],ax
00401075 mov byte ptr [ebp-21h],al
运算符与表达式
char型与int型做运算时都会变为int型
源代码
#include <stdio.h>
int main()
{
char x=1;
short y=2;
short z=x+y;
int j=x+y;
return 0;
}
反汇编
/*
给局部变量x y赋初始值
*/
4: char x=1;
00401028 mov byte ptr [ebp-4],1
5: short y=2;
0040102C mov word ptr [ebp-8],offset main+20h (00401030)
6: short z=x+y;
/*
可以看到 不管计算结果为int还是short
char和short在计算中总会转化为四个字节的int型
*/
00401032 movsx eax,byte ptr [ebp-4]
00401036 movsx ecx,word ptr [ebp-8] //movsx有符号扩展转移指令
0040103A add eax,ecx
0040103C mov word ptr [ebp-0Ch],ax
7: int j=x+y;
00401040 movsx edx,byte ptr [ebp-4]
00401044 movsx eax,word ptr [ebp-8]
00401048 add edx,eax
0040104A mov dword ptr [ebp-10h],edx
char -> short -> int -> float -> double
分支语句if
IF-THEN
源代码
#include <stdio.h>
int main()
{
if(1)
printf("ok");
int x=1;
int y=2;
if (x<y)
printf("x<y");
return 0;
}
反汇编
4: if(1)
00401028 mov eax,1
0040102D test eax,eax
0040102F je main+2Eh (0040103e)
5: printf("ok");
00401031 push offset string "ok" (00422020)
00401036 call printf (0040d6b0)
0040103B add esp,4
6: int x=1;
0040103E mov dword ptr [ebp-4],1
7: int y=2;
00401045 mov dword ptr [ebp-8],2
8: if (x<y)
/*
这里的jge是大于等于则跳转
而源代码是x<y
这就很巧妙了,大家可以看到如果不满足x<y那也就是大于等于
大于等于就直接返回,不执行if语句中的内容
*/
0040104C mov ecx,dword ptr [ebp-4]
0040104F cmp ecx,dword ptr [ebp-8]
00401052 jge main+51h (00401061)
9: printf("x<y");
00401054 push offset string "x<y" (0042201c)
00401059 call printf (0040d6b0)
0040105E add esp,4
10: return 0;
00401061 xor eax,eax
说一下test和cmp指令
test是进行逻辑与运算,test指令常用来判断一个寄存器是否为空
如 test eax,eax
cmp是进行减法运算,其结果不保存,只影响标志位寄存器
但编译器经常将其优化为test或or之类较短的逻辑指令来替换cmp指令,形式通常为test eax,eax
IF-THEN-ELSE及嵌套
源代码
#include <stdio.h>
int main()
{
int x=1;
int y=2;
if (x<y)
{
printf("x<y");
if(x==-1)
{
printf("x=-1");
}
}
else if(x==y)
printf("x=y");
else
printf("else");
return 0;
}
反汇编
6: int x=1;
00401028 mov dword ptr [ebp-4],1
7: int y=2;
0040102F mov dword ptr [ebp-8],2
8: if (x<y)
00401036 mov eax,dword ptr [ebp-4]
00401039 cmp eax,dword ptr [ebp-8]
0040103C jge main+50h (00401060)
9: {
10: printf("x<y");
0040103E push offset string "x<y" (0042213c)
00401043 call printf (004010d0)
00401048 add esp,4
11: if(x==-1)
0040104B cmp dword ptr [ebp-4],0FFh
0040104F jne main+4Eh (0040105e)
12: {
13: printf("x=-1");
00401051 push offset string "x=-1" (00422028)
00401056 call printf (004010d0)
0040105B add esp,4
14: }
15: }
16: else if(x==y)
0040105E jmp main+74h (00401084)
00401060 mov ecx,dword ptr [ebp-4]
00401063 cmp ecx,dword ptr [ebp-8]
00401066 jne main+67h (00401077)
17: printf("x=y");
00401068 push offset string "x=y" (00422024)
0040106D call printf (004010d0)
00401072 add esp,4
18: else
00401075 jmp main+74h (00401084)
19: printf("else");
00401077 push offset string "else" (0042201c)
0040107C call printf (004010d0)
00401081 add esp,4
20:
21: return 0;
00401084 xor eax,eax
22: }
总结
比较指令后,同意一条条件转移指令跳转到另一条指令上,跳转到的这条指令上,如果还有个无条件跳转指令,大概率是if else语句,但还是要结合实际情况
切记不能死记命令因为编译器的不同,相同的C代码可能会有不同的表现形式,可以归纳其规律,但也要学会变通
switch语句为什么比if…else语句高效
源代码
int main()
{
int a=1;
switch(a)
{
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
default:
printf("default");
};
return 0;
}
反汇编
6: int a=1;
0040D778 mov dword ptr [ebp-4],1
7: switch(a)
8: {
/*
case比较
*/
0040D77F mov eax,dword ptr [ebp-4]
0040D782 mov dword ptr [ebp-8],eax
0040D785 cmp dword ptr [ebp-8],1
0040D789 je main+39h (0040d799)
0040D78B cmp dword ptr [ebp-8],2
0040D78F je main+48h (0040d7a8)
0040D791 cmp dword ptr [ebp-8],3
0040D795 je main+57h (0040d7b7)
0040D797 jmp main+66h (0040d7c6)
/*
case 所要执行的代码
*/
9: case 1:
10: printf("1");
0040D799 push offset string "x<y" (0042213c)
0040D79E call printf (004010d0)
0040D7A3 add esp,4
11: break;
0040D7A6 jmp main+73h (0040d7d3)
12: case 2:
13: printf("2");
0040D7A8 push offset string "x=-1" (00422028)
0040D7AD call printf (004010d0)
0040D7B2 add esp,4
14: break;
0040D7B5 jmp main+73h (0040d7d3)
15: case 3:
16: printf("3");
0040D7B7 push offset string "x=y" (00422024)
0040D7BC call printf (004010d0)
0040D7C1 add esp,4
17: break;
0040D7C4 jmp main+73h (0040d7d3)
18: default:
19: printf("default");
0040D7C6 push offset string "else" (0042201c)
0040D7CB call printf (004010d0)
0040D7D0 add esp,4
20: };
21: return 0;
0040D7D3 xor eax,eax
22: }
通过上述反汇编我们发现,switch语句也是不断的比较跳转来实现的,并没有比if…else语句高效,
那么我们试着增添case条目再来看看
源代码
#include <stdio.h>
int main()
{
int a=1;
switch(a)
{
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
case 4:
printf("3");
break;
case 5:
printf("3");
break;
case 6:
printf("3");
break;
default:
printf("default");
};
return 0;
}
反汇编
6: int a=1;
0040D778 mov dword ptr [ebp-4],1
7: switch(a)
8: {
0040D77F mov eax,dword ptr [ebp-4]
0040D782 mov dword ptr [ebp-8],eax
0040D785 mov ecx,dword ptr [ebp-8]
/*
SUB减多少不是固定的,是根据case中最小的一个数来决定的
*/
0040D788 sub ecx,1
0040D78B mov dword ptr [ebp-8],ecx
/
判断是否满足default条件
/
0040D78E cmp dword ptr [ebp-8],5
0040D792 ja $L539+0Fh (0040d7f8)
/*
编译器生成了一个跳转表
*/
0040D794 mov edx,dword ptr [ebp-8]
0040D797 jmp dword ptr [edx*4+40D818h]
9: case 1:
10: printf("1");
0040D79E push offset string "x<y" (0042213c)
0040D7A3 call printf (004010d0)
0040D7A8 add esp,4
11: break;
0040D7AB jmp $L539+1Ch (0040d805)
12: case 2:
13: printf("2");
0040D7AD push offset string "x=-1" (00422028)
0040D7B2 call printf (004010d0)
0040D7B7 add esp,4
14: break;
0040D7BA jmp $L539+1Ch (0040d805)
15: case 3:
16: printf("3");
0040D7BC push offset string "x=y" (00422024)
0040D7C1 call printf (004010d0)
0040D7C6 add esp,4
17: break;
0040D7C9 jmp $L539+1Ch (0040d805)
18: case 4:
19: printf("3");
0040D7CB push offset string "x=y" (00422024)
0040D7D0 call printf (004010d0)
0040D7D5 add esp,4
20: break;
0040D7D8 jmp $L539+1Ch (0040d805)
21: case 5:
22: printf("3");
0040D7DA push offset string "x=y" (00422024)
0040D7DF call printf (004010d0)
0040D7E4 add esp,4
23: break;
0040D7E7 jmp $L539+1Ch (0040d805)
24: case 6:
25: printf("3");
0040D7E9 push offset string "x=y" (00422024)
0040D7EE call printf (004010d0)
0040D7F3 add esp,4
26: break;
0040D7F6 jmp $L539+1Ch (0040d805)
27: default:
28: printf("default");
0040D7F8 push offset string "else" (0042201c)
0040D7FD call printf (004010d0)
0040D802 add esp,4
29: };
30: return 0;
0040D805 xor eax,eax
31: }
可以看到,现在就只有两条跳转指令了,这就是跳转表的作用,如果各case的取值表示一个算术级,那么编译器就会利用一个跳转表(Jump Table)来实现
/*
编译器生成了一个跳转表
*/
0040D794 mov edx,dword ptr [ebp-8]
0040D797 jmp dword ptr [edx*4+40D818h]
我们来看一下这个跳转表,可以看到起储存的是各个case节点的地址
关于优化
编译器在优化时,有可能会用 DEC EAX 指令来代替 CMP 指令,使指令更短