函数

  1. 裸函数
  2. 常见的函数调用约定

image-20230730113710435

裸函数

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
github