转载 《基于VC平台下C++反汇编与逆向分析研究——No.3》
转载来自 “鱼C工作室” 作者: 小生我怕怕
分析环境:WIN7sp1
所用工具:VC++6.0/OllyDBG/IDA
适用人群:有一定计算机基础,熟悉C/C++编程,熟悉X86系列汇编/了解OD/IDA等调试工具使用,对逆向安全有极大兴趣者!
开篇前言:
数据类型和运算符是任何编程语言的基础,而对于逆向而言亦是,只有牢固的基础才能走得更远...
正文部分:
本节主要从汇编层面来全面解析程序的基本流程控制语句:if -else,switch,for 如下:
源码:
#include<stdio.h> int main() { int x; printf("请输入密码"); scanf("%d",&x); if(x==123456) printf("成功"); else printf("失败"); return 0; }
Debug版本:
00401010 >|> \55 push ebp ; ebp入栈保存 00401011 |. 8BEC mov ebp,esp ; esp保存到ebp中 00401013 |. 83EC 44 sub esp,0x44 ; 开辟局部变量空间 00401016 |. 53 push ebx ; 入栈ebx 00401017 |. 56 push esi ; 入栈esi 00401018 |. 57 push edi ; 入栈edi 00401019 |. 8D7D BC lea edi,[local.17] ; 设置CC操作起始地址 0040101C |. B9 11000000 mov ecx,0x11 ; 设置循环次数 00401021 |. B8 CCCCCCCC mov eax,0xCCCCCCCC ; 赋值eax CC,int 3 00401026 |. F3:AB rep stos dword ptr es:[edi] ; 循环赋值 00401028 |. 68 10604200 push Test3.00426010 ; /format = "请输入密码" 0040102D |. E8 6E000000 call Test3.printf ; \printf 00401032 |. 83C4 04 add esp,0x4 ; 恢复esp 00401035 |. 8D45 FC lea eax,[local.1] ; 变量地址放入eax 00401038 |. 50 push eax ; 参数入栈 00401039 |. 68 1C504200 push Test3.0042501C ; /format = "%d" 0040103E |. E8 DD000000 call Test3.scanf ; \scanf 00401043 |. 83C4 08 add esp,0x8 ; 恢复esp 00401046 |. 817D FC 40E20>cmp [local.1],0x1E240 ; 判断是否为123456 0040104D |. 75 0F jnz short Test3.0040105E ; 不等于就跳转 0040104F |. 68 20504200 push Test3.00425020 ; /format = "成功" 00401054 |. E8 47000000 call Test3.printf ; \printf 00401059 |. 83C4 04 add esp,0x4 ; 恢复esp 0040105C |. EB 0D jmp short Test3.0040106B ; 跳出循环 0040105E |> 68 08604200 push Test3.00426008 ; /format = "失败" 00401063 |. E8 38000000 call Test3.printf ; \printf 00401068 |. 83C4 04 add esp,0x4 ; 恢复esp 0040106B |> 33C0 xor eax,eax ; eax清零 0040106D |. 5F pop edi ; 恢复edi 0040106E |. 5E pop esi ; 恢复esi 0040106F |. 5B pop ebx ; 恢复ebx 00401070 |. 83C4 44 add esp,0x44 ; 恢复局部变量空间 00401073 |. 3BEC cmp ebp,esp ; 判断堆栈平衡 00401075 |. E8 06010000 call Test3._chkesp ; 调用调试信息 0040107A |. 8BE5 mov esp,ebp ; 恢复esp 0040107C |. 5D pop ebp ; 恢复ebp 0040107D \. C3 retn ; 返回 ret add esp,4
if-else的基本框架:
cmp xx1,xx2 ;测试比较指令,如test
jnz ;条件转移指令,如jle,jne等
........... ;if 语句内容
jmp ;跳出判断
........... ;else语句内容
Release版本:
00401000 /$ 51 push ecx ; ecx入栈保存 00401001 |. 68 44804000 push Test3.00408044 ; 请输入密码 00401006 |. E8 5C000000 call Test3.00401067 ; printf函数 0040100B |. 8D4424 04 lea eax,dword ptr ss:[esp+4] ; 获取变量指针到eax 0040100F |. 50 push eax ; 参数 00401010 |. 68 40804000 push Test3.00408040 ; %d 00401015 |. E8 36000000 call Test3.00401050 ; scanf函数 0040101A |. 8B4424 0C mov eax,dword ptr ss:[esp+C] ; 获取输入数到eax 0040101E |. 83C4 0C add esp,0C ; 恢复esp 00401021 |. 3D 40E20100 cmp eax,1E240 ; 比较是否相等 00401026 |. 75 11 jnz short Test3.00401039 ; 不相等就跳转 00401028 |. 68 38804000 push Test3.00408038 ; 成功 0040102D |. E8 35000000 call Test3.00401067 ; printf函数 00401032 |. 83C4 04 add esp,4 ; 恢复esp 00401035 |. 33C0 xor eax,eax ; eax清零 00401037 |. 59 pop ecx ; 恢复ecx 00401038 |. C3 retn ; 返回 ret add esp,4 00401039 |> 68 30804000 push Test3.00408030 ; 失败 0040103E |. E8 24000000 call Test3.00401067 ; printf函数 00401043 |. 83C4 04 add esp,4 ; 恢复esp 00401046 |. 33C0 xor eax,eax ; eax清零 00401048 |. 59 pop ecx ; 恢复ecx 00401049 \. C3 retn ; 返回 ret add esp,4
if-else的基本框架:
cmp xx1,xx2 ;测试比较指令,如test
jnz ;条件转移指令,如jle,jne等
........... ;if 语句内容
retn
........... ;else语句内容
retn
#include<stdio.h> int main() { int x; printf("请输入一个数字"); scanf("%d",&x); switch(x) { case 0: printf("你输入的数字是0"); break; case 1: printf("你输入的数字是1"); break; case 2: printf("你输入的数字是2"); break; default: printf("你输入的数字大于2"); } return 0; }
上面通过switch语句,来判断用户输入,有默认执行部分!
Debug版本:
0040F980 >/> \55 push ebp ; ebp入栈保存 0040F981 |. 8BEC mov ebp,esp ; esp保存到ebp中 0040F983 |. 83EC 48 sub esp,0x48 ; 开辟局部变量空间 0040F986 |. 53 push ebx ; ebx入栈保存 0040F987 |. 56 push esi ; esi入栈保存 0040F988 |. 57 push edi ; edi入栈保存 0040F989 |. 8D7D B8 lea edi,[local.18] ; 设置CC初始化起始地址 0040F98C |. B9 12000000 mov ecx,0x12 ; 设置循环次数 0040F991 |. B8 CCCCCCCC mov eax,0xCCCCCCCC ; 赋值eax CC,int 3 0040F996 |. F3:AB rep stos dword ptr es:[edi] ; 循环复制cc 0040F998 |. 68 4C604200 push Test3.0042604C ; /format = "请输入一个数字" 0040F99D |. E8 FE16FFFF call Test3.printf ; \printf 0040F9A2 |. 83C4 04 add esp,0x4 ; 恢复esp 0040F9A5 |. 8D45 FC lea eax,[local.1] ; 取变量地址放入eax 0040F9A8 |. 50 push eax ; 参数入栈 0040F9A9 |. 68 1C504200 push Test3.0042501C ; /format = "%d" 0040F9AE |. E8 6D17FFFF call Test3.scanf ; \scanf 0040F9B3 |. 83C4 08 add esp,0x8 ; 恢复esp 0040F9B6 |. 8B4D FC mov ecx,[local.1] ; 赋值变量值到ecx 0040F9B9 |. 894D F8 mov [local.2],ecx ; ecx放入局部变量中 0040F9BC |. 837D F8 00 cmp [local.2],0x0 ; 判断是否为0 0040F9C0 |. 74 0E je short Test3.0040F9D0 ; 等于0就跳转,执行相应内容 0040F9C2 |. 837D F8 01 cmp [local.2],0x1 ; 判断是否为1 0040F9C6 |. 74 17 je short Test3.0040F9DF ; 等于1就跳转,执行相应内容 0040F9C8 |. 837D F8 02 cmp [local.2],0x2 ; 判断是否为2 0040F9CC |. 74 20 je short Test3.0040F9EE ; 等于2就跳转,执行相应内容 0040F9CE |. EB 2D jmp short Test3.0040F9FD ; 条件均不成立时,跳到默认执行处 0040F9D0 |> 68 3C604200 push Test3.0042603C ; /format = "你输入的数字是0" 0040F9D5 |. E8 C616FFFF call Test3.printf ; \printf 0040F9DA |. 83C4 04 add esp,0x4 ; 恢复esp 0040F9DD |. EB 2B jmp short Test3.0040FA0A ; 跳出switch语句 0040F9DF |> 68 2C604200 push Test3.0042602C ; /format = "你输入的数字是1" 0040F9E4 |. E8 B716FFFF call Test3.printf ; \printf 0040F9E9 |. 83C4 04 add esp,0x4 ; 恢复esp 0040F9EC |. EB 1C jmp short Test3.0040FA0A ; 跳出switch语句 0040F9EE |> 68 1C604200 push Test3.0042601C ; /format = "你输入的数字是2" 0040F9F3 |. E8 A816FFFF call Test3.printf ; \printf 0040F9F8 |. 83C4 04 add esp,0x4 ; 恢复esp 0040F9FB |. EB 0D jmp short Test3.0040FA0A ; 跳出switch语句 0040F9FD |> 68 08604200 push Test3.00426008 ; /format = "你输入的数字大于2" 0040FA02 |. E8 9916FFFF call Test3.printf ; \printf 0040FA07 |. 83C4 04 add esp,0x4 ; 恢复esp 0040FA0A |> 33C0 xor eax,eax ; eax清零 0040FA0C |. 5F pop edi ; 恢复edi 0040FA0D |. 5E pop esi ; 恢复esi 0040FA0E |. 5B pop ebx ; 恢复ebx 0040FA0F |. 83C4 48 add esp,0x48 ; 恢复局部变量空间 0040FA12 |. 3BEC cmp ebp,esp ; 堆栈平衡判断 0040FA14 |. E8 6717FFFF call Test3._chkesp ; 调用调试信息 0040FA19 |. 8BE5 mov esp,ebp ; 恢复esp 0040FA1B |. 5D pop ebp ; 恢复ebp 0040FA1C \. C3 retn ; 返回 ret add esp,4
switch语句基本框架:
cmp xx0,xx1 ;可以是test等
je ;跳转执行体1 ;此处也可以是其他条件转移指令
cmp xx0,xx2
je ;跳转执行体2
0,xx3
je cmp xx;跳转执行体3
jmp ;跳转默认执行体处
...... ;执行体1
jmp
...... ;执行体2
jmp
...... ;执行体3
jmp
...... ;默认执行体
Release版本:
00401000 /$ 51 push ecx ; ecx入栈 00401001 |. 68 78804000 push Test3.00408078 ; 请输入一个数字 00401006 |. E8 7C000000 call Test3.00401087 ; printf函数 0040100B |. 8D4424 04 lea eax,dword ptr ss:[esp+0x4] ; 取变量指针到eax 0040100F |. 50 push eax ; 参数 00401010 |. 68 74804000 push Test3.00408074 ; %d 00401015 |. E8 56000000 call Test3.00401070 ; scanf函数 0040101A |. 8B4424 0C mov eax,dword ptr ss:[esp+0xC] ; 输入数值到eax 0040101E |. 83C4 0C add esp,0xC ; 恢复esp 00401021 |. 83E8 00 sub eax,0x0 ; 判断; Switch (cases 0..2) 00401024 |. 74 39 je short Test3.0040105F ; 如为0就跳转 00401026 |. 48 dec eax ; 判断 00401027 |. 74 25 je short Test3.0040104E ; 如为1就跳转 00401029 |. 48 dec eax ; 判断 0040102A |. 74 11 je short Test3.0040103D ; 如为2就跳转 0040102C |. 68 60804000 push Test3.00408060 ; 你输入的数字大于2; Default case of switch 00401021 00401031 |. E8 51000000 call Test3.00401087 ; printf函数 00401036 |. 83C4 04 add esp,0x4 ; 恢复esp 00401039 |. 33C0 xor eax,eax ; eax清零 0040103B |. 59 pop ecx ; 恢复ecx 0040103C |. C3 retn ; 返回 0040103D |> 68 50804000 push Test3.00408050 ; 你输入的数字是2; Case 2 of switch 00401021 00401042 |. E8 40000000 call Test3.00401087 ; printf函数 00401047 |. 83C4 04 add esp,0x4 ; 恢复esp 0040104A |. 33C0 xor eax,eax ; eax清零 0040104C |. 59 pop ecx ; 恢复ecx 0040104D |. C3 retn ; 返回 0040104E |> 68 40804000 push Test3.00408040 ; 你输入的数字是1; Case 1 of switch 00401021 00401053 |. E8 2F000000 call Test3.00401087 ; printf函数 00401058 |. 83C4 04 add esp,0x4 ; 恢复esp 0040105B |. 33C0 xor eax,eax ; eax清零 0040105D |. 59 pop ecx ; 恢复ecx 0040105E |. C3 retn ; 返回 0040105F |> 68 30804000 push Test3.00408030 ; 你输入的数字是0; Case 0 of switch 00401021 00401064 |. E8 1E000000 call Test3.00401087 ; printf函数 00401069 |. 83C4 04 add esp,0x4 ; 恢复esp 0040106C |. 33C0 xor eax,eax ; eax清零 0040106E |. 59 pop ecx ; 恢复ecx 0040106F \. C3 retn ; 返回
switch语句基本框架:
cmp xx0,xx1 ;可以是test等
je ;跳转执行体1 ;此处也可以是其他条件转移指令
cmp xx0,xx2
je ;跳转执行体2
cmp xx0,xx3
je ;跳转执行体3
...... ;默认执行体
retn
...... ;执行体1
retn
...... ;执行体2
retn
...... ;执行体3
retn
#include<stdio.h> int main() { int x,y=0; for(x=1;x<=100;x++) { y=x+y; } printf("%d",y); return 0; }
上述程序通过for循环来计算1+2+3...+100的值
Debug版本:
0040F980 >/> \55 push ebp ; ebp入栈保存 0040F981 |. 8BEC mov ebp,esp ; 保存esp到ebp 0040F983 |. 83EC 48 sub esp,0x48 ; 开辟局部变量空间 0040F986 |. 53 push ebx ; ebx入栈保存 0040F987 |. 56 push esi ; esi入栈保存 0040F988 |. 57 push edi ; edi入栈保存 0040F989 |. 8D7D B8 lea edi,[local.18] ; 设置CC操作起始地址 0040F98C |. B9 12000000 mov ecx,0x12 ; 设置循环地址 0040F991 |. B8 CCCCCCCC mov eax,0xCCCCCCCC ; 赋值eax CC int 3 0040F996 |. F3:AB rep stos dword ptr es:[edi] ; 循环复制CC 0040F998 |. C745 F8 00000>mov [local.2],0x0 ; 赋值变量y 0040F99F |. C745 FC 01000>mov [local.1],0x1 ; 赋值变量x 0040F9A6 |. EB 09 jmp short Test3.0040F9B1 ; 跳转到for循环判断 0040F9A8 |> 8B45 FC /mov eax,[local.1] ; x的值放入eax 0040F9AB |. 83C0 01 |add eax,0x1 ; eax加1 0040F9AE |. 8945 FC |mov [local.1],eax ; 自加1的值放入变量x 0040F9B1 |> 837D FC 64 cmp [local.1],0x64 ; 判断x值是否为100 0040F9B5 |. 7F 0B |jg short Test3.0040F9C2 ; 相等就跳转 0040F9B7 |. 8B4D FC |mov ecx,[local.1] ; 变量x值放入ecx 0040F9BA |. 034D F8 |add ecx,[local.2] ; x+y的值放入ecx 0040F9BD |. 894D F8 |mov [local.2],ecx ; 把ecx的值放入变量y 0040F9C0 |.^ EB E6 \jmp short Test3.0040F9A8 ; 跳转到判断处 0040F9C2 |> 8B55 F8 mov edx,[local.2] ; 最终变量y的值放入edx 0040F9C5 |. 52 push edx ; /<%d> 0040F9C6 |. 68 08604200 push Test3.00426008 ; |format = "%d" 0040F9CB |. E8 D016FFFF call Test3.printf ; \printf 0040F9D0 |. 83C4 08 add esp,0x8 ; 恢复esp 0040F9D3 |. 33C0 xor eax,eax ; eax清零 0040F9D5 |. 5F pop edi ; 恢复edi 0040F9D6 |. 5E pop esi ; 恢复esi 0040F9D7 |. 5B pop ebx ; 恢复ebx 0040F9D8 |. 83C4 48 add esp,0x48 ; 恢复局部变量空间 0040F9DB |. 3BEC cmp ebp,esp ; 测试堆栈平衡 0040F9DD |. E8 9E17FFFF call Test3._chkesp ; 调用调试信息 0040F9E2 |. 8BE5 mov esp,ebp ; 恢复esp 0040F9E4 |. 5D pop ebp ; 恢复ebp 0040F9E5 \. C3 retn ; 返回 ret add esp,4
for基本循环框架:
jmp ;无条件跳转到条件判断语句
.... ;for循环条件
cmp ;判断是否满足条件 ;可以是其他条件测试指令
jg ;如果不满足就跳出for循环;可以是其他条件转移指令
.... ;for循环执行体
jmp ;无条件跳转到for循环条件
Release版本:
00401000 /$ 33C9 xor ecx,ecx ; ecx清零,作为计数器 00401002 |. B8 01000000 mov eax,0x1 ; eax初始化为为1 00401007 |> 03C8 /add ecx,eax ; eax加上ecx的值放入ecx中 00401009 |. 40 |inc eax ; eax自加1 0040100A |. 83F8 64 |cmp eax,0x64 ; 判断是否为100 0040100D |.^ 7E F8 \jle short Test3.00401007 ; 如不为100就跳转 0040100F |. 51 push ecx ; 参数入栈 00401010 |. 68 30704000 push Test3.00407030 ; ASCII "%d" 00401015 |. E8 06000000 call Test3.00401020 ; printf函数 0040101A |. 83C4 08 add esp,0x8 ; 恢复esp 0040101D |. 33C0 xor eax,eax ; eax清零 0040101F \. C3 retn ; 返回
for基本循环框架:
...... ;初始化各个参数
jle ;判断是否满足条件
...... ;跳出循环
___________________________________________________________________________________________________
本节主要对程序的流程化控制做逆向分析,此节对以后无论是逆向还是病毒分析都极其重要,下半节更新,while,do-while,goto语句