C语言反汇编-数据类型与常量

反汇编(Disassembly) 即把目标二进制机器码转为汇编代码的过程,该技术常用于软件破解、外挂技术、病毒分析、逆向工程、软件汉化等领域,学习和理解反汇编对软件调试、系统漏洞挖掘、内核原理及理解高级语言代码都有相当大的帮助,软件一切神秘的运行机制全在反汇编代码里面。

本笔记的内容参考了《C++反汇编与逆向分析技术揭秘》,提取出书中的重点内容,并加以巩固学习,提高自己做好笔记。

1.使用编译器 VS 2013 Express 版本,写代码,并关闭基地址随机化,编译采用debug版。

C语言反汇编-数据类型与常量

2.寻找OEP,x64dbg 载入,默认停在,回车跟进。

C语言反汇编-数据类型与常量

call <__tmainCRTStartup> 再次回车跟进。

C语言反汇编-数据类型与常量

下方看到一个安全cookie检测,主要防止在目标堆栈中瞎搞,而设计的安全机制。编译器开启后,每次运行都会生成随机cookie,结束时会验证是否一致,防止瞎搞,老版本编译器中不存在这个选项,一开始开发人员也没想那么多,后来瞎搞的人多了,才加上的,主要是害怕自己的windows被玩坏。

C语言反汇编-数据类型与常量

继续向下看代码,看到以下代码就到家了,call 0x0041113B 跟进去就是main.

C语言反汇编-数据类型与常量


数值类型变量: 数值类型默认在32位编译器上是4字节存储的。

#include <stdio.h>

int main(int argc, char* argv[])
{
	int x = 10, y = 20, z = 0;
	printf("%d\n", x + y);
	return 0;
}

反汇编结果如下,如果在vc6下mov ecx,0xC 原因 int=4 3*4=0xC

004113CC | 8DBD 1CFFFFFF            | lea edi,dword ptr ss:[ebp-0xE4]                | 取出内存地址
004113D2 | B9 39000000              | mov ecx,0x39                                   | 指定要填充的字节数
004113D7 | B8 CCCCCCCC              | mov eax,0xCCCCCCCC                             | 将内存填充为0xcccccc
004113DC | F3:AB                    | rep stosd                                      | 调用rep指令,对内存进行初始化
004113DE | C745 F8 0A000000         | mov dword ptr ss:[ebp-0x8],0xA                 | 对变量x初始化为10
004113E5 | C745 EC 14000000         | mov dword ptr ss:[ebp-0x14],0x14               | 对变量y初始化为20
004113EC | C745 E0 00000000         | mov dword ptr ss:[ebp-0x20],0x0                | 对变量c初始化为0
004113F3 | 8B45 F8                  | mov eax,dword ptr ss:[ebp-0x8]                 | 取出10并放入eax
004113F6 | 0345 EC                  | add eax,dword ptr ss:[ebp-0x14]                | 与20相加得到结果付给eax
004113F9 | 8BF4                     | mov esi,esp                                    | 保存当前栈帧
004113FB | 50                       | push eax                                       |
004113FC | 68 58584100              | push consoleapplication1.415858                | 415858:"%d\n"
00411401 | FF15 14914100            | call dword ptr ds:[<&printf>]                  |
00411407 | 83C4 08                  | add esp,0x8                                    |

除了整数类型外,浮点数也是一种数值运算方式,将上方代码稍微修改后,编译并查看其反汇编代码.

#include <stdio.h>

int main(int argc, char* argv[])
{
	double x = 1.05, y = 20.56, z = 0;
	z = x + y;
	printf("%f\n", z);
	return 0;
}

观察以下汇编代码,会发现前面的初始化部分完全一致,但后面则使用了xmm0寄存器,该寄存器是专门用于运算浮点数而设计的浮点运算模块默认大小就是64位,其将内存中的值取出来并放入xmm0中进行中转,然后复制到堆栈中,等待最后调用addsd命令完成对浮点数的加法运算,并将运算结果回写到缓冲区,最后调用打印函数实现输出.

