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
收藏起来,慢慢学习{:1_908:}
页:
[1]