x64逆向技术

1. 寄存器

64位寄存大小扩展64位,数量增加8个(R8-R15),扩充8个128位的xmm寄存器, 64位兼容32应用程序,64程序用rax, 32位程序用eax底32位, x64高位访问通过WORD byte DWORD来进行访问。

1.1 函数

栈平衡
  1. 栈的特征是先进后出,RSP是栈顶指针,x86栈大小是4字节x64是8字节,push是进行压栈pop是出栈
  2. 栈中存储的数据主要包括 局部变量,函数参数,函数返回,函数的返回地址。
  3. 函数调用完需要释放掉申请的栈空间,保证栈空间与函数调用钱一致,这个过程叫做栈平衡。
启动函数

想要找打x64main函数可以通过

  1. start 里面找到 jmp __timainCRTStartup
  2. 找到exit函数(通常)上面第一个call是main函数(更高版本的编译器main call会在第一个call里面)
调用约定

x86架构下调用约定有stdcall、__cdecl 等方式。 x64值只存在一种调用约定 快速调用约定 如果参数超过4个,多余的参数放到栈中,入栈顺序从右到左。 前4个存放参数寄存器是固定的rcx, rdx, r8, r9 浮点数存放寄存器xmm0, xmm1,xmm2,xmm3

参数传递

虽然前4个参数传参依靠寄存器,但是栈依旧保留4参数的栈空间。 当参数少于4 个的时候
(1) 2个参数传递
源码

#include <iostream>

int Add(int Num1, int Num2)
{
	return Num1 + Num2;
}

int main()
{
	printf("%d\r\n", Add(1,2));
	return 0;
}

main

.text:0000000140011940                 push    rbp
.text:0000000140011942                 push    rdi
.text:0000000140011943                 sub     rsp, 0E8h
.text:000000014001194A                 lea     rbp, [rsp+20h] //申请了栈空间 20h=32=4*8 
.text:000000014001194F                 lea     rcx, unk_140021029
.text:0000000140011956                 call    sub_140011366
.text:000000014001195B                 mov     edx, 2  //参数二
.text:0000000140011960                 mov     ecx, 1  //参数一
.text:0000000140011965                 call    sub_1400111C2
.text:000000014001196A                 mov     edx, eax
.text:000000014001196C                 lea     rcx, aD         ; "%d\r\n"
.text:0000000140011973                 call    sub_140011190
.text:0000000140011978                 xor     eax, eax
.text:000000014001197A                 lea     rsp, [rbp+0C8h]
.text:0000000140011981                 pop     rdi
.text:0000000140011982                 pop     rbp
.text:0000000140011983                 retn
.text:0000000140011983 sub_140011940   endp

是两个参数通过寄存器传参edx,ecx, 但是依旧为参数申请了20h大小的栈空间。

(2)4个参数以上
源码

#include <iostream>

int Add(int Num1, int Num2, int Num3, int Num4, int Num5, int Num6)
{
	return Num1 + Num2 + Num3 + Num4 + Num5 + Num6;
}

int main()
{
	printf("%d\r\n", Add(1,2,3,4,5,6));
	return 0;
}
.text:0000000140011970                 push    rbp
.text:0000000140011972                 push    rdi
.text:0000000140011973                 sub     rsp, 0F8h       ; 申请栈空间。
.text:000000014001197A                 lea     rbp, [rsp+30h]  ; 为参数划分栈空间
.text:000000014001197F                 lea     rcx, unk_140021029
.text:0000000140011986                 call    sub_140011366
.text:000000014001198B                 mov     [rsp+100h+var_D8], 6 ; 参数6
.text:0000000140011993                 mov     [rsp+100h+var_E0], 5 ; 参数5
.text:000000014001199B                 mov     r9d, 4          ; 参数4
.text:00000001400119A1                 mov     r8d, 3          ; 参数3
.text:00000001400119A7                 mov     edx, 2          ; 参数2
.text:00000001400119AC                 mov     ecx, 1          ; 参数1
.text:00000001400119B1                 call    sub_1400110AF   ; add()函数
.text:00000001400119B6                 mov     edx, eax
.text:00000001400119B8                 lea     rcx, Format     ; "%d\r\n"
.text:00000001400119BF                 call    printf
.text:00000001400119C4                 xor     eax, eax
.text:00000001400119C6                 lea     rsp, [rbp+0C8h]
.text:00000001400119CD                 pop     rdi
.text:00000001400119CE                 pop     rbp
.text:00000001400119CF                 retn
.text:00000001400119CF sub_140011970   endp

lea rbp, [rsp+30h] 4个参数是默认保留空间的加上2个超出的参数就是6个 8*6=48=30h 划分了30h大小栈空间。
sub rsp, 0F8h 这个可以看到申请0F8h大小的栈栈空间但是没有平栈,这是非平栈,非平栈操作的目的是为了在栈上为后续的函数调用或局部变量分配存储空间。非平栈操作不会影响栈的平衡,因为在函数调用结束时,rsp 会被恢复到之前的值。非平栈操作只是在栈上分配了一定的空间,而平栈操作则是恢复之前的栈状态。非平栈操作和平栈操作是两个不同的概念。非平栈操作用于分配栈空间,而平栈操作用于恢复栈状态。非平栈操作不会自动平衡栈,需要在函数调用结束时通过平栈操作来恢复栈的平衡。非平栈不会影响栈平衡。

(3)参数为结构体 源码

#include <iostream>

struct tagproint {
	int x1;
	int y1;
};

void fun(tagproint pt) {
	printf("x=%d y=%d\r\n", pt.x1, pt.y1);
}

int main() {
	tagproint pt = { 1,2 };
	fun(pt);
	return 0;
}
.text:000000014001195A                 lea     rbp, [rsp+20h]  ; 申请参数预留空间
.text:000000014001195F                 lea     rdi, [rsp+110h+var_F0]
.text:0000000140011964                 mov     ecx, 0Ah
.text:0000000140011969                 mov     eax, 0CCCCCCCCh
.text:000000014001196E                 rep stosd
.text:0000000140011970                 lea     rcx, unk_140021029
.text:0000000140011977                 call    sub_140011366
.text:000000014001197C                 mov     dword ptr [rbp+0F0h+var_E8], 1 ; x1 = 1 放入栈中
.text:0000000140011983                 mov     dword ptr [rbp+0F0h+var_E8+4], 2 ; y1=2放入栈中
.text:000000014001198A                 mov     rcx, [rbp+0F0h+var_E8] ; 把rsp指向存放变量的栈顶
.text:000000014001198E                 call    sub_1400110E1   ; func()

dword ptr 2个字节 先把结构的成员放栈空间, 然后指针指向栈顶把指针传入call函数中 func