004113CC | 8DBD 10FFFFFF            | lea edi,dword ptr ss:[ebp-0xF0]                          |
004113D2 | B9 3C000000              | mov ecx,0x3C                                             |
004113D7 | B8 CCCCCCCC              | mov eax,0xCCCCCCCC                                       |
004113DC | F3:AB                    | rep stosd                                                |
004113DE | F2:0F1005 70584100       | movsd xmm0,qword ptr ds:[<__real@3ff0cccccccccccd>]      | 将内存地址中的值取出并放入xmm0中
004113E6 | F2:0F1145 F4             | movsd qword ptr ss:[ebp-0xC],xmm0                        | 将数据放入堆栈中存储
004113EB | F2:0F1005 80584100       | movsd xmm0,qword ptr ds:[<__real@40348f5c28f5c28f>]      |
004113F3 | F2:0F1145 E4             | movsd qword ptr ss:[ebp-0x1C],xmm0                       |
004113F8 | F2:0F1005 60584100       | movsd xmm0,qword ptr ds:[<__real@0000000000000000>]      |
00411400 | F2:0F1145 D4             | movsd qword ptr ss:[ebp-0x2C],xmm0                       |
00411405 | F2:0F1045 F4             | movsd xmm0,qword ptr ss:[ebp-0xC]                        | main.c:6
0041140A | F2:0F5845 E4             | addsd xmm0,qword ptr ss:[ebp-0x1C]                       | 对浮点数进行相加操作
0041140F | F2:0F1145 D4             | movsd qword ptr ss:[ebp-0x2C],xmm0                       | 结果放入堆栈等待被打印
00411414 | 8BF4                     | mov esi,esp                                              |
00411416 | 83EC 08                  | sub esp,0x8                                              |
00411419 | F2:0F1045 D4             | movsd xmm0,qword ptr ss:[ebp-0x2C]                       |
0041141E | F2:0F110424              | movsd qword ptr ss:[esp],xmm0                            |
00411423 | 68 58584100              | push consoleapplication1.415858                          | 415858:"%f\n"==L"春\n"
00411428 | FF15 14914100            | call dword ptr ds:[<&printf>]                            |
0041142E | 83C4 0C                  | add esp,0xC                                              |

C语言反汇编-数据类型与常量

字符与字符串变量:

#include <stdio.h>

int main(int argc, char* argv[])
{
	char x = 'a', y = 'b',z = 'c';
	printf("x = %d --> y = %d --> z = %d", x,y,z);
	return 0;
}

反汇编结果如下,观察发现字符型的表现形式与整数类型基本一致,只是在数据位大小方面有所区别,如上int类型使用dword作为存储单位,而字符类型则默认使用byte形式存储。

004113CC | 8DBD 1CFFFFFF            | lea edi,dword ptr ss:[ebp-0xE4]          |
004113D2 | B9 39000000              | mov ecx,0x39                             |  
004113D7 | B8 CCCCCCCC              | mov eax,0xCCCCCCCC                       |  
004113DC | F3:AB                    | rep stosd                                |
004113DE | C645 FB 61               | mov byte ptr ss:[ebp-0x5],0x61           | 第一个字符
004113E2 | C645 EF 62               | mov byte ptr ss:[ebp-0x11],0x62          | 第二个字符
004113E6 | C645 E3 63               | mov byte ptr ss:[ebp-0x1D],0x63          | 第三个字符
004113EA | 0FBE45 E3                | movsx eax,byte ptr ss:[ebp-0x1D]         | 拷贝第三个字符
004113EE | 8BF4                     | mov esi,esp                              | esi:__enc$textbss$end+109
004113F0 | 50                       | push eax                                 |
004113F1 | 0FBE4D EF                | movsx ecx,byte ptr ss:[ebp-0x11]         | 拷贝第二个字符
004113F5 | 51                       | push ecx                                 |
004113F6 | 0FBE55 FB                | movsx edx,byte ptr ss:[ebp-0x5]          | 拷贝第一个字符
004113FA | 52                       | push edx                                 |
004113FB | 68 58584100              | push consoleapplication1.415858          | 415858:"x = %d --> y = %d --> z = %d"
00411400 | FF15 14914100            | call dword ptr ds:[<&printf>]            | 打印输出
00411406 | 83C4 10                  | add esp,0x10                             |

虽然使用的是byte存储一个字符,但是每一个字符在分配上还是占用了12个字节的空间4x3=12,编译器并没有因为它是一个字符而区别对待,同样是采用了4字节的分配风格.

C语言反汇编-数据类型与常量

反汇编第一种形式的字符串类型,发现首先会从常量字符串中ds:[0x415858]取出前四个字节子串,并将其压入堆栈中,然后再循环后四个字节子串并压栈,最后取出第一个字符串的堆栈地址并输出打印,该方法只适用于小字符串.

004113E8 | A1 58584100              | mov eax,dword ptr ds:[0x415858]         | 在常量字符串中取出数据 "hell"
004113ED | 8945 E8                  | mov dword ptr ss:[ebp-0x18],eax         | 将常量前面的hell压栈
004113F0 | 8B0D 5C584100            | mov ecx,dword ptr ds:[0x41585C]         | 继续压栈 "o lyshark"
004113F6 | 894D EC                  | mov dword ptr ss:[ebp-0x14],ecx         |
004113F9 | 8B15 60584100            | mov edx,dword ptr ds:[0x415860]         | 00415860:"shark"
004113FF | 8955 F0                  | mov dword ptr ss:[ebp-0x10],edx         |
00411402 | 66:A1 64584100           | mov ax,word ptr ds:[0x415864]           | 00415864:L"k"
00411408 | 66:8945 F4               | mov word ptr ss:[ebp-0xC],ax            | 最后将剩下的一个字单位压栈
0041140C | 8BF4                     | mov esi,esp                             | main.c:8
0041140E | 8D45 E8                  | lea eax,dword ptr ss:[ebp-0x18]         | 取出hell的内存地址作为首地址使用
00411411 | 50                       | push eax                                |
00411412 | 68 68584100              | push consoleapplication1.415868         | 415868:"短字符串: %s\n"
00411417 | FF15 14914100            | call dword ptr ds:[<&printf>]           | 打印输出
0041141D | 83C4 08                  | add esp,0x8                             |

C语言反汇编-数据类型与常量

反汇编长字符串与字符串指针,由于这两个类型具有很强的对比性所以放在一起,第1种字符串数组存储,可以看到内部是通过拷贝内存实现的,而第2种指针方式则是直接引用常量地址,从效率上来说指针效率更高,因为没有拷贝造成的性能损失.

004113E5 | 8945 FC                  | mov dword ptr ss:[ebp-0x4],eax          |
004113E8 | B9 06000000              | mov ecx,0x6                             | 循环拷贝6次
004113ED | BE 58584100              | mov esi,0x415858                        | 字符串常量地址
004113F2 | 8D7D DC                  | lea edi,dword ptr ss:[ebp-0x24]         | 取出初始化的内存空间首地址
004113F5 | F3:A5                    | rep movsd                               | 循环拷贝6次
004113F7 | 66:A5                    | movsw                                   | 单独拷贝最后的y字符

004113F9 | C745 D0 78584100         | mov dword ptr ss:[ebp-0x30],0x415858    | 取字符串常量地址
00411400 | 8BF4                     | mov esi,esp                             | main.c:8
00411402 | 8D45 DC                  | lea eax,dword ptr ss:[ebp-0x24]         | 通过指针指向字符串首地址
00411405 | 50                       | push eax                                |

00411406 | 68 98584100              | push consoleapplication1.415898         | 415898:"长字符串: %s\n"
0041140B | FF15 14914100            | call dword ptr ds:[<&printf>]           |
00411411 | 83C4 08                  | add esp,0x8                             |
00411414 | 3BF4                     | cmp esi,esp                             |
00411416 | E8 1BFDFFFF              | call 0x411136                           | 此处忽略,堆栈检测仪
0041141B | 8BF4                     | mov esi,esp                             | main.c:9

0041141D | 8B45 D0                  | mov eax,dword ptr ss:[ebp-0x30]         | 取地址直接打印常量
00411420 | 50                       | push eax                                |
00411421 | 68 A8584100              | push consoleapplication1.4158A8         | 4158A8:"字符串指针: %s\n"
00411426 | FF15 14914100            | call dword ptr ds:[<&printf>]           |
0041142C | 83C4 08                  | add esp,0x8                             |

C语言反汇编-数据类型与常量

常量折叠/压缩: 在Release模式下,编译器会对常量取值进行优化以提高程序效率,通常在编译前遇到常量,都会进行计算,得出一个新的常量值.

#include <stdio.h>

int main(int argc, char* argv[])
{
	int x = 1, y = 2,z = 3;
	printf("常量+常量: %d\n", 10 + 25);
	printf("变量+变量: %d\n", x + y);
	printf("常量+变量: %d\n", 100 + x + y + z);
	return 0;
}

观察Release模式下的汇编代码,下方代码不难看出,程序会将可以提前计算的常量值进行计算,并将该结果压栈,从而可以直接Push常量节约运算资源,另外常量折叠优化通常伴随有常量传播,如果两个变量在程序中从来都没有被修改过,也没有传入过参数,为了提升运行效率在VS2013编译器中会将这种变量自动的优化为常量.

00B11000 | 56                       | push esi                              | main.c:4
00B11001 | 8B35 9020B100            | mov esi,dword ptr ds:[<&printf>]      | 获取printf基地址,并存入esi
00B11007 | 6A 23                    | push 0x23                             | 0x23 = 10 + 25 的结果
00B11009 | 68 0021B100              | push consoleapplication1.B12100       | B12100:"常量+常量: %d\n"
00B1100E | FFD6                     | call esi                              |
00B11010 | 6A 03                    | push 0x3                              | 0x3 = x + y 的结果
00B11012 | 68 1021B100              | push consoleapplication1.B12110       | B12110:"变量+变量: %d\n"
00B11017 | FFD6                     | call esi                              |
00B11019 | 6A 6A                    | push 0x6A                             | main.c:8
00B1101B | 68 2021B100              | push consoleapplication1.B12120       | B12120:"常量+变量: %d\n"
00B11020 | FFD6                     | call esi                              |
00B11022 | 83C4 18                  | add esp,0x18                          |
00B11025 | 33C0                     | xor eax,eax                           | main.c:9
00B11027 | 5E                       | pop esi                               |
00B11028 | C3                       | ret                                   | main.c:10

窥孔优化:一种很局部的优化方式,编译器仅仅在一个基本块或者多个基本块中,针对已经生成的代码,结合CPU自己指令的特点,过一些认为可能带来性能提升的转换规则,或者通过整体的分析,通过指令转换,提升代码性能。这个窥孔,你可以认为是一个滑动窗口,编译器在实施窥孔优化时,就仅仅分析这个窗口内的指令。每次转换之后,可能还会暴露相邻窗口之间的某些优化机会,所以可以多次调用窥孔优化,尽可能提升性能

上一篇:ShellCode模板


下一篇:汇编学习一