jmp指令初一看感覺挺簡單,無條件跳轉指令。通常我們通過百度和查閱資料可以瞭解到以下知識
格式: JMP OPRD
----
功能: JMP指令將無條件地控制程序轉移到目的地址去執行.當目的地址仍在同一個代碼段內,稱爲段內轉移;當目標地址不在同一個代碼段內,則稱爲段間轉移.這兩種情況都 將產生不同的指令代碼,以便能正確地生成目的地址,在段內轉移時,指令只要能提供目的地址的段內偏移量即夠了;而在段間轉移時,指令應能提供目的地址的段地址及段內偏移地址值.
說明: 1. 其中OPRD爲轉移的目的地址.程序轉移到目的地址所指向的指令繼續往下執行.
----
2. 本組指令對標誌位無影響.
3. <1> 段內直接轉移指令: JMP NEAR 標號
即: JMP NEAR 標號; (IP)<--disp16+(IP)
JMP SHORT 標號; (IP)<--disp8+(IP)
<2> 段內間接轉移指令: JMP OPRD
例如: JMP BP ; 轉向(SS):(BP)
JMP JNEAR[BX] ; 轉向(CS):(BX)+JNEAR
JMP WORD PTR[BX][DI] ; 轉向(CS):(BX)+(DI)
<3> 段間直接轉移指令: JMP FAR 標號
由於標號之前用FAR說明爲遠的屬性,因而只能是一條段間轉移指令.執行該指令時,將把標號所在的段的值送CS,將標號在所屬段內的偏移量送IP,從而形成新的轉移地址CS:IP
<4> 段間間接轉移指令:JMP OPRD其中的OPRD爲存儲器雙字操作數.段間間接轉移只能通過存儲器操作數來實現.
例如:指令JMP DWORD PTR[BX],其操作數是一個雙字類型的存儲器操作數,它指向數據段DS,段內偏移爲(BX).從這個DS:BX開始的前兩個字節中,存放了目標地址的段內偏移值,後兩個字節中,存放了目標地址所在的新的段的段基址,分別將它們送至IP及CS,便形成了新的轉移地址.
以上站在一個開發者的角度上來講,應該知道這些足夠了,可是在做逆向分析或inline hook的時候,我們發現在這些還不夠,貌似jmp沒有那麼簡單,先看看一張截圖:
發現了點什麼了沒?jmp的機器碼爲E9,oppd = 跳轉到的地址-當前地址-5 byte
上圖中oppd = A30B0000,當前地址=00C21118,跳轉後的地址爲:00C21CC0
發現 00C21CC0 - 00C21118 - 5 = BA3,並不等於A30B0000哇。可是別忘記了機器碼是按照字節排列的,所以A30B0000實際上是 00000BA3
知道了這個規律之後,我們可以輕鬆的知道,如果oppd爲負(oppd採用補碼,即高位爲1時,也就是oppd最後倒數第二位>9),那麼會向下跳,最高位爲0的時候會向上跳。
在編程應用中,發現很多人喜歡寫成這樣的結構體就見怪不怪啦
typedef struct _JMPCODE
{
BYTE E9;
ULONGJMPADDR;
}JMPCODE,*PJMPCODE
可是爲什麼要這樣用那個公式計算出oppd呢,原來在設計jmp指令的時候,用的是偏移,跳到的地址 = 當前地址+oppd+5,那麼就很好理解了,jmp指令,其對應的機器碼中並沒有轉移的目的地址,而是相對於IP的轉移位移,而這個轉移位移在編譯時就已經由編譯器編譯完成。至於說爲什麼有個5字節,因爲jmp指令的長度有2個字節,3個字節和5個字節的說法,在這裏的段間跳轉顯然是 jmp指令長度爲5,所以轉移位移實際上應該是應該是目的地址和jmp下一條指令的位移差咯!