裸函数
void __declspec(naked) Plus(){
__asm{
ret
}//自己加上汇编代码
}
让编译器和连接器不生成对应汇编代码。
用裸函数实现x+y的功能:
int __declspec(naked) Plus(int x,int y){
//此时x和y已经进入栈中,分别是[ebp+8]和[ebp+0xC]
__asm{
//保留栈底
push ebp
//提升堆栈
mov ebp,esp
sub esp,0x40
//保留现场
push ebx
push esi
push edi
//填充缓冲区
mov eax,0xCCCCCCCC
mov ecx,0x10
lea edi,dword ptr ds:[ebp-0x40]
rep stosd
//函数核心功能
mov eax,dword ptr ds:[ebp+8]
add eax,dword ptr ds:[ebp+0xC]
//恢复现场
pop edi
pop esi
pop ebx
//降低堆栈
mov esp,ebp
//恢复栈底
pop ebp
ret
}
}
练习,汇编实现:
int plus(int x,int y,int z)
{
int a = 2;
int b = 3;
int c = 4;
return x+y+z+a+b+c;
}
__asm {
push ebp
mov ebp,esp
sub esp,0x4C
push ebx
push esi
push edi
mov eax,0xCCCCCCCC
mov ecx,0x13
lea edi,dword ptr ds:[ebp-0x40]
rep stos dword ptr [edi]
mov dword ptr [ebp-4],2
mov dword ptr [ebp-8],3
mov dword ptr [ebp-0xC],4
mov eax,dword ptr ds:[ebp+8]
add eax,dword ptr ds:[ebp+0xC]
add eax,dword ptr ds:[ebp+0x10]
add eax,dword ptr ds:[ebp-4]
add eax,dword ptr ds:[ebp-8]
add eax,dword ptr ds:[ebp-0XC]
pop edi
pop esi
pop ebx
mov esp,ebp
pop ebp
ret
}
常见的函数调用约定
__cdecl(默认)
参数压栈顺序:从右至左入栈
平衡堆栈:调用者清理堆栈(即哪个函数调用的,哪个函数来平衡堆栈,外平栈)
__stdcall
参数压栈顺序:从右至左入栈
平衡堆栈:自身清理堆栈(即在调用的函数里面平衡堆栈,内平栈)
__fastcall
参数压栈顺序:ECX/EDX传送前两个参数,剩下的参数从右至左入栈
平衡堆栈:自身清理堆栈
很显然,对于__fastcall,当传入的参数个数小于等于2时,不需要平衡堆栈;当传入的参数个数大于2时,使用内平栈。所以当传入的参数个数小于等于2时,使用__fastcall,用寄存器传送参数速度会快很多。另一个需要注意的是,ret 后值不一定代表了参数的个数,使用__fastcall需要额外加上两个寄存器的参数
main函数的识别:
main函数被调用前要先调用的函数如下:
GetVersion()
_heap_init()
GetCommandLineA()
_crtGetEnvironmentStringsA()
_setargv()
_setenvp()
_cinit()
例题分析:
找main的入口点,找到上面调用main前需调用的函数后,接着往下找传三个参数的地方(这里是GetCommandLineA()函数后):
PUSH EDX
MOV EAX,DWORD PTR DS:[4225F4]
PUSH EAX
MOV ECX,DWORD PTR DS:[4225F0]
PUSH ECX
CALL 00401014
ADD ESP,0C
那么CALL 00401014就是入口,进入main:
00401110 /> \55 PUSH EBP;保留栈底
00401111 |. 8BEC MOV EBP,ESP
00401113 |. 83EC 44 SUB ESP,44;提升堆栈
00401116 |. 53 PUSH EBX
00401117 |. 56 PUSH ESI
00401118 |. 57 PUSH EDI;保留现场
00401119 |. 8D7D BC LEA EDI,[EBP-44]
0040111C |. B9 11000000 MOV ECX,11
00401121 |. B8 CCCCCCCC MOV EAX,CCCCCCCC
00401126 |. F3:AB REP STOS DWORD PTR ES:[EDI];填充缓冲区
;注意这上面的部分是保存调用main函数的函数而生成的汇编,我们分析main从下面这一行开始分析
00401128 |. 6A 07 PUSH 7
0040112A |. 6A 06 PUSH 6
0040112C |. 6A 04 PUSH 4
0040112E |. BA 03000000 MOV EDX,3
00401133 |. B9 01000000 MOV ECX,1;传入5个参数,可以知道main采用的是__fastcall,这里我分析错的地方是:1和3是交给寄存器的,实际不用压入堆栈,我一直当成压入堆栈,然后后面就分析错了。。。
00401138 |. E8 DCFEFFFF CALL 00401019;调用函数
0040113D |. 8945 FC MOV DWORD PTR SS:[EBP-4],EAX
00401140 |. 8B45 FC MOV EAX,DWORD PTR SS:[EBP-4]
00401143 |. 50 PUSH EAX
00401144 |. 68 0CF14100 PUSH OFFSET 0041F10C ; ASCII "%d"
00401149 |. E8 92A60000 CALL 0040B7E0
0040114E |. 83C4 08 ADD ESP,8
00401151 |. 33C0 XOR EAX,EAX
00401153 |. 5F POP EDI
00401154 |. 5E POP ESI
00401155 |. 5B POP EBX
00401156 |. 83C4 44 ADD ESP,44
00401159 |. 3BEC CMP EBP,ESP
0040115B |. E8 10000000 CALL 00401170
00401160 |. 8BE5 MOV ESP,EBP
00401162 |. 5D POP EBP
00401163 \. C3 RETN
;从左至右依次是地址,硬编码,汇编代码,我就不改了,有点麻烦
进入CALL 00401019:
004010A0 /> \55 PUSH EBP;保留栈底
004010A1 |. 8BEC MOV EBP,ESP
004010A3 |. 83EC 50 SUB ESP,50;提升堆栈
004010A6 |. 53 PUSH EBX
004010A7 |. 56 PUSH ESI
004010A8 |. 57 PUSH EDI
004010A9 |. 51 PUSH ECX;保留现场
004010AA |. 8D7D B0 LEA EDI,[EBP-50]
004010AD |. B9 14000000 MOV ECX,14
004010B2 |. B8 CCCCCCCC MOV EAX,CCCCCCCC
004010B7 |. F3:AB REP STOS DWORD PTR ES:[EDI];填充缓冲区
004010B9 |. 59 POP ECX
004010BA |. 8955 F8 MOV DWORD PTR SS:[EBP-8],EDX
004010BD |. 894D FC MOV DWORD PTR SS:[EBP-4],ECX;依次传入参数3和1
004010C0 |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
004010C3 |. 50 PUSH EAX;4
004010C4 |. 8B4D F8 MOV ECX,DWORD PTR SS:[EBP-8]
004010C7 |. 51 PUSH ECX;3
004010C8 |. 8B55 FC MOV EDX,DWORD PTR SS:[EBP-4]
004010CB |. 52 PUSH EDX;1
004010CC |. E8 4DFFFFFF CALL 0040101E;调用函数,根据上面几行,可以知道调用这个函数也是用的__fastcall,用了eax、ecx和edx传参
004010D1 |. 8945 F4 MOV DWORD PTR SS:[EBP-0C],EAX;将放在eax里的结果存入缓冲区中
004010D4 |. 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]
004010D7 |. 50 PUSH EAX
004010D8 |. 8B4D FC MOV ECX,DWORD PTR SS:[EBP-4]
004010DB |. 51 PUSH ECX
004010DC |. E8 24FFFFFF CALL 00401005;这里又传入2个参数,
;SS:[EBP-8]--3 SS:[EBP-4]--1
004010E1 |. 83C4 08 ADD ESP,8;使用外平栈
004010E4 |. 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX;同样将放在eax里的结果存入缓冲区中,就放在8的上面(这个说法不太准确)
004010E7 |. 8B55 F0 MOV EDX,DWORD PTR SS:[EBP-10]
004010EA |. 52 PUSH EDX
004010EB |. 8B45 F4 MOV EAX,DWORD PTR SS:[EBP-0C]
004010EE |. 50 PUSH EAX
004010EF |. E8 11FFFFFF CALL 00401005;又调用相同的函数,即取上面两次相加的结果进行相加,(1+3+4=8) + (1+3=4) = 12
004010F4 |. 83C4 08 ADD ESP,8
004010F7 |. 5F POP EDI
004010F8 |. 5E POP ESI
004010F9 |. 5B POP EBX
004010FA |. 83C4 50 ADD ESP,50
004010FD |. 3BEC CMP EBP,ESP
004010FF |. E8 6C000000 CALL 00401170;这个函数编译器加上去的,不用逆,用来判断堆栈是否平衡
00401104 |. 8BE5 MOV ESP,EBP
00401106 |. 5D POP EBP
00401107 \. C2 0C00 RETN 0C
进入CALL 0040101E:
00401060 /> \55 PUSH EBP;保留栈底
00401061 |. 8BEC MOV EBP,ESP
00401063 |. 83EC 40 SUB ESP,40;提升堆栈
00401066 |. 53 PUSH EBX
00401067 |. 56 PUSH ESI
00401068 |. 57 PUSH EDI;保留现场
00401069 |. 8D7D C0 LEA EDI,[EBP-40]
0040106C |. B9 10000000 MOV ECX,10
00401071 |. B8 CCCCCCCC MOV EAX,CCCCCCCC
00401076 |. F3:AB REP STOS DWORD PTR ES:[EDI];填充缓冲区
00401078 |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
0040107B |. 0345 0C ADD EAX,DWORD PTR SS:[EBP+0C]
0040107E |. 0345 10 ADD EAX,DWORD PTR SS:[EBP+10];函数核心功能,可以知道就是取几个值进行相加,关键是要知道取的都是哪些值,
;SS:[EBP+8]--1 SS:[EBP+0C]--3 SS:[EBP+10]--4
00401081 |. 5F POP EDI
00401082 |. 5E POP ESI
00401083 |. 5B POP EBX;恢复现场
00401084 |. 8BE5 MOV ESP,EBP;降低堆栈
00401086 |. 5D POP EBP;恢复栈底
00401087 \. C2 0C00 RETN 0C;返回并平衡堆栈,详单与retN后进行,esp+0C,因为传了三个参数
注意最后相加得的值给了EAX。
进入CALL 00401005:
00401030 /> \55 PUSH EBP
00401031 |. 8BEC MOV EBP,ESP
00401033 |. 83EC 40 SUB ESP,40
00401036 |. 53 PUSH EBX
00401037 |. 56 PUSH ESI
00401038 |. 57 PUSH EDI
00401039 |. 8D7D C0 LEA EDI,[EBP-40]
0040103C |. B9 10000000 MOV ECX,10
00401041 |. B8 CCCCCCCC MOV EAX,CCCCCCCC
00401046 |. F3:AB REP STOS DWORD PTR ES:[EDI]
00401048 |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
0040104B |. 0345 0C ADD EAX,DWORD PTR SS:[EBP+0C]
0040104E |. 5F POP EDI
0040104F |. 5E POP ESI
00401050 |. 5B POP EBX
00401051 |. 8BE5 MOV ESP,EBP
00401053 |. 5D POP EBP
00401054 \. C3 RETN
与之前的函数功能相同,就是将传入的参数相加并返回,即返回1+3 = 4。同样最后相加得的值给了EAX。
通过分析,我们可以写出C语言的代码来。
int __cdecl fun3(int x,int y) {
return x+y;
}
int __stdcall fun2(int x,int y,int z) {
return x+y+z;
}
int __fastcall fun1(int a,int b,int c,int d,int e) {
int x = a;
int y = b;
int m = fun2(x,y,c);
int n = fun3(x,y);
return fun3(m,n);
}
int main(int argc,char* argv []) {
int x = func1(1,3,4,6,7);
printf("%d\n",x);
}
下面是我用IDA反汇编得到的C语言代码,有些差异。
int __cdecl sub_401030(int a1, int a2)
{
return a2 + a1;
}
int __cdecl sub_401005(int a1, int a2)
{
return sub_401030(a1, a2);
}
int __stdcall sub_401060(int a1, int a2, int a3)
{
return a3 + a2 + a1;
}
int __stdcall sub_40101E(int a1, int a2, int a3)
{
return sub_401060(a1, a2, a3);
}
int __fastcall sub_4010A0(int a1, int a2, int a3, int a4, int a5)
{
int v6; // [esp+4Ch] [ebp-10h]
int v7; // [esp+50h] [ebp-Ch]
v7 = sub_40101E(a1, a2, a3);
v6 = sub_401005(a1, a2);
return sub_401005(v7, v6);
}
int __stdcall sub_401019(int a1, int a2, int a3)
{
return sub_4010A0(a1, a2, a3);
}
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
int v4; // [esp+4Ch] [ebp-4h]
v4 = sub_401019(4, 6, 7);
printf("%d", v4);
return 0;
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1666739907@qq.com