主要區別
1. 所有地址指針都是64位。 2. 增加和擴展新的寄存器,並兼容原32位版本的通用寄存器。 3. 原指令指針寄存器EIP擴展為RIP。
寄存器
1. 64位寄存器兼容原32位寄存器。 2. 新增加8個XMM寄存器(XMM8-XMM15)。 3. 擴展原32位寄存器的64位版本,並增加8個新的64位寄存器(R8-R15)。

// 通用寄存器: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位。所以當我們開始理解一個函數的功能時,這些信息對判斷一個函數的用途能起到關鍵作用。