x64逆向技术
1. 寄存器
64位寄存大小扩展64位,数量增加8个(R8-R15),扩充8个128位的xmm寄存器, 64位兼容32应用程序,64程序用rax, 32位程序用eax底32位, x64高位访问通过WORD byte DWORD
来进行访问。
1.1 函数
栈平衡
- 栈的特征是先进后出,RSP是栈顶指针,x86栈大小是4字节x64是8字节,push是进行压栈pop是出栈
- 栈中存储的数据主要包括 局部变量,函数参数,函数返回,函数的返回地址。
- 函数调用完需要释放掉申请的栈空间,保证栈空间与函数调用钱一致,这个过程叫做栈平衡。
启动函数
想要找打x64main函数可以通过
- start 里面找到 jmp __timainCRTStartup
- 找到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
...