b917893200 发表于 2020-9-26 12:25

x64调用约定理论及实战

fastcall资料网上很多了,我再总结一下,调用方为被调用方开辟堆栈(维护堆栈),填充函数,准备调用环境。

样本程序。a函数入口打断点.
断下之后

void a()
{
00007FF6B11117C0 40 55                push      rbp
00007FF6B11117C2 57                   push      rdi
00007FF6B11117C3 48 81 EC F8 00 00 00 sub         rsp,0F8h
00007FF6B11117CA 48 8D 6C 24 30       lea         rbp,
00007FF6B11117CF 48 8B FC             mov         rdi,rsp
00007FF6B11117D2 B9 3E 00 00 00       mov         ecx,3Eh
00007FF6B11117D7 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
00007FF6B11117DC F3 AB                rep stos    dword ptr
00007FF6B11117DE 48 8D 0D 60 F8 00 00 lea         rcx,@cpp (07FF6B1121045h)]
00007FF6B11117E5 E8 9D F8 FF FF       call      __CheckForDebuggerJustMyCode (07FF6B1111087h)   //上面是Debug模式下的初始化代码 rep stos类似循环,循环次数在rcx中,一般都是for(;;;){memcpy();}这种代码
    f(1,2,3,4,5);
00007FF6B11117EA C7 44 24 20 05 00 00 00 mov         dword ptr ,5 //超过四个参数,存放在栈中。这里为什么是20h,因为rsp->rsp+16h是暂存前4个参数,寄存器有易失性,你要多次用参数就得保存
00007FF6B11117F2 41 B9 04 00 00 00    mov         r9d,4//第四个参数r9中
00007FF6B11117F8 41 B8 03 00 00 00    mov         r8d,3//第三个参数r8中
00007FF6B11117FE BA 02 00 00 00       mov         edx,2//第二个参数rdx中
00007FF6B1111803 B9 01 00 00 00       mov         ecx,1//第一个参数rcx中
00007FF6B1111808 E8 8D F9 FF FF       call      f (07FF6B111119Ah)   
}
00007FF6B111180D 48 8D A5 C8 00 00 00 lea         rsp,
00007FF6B1111814 5F                   pop         rdi
00007FF6B1111815 5D                   pop         rbp
00007FF6B1111816 C3                   ret


bool f(int a,int b, int c,int d ,int f)
{
//x86可以根据ret n 来看函数参数,ia-64的话可以根据入口堆栈赋值和入口前堆栈赋值的情况判断参数个数
00007FF6B1111830 44 89 4C 24 20       mov         dword ptr ,r9d//进入函数的第一件事,保存前4个参数,因为返回地址压入占8个字节,所以是rsp+8到rsp+20
00007FF6B1111835 44 89 44 24 18       mov         dword ptr ,r8d
00007FF6B111183A 89 54 24 10          mov         dword ptr ,edx
00007FF6B111183E 89 4C 24 08          mov         dword ptr ,ecx
00007FF6B1111842 55                   push      rbp
00007FF6B1111843 57                   push      rdi
00007FF6B1111844 48 81 EC 68 01 00 00 sub         rsp,168h
00007FF6B111184B 48 8D 6C 24 20       lea         rbp,
00007FF6B1111850 48 8B FC             mov         rdi,rsp
00007FF6B1111853 B9 5A 00 00 00       mov         ecx,5Ah
00007FF6B1111858 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
00007FF6B111185D F3 AB                rep stos    dword ptr
00007FF6B111185F 8B 8C 24 88 01 00 00 mov         ecx,dword ptr
00007FF6B1111866 48 8D 0D D8 F7 00 00 lea         rcx,
00007FF6B111186D E8 15 F8 FF FF       call      __CheckForDebuggerJustMyCode (07FF6B1111087h)
    b = c = d = f = 1;
00007FF6B1111872 C7 85 80 01 00 00 01 00 00 00 mov         dword ptr ,1//用rbp索引,更常见的其实是rsp索引
00007FF6B111187C 8B 85 80 01 00 00    mov         eax,dword ptr
00007FF6B1111882 89 85 78 01 00 00    mov         dword ptr ,eax
00007FF6B1111888 8B 85 78 01 00 00    mov         eax,dword ptr
00007FF6B111188E 89 85 70 01 00 00    mov         dword ptr ,eax
00007FF6B1111894 8B 85 70 01 00 00    mov         eax,dword ptr
00007FF6B111189A 89 85 68 01 00 00    mov         dword ptr ,eax
    int k1, k12k2, k2, k;
    if (a == 1)
00007FF6B11118A0 83 BD 60 01 00 00 01 cmp         dword ptr ,1
00007FF6B11118A7 75 02                jne         f+7Bh (07FF6B11118ABh)
      return true;
00007FF6B11118A9 B0 01                mov         al,1
}
00007FF6B11118AB 48 8D A5 48 01 00 00 lea         rsp,
00007FF6B11118B2 5F                   pop         rdi
00007FF6B11118B3 5D                   pop         rbp
00007FF6B11118B4 C3                   ret

