1. 程式人生 > >彙編指令之OpCode快速入門

彙編指令之OpCode快速入門

作者:A1Pass
時間:2010-05-20
來源:黑客反病毒 (http://bbs.hackav.com)
出處:看雪論壇    (http://bbs.pediy.com)

注意:轉載請務必附帶本組資訊,否則侵權必究!

轉載自:http://bbs.pediy.com/thread-113402.htm

    最近一直被一些初學者問及有關於彙編指令的長度問題,因此為此專門撰寫本文,以求為不知OpCode為何物,或者正為彙編長短不一的指令而煩惱的朋友一個最為快速的指引。
    其實,OpCode並不複雜,在本文中我不打算細緻入微的告訴大家OpCode的原理,不會為大家帶來一大堆有關於什麼是定長指令、什麼是變長指令的理論知識,更不會帶著各位讀者玩OpCode Hacking,我只會告訴你“怎麼了”、“為什麼”以及“如何解決”。


1、我的彙編指令怎麼了?
    ;哦,天啊!怎麼我今天突然發現彙編指令竟然是長短不一的!你還沒發現嗎?那麼請過目:
1 2 3 4 5 6 E8 31880000      CALL 00430B86
E9 17FEFFFF      JMP 00428171 8B4424 04        MOV EAX, DWORD PTR SS:[ESP+4] 85C0             TEST EAX, EAX
56               PUSH ESI 8BF1             MOV ESI, ECX

    我們可以看見“CALL 00430B86”這條彙編指令竟然佔用了5個位元組,而“PUSH ESI”則只佔用了1個位元組,彙編指令的脾氣猶如一隻滑頭的猴子一樣讓你摸不到頭腦,它很明顯的告訴了你“嘿!兄弟,你別想搞懂我!”你也許會感到很鬱悶,但是我並不這麼想,因為如果我要想自己搞一個反彙編引擎,或者是我要在我的殼裡加上程式碼混淆功能……嗯,算了,就算是我想娛樂一下搞搞免殺吧,那麼我終歸是要搞懂它的,為什麼?因為如果不能搞懂它的話,那麼我就沒辦法做到這些!
    很明顯我們的彙編指令繼承了Intel工程師的狡猾本質,為了儘可能的減少體積,所以它們的體積被設計的不盡相同。
    哇哦!很多讀者此時似乎已經想明白是怎麼回事了,肯定是不同的指令對應的位元組數不一樣,恩……這樣只要我們搞到一張表就可以了!不是嗎?一張可以描述每個指令所用二進位制碼的表格,然後我們就萬事大吉了。
    但是很不幸,我在初次接觸OpCode時也想出了這個“超級點子”,但是很可惜我的“超級點子”與各位讀者的一樣,並沒有為我解決任何問題,請過目:
1 2 3 B8 01000000   MOV EAX, 1 8BC3          MOV EAX, EBX 8BC7          MOV EAX, EDI

    看到了嗎,一樣的指令,一樣的目的運算元,得到的確是完全不同的二進位制碼……

2、這是為什麼?
    嗯,我想這個問題是很明顯的,源運算元如果是一個暫存器的話,那麼能有幾種可能呢?按照規則來講貌似只有不超過50種可能,那麼如果被運算元是一個數值呢?你想想,32位能表示多少數,將其乘以2就是最終的可能性了,這麼多的可能性一定不是區區兩個16位數就能表示過來的。
    所以說我們的OpCode的長度不是一成不變是有道理的,那麼既然如此,那麼既然CPU可以正確時識別它,這裡面肯定有什麼方法是可以計算這些的,沒錯!這些確實是可以計算的,而且正像我們上面所設想的那樣,Intel也確實為我們準備了表格,只不過不是一張,只不過有些複雜……
    首先,我們要現擁有這些,以下是我提供的一些連線,因為我們需要這些,請你下載他們:
      zip附件上傳限制1000KB,而且不能上傳分卷,所以大家還是到以下下載方便些:
http://bbs.hackav.com/thread-1641-1-1.html
      擁有了這些文件後,我們就可以開始“破譯”它了,現在加入我們要“破譯”的是“ADD EAX,1”這條指令,請各位讀者跟我一起做:
    我們先開啟《處理器指令參考》手冊(x86eas.hlp),找到彙編指令ADD,我滿看到了如下解釋:
注:前面的標號是筆者為了大家方便閱讀而加上去的。     Opcode    Instruction  Description 01  04 ib    ADD AL,imm8  Add imm8 to AL 02  05 iw    ADD AX,imm16  Add imm16 to AX 03  05 id    ADD EAX,imm32  Add imm32 to EAX 04  80 /0 ib    ADD r/m8,imm8  Add imm8 to r/m8 05  81 /0 iw    ADD r/m16,imm16  Add imm16 to r/m16 06  81 /0 id    ADD r/m32,imm32  Add imm32 to r/m32  07  83 /0 ib    ADD r/m16,imm8  Add sign-extended imm8 to r/m16 08  83 /0 ib    ADD r/m32,imm8  Add sign-extended imm8 to r/m32 09  00 /r    ADD r/m8,r8  Add r8 to r/m8 10  01 /r    ADD r/m16,r16  Add r16 to r/m16 11  01 /r    ADD r/m32,r32  Add r32 to r/m32 12  02 /r    ADD r8,r/m8  Add r/m8 to r8 13  03 /r    ADD r16,r/m16  Add r/m16 to r16 14  03 /r    ADD r32,r/m32  Add r/m32 to r32 解釋: imm是立即數的意思,而imm8就是指8個位元大小的立即數,下面將一一對上面的簡寫作出解釋 imm:立即數,例如01、123、0FAB等 r:暫存器,如r16就代表ax、cx等,r32就代表eax、ebx等 m:記憶體地址,如[01]、[123]、[0FFFF]等 r/m:暫存器或記憶體 ib:代表OpCode後面跟著一個byte型數值 iw:代表OpCode後面跟著一個word型數值 id:代表OpCode後面跟著一個dword型數值 /0:代表此OpCode存在ModR/M結構(後面有講) /r:代表此OpCode存在ModR/M結構(後面有講)

    這是什麼意思呢?我們以第一條資訊為例,它的意思是,如果OpCode的表現形式為04後面在跟一個位元組,那麼它的指令格式(Description)必然是“ADD AL,8位立即數”,例如“ADD AL,11”。
    啊哈,那麼問題到這就解決了,我們上面的“ADD EAX,1”符合第8行的“ADD r/m32,imm8”,那麼它的OpCode就應該是“83 01”了吧……
    結果估計大家已經猜到了“事情沒那麼簡單”,實際上我們的彙編指令“ADD EAX,1”所對應的OpCode是如下玩意:
1 83C0 01       ADD EAX, 1

      我們可以看到它很神奇的多出來個“C0”不知道是幹什麼的,這讓我們很鬱悶!

3、我們如何解決這個問題?
    到這裡,我們就要步入正軌了,通過這一節我們要搞明白那個“C0”究竟是怎麼出來的。
    既然要步入正軌,我們就要了解一下Intel的指令結構(在24319102.PDF的第31頁),具體情況如下:

Prefixes:字首(最多4個字首,每個1位元組,並不是必需的)
code:主操作碼(1-3位元組不等)
ModR/M:固定1位元組大小,並不是必需的
SIB:固定1位元組大小,並不是必需的
Displacement:偏移量(1、2、4位元組,並不是必需的)
Immediate:立即數(1、2、4位元組,並不是必需的)

    由上可見,其實Intel指令格式中只有一個是必須存在的,就是“主操作碼”,也就是我們在上一節查到的那堆東西。不過其他結構索然是可有可無,但是往往在某些時候它們當中的某些結構是必須新增上去的,例如上個例子中的“ADD EAX,1”就是如此。
    在我們講解Prefixes之前,首先請大家務必牢記一件事,就是OpCode的結構是絕對不能被打亂的,例如Prefixes肯定是要在code前面,而Immediate肯定是在最後面。
    好了,記住上面的基本原則後,我就為大家簡單講解一下這個字首(Prefixes)究竟做了些什麼,非要把指令結構搞得這麼複雜,我在24319102.PDF的第31頁下面找到了這些資訊:

—F0H—LOCK prefix.
—F2H—REPNE/REPNZ prefix (used only with string instructions)
—F3H—REP prefix (used only with string instructions).
—F3H—REPE/REPZ prefix (used only with string instructions).
—F3H—Streaming SIMD Extensions prefix.

    這都是什麼意思呢?我們拿第一個來說,Intel對它的解釋是鎖定字首,首先各位的組合語言要過關,所謂的鎖定就是將我們的指令變為原子指令,具體例子如下:

1 2 F0:8300 01    LOCK ADD DWORD PTR DS:[EAX], 1           ;  鎖定字首 F0:0FB10A     LOCK CMPXCHG DWORD PTR DS:[EDX], ECX     ;  鎖定字首


    這兩條指令前都多了個“LOCK”,但是請注意,這只是一個特例,並不是所有的字首都會導致彙編指令前非要加些什麼,這點一定要注意。
    Intel手冊給了我們很多其他的字首,功能也各不相同,本文中作者不可能對其一一進行解釋,因此深入的學習就要靠各位自己的努力了。
    而關於操作碼,我們在上一節中已經講了,這裡不再多說,因此直接入“ModR/M”與“SIB”中。
    關於“ModR/M”,我認為它在彙編指令中應該是最難的了(雖然只是簡單的查表,不過我說的是程式設計實現),有關於ModR/M的表格在Intel指令手冊24319102.PDF的第36頁,讀到這裡的朋友不妨先去看看。
    看完後千萬不要頭大,我們那一條指令解釋一下,就什麼都清楚了,其實很簡單的,我們仍然拿“add EAX,1”為例吧。
    我在倒數第8行找到了目的運算元,Intel在表中描述如下:

Effective Address   Mod  R/M EAX/AX/AL/MM0/XMM0  11   000


    但是我們的源運算元要怎麼找呢?上面一行似乎並沒有符合的,這就要看我們此條彙編語句在定義時指定了那裡,還記得我們在上一節中查到的資訊嗎:

83 /0 ib  ADD r/m32,imm8  Add sign-extended imm8 to r/m32

    在上一節我僅告訴各位“/0”是代表此OpCode裡存在ModR/M結構,但並沒有多說什麼,其實這裡的“/0”就是代表此表中豎排(列)中第一排,其內容如下:

r8(/r)    AL r16(/r)    AX r32(/r)    EAX mm(/r)    MM0 xmm(/r)    XMM0 /digit (Opcode)  0 REG =    000

     到這裡,其實我們的“ModR/M”已經出來了,我們將其以“Mod”“R/M”“/digit”“REG”的方式組合到一起後,正好組合為如下數值:
/digit REG Mod R/M    0     000 11  000 = 000011000 = C0h

    其實在他們的交匯處我們可以看到Intel已經幫我們算好了,真實一張貼心的表呀。
    “ModR/M”解決了,還剩最後的“SIB”了,要想學習“SIB”,我們先要搞明白他什麼時候會出現,因此我找到了第36頁下面的註釋:
NOTES: 1.The [--][--] nomenclature means a SIB follows the ModR/M byte. 2.……

    註釋一的大致意思是“[--][--]”表示ModR/M 後跟隨有一個SIB位元組,因此我們現在創造一個帶有“SIB”結構的彙編指令:
1 01048E  ADD DWORD PTR DS:[ESI+ECX*4], EAX

    我們重新回顧一下所學知識,首先我們分析它的指令格式如下:
01 /r    ADD r/m32,r32  Add r32 to r/m32

    根據“/r”我們可以得知這是一個有“ModR/M”結構的OpCode,因此查表得出其“ModR/M”資訊如下:
橫排 Effective Address  Mod  R/M [--][--]            00  100 豎列 r8(/r)    AL r16(/r)    AX r32(/r)    EAX mm(/r)    MM0 xmm(/r)    XMM0 /digit (Opcode)  0 REG =    000 結果 /digit REG Mod R/M   0      000 00  100 = 000000100 = 04h

    根據“Effective Address”的“[--][--]”可知此OpCode還存在“SIB”結構,於是繼續查位於Intel指令手冊24319102.PDF第37頁的表格。
    這裡我們要著重分解目的運算元“[ESI+ECX*4]”裡的內容,我們可以將其分為兩部分,既索引與倍率因子(或叫做比率因子)。
    索引指的是基址,本例中就是ESI了,而倍率因子在本例中則是“ECX*4”,我們先從橫排取得倍率因子資訊如下:
Scaled Index  SS  Index [ECX*4]       10  001     而後由豎排取得索引資訊如下: r32  ESI Base=  6 Base=  110     將其組合起來就是: SS  Index  Base 10  001    110  = 10001110 = 8Eh

    由此,我們便成功的解析了彙編指令“ADD DWORD PTR DS:[ESI+ECX*4], EAX”。
    到了這裡本文也該結束了,但是各位讀者需要注意的是,本文的責任就像是標題所體現的一樣,只是帶領大家快速入門,因此有關於很多OpCode的細節本文並沒有體現出來,如果你需要深入瞭解的話,建議各位還是以Intel手冊為藍本手動試驗,慢慢摸索。