【逆向】x64程序逆向基础
主要区别
1. 所有地址指针都是64位。
2. 增加和扩展新的寄存器,并兼容原32位版本的通用寄存器。
3. 原指令指针寄存器EIP扩展为RIP。
寄存器
1. 64位寄存器兼容原32位寄存器。
2. 新增加8个XMM寄存器(XMM8-XMM15)。
3. 扩展原32位寄存器的64位版本,并增加8个新的64位寄存器(R8-R15)。
![](https://img2018.cnblogs.com/common/1743055/201912/1743055-20191210014625984-677160712.jpg)
// 通用寄存器:RAX(64位),EAX(32位),AX(16位),AL(0-7位),AH(8-15位) // 新增寄存器:R8(64位),R8D(32位),R8W(16位),R8B(8位)
调用约定
1. x86使用stdcall、cdecl、Fastcall等。
2. x64使用类似“Fastcall”的调用约定。
使用RCX、RDX、R8、R9寄存器传递前4个参数,其余参数从右往左依次保存在栈上。
3. 浮点参数使用XMM寄存器传递(XMM0-XMM3)。
4. 任何在函数开头的mov指令都是在保存被传递到这个函数的参数,编译器不会再其中插入做其它事情的mov指令。
1 mov dword ptr [rsp+28h] ,6 //参数6 保存在栈中 2 mov dword ptr [rsp+20h] ,5 //参数5 保存在栈中 3 mov r9d ,4 //参数4 保存在寄存器中 4 mov r8d ,3 //参数3 保存在寄存器中 5 mov edx ,2 //参数2 保存在寄存器中 6 mov ecx ,1 //参数1 保存在寄存器中 7 call Fun //调用函数
栈使用
1. 32位代码在函数中使用push和pop等指令改变栈的大小。 2. 64位代码在函数中从不改变栈的大小,栈在函数的开始增长,期间一直保持不变,直到函数末尾。 3. 当一个函数调用另一个函数时,调用函数会多申请32字节(0x20)的预留栈空间,当被调用函数寄存器不够用时,可以将4个参数寄存器(RCX、RDX、R8、R9)中的值保存在申请的预留栈空间中。 预留栈空间由函数调用者提前申请,也由函数调用者负责平衡回收。
注意:如果一个函数有其他参数(>4个)或局部栈变量,函数会在0x20的基础上增加预留栈空间的大小,有时增加大小后的值需要与16进行对齐。
示例代码
1 #include "stdafx.h" 2 3 // Add 4 int Add(int nl, int n2, int n3, int n4, int n5, int n6) 5 { 6 return nl+n2+n3+n4+n5+n6; 7 } 8 9 // Main 10 int tmain(int argc, TCHAR* argv[]) 11 { 12 printf("%d\r\n", Add(1,2,3,4,5,6)); 13 return 0; 14 }
Main函数反汇编
1 // 保存Main函数参数到预留栈空间,此预留栈空间为其它函数调用Main函数时申请 2 mov[rsp+10h], rdx // 将参数2保存到预留栈空间中 3 mov[rsp+8h], ecx // 将参数1保存到预留栈空间中 4 5 // Main函数作为调用者申请预留栈空间,用于保存Add函数的参数 6 push rdi // 保存环境 7 sub rsp, 30h // 申请预留栈空间(Add函数6个参数)(6*8=48 0x30) 8 mov rdi, rsp // 将栈空间初始化为0xcC 9 mov ecx, 0Ch 10 mov eax, 0CCCCCCCCh 11 rep stosd 12 13 // 调用Add函数,前4个参数使用寄存器,其余参数入栈 14 mov ecx, [rsp+40h] 15 mov dword ptr [rsp+28h], 6 // 参数6入栈 16 mov dword ptr [rsp+20h], 5 // 参数5入栈 17 mov r9d, 4 // 参数4 18 mov r8d, 3 // 参数3 19 mov edx, 2 // 参数2 20 mov ecx, 1 // 参数1 21 cal1 Add // 调用Add函数 22 23 // 调用pirntf函数 24 mov edx, eax // 将返回值保存到edx中 25 lea rcx, Format // "%d\r\n" 26 cal1 printf // 调用pirntf函数 27 xor eax, eax // 设置返回值 28 29 // Main函数作为调用者释放预留栈空间 30 add rsp, 30h // 释放预留栈空间+2个参数的栈空间(Add参数5,6) 31 pop rdi // 恢复环境 32 retn // 函数返回
Add函数反汇编
1 // 保存Add函数前4个参数到预留栈空间,预留栈空间由Mian函数申请和释放 2 mov[rsp + 20h], r9d // 参数4 3 mov[rsp + 18h], r8d // 参数3 4 mov[rsp + 10h], edx // 参数2 5 mov[rsp + 08h], ecx // 参数1 6 7 // Add函数中没有调用其它函数和局部变量,所以没有申请预留栈空间 8 push rdi // 保存环境 9 mov eax, [rsp + 18h] // eax = 参数2 10 mov ecx, [rsp + 10h] // ecx = 参数1 11 add ecx, eax // ecx = 参数1+参数2 12 mov eax, ecx // eax = ecx 13 14 // 使用预留栈空间来获取Add函数参数 15 add eax, [rsp + 20h] // eax+参数3 16 add eax, [rsp + 28h] // eax+参数4 17 add eax, [rsp + 30h] // eax+参数5 18 add eax, [rsp + 38h] // eax+参数6 19 20 // 再次印证预留栈空间由调用函数(Main函数)释放 21 pop rdi // 恢复环境 22 retn // 函数返回
WOW64与重定位
1. 微软开发的一个在Win64位系统上运行的Win32位子系统(wow64),用于允许32位程序在64位机器上运行。
2. 由于Wow64使用x64处理器的32位模式来执行指令,为了不同版本的程序能正确访问系统核心组件(DLL、EXE),就需要对文件系统和注册表进行重定位处理。
文件重定位: // 32位程序重定位目录到:“\SysWOW64”。 // 64位程序重定位目录到:“\System32”。 // 32位程序通过访问:"C:\Windows\Sysnative"目录可以进入真实的\System32目录,不管是否存在重定位。 注册表重定位: // 32位程序访问注册表:“HKEY_LOCAL MACHINE\Software” // 也会被重定位到:“HKEY_LOCAL_MACHINE\Software\Wow6432Node”。 // 使用“RegCreateKeyEx”等函数可以通过参数的标志位来决定访问注册表的32位或64位版本。
3. 使用以下函数可以查询当前程序是否运行在Wow64环境中。
1 // 确定指定的进程是否在WOW64下运行 2 BOOL WINAPI IsWow64Process( 3 _In_ HANDLE hProcess, // 进程句柄 4 _Out_ PBOOL Wow64Process // Wow64下运行返回TRUE,否则FALSE 5 );
4. 使用以下函数可以禁用或开启当前线程的文件重定位。
1 Wow64DisableWow64FsRedirection // 禁用调用线程的文件系统重定向(默认开启) 2 Wow64RevertWow64FsRedirection // 为调用线程恢复文件系统重定向 3 Wow64EnableWow64FsRedirection // 为调用线程启用或禁用文件系统重定向
指针与常量数据识别
一般由编译器生成的代码,一个整数最常见的大小是32位(特殊情况下也可能是64位),而一个指针数据的大小一定是64位。所以当我们开始理解一个函数的功能时,这些信息对判断一个函数的用途能起到关键作用。