//接下来实战分析一个内核apiPsLookupProcessByProcessId
PAGE:00000001403530A6 loc_1403530A6:                        ; CODE XREF: PsOpenProcess+230↓j
PAGE:00000001403530A6               cmp   qword ptr , 0
PAGE:00000001403530AC               jnz   loc_140353147
PAGE:00000001403530B2               lea   rdx,
PAGE:00000001403530B7               mov   rcx, qword ptr
PAGE:00000001403530BC               call    PsLookupProcessByProcessId//我们可以看到这个函数有2个参数


PsLookupProcessByProcessId proc near    ; CODE XREF: ViCreateProcessCallback+AA058↑p
PAGE:00000001403531FC                                       ; PsOpenProcess+15C↑p ...
PAGE:00000001403531FC
PAGE:00000001403531FC var_38          = dword ptr -38h
PAGE:00000001403531FC arg_0         = qword ptr8
PAGE:00000001403531FC arg_8         = qword ptr10h
PAGE:00000001403531FC arg_10          = qword ptr18h
PAGE:00000001403531FC
PAGE:00000001403531FC ; FUNCTION CHUNK AT PAGE:00000001403B8030 SIZE 00000082 BYTES
PAGE:00000001403531FC
PAGE:00000001403531FC               mov   , rbx    //我们看到这里并不是将参数保存,而是保存其他的寄存器,说明这个函数的流程一定不长,并且参数只用一次(这点可以在wrk或者reactos中验证)
PAGE:0000000140353201               mov   , rbp
PAGE:0000000140353206               mov   , rsi
PAGE:000000014035320B               push    rdi
PAGE:000000014035320C               push    r12
PAGE:000000014035320E               push    r13
PAGE:0000000140353210               sub   rsp, 20h
PAGE:0000000140353214               mov   rdi, gs:_KPCR.Prcb.CurrentThread
PAGE:000000014035321D               xor   r12d, r12d
PAGE:0000000140353220               mov   rbp, rdx
PAGE:0000000140353223               dec   word ptr
PAGE:000000014035322A               mov   rbx, r12
PAGE:000000014035322D               mov   rdx, rcx   //rcx(arg0 ) ProcessId 这里调用完之后此参数就不用了,所以不用保存这个参数,因此前面保存的是寄存器环境
PAGE:0000000140353230               mov   rcx, cs:PspCidTable
PAGE:0000000140353237               call    ExMapHandleToPointer
………………………………………………
函数末尾
PAGE:00000001403532C4               mov   rbx,    //将原先保存的寄存器恢复
PAGE:00000001403532C9               mov   rbp,
PAGE:00000001403532CE               mov   rsi,
PAGE:00000001403532D3               add   rsp, 20h
PAGE:00000001403532D7               pop   r13
PAGE:00000001403532D9               pop   r12
PAGE:00000001403532DB               pop   rdi
PAGE:00000001403532DC               retn



tark 发表于 2020-9-26 12:57

收藏起来,慢慢学习{:1_908:}
页: [1]
查看完整版本: x64调用约定理论及实战