转载 《基于VC平台下C++反汇编与逆向分析研究——No.2》
转载来自 “鱼C工作室” 作者: 小生我怕怕
分析环境:WIN7sp1
所用工具:VC++6.0/OllyDBG/IDA
适用人群:有一定计算机基础,熟悉C/C++编程,熟悉X86系列汇编/了解OD/IDA等调试工具使用,对逆向安全有极大兴趣者!
开篇前言:
数据类型和运算符是任何编程语言的基础,而对于逆向而言亦是,只有牢固的基础才能走得更远...
正文部分:
本节主要从汇编层面来全面解析基本的数据类型和运算符,如下:
#include<stdio.h> int main() { int a,b,c,s; scanf("%d%d%d",&a,&b,&c); s=a+b+c; printf("%d",s); return 0; }
上面的源码是一个简单的三角形求周边的程序,好了,直接复制到VC++6.0中选择Debug方式编译生成,直接载入OD,来到main函数:
Debug版本:
00401010 >|> \55 push ebp ; 保存ebp 00401011 |. 8BEC mov ebp,esp ; 保存esp到ebp 00401013 |. 83EC 50 sub esp,0x50 ; 开辟局部变量空间 00401016 |. 53 push ebx ; 入栈ebx 00401017 |. 56 push esi ; 入栈esi 00401018 |. 57 push edi ; 入栈edi 00401019 |. 8D7D B0 lea edi,[local.20] ; 设置初始化内存地址 0040101C |. B9 14000000 mov ecx,0x14 ; 设置循环次数 00401021 |. B8 CCCCCCCC mov eax,0xCCCCCCCC ; 赋值eax int 3中断 00401026 |. F3:AB rep stos dword ptr es:[edi] ; 循环复制int 3 00401028 |. 8D45 F4 lea eax,[local.3] ; 变量c的内存地址放到eax 0040102B |. 50 push eax ; 入栈c的地址 0040102C |. 8D4D F8 lea ecx,[local.2] ; 变量b地址放到eax 0040102F |. 51 push ecx ; 入栈b的地址 00401030 |. 8D55 FC lea edx,[local.1] ; 变量a地址放到eax 00401033 |. 52 push edx ; 入栈a的地址 00401034 |. 68 20504200 push Test2.00425020 ; /format = "%d%d%d" 00401039 |. E8 D2000000 call Test2.scanf ; \scanf 0040103E |. 83C4 10 add esp,0x10 ; 恢复esp的值 4个push 00401041 |. 8B45 FC mov eax,[local.1] ; 变量a的值放入eax 00401044 |. 0345 F8 add eax,[local.2] ; 变量b的值加上a的和放入eax中 00401047 |. 0345 F4 add eax,[local.3] ; 变量c的值加上ab的和放入eax 0040104A |. 8945 F0 mov [local.4],eax ; 把abc的和放入变量s中 0040104D |. 8B4D F0 mov ecx,[local.4] ; 变量s的值放入ecx 00401050 |. 51 push ecx ; /<%d> 00401051 |. 68 1C504200 push Test2.0042501C ; |format = "%d" 00401056 |. E8 35000000 call Test2.printf ; \printf 0040105B |. 83C4 08 add esp,0x8 ; 恢复esp,2个push 0040105E |. 33C0 xor eax,eax ; eax清零 00401060 |. 5F pop edi ; 恢复寄存器edi 00401061 |. 5E pop esi ; 恢复寄存器esi 00401062 |. 5B pop ebx ; 恢复寄存器ebx 00401063 |. 83C4 50 add esp,0x50 ; 恢复局部变量空间 00401066 |. 3BEC cmp ebp,esp ; 测试esp值是否与之前一致 00401068 |. E8 03010000 call Test2._chkesp ; 如esp值不一致,调用调试信息 0040106D |. 8BE5 mov esp,ebp ; 恢复esp值 0040106F |. 5D pop ebp ; 恢复ebp 00401070 \. C3 retn ; 返回 等于ret加上add esp,4
和之前的Hello World大同小异,相信大家看起来都没问题,我希望大家能记熟这种框架模式,对以后逆向的深入有很大用处,我们再来看看 Release版本的:
Release版本:
00401000 /$ 83EC 0C sub esp,0xC ; 开辟局部变量空间 00401003 |. 8D4424 08 lea eax,dword ptr ss:[esp+0x8] ; 把变量c的地址放入eax 00401007 |. 8D4C24 04 lea ecx,dword ptr ss:[esp+0x4] ; 把变量b的地址放入ecx 0040100B |. 8D5424 00 lea edx,dword ptr ss:[esp] ; 把变量a的地址放入edx 0040100F |. 50 push eax ; 入栈c的地址 00401010 |. 51 push ecx ; 入栈b的地址 00401011 |. 52 push edx ; 入栈a的地址 00401012 |. 68 34804000 push Test2.00408034 ; ASCII "%d%d%d" 00401017 |. E8 55000000 call Test2.00401071 ; 调用scanf函数 0040101C |. 8B4424 10 mov eax,dword ptr ss:[esp+0x10] ; 把a的值放入eax 00401020 |. 8B4C24 14 mov ecx,dword ptr ss:[esp+0x14] ; 把b的值放入ecx 00401024 |. 03C1 add eax,ecx ; a+b的和放入eax中 00401026 |. 8B4C24 18 mov ecx,dword ptr ss:[esp+0x18] ; 把c的值放入ecx 0040102A |. 03C1 add eax,ecx ; c的值和ab的和放入eax中 0040102C |. 50 push eax ; 入栈三数之和 0040102D |. 68 30804000 push Test2.00408030 ; ASCII "%d" 00401032 |. E8 09000000 call Test2.00401040 ; 调用printf函数 00401037 |. 33C0 xor eax,eax ; eax清零 00401039 |. 83C4 24 add esp,0x24 ; 恢复esp 0040103C \. C3 retn ; 返回 等于ret add esp,4
大家可能不理解的就是最后的add esp,0x24,OK,先看下入口处的sub esp,0xC,注意次数是十六进制,大家都知道一个push操作,eap就相当于sub esp,4 此处共6个push,所以这里的值等于6个push的值0x18加上sub esp,0xc 的0xC 正好是0x24,有疑问的大家可以试试在源码中多添加一个push操作来看看此处add esp的值!
#include<stdio.h> int main() { short int a=1; int b=2; long int c=3; float d=1.222; double e=1.333; long double f=1.234; char g='a'; char *h; h=&g; return 0; }
上面这段简单的源码中,涉及了很多基本数据类型,如:int整型 ,float单精度浮点型,double双精度浮点型,char字符型,指针类型,本节重要点就是来分析这些基本数据类型在汇编中的表现形式,先看下Debug版本的:
Debug版本:
00401250 >/> \55 push ebp ; 保存ebp 00401251 |. 8BEC mov ebp,esp ; 保存esp到ebp 00401253 |. 83EC 68 sub esp,0x68 ; 开辟局部变量空间 00401256 |. 53 push ebx ; 入栈ebx 00401257 |. 56 push esi ; 入栈esi 00401258 |. 57 push edi ; 入栈edi 00401259 |. 8D7D 98 lea edi,[local.26] ; 设置初始化局部变量地址 0040125C |. B9 1A000000 mov ecx,0x1A ; 设置循环次数 00401261 |. B8 CCCCCCCC mov eax,0xCCCCCCCC ; 赋值eax int 3中断 00401266 |. F3:AB rep stos dword ptr es:[edi] ; 循环复制int3 00401268 |. 66:C745 FC 01>mov word ptr ss:[ebp-0x4],0x1 ; 赋值1到局部变量short int类型a中,参数:word 16位 0040126E |. C745 F8 02000>mov [local.2],0x2 ; 赋值2到局部变量int类型b中,参数:dword 32位 00401275 |. C745 F4 03000>mov [local.3],0x3 ; 赋值3到局部变量float类型c中,参数:dword 32位 0040127C |. C745 F0 7F6A9>mov [local.4],0x3F9C6A7F ; 赋值0x3F9DF3B6到局部变量float类型d中,参数:dword 32位 00401283 |. C745 E8 8716D>mov [local.6],0xCED91687 ; 存放低位 0040128A |. C745 EC F753F>mov [local.5],0x3FF553F7 ; double类型e 存放高位 参数:qword 64位 00401291 |. C745 E0 5839B>mov [local.8],0xC8B43958 ; 存放低位 00401298 |. C745 E4 76BEF>mov [local.7],0x3FF3BE76 ; long double类型f 存放高位 参数:qword 64位 0040129F |. C645 DC 61 mov byte ptr ss:[ebp-0x24],0x61 ; char类型g Ascll码的a对应0x61 参数:byte 8位 004012A3 |. 8D45 DC lea eax,[local.9] ; 获取字符变量地址到eax 004012A6 |. 8945 D8 mov [local.10],eax ; 把eax放入指针中h中 004012A9 |. 33C0 xor eax,eax ; eax清零 004012AB |. 5F pop edi ; 出栈edi 004012AC |. 5E pop esi ; 出栈esi 004012AD |. 5B pop ebx ; 出栈ebx 004012AE |. 8BE5 mov esp,ebp ; 恢复esp 004012B0 >|. 5D pop ebp ; 恢复ebp 004012B1 \. C3 retn ; 返回 等于ret add esp,4
分析前,我们先普及一下计算机微计量单位,位bit 即10101010 每个数为1位,字节byte 即大家看到的文件大小字节,一个字节等于8位,字word 1个字等于2个字节即16位 双字Dword 1个双字等于2个字,即32位 eax寄存器就是32位,4字qword 同理就是64位!当然还有更大的,我们这里不做描述!
short int 类型为16位故:mov word ptr ss:[ebp-0x4],0x1 int类型为32位,故:mov [local.2],0x2 默认就是32位,double为64位,就会使用2个32位来存放,即一个存放高位,一个低位!例如这句:double e=1.333; 1.333在内存中是以IEEE编码来存储,1.333对应的IEEE是3FF553F7CED91687,故会出现这两句汇编指令:mov [local.6],0xCED91687 mov [local.5],0x3FF553F7,Ascll码的0x61对应的就是小写字母a,好了,看下Release版
Release版本:
00401000 /$ 33C0 xor eax,eax ; eax清零 00401002 \. C3 retn ; 返回 等于ret add esp,4 这些变量未参数实际使用,Release会自动优化掉!
#include < stdio.h > int main() { int a = 2, b = 4, c; c = a + b; c = a * b; c = a / b; c = a % b; c = a && b; c = a || b; c = !b; c++; c--; return 0; }
Debug版本:
00410680 >/> \55 push ebp ; ebp入栈保存 00410681 |. 8BEC mov ebp,esp ; 保存esp 00410683 |. 83EC 54 sub esp,0x54 ; 开辟局部变量空间 00410686 |. 53 push ebx ; 入栈ebx 00410687 |. 56 push esi ; 入栈esi 00410688 |. 57 push edi ; 入栈edi 00410689 |. 8D7D AC lea edi,[local.21] ; 设置初始化局部变量地址 0041068C |. B9 15000000 mov ecx,0x15 ; 循环次数 00410691 |. B8 CCCCCCCC mov eax,0xCCCCCCCC ; 赋值eax int 中断 00410696 |. F3:AB rep stos dword ptr es:[edi] ; 循环复制int 3 中断 00410698 |. C745 FC 05000>mov [local.1],0x5 ; 赋值2到变量a 0041069F |. C745 F8 02000>mov [local.2],0x2 ; 赋值4到变量b 004106A6 |. 8B45 FC mov eax,[local.1] ; 赋值a的值到eax 004106A9 |. 0345 F8 add eax,[local.2] ; b的值与a的值相加放入eax 004106AC |. 8945 F4 mov [local.3],eax ; ab的和放入变量c 004106AF |. 8B4D FC mov ecx,[local.1] ; 赋值a的值到ecx 004106B2 |. 0FAF4D F8 imul ecx,[local.2] ; 变量b与变量a的乘积放入ecx 004106B6 |. 894D F4 mov [local.3],ecx ; ab的积值放入变量c 004106B9 |. 8B45 FC mov eax,[local.1] ; 赋值a的值到eax 004106BC |. 99 cdq ; 双字32位扩展到64位,eax的高位放入edx中 004106BD |. F77D F8 idiv [local.2] ; 除法运算 idiv无符号除法 004106C0 |. 8945 F4 mov [local.3],eax ; ab的商放入变量c 004106C3 |. 8B45 FC mov eax,[local.1] ; 赋值a的值到eax 004106C6 |. 99 cdq ; 双字32位扩展到64位,eax的高位放入edx中 004106C7 |. F77D F8 idiv [local.2] ; 除法运算 idiv无符号除法 004106CA |. 8955 F4 mov [local.3],edx ; ab的余数放入变量c 004106CD |. 837D FC 00 cmp [local.1],0x0 ; 与运算,测试变量a是否为0,如为0就跳转 004106D1 |. 74 0F je short Test2.004106E2 ; a值不为0,未发生跳转 004106D3 |. 837D F8 00 cmp [local.2],0x0 ; 与运算,测试变量b是否为0,如为0就跳转 004106D7 |. 74 09 je short Test2.004106E2 ; b值不为0,未发生跳转 004106D9 |. C745 F0 01000>mov [local.4],0x1 ; 与运算为真 004106E0 |. EB 07 jmp short Test2.004106E9 ; 无条件跳转 004106E2 |> C745 F0 00000>mov [local.4],0x0 004106E9 |> 8B55 F0 mov edx,[local.4] ; 与运算值放入edx 004106EC |. 8955 F4 mov [local.3],edx ; ab的与运算值放入变量c 004106EF |. 837D FC 00 cmp [local.1],0x0 ; 或运算,测试变量a是否为0,如不等于0就跳转 004106F3 |. 75 0F jnz short Test2.00410704 ; a值不为0,发生跳转 004106F5 |. 837D F8 00 cmp [local.2],0x0 004106F9 |. 75 09 jnz short Test2.00410704 004106FB |. C745 EC 00000>mov [local.5],0x0 00410702 |. EB 07 jmp short Test2.0041070B 00410704 |> C745 EC 01000>mov [local.5],0x1 ; ab或运算为真 0041070B |> 8B45 EC mov eax,[local.5] ; 或运算值放入edx 0041070E |. 8945 F4 mov [local.3],eax ; ab的或运算值放入变量c 00410711 |. 33C9 xor ecx,ecx ; ecx清零 00410713 |. 837D F8 00 cmp [local.2],0x0 ; 求反运算,测试变量b是否为0 00410717 |. 0F94C1 sete cl ; 依据zf标志位的值设置cl的值 0041071A |. 894D F4 mov [local.3],ecx ; b逻辑取反的值放入变量c 0041071D |. 8B55 F4 mov edx,[local.3] ; 变量c的值放入edx 00410720 |. 83C2 01 add edx,0x1 ; edx自加1 00410723 |. 8955 F4 mov [local.3],edx ; edx的值放入变量c 00410726 |. 8B45 F4 mov eax,[local.3] ; 变量c的值放入eax 00410729 |. 83E8 01 sub eax,0x1 ; eax自减1 0041072C |. 8945 F4 mov [local.3],eax ; eax的值放入变量c 0041072F |. 33C0 xor eax,eax ; eax清零 00410731 |? 5F pop edi ; 恢复edi 00410732 |. 5E pop esi ; 恢复esi 00410733 |? 5B pop ebx ; 恢复ebx 00410734 |? 8BE5 mov esp,ebp ; 恢复esp的值 00410736 |? 5D pop ebp ; 恢复ebp 00410737 |? C3 retn ; 返回 等于ret add esp,4
上述每条汇编指令的注释我写的都很详细,大家可以对照源码来看,注意cdq指令 ,重点看下理解乘除法运算,以及逻辑运算!Release版本同上。
Release版本:
00401000 /$ 33C0 xor eax,eax 00401002 \. C3 retn 这些变量未参数实际使用,Release会自动优化掉!
本节中,肯定没有全面涉及数据类型和运算符,这里只做为一种分析的方法,各位私下多练习下这些基础,吃透吃熟,对以深入后尤关重要!至于逻辑判断,下节我会详细介绍。