转载 《基于VC平台下C++反汇编与逆向分析研究——No.4》
转载来自 “鱼C工作室” 作者: 小生我怕怕
分析环境:WIN7sp1
所用工具:VC++6.0/OllyDBG/IDA
适用人群:有一定计算机基础,熟悉C/C++编程,熟悉X86系列汇编/了解OD/IDA等调试工具使用,对逆向安全有极大兴趣者!
————————————————————————————————————————————————
开篇前言:
数据类型和运算符是任何编程语言的基础,而对于逆向而言亦是,只有牢固的基础才能走得更远...
正文部分:
本节主要从汇编层面来全面解析程序的基本流程控制语句:while,do-while,goto 如下:
#include < stdio.h > int main() { int x = 1, y = 0; while (x <= 100) { y = x + y; x++; } printf("%d", y); return 0; }
上面是一个使用while语句来计算1+2+3...+100的和的程序,使用了一个计数器!
Debug版本:
00401010 >|> \55 push ebp ; ebp入栈保存 00401011 |. 8BEC mov ebp,esp ; esp保存到ebp 00401013 |. 83EC 48 sub esp,0x48 ; 开辟局部变量空间 00401016 |. 53 push ebx ; ebx入栈保存 00401017 |. 56 push esi ; esi入栈保存 00401018 |. 57 push edi ; edi入栈保存 00401019 |. 8D7D B8 lea edi,[local.18] ; 设置CC操作起始化地址 0040101C |. B9 12000000 mov ecx,0x12 ; 设置循环次数 00401021 |. B8 CCCCCCCC mov eax,0xCCCCCCCC ; 赋值eax CC int 3 00401026 |. F3:AB rep stos dword ptr es:[edi] ; 循环复制cc 00401028 |. C745 FC 01000>mov [local.1],0x1 ; 赋值局部变量x 0040102F |. C745 F8 00000>mov [local.2],0x0 ; 赋值局部变量y 00401036 |> 837D FC 64 /cmp [local.1],0x64 ; 测试x是否为100 0040103A |. 7F 14 |jg short Test3.00401050 ; 如果为100就跳出while循环 0040103C |. 8B45 FC |mov eax,[local.1] ; 变量x值放入eax 0040103F |. 0345 F8 |add eax,[local.2] ; x+y的值放入eax 00401042 |. 8945 F8 |mov [local.2],eax ; eax的值放入变量y 00401045 |. 8B4D FC |mov ecx,[local.1] ; 变量x的值放入ecx 00401048 |. 83C1 01 |add ecx,0x1 ; exc自加1 0040104B |. 894D FC |mov [local.1],ecx ; ecx赋值到变量x 0040104E |.^ EB E6 \jmp short Test3.00401036 ; 无条件跳转到while条件判断处 00401050 |> 8B55 F8 mov edx,[local.2] ; 最终y的值放入edx 00401053 |. 52 push edx ; /<%d> 00401054 |. 68 1C204200 push Test3.0042201C ; |format = "%d" 00401059 |. E8 32000000 call Test3.printf ; \printf 0040105E |. 83C4 08 add esp,0x8 ; 恢复esp 00401061 |. 33C0 xor eax,eax ; eax清零 00401063 |. 5F pop edi ; 恢复edi 00401064 |. 5E pop esi ; 恢复esi 00401065 |. 5B pop ebx ; 恢复ebx 00401066 |. 83C4 48 add esp,0x48 ; 恢复局部变量空间 00401069 |. 3BEC cmp ebp,esp ; 测试堆栈平衡 0040106B |. E8 A0000000 call Test3._chkesp ; 调用调试信息 00401070 |. 8BE5 mov esp,ebp ; 恢复esp 00401072 |. 5D pop ebp ; 恢复ebp 00401073 \. C3 retn ; 返回 ret add esp,4
while循环基本结构:
cmp ;判断while条件是否满足
jg ;不满足就跳出while循环
... ;while执行体
jmp ;无条件跳转到条件判断处
Release版本:
00401000 /$ B8 01000000 mov eax,1 ; eax赋值为1 00401005 |. 33C9 xor ecx,ecx ; 清零ecx,计数之用 00401007 |> 03C8 /add ecx,eax ; eax加上ecx放入ecx 00401009 |. 40 |inc eax ; eax自加1 0040100A |. 83F8 64 |cmp eax,64 ; 和100做比较 0040100D |.^ 7E F8 \jle short Test3.00401007 ; 如果不满足条件就跳回add ecx,eax 0040100F |. 51 push ecx ; 参数 00401010 |. 68 30704000 push Test3.00407030 ; ASCII "%d" 00401015 |. E8 06000000 call Test3.00401020 ; printf函数 0040101A |. 83C4 08 add esp,8 ; 恢复esp 0040101D |. 33C0 xor eax,eax ; eax清零 0040101F \. C3 retn ; 返回 ret add esp,4
while循环基本结构:
自动判断是否满足while条件,如果否就直接跳过while,这里满足
... ;while执行体
cmp ;判断while条件是否满足
jle ;不满足就跳出while循环
总结:根据反汇编两种不同的编译方式可以看出,编译器已经自动识别出原始条件是否满足首次while条件,如满足则直接执行while内容,不满足就直接跳过while。
#include < stdio.h > int main() { int x; do { printf("请输入一个数字"); scanf("%d", &x); printf("你输入的数字是:%d\n", x); } while ( x != 0 ); return 0; }
上面程序是判断用户的输入数值,不等于0就一直输入,直到等于0就跳出do-while循环
Debug版本:
00401010 |> \55 push ebp ; ebp入栈保存 00401011 |. 8BEC mov ebp,esp ; esp保存到ebp 00401013 |. 83EC 44 sub esp,44 ; 开辟局部变量空间 00401016 |. 53 push ebx ; ebx入栈保存 00401017 |. 56 push esi ; esi入栈保存 00401018 |. 57 push edi ; edi入栈保存 00401019 |. 8D7D BC lea edi,dword ptr ss:[ebp-44] ; 设置CC操作起始地址 0040101C |. B9 11000000 mov ecx,11 ; 设置循环次数 00401021 |. B8 CCCCCCCC mov eax,CCCCCCCC ; 赋值 eax CC int 3 00401026 |. F3:AB rep stos dword ptr es:[edi] ; 循环复制 CC 00401028 |> 68 B82F4200 /push Test3.00422FB8 ; /参数入栈 0040102D |. E8 5E000000 |call Test3.00401090 ; \调用printf函数 00401032 |. 83C4 04 |add esp,4 ; 恢复esp 00401035 |. 8D45 FC |lea eax,dword ptr ss:[ebp-4] ; 取变量x的地址到eax 00401038 |. 50 |push eax ; /参数入栈 00401039 |. 68 1C204200 |push Test3.0042201C ; |Arg1 = 0042201C ASCII "%d" 0040103E |. E8 BDE80000 |call Test3.0040F900 ; \调用scanf函数 00401043 |. 83C4 08 |add esp,8 ; 恢复esp 00401046 |. 8B4D FC |mov ecx,dword ptr ss:[ebp-4] ; 变量x的值放入ecx 00401049 |. 51 |push ecx ; /参数入栈 0040104A |. 68 A42F4200 |push Test3.00422FA4 ; |参数入栈 0040104F |. E8 3C000000 |call Test3.00401090 ; \调用printf函数 00401054 |. 83C4 08 |add esp,8 ; 恢复esp 00401057 |. 837D FC 00 |cmp dword ptr ss:[ebp-4],0 ; 判断x值是否为0 0040105B |.^ 75 CB \jnz short Test3.00401028 ; 不为0即跳到到do循环出执行 0040105D |. 33C0 xor eax,eax ; eax清零 0040105F |. 5F pop edi ; 恢复edi 00401060 |. 5E pop esi ; 恢复esi 00401061 |. 5B pop ebx ; 恢复ebx 00401062 |. 83C4 44 add esp,44 ; 恢复局部变量空间 00401065 |. 3BEC cmp ebp,esp ; 测试堆栈平衡 00401067 |. E8 A4000000 call Test3.00401110 ; 调用调试信息 0040106C |. 8BE5 mov esp,ebp ; 恢复esp 0040106E |. 5D pop ebp ; 恢复ebp 0040106F \. C3 retn ; 返回 ret add esp,4
do-while基本结构:
.... ;do循环执行体
cmp ;条件判断
jnz ;如果不满足条件就跳出循环
Release版本:
00401000 /$ 51 push ecx ; ecx入栈保存 00401001 |> 68 48804000 /push Test3.00408048 ; 参数 00401006 |. E8 4C000000 |call Test3.00401057 ; printf函数 0040100B |. 8D4424 04 |lea eax,dword ptr ss:[esp+4] ; 取变量x地址到eax 0040100F |. 50 |push eax ; 参数入栈 00401010 |. 68 44804000 |push Test3.00408044 ; ASCII "%d" 00401015 |. E8 26000000 |call Test3.00401040 ; scanf函数 0040101A |. 8B4C24 0C |mov ecx,dword ptr ss:[esp+C] ; 变量值赋值到ecx 0040101E |. 51 |push ecx ; 参数 0040101F |. 68 30804000 |push Test3.00408030 ; 参数 00401024 |. E8 2E000000 |call Test3.00401057 ; printf函数 00401029 |. 8B4424 14 |mov eax,dword ptr ss:[esp+14] ; 变量值赋值到eax中 0040102D |. 83C4 14 |add esp,14 ; 恢复esp 00401030 |. 85C0 |test eax,eax ; 判断eax是否为零 00401032 |.^ 75 CD \jnz short Test3.00401001 ; 如果不为零,就跳回执行 00401034 |. 33C0 xor eax,eax ; eax清零 00401036 |. 59 pop ecx ; 恢复ecx 00401037 \. C3 retn ; 返回 ret add esp,4
do-while基本结构:
.... ;do循环执行体
test ;条件判断
jnz ;如果不满足条件就跳出循环
两种编译模式的汇编判断流程很相似,这也就是do-while和while的区别,不管是否满足条件,都会先执行一次再做判断!
___________________________________________________________________________________________________
goto语句:
#include < stdio.h > int main() { int a; qq: printf("请输入一个数字:"); scanf("%d", &a); if (a == 0) goto qq; return 0; }
上面程序是判断用户输入,如果值为0就跳回重新输入判断,这里使用了goto语句
Debug版本:
00401010 >|> \55 push ebp ; 保存ebp 00401011 |. 8BEC mov ebp,esp ; 保存esp到ebp 00401013 |. 83EC 44 sub esp,44 ; 开辟局部变量空间 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,11 ; 设置循环次数 00401021 |. B8 CCCCCCCC mov eax,CCCCCCCC ; 赋值 eax CC int 3 00401026 |. F3:AB rep stos dword ptr es:[edi] ; 循环复制 CC 00401028 |> 68 20504200 /push offset Test3.??_C@_0BB@FLPH@?G?k?J?d?H?k?R?$LL?$LI?>; 参数 0040102D |. E8 AE000000 |call Test3.printfgvdbgind_blockeressges ; printf函数 00401032 |. 83C4 04 |add esp,4 ; 恢复esp 00401035 |. 8D45 FC |lea eax,[local.1] ; 取变量地址到eax 00401038 |. 50 |push eax ; 参数 00401039 |. 68 1C504200 |push offset Test3.??_C@_02MECO@?$CFd?$AA@4k?5at?50x?$CF0>; ASCII "%d" 0040103E |. E8 3D000000 |call Test3.scanfalloc_basee_block_pages ; scanf函数 00401043 |. 83C4 08 |add esp,8 ; 恢复esp 00401046 |. 837D FC 00 |cmp [local.1],0 ; 比较是否为0 0040104A |. 75 02 |jnz short Test3.0040104E ; 为0就执行if语句内容 0040104C |.^ EB DA \jmp short Test3.00401028 ; jmp无条件跳转 0040104E |> 33C0 xor eax,eax ; eax清零 00401050 |. 5F pop edi ; 恢复edi 00401051 |. 5E pop esi ; 恢复esi 00401052 |. 5B pop ebx ; 恢复ebx 00401053 |. 83C4 44 add esp,44 ; 恢复局部变量空间 00401056 |. 3BEC cmp ebp,esp ; 测试堆栈平衡 00401058 |. E8 03010000 call Test3.__chkespleBuffers@4ingsW@4location?5size?3?5?$>; 调用调试信息 0040105D |. 8BE5 mov esp,ebp ; 恢复esp 0040105F |. 5D pop ebp ; 恢复ebp 00401060 \. C3 retn ; 返回 ret add esp,4
Release版本:
00401000 /$ 51 push ecx ; ecx入栈保存 00401001 |. 68 34804000 push Test3.00408034 ; 参数 00401006 |. E8 5C000000 call Test3.00401067 ; printf函数 0040100B |. 8D4424 04 lea eax,dword ptr ss:[esp+4] ; 取变量地址到eax 0040100F |. 50 push eax ; 参数 00401010 |. 68 30804000 push Test3.00408030 ; ASCII "%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 |. 85C0 test eax,eax ; 测试eax是否为0 00401023 |. 75 24 jnz short Test3.00401049 ; 不满足就跳出if语句 00401025 |> 68 34804000 /push Test3.00408034 ; 参数 0040102A |. E8 38000000 |call Test3.00401067 ; printf函数 0040102F |. 8D4C24 04 |lea ecx,dword ptr ss:[esp+4] ; 取变量地址到ecx 00401033 |. 51 |push ecx ; 参数 00401034 |. 68 30804000 |push Test3.00408030 ; ASCII "%d" 00401039 |. E8 12000000 |call Test3.00401050 ; scanf函数 0040103E |. 8B4424 0C |mov eax,dword ptr ss:[esp+C] ; 变量值赋值到eax 00401042 |. 83C4 0C |add esp,0C ; 恢复esp 00401045 |. 85C0 |test eax,eax ; 测试eax是否为0 00401047 |.^ 74 DC \je short Test3.00401025 ; 满足就跳回执行if语句 00401049 |> 33C0 xor eax,eax ; eax清零 0040104B |. 59 pop ecx ; 恢复ecx 0040104C \. C3 retn ; 返回 ret add esp,4
goto语句框架:
在debug版本中,goto语句等价鱼jmp指令,但在这个Release版本中却没有goto语句的影子,原因在于由于if语句的原因,这里直接忽略了goto语句,而是把goto语句对应的内容直接拷贝到了if语句下!换种情况,也是jmp指令!
本节主要对程序的流程化控制逆向分析完毕,各位私下还需要多多逆向联系,对日后大有帮助!好了,基础篇到此为止,中级篇主要讲解数组,指针,函数,以及类和对象...