最近因爲項目組內要作特徵碼搜索的東西,便於去Hook一些未導出函數,你懂得...因而就閒着學習了一下x86/x64的彙編指令格式。x86的彙編指令格式請參照http://bbs.pediy.com/showthread.php?t=191802。總要有人來完成剩下的工做吧,這裏我就把研究一天的x64彙編指令格式共享給你們。
一.首先打開Inter手冊,看到x64彙編指令格式有多大改動,很少說,看圖。
很明顯,比x86多出了一點東西,Legacy Prefix
按功能組別,我將這個指令序列分爲 4 個部分:
Prefix
Opcode
ModRM 與 SIB
Displacement 與 Immediate
其中,只有 opcode 是必須的,其它組成部分均可選!
好好看一下這些結構
1.Prefix前綴
AMD推出 x86 擴展 64 位技術時,增長了一個用於訪問擴展的 64 位數據 prefix,它是:REX prefix,而 x86 原有的 prefix 則變爲了指令格式中的:Legacy prefix。REX prefix 僅存在於 x64 的 64-bit 模式中,在 legacy x86 模式下,REX prefix 是無效的,可是在 x64 的 64-bit 模式下 Legacy prefix 是有效的。
REX prefix 的取值範圍是:40H - 4FH。
在非 64 位模式下,它們是 inc 與 dec 指令,也就是說這些指令在 64 位模式下被重定義爲 REX prefix
關於Prefix的介紹真的是少之又少,不過我在x86/x64 指令編碼內幕(適用於 AMD/Intel)學到不少,連接以下:
http://www.mouseos.com/x64/index.html。
我這裏就簡單說一下吧,一個Legacy prefix還有一個REX prefix。這些修飾符是爲了改變缺省的寄存器,好比說在32位下mov ax,bx。那麼就要有Legacy prefix 0x66修飾,前者還有個做用就是改變當前段寄存器,很少這在目前已經顯得不怎麼重要了。到了x64的時候,因爲通用寄存器的擴展(主要緣由),本來的8個通用寄存器只要3個位來標識,可是如今多了r8~r15,16個通用寄存器怎麼辦呢,很明顯加上一位就能徹底標識了。那麼就是REX prefix的做用,看一下這個傢伙的結構嘍...
他有取值範圍,前4位必定是4,後四位可選,W,R,X,B。W標識改變默認操做數大小,好比如今x64有個彙編代碼mov r8,r10。通常不少指令都是默認32位操做數的,只有在CS.L==1&&CS.D==0的時候纔會是64位操做數(我沒見過)。因此通常都要去改變默認操做數大小,那就是0x48 (Inter手冊上常說是 REX.W 懂了要感謝我哦);其次就是R位,用來擴展 ModRM.reg 域,000 ~ 111 ---> 0 000 ~ 1 111 ,
因而如今寄存器的狀況變成了以下
X: 用來擴展 SIB.index 域;
B: 用來擴展 SIB.base, ModRM.r/m 以及 Opcode.reg。
2.Opcode(操做碼)
大多數通用指令的 Opcode 是單字節,最可能是 2 字節。可是對 x87 FPU 指令和 SSEx 等 SMID 指令來講能夠有 3 個字節的,Opcode 碼錶明着指令是作什麼操做。
這裏的Opcode並非完整的,由於他還有擴展位,就是ModR/M裏面的reg/opcode這一項,3位。好比說一個簡單的例子,x64下的絕對跳轉0xFF25 + 0x0000 + [8字節絕對地址]。咱們去搜索Inter手冊的jump指令看一下:
FF /4 JMP r/m32 M N.S. Valid.(Jump near, absolute indirect, address given in
r/m32. Not supported in 64-bit mode.)
FF /4 JMP r/m64 M Valid N.E.(Jump near, absolute indirect, RIP = 64-Bit
offset from register or memory)
其實不少人不明白0xFF25 爲何後面要加4個0x00.如今工做機上沒x64內聯彙編環境驗證不了,我理解這個實際上是一個偏移指示這條指令以後多遠的地方存放着一個64位地址,而後再jump到這個64位地址上去。這個指令相信你們常常在調導入表函數的時候可以看到。好,閒話很少說,咱們看一下,後面的這個ModR/M怎麼來的,因爲是/4,所以 擴展Opcode爲 100,並且這個jump後面跟一個當即數地址,查表能夠看到ModRM= 00 101 ---> ModR/M ---> 00 100 101。這個是多少,我不知道啦。
3.ModR/M,這個就很少說了,x86的那張指令到機器碼映射表表適用於x64。至於怎麼玩待會用例子來講明。
4.SIB & displacement。這裏我要補充下,SIB不只是當基址加變址尋址(base-plus-index)和比例尋址(scale-plus-index)的時候須要用到SIB.並且涉及到esp/rsp寄存器的時候也很大可能須要用到SIB,咱們看下x86的指令圖就知道了,沒有任何一項是涉及到esp寄存器的,咱們再看x86,SIB的圖,來自Inter手冊,看了就明白
這裏在縱行找到了ESP,可見ESP這個東西,不走尋常路!!!
displacement就不用多說了吧,好比說[rsp+0x8],那就是一個字節的8,能夠爲負數
5.immediate,這個最好理解啦,就是當即數,好比mov eax, 0x12345678.那它這個部分就是0x78 0x56 0x34 0x12 倒着寫。。。
二.實驗
用windbg隨便打開一個64位進程,找幾條彙編指令,推一下。搞簡單的吧,汗直冒撒.....
這個是32位指令,mov ebx, eax。首先去查MOV表...這裏甩出來一個帶x64的MOV指令表
then , we see ...
8B /r MOV r32,r/m32 RM Valid Valid Move r/m32 to r32.
默認是32位操做數因此不用加Prefix了,/r 標識ModR/M裏面的reg/opcode表明的是reg
從表裏看到ModR/M的值爲 11 011 000 == 0xd8(第二個寄存器優先作橫行).不帶SIB 因此最終指令爲 8B D8。
一樣是mov指令,可是是64位,mov r8,r12。首先去查MOV表... then we see...
REX.W + 8B /r MOV r64,r/m64 RM Valid N.E. Move r/m64 to r64.
其實Inter手冊已經告訴你了要加REX Prefix。並且是0x48(爲何去看上面),這裏注意了,因爲開啓了寄存器擴展位,因此這得從原來的3位變成4位(從彙編往機器碼上推的時候就沒多大必要了)
r8,r12分別爲1000和1100把第四位去掉,再從x86指令表中去找就好了。因而咱們看到ModR/M爲0xC4。所以最終結果爲 48 8B C4 。發現跟結果不同,爲何呢,REX Prefix沒寫對,其實這是由於 /r 指示ModR/M中存在擴展寄存器,因此REX.R = 1,這條指令的 ModRM.reg 提供源操做數尋址,而 ModRM.r/m 提供目標操做數尋址,目標寄存器 r8 須要 REX.B 進行擴展,它的指令編碼是:
目標操做數 r12 寄存器的編碼通過 REX.B 擴展爲 1100
因此REX Prefix爲0x4d而不是0x48。這個必定要注意...
做者:UltraCopy
*轉載請註明來自看雪論壇@PEdiy.comphp