【逆向工程】C/C++的反汇编表示详解(2) 整数,字符串,分支语句

上一篇:【逆向工程】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 指令,使指令更短

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘乙兵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值