【黑客免杀攻防】读书笔记7 - 软件逆向工程基础1(函数调用约定、Main函数查找)
2017-10-14 18:30
417 查看
0x1 准备工作
1.1、准备工具
IDA:交互式反汇编工具OllyDbg:用户层调试工具
Visual Studio:微软开发工具
1.2、基础知识
C++开发汇编语言
0x2 查找真正的main()函数
入口点开始到Main()函数之间的代码都是编译器加进去用于初始化环境用的。main()函数其实是有3个参数的,这取决于Windows系统的机制。
查找方法:
1、字符串搜索法
2、栈回溯法
3、逐步分析法
4、小例子
C源代码:程序执行的时候会将路径保存在argv字符数组中,因此argc的值始终是等于1的。程序未经处理会显示”Helllo world“。
#include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { if (argc) { printf("Hello world!\r\n"); } else { printf("Hello everybody!\r\n"); } return 0; }
反汇编后的代码:
ESP是栈指针,也就是当前栈的所在位置。+4就是在当前堆栈+4的地方取内容。
CMP指令是对两个操作数做减法操作,仅影响标志位。
00041000 >/$ 55 push ebp 00041001 |. 8BEC mov ebp,esp 00041003 |. 837D 08 00 cmp [arg.1],0x0 00041007 |. 74 11 je X9-3.0004101A 00041009 |. 68 48640500 push 9-3.00056448 ; /Hello world!\r\n 0004100E |. E8 2D000000 call 9-3.printf ; \printf 00041013 |. 83C4 04 add esp,0x4 ; esp+4的意思就是在当前堆栈+4的地方取其内容 00041016 |. 33C0 xor eax,eax 00041018 |. 5D pop ebp 00041019 |. C3 retn 0004101A |> 68 58640500 push 9-3.00056458 ; /Hello everybody!\r\n 0004101F |. E8 1C000000 call 9-3.printf ; \printf 00041024 |. 83C4 04 add esp,0x4 00041027 |. 33C0 xor eax,eax 00041029 |. 5D pop ebp 0004102A \. C3 retn
0x3 函数识别初探
1、函数调用约定的参数入栈书序、回收堆栈的规则
C规范 _cdecl 参数入栈顺序从右到左 调用者负责回收堆栈pascal规范 pascal 参数入站从左到右 被调用者负责回收堆栈
快速调用规范 __fastcall 从右到左 被调用者负责回收堆栈
标准调用规范 __stdcall 从右到左 被调用者负责回收堆栈
2、调用方式示例
调用约定 | 关键字 | 参数入栈顺序 | 回收堆栈 |
---|---|---|---|
C规范 | _cdecl | 从右到左 | 调用者负责 |
Pascal规范 | pascal | 从左到右 | 被调用者负责 |
快速调用规范 | _fastcall | 从右到左,使用寄存器传参 | 被调用者负责 |
标准调用规范 | _stdcall | 从右到左 | 被调用者负责 |
#include "stdafx.h" int __cdecl fun_a(int nNumA, int nNumB, int nNumC) // C规范 { return nNumA+nNumB+nNumC; } int __fastcall fun_b(int nNumA, int nNumB, int nNumC)// 快速调用 { return nNumA+nNumB-nNumC; } int __stdcall fun_c(int nNumA, int nNumB, int nNumC) // 标准调用 { return nNumA-nNumB-nNumC; } int _tmain(int argc, _TCHAR* argv[]) { printf("11111111111111111111111111111"); fun_a(argc, 1, 2); //func_a,入栈顺序从右向左,调用者平衡堆栈 fun_b(argc, 1, 2); //fun_b,入栈顺序从右向左,被调用者平衡堆栈 fun_c(argc, 1, 2);//fun_c,入栈顺序从右向左,被调用者平衡堆栈 return 0; }
反汇编程序:
Main()函数
.text:004016A0 main__ proc near ; CODE XREF: j_main__j .text:004016A0 ................ .text:00401707 push 2 ; 参数3入栈 .text:00401709 push 1 ; 参数2入栈 .text:0040170B mov eax, [ebp+arg_0] .text:0040170E push eax ; 参数1入栈 .text:0040170F call j_fun_a__ ; func_a,cdecl调用方式,入栈顺序从右向左,调用者平衡堆栈 .text:00401714 add esp, 0Ch ; 由Main()函数销毁fun_a用到的局部变量平衡堆栈 .text:00401714 ; .text:00401717 push 2 ; 参数3入栈 .text:00401719 mov edx, 1 ; 参数2入栈 .text:0040171E mov ecx, [ebp+arg_0] ; 参数1传递给ecx .text:00401721 call j_fun_b__ ; fun_b,fastcall调用方式,入栈顺序从右向左,被调用者平衡堆栈 .text:00401726 push 2 ; 参数3入栈 .text:00401728 push 1 ; 参数2入栈 .text:0040172A mov eax, [ebp+arg_0] .text:0040172D push eax ; 参数1入栈 .text:0040172E call j_fun_c__ ; fun_c,stdcall调用方式,入栈顺序从右向左,被调用者平衡堆栈 .text:00401733 xor eax, eax .text:00401735 pop edi .text:00401736 pop esi .text:00401737 pop ebx .text:00401738 add esp, 0C0h ; 销毁局部变量,平衡堆栈 .text:0040173E cmp ebp, esp ; 比较esp的值是否正常 .text:00401740 call sub_4010B4 ; 调用检查esp的函数 .text:00401745 mov esp, ebp .text:00401747 pop ebp .text:00401748 retn .text:00401748 main__ endp
fun_a()函数是cdecl调用,所以没有堆栈平衡,由调用者main()函数进行堆栈平衡
.text:00401480 fun_a__ proc near ; CODE XREF: j_fun_a__j .text:00401480 .text:00401480 var_C0 = byte ptr -0C0h .text:00401480 arg_0 = dword ptr 8 .text:00401480 arg_4 = dword ptr 0Ch .text:00401480 arg_8 = dword ptr 10h .text:00401480 .text:00401480 push ebp ; EBP入栈保存 .text:00401481 mov ebp, esp ; 然后将堆栈指针ESP的值传递给EBP .text:00401481 ; 如此一来在这个函数内只需要使用EBP就可对栈进行操作了。 .text:00401481 ; 这样做的好处是不需要对ESP做过多的操作 .text:00401483 sub esp, 0C0h ; 将ESP减0xC0 .text:00401489 push ebx .text:0040148A push esi .text:0040148B push edi ; 保存EBX、ESI、EDI .text:0040148C lea edi, [ebp+var_C0] .text:00401492 mov ecx, 30h .text:00401497 mov eax, 0CCCCCCCCh .text:0040149C rep stosd .text:0040149E mov eax, [ebp+arg_0] ; 将参数1传递给eax .text:004014A1 add eax, [ebp+arg_4] ; 将eax与参数2相加 .text:004014A4 add eax, [ebp+arg_8] ; 将eax与参数3相加 .text:004014A7 pop edi .text:004014A8 pop esi .text:004014A9 pop ebx .text:004014AA mov esp, ebp .text:004014AC pop ebp .text:004014AD retn .text:004014AD fun_a__ endp
fun_b()函数是fastcall调用,参数是由ECX与EDX这两个寄存器完成的,超出部分的参数依然采用压栈方式传递。被调用者fun_b()负责堆栈平衡。
.text:004014C0 fun_b__ proc near ; CODE XREF: j_fun_b__j .text:004014C0 .text:004014C0 var_D8 = byte ptr -0D8h .text:004014C0 var_14 = dword ptr -14h .text:004014C0 var_8 = dword ptr -8 .text:004014C0 arg_0 = dword ptr 8 .text:004014C0 .text:004014C0 push ebp .text:004014C1 mov ebp, esp .text:004014C3 sub esp, 0D8h .text:004014C9 push ebx .text:004014CA push esi .text:004014CB push edi .text:004014CC push ecx .text:004014CD lea edi, [ebp+var_D8] .text:004014D3 mov ecx, 36h .text:004014D8 mov eax, 0CCCCCCCCh .text:004014DD rep stosd .text:004014DF pop ecx .text:004014E0 mov [ebp+var_14], edx ; 将参数2的值传递给局部变量2 .text:004014E3 mov [ebp+var_8], ecx ; 将参数1的值传递给局部变量1 .text:004014E6 mov eax, [ebp+var_8] ; 将局部变量1的值传递给eax .text:004014E9 add eax, [ebp+var_14] ; 将eax与局部变量2相加 .text:004014EC sub eax, [ebp+arg_0] ; 将eax与参数3相减 .text:004014EC ; .text:004014EC ; 采用快速调用的函数参数是由exc与edx这两个寄存器完成的, .text:004014EC ; 而超出部分的参数则依然要使用传统的压栈方式传递,以下就是本函数的参数与局部变量的结构 .text:004014EC ; .text:004014EC ; 参数1:ecx .text:004014EC ; 参数2:edx .text:004014EC ; 参数3:ebp+0x8 .text:004014EC ; 局部变量1:ebp-0x8 .text:004014EC ; 局部变量2:ebp-0x1 .text:004014EF pop edi .text:004014F0 pop esi .text:004014F1 pop ebx .text:004014F2 mov esp, ebp .text:004014F4 pop ebp .text:004014F5 retn 4 .text:004014F5 fun_b__ endp
fun_c函数是stdcall调用,函数返回时销毁局部变量,平衡堆栈。
.text:00401510 fun_c__ proc near ; CODE XREF: j_fun_c__j .text:00401510 .text:00401510 var_C0 = byte ptr -0C0h .text:00401510 arg_0 = dword ptr 8 .text:00401510 arg_4 = dword ptr 0Ch .text:00401510 arg_8 = dword ptr 10h .text:00401510 .text:00401510 push ebp .text:00401511 mov ebp, esp .text:00401513 sub esp, 0C0h .text:00401519 push ebx .text:0040151A push esi .text:0040151B push edi .text:0040151C lea edi, [ebp+var_C0] .text:00401522 mov ecx, 30h .text:00401527 mov eax, 0CCCCCCCCh .text:0040152C rep stosd .text:0040152E mov eax, [ebp+arg_0] ; 将参数1传递给eax .text:00401531 sub eax, [ebp+arg_4] ; 将eax与参数2相减 .text:00401534 sub eax, [ebp+arg_8] ; 将eax与参数3相减 .text:00401537 pop edi .text:00401538 pop esi .text:00401539 pop ebx .text:0040153A mov esp, ebp .text:0040153C pop ebp .text:0040153D retn 0Ch ; 函数返回时销毁局部变量,平衡堆栈。 .text:0040153D fun_c__ endp
3、汇编改变特征小技巧
并不是所有的函数都只有用call指令才能调用,使用lea、push加jmp的组合也可以达到相同的目的。例如将call Demo.013A1127可以转换为以下形式:
lea esi,return_addr ; 取到jmp Demo.013A1127指令后面的地址 push esi ; 将这个地址压入栈 jmp Demo.013A1127 ; 跳转到Demo.013A1127处执行函数代码
4、裸函数
C++使用naked标识创建的裸函数将不包含任何用户代码以外的指令,即便是函数末尾的retn也要用户自己来实现。代码如下:
#include "stdafx.h" __declspec(naked) int fun(int nNumA, int nNumB, int nNumC) { __asm { push ebp mov ebp, esp sub esp, 0x4 } nNumA += (nNumB+nNumC); // 注意,此行为c语句。 __asm { mov eax, nNumA add esp, 0x4 mov esp, ebp pop ebp retn } } int _tmain(int argc, _TCHAR* argv[]) { printf("fun=%d", fun(argc,1,2)); return 0; }
5、小结
采用不同的调用方式,反汇编代码不同。a) 几乎全部函数调用方式都会用栈来传递参数,只有使用快速调用约定后且参数少于等于2时才会全部采用寄存器传参。
b) 函数起始部分:以push ebp和mov ebp,esp汇编指令开始
c) 每个函数由call指令调用,且以retn指令结尾。
d) 裸函数进行内联汇编可以改变以上的某些规律。
注:裸函数是指编译器生成汇编代码时不添加任何额外的指令,包括retn。
0x5 参考文章
《黑客免杀攻防》 软件逆向工程1-3http://blog.csdn.net/dalerkd/article/details/41173623
相关文章推荐
- 【黑客免杀攻防】读书笔记8 - 软件逆向工程基础2(if-else,三目运算符)
- 《黑客免杀攻防》读书笔记-软件逆向工程(8)乘法与除法的识别与优化原理
- 函数调用分析----------软件逆向基础
- 《黑客免杀攻防》读书笔记-软件逆向工程-5循环分支
- 《黑客免杀攻防》读书笔记-软件逆向工程(7)加法与减法的识别与优化原理
- 《黑客免杀攻防》读书笔记-软件逆向工程(6) switch-case分支
- 逆向工程核心原理学习笔记(二):字符串检索法查找main函数
- 逆向工程核心原理学习笔记(二):字符串检索法查找main函数
- 【C++基础之五】函数调用机制与调用约定
- 《黑客免杀攻防》 软件逆向工程(4)
- 逆向工程核心原理学习笔记(十六):栈帧3:add()函数参数传递与调用
- 【C++基础之五】函数调用机制与调用约定
- 《黑客免杀攻防》 软件逆向工程1-3
- 逆向工程核心原理学习笔记(二十二):栈帧9:调用printf()函数
- 逆向知识十一讲,识别函数的调用约定,函数参数,函数返回值.
- C++之:函数调用的名字查找与继承
- c++中的几种函数调用约定
- IDA学习笔记 函数调用约定
- 《需求工程-软件建模与分析》读书笔记3
- mybatis入门基础(九)----逆向工程