2011-08-03 54 views
4

我打算編寫自己的小反彙編程序。我想解讀讀取可執行文件時得到的操作碼。我看到下面的操作碼:在操作碼指令中查找操作數的數量

69 62 2f 6c 64 2d 6c 

必須符合下列條件:

imul $0x6c2d646c,0x2f(%edx),%esp 

現在,「IMUL」指令可以有兩個或三個操作數。我如何從我在那裏的操作碼中弄清楚這一點?

它基於英特爾的i386指令集。

感謝和問候,
Hrishikesh穆拉利

+0

反彙編程序的指令集? –

+2

查看英特爾的手冊? – jalf

+0

哦,對不起,我忘了提。英特爾的i386指令集。我會馬上添加它。 –

回答

2

的手冊都描述瞭如何一個,兩個或三個操作版本之間差異。

IMUL instruction

F6/F7:一個操作數; 0F AF:兩個操作數; 6B/69:三個操作數。

+0

謝謝,我完全忽略了這張桌子。 :-( –

+0

)如何從0x69操作碼的32位立即數中解碼16位立即事件? –

+2

@dwlech:根據當前的默認操作數大小(16位或32位),操作碼前綴爲0x66 '(操作數大小覆蓋前綴),例如在32位模式下,'69 ..'表示'IMUL r32,r/m32',而'66 69 ..表示'IMUL r16,r/m16'。 – user786653

0

如果你不喜歡在操作碼錶/手冊中移動,它總是有助於從其他項目學習,如開源反彙編程序bea-engine,你可能會發現你甚至不需要創建自己的項目現在,取決於你做什麼。

+1

通過編程一個反彙編程序來強化組裝的基本原理。:-) –

+0

@Hrishikesh Murali:我想說你會更好地編寫彙編程序,IMO反彙編程序更適合那些想寫代碼生成器/ JIT's。但祝你好運不管:) – Necrolis

2

一些建議,首先獲取您可以獲得的所有指令集文檔。對於這個x86的情況,請嘗試一些舊的8088/86手冊以及更新的,來自英特爾以及網絡上的大量操作碼錶。各種解釋和文檔可能首先會有細微的文檔錯誤或差異,其次,一些人可能會以不同的和更容易理解的方式呈現信息。其次,如果這是你的第一個反彙編程序,我建議避免x86,這是非常困難的。由於您的問題意味着可變字長指令集很難實現,所以要成功實現遠程反彙編,您需要按照執行順序執行代碼,而不是內存順序。所以你的反彙編器不得不使用某種方案來解碼和打印指令,而是解碼跳轉指令並將目標地址標記爲指令中的入口點。例如ARM,是固定的指令長度,你可以編寫一個ARM反彙編程序,從ram的開始處開始並直接分解每個單詞(假設它不是手臂和拇指代碼的混合體)。由於只有一個32位指令的味道,其他的都是16位,並且一個風味可以在簡單的狀態機中處理,因爲這兩個16位指令顯示爲成對,所以可以拆卸拇指(不是thumb2)。

你不能夠反彙編所有的東西(使用可變長度的指令集),並且由於某些手工編碼或有意識的策略的細微差別,以防止反彙編你的執行順序中的代碼的前端代碼可能有什麼我會稱之爲碰撞,例如您上面的說明。假設一條路徑將您帶入指令的入口點0x69,並且您從中確定這是一條7字節的指令,但是在別的地方有一條分支指令,其目的地計算爲0x2f是指令的操作碼,儘管非常聰明的編程可能會導致類似的問題,反彙編程序更有可能導致反彙編數據。例如

clear condition flag 
branch if condition flag clear 
data 

反彙編器不會知道該數據是數據,並且無需額外的聰明的反彙編不會意識到的是,條件分支實際上是一個無條件分支(有可能是在清晰的條件之間的不同分支路徑許多指令如果條件清除,則分支),因此假設條件分支是指令後的字節。我經常鼓吹編寫簡單的反彙編程序(假設代碼很短,故意製作代碼)來學習指令集。如果你不把反彙編程序放到需要按照執行順序執行的情況下,而是可以按內存順序進行(基本上不要在指令之間嵌入數據,而是把它放在最後或其他地方,只留下一串指令被拆卸)。理解指令集的操作碼解碼可以使您在針對低級別和高級語言的平臺進行編程時更好。

簡短的回答,英特爾用來發布,也許還有,處理器的技術參考手冊,我還有我的8088/86手冊,一個用於電子產品的硬件和一個用於指令集和軟件的手冊怎麼運行的。我有一個486可能是386。伊戈爾答案中的快照直接類似於英特爾手冊。因爲隨着時間的推移,指令集發展得如此之快,使得x86成爲一個很好的野獸。同時,如果處理器本身可以通過這些字節執行並執行它們,那麼可以編寫一個可以做同樣的事情但解碼它們的程序。不同之處在於你很可能不會創建一個模擬器和任何由代碼計算出來的分支,而且在代碼中沒有明確指出你將無法看到,並且該分支的目標可能不會顯示在你的字節列表中拆卸。

+0

非常感謝,我會盡力弄清楚,儘可能地嘗試完成我的反彙編。 –

1

這不是一個機器碼指令(它將包含一個操作碼和零個或多個操作數)。

這是一個文本字符串的一部分,其意爲:

$ echo -e "\x69\x62\x2f\x6c\x64\x2d\x6c" 
ib/ld-l 

這顯然是字符串"/lib/ld-linux.so.2"的一部分。

+0

你可能是對的。但它仍然是一個解釋的問題。字節只是字節。由您(或CPU)來正確解釋它們。我猜測OP只是試圖反彙編一些隨機數據,看看他的反彙編器會顯示什麼,然後弄清楚爲什麼是這樣。這是瞭解反彙編程序如何工作以及如何編碼指令的好方法。所以儘管如此,還是值得一試。 – SasQ

+0

一旦我嘗試對指令編碼格式進行反向工程,我只寫了一個簡單的腳本來用隨後的數字填充文件,以查看反彙編程序將如何解釋它們。隨後的數字是一種合理的方法,因爲那樣你可以看到反彙編指令中的位序列變化是如何反映的,並且很可能只有一小部分指令會改變。例如。試着用'88 C0'到'88 CF',然後'89 C0'到'89 CF',你會發現寄存器尋址模式。 – SasQ

2

儘管x86指令集相當複雜(無論如何都是CISC),我看到很多人在這裏試圖理解它,但我會說相反:它仍然可以被理解,並且你可以學習在路上爲什麼是如此複雜,以及英特爾如何設法將其從8086擴展到現代處理器的幾倍。

x86指令使用可變長度編碼,因此它們可以由多個字節組成。每個字節用於編碼不同的內容,其中一些是可選的(它在操作碼中編碼,不管這些可選字段是否被使用)。

例如,每個操作碼之前可以有0到4個前綴字節,它們是可選的。通常你不需要擔心它們。它們用於改變操作數的大小,或用現代CPU(MMX,SSE等)的擴展指令將操作數的大小或轉義代碼更改爲操作碼錶的「第二層」。

然後是實際的操作碼,通常是一個字節,但擴展指令最多可以有三個字節。如果僅使用基本指令集,則不需要擔心它們。

接下來,有所謂的ModR/M字節(有時也稱爲mode-reg-reg/mem),它編碼尋址模式和操作數類型。它只能被操作碼使用,其中有任何這樣的操作數。它有三個比特字段:

  • 前兩個比特(從左邊,最顯著的)編碼的尋址模式(4個可能的比特組合)。
  • 接下來的三位對第一個寄存器進行編碼(8個可能的位組合)。
  • 最後三位可以編碼另一個寄存器,或者擴展尋址模式,具體取決於前兩位的設置。

ModR/M字節後,有可能是另一種可選的字節(取決於尋址模式)稱爲SIBS卡爾I ndex B ASE)。它用於更奇特的尋址模式,以對比例因子(1x,2x,4x),基地址/寄存器和使用的索引寄存器進行編碼。它具有與ModR/M字節相似的佈局,但左側(最高位)的前兩位用於對比例進行編碼,而後三位和最後三位對索引和基址寄存器進行編碼,如名稱所示。

如果有任何位移使用,它會在那之後。它可以是0,1,2或4個字節長,具體取決於尋址模式和執行模式(16位/ 32位/ 64位)。

最後一個總是即時數據,如果有的話。它也可以是0,1,2或4個字節。

所以現在,當你知道x86指令的整體格式時,你只需要知道所有這些字節的編碼是什麼。並有一些模式,違背了共同的信念。

例如,所有的寄存器編碼遵循整潔的模式ACDB。即,對於8位指令,寄存器代碼編碼的最低兩個比特的A,C,d和B寄存器,相應地:

00 = A寄存器(累加器)
01 = C寄存器(計數器)
10 = D寄存器(數據)
11 = B寄存器(鹼)

我懷疑的8位處理器使用只是這些4個8位寄存器編碼這種方式:

 second 
     +---+---+ 
f  | 0 | 1 |   00 = A 
i +---+---+---+   01 = C 
r | 0 | A : C |   10 = D 
s +---+ - + - +   11 = B 
t | 1 | D : B | 
    +---+---+---+ 

然後,在16位處理器,它們增加了一倍寄存器的這家銀行,並在登記編碼增加了一個位,以選擇銀行,這樣一來:

 second    second   0 00 = AL 
     +----+----+   +----+----+  0 01 = CL 
f  | 0 | 1 |  f  | 0 | 1 |  0 10 = DL 
i +---+----+----+  i +---+----+----+  0 11 = BL 
r | 0 | AL : CL |  r | 0 | AH : CH | 
s +---+ - -+ - -+  s +---+ - -+ - -+  1 00 = AH 
t | 1 | DL : BL |  t | 1 | DH : BH |  1 01 = CH 
    +---+---+-----+  +---+----+----+  1 10 = DH 
    0 = BANK L    1 = BANK H  1 11 = BH 

但現在你也可以選擇將這兩個寄存器的一半用作完整的16位寄存器。這是通過操作碼的最後一位(最低有效位,最右邊一位)的完成的:如果它是0,則這是一個8位指令。但是,如果該位置位(即操作碼是奇數),則這是一個16位指令。在這種模式下,如前所述,兩位對ACDB寄存器之一進行編碼。圖案保持不變。但他們現在編碼完整的16位寄存器。但是當第三個字節(最高位)也被設置時,它們切換到另一個稱爲索引/指針寄存器的寄存器組,它們是:SP(堆棧指針),BP(基址指針),SI(源索引) ,DI(目標/數據索引)。所以尋址如下:

 second    second   0 00 = AX 
     +----+----+   +----+----+  0 01 = CX 
f  | 0 | 1 |  f  | 0 | 1 |  0 10 = DX 
i +---+----+----+  i +---+----+----+  0 11 = BX 
r | 0 | AX : CX |  r | 0 | SP : BP | 
s +---+ - -+ - -+  s +---+ - -+ - -+  1 00 = SP 
t | 1 | DX : BX |  t | 1 | SI : DI |  1 01 = BP 
    +---+----+----+  +---+----+----+  1 10 = SI 
    0 = BANK OF   1 = BANK OF  1 11 = DI 
    GENERAL-PURPOSE  POINTER/INDEX 
    REGISTERS    REGISTERS 

當引入32位CPU時,他們再次使這些庫加倍。但模式保持不變。就在現在,奇數操作碼意味着32位寄存器和偶數操作碼,如前所述,是8位寄存器。我會將奇數操作碼稱爲「長」版本,因爲根據CPU及其當前的操作模式使用16/32位版本。當它工作在16位模式時,奇數(「長」)操作碼意味着16位寄存器,但是當它工作在32位模式時,奇數(「長」)操作碼意味着32位寄存器。可以通過在整個指令前加上前綴66(操作數大小覆蓋)來翻轉它。偶數操作碼(「短」)總是8位。因此,在32位CPU,寄存器代碼:

0 00 = EAX  1 00 = ESP 
0 01 = ECX  1 01 = EBP 
0 10 = EDX  1 10 = ESI 
0 11 = EBX  1 11 = EDI 

正如你所看到的,ACDB模式保持不變。 SP,BP,SI,SI模式也保持不變。它只是使用較長版本的寄存器。

操作碼中也有一些模式。其中之一我已經描述過(偶數與奇數= 8位「短」與16/32位「長」的東西)。更多的人可以在這個操作碼映射中看到我爲快速參考和手動組裝/拆卸而創建的一次映射: enter image description here (這不是一個完整的表格,有些操作碼缺失,也許我會更新它)

正如你所看到的,算術邏輯指令&大多位於表格的上半部分,左邊的&右邊的一半遵循相似的佈局。數據移動指令位於下半部分。所有分支指令(條件跳轉)位於第7*行。還有一個滿行B*mov指令保留,這是將立即數(常量)加載到寄存器的簡寫形式。它們都是一個字節的操作碼,緊接着是立即數,因爲它們將操作碼中的目標寄存器編碼(它們由該表中的列號選擇),三個最低有效字節(最右邊的字節) 。它們遵循相同的寄存器編碼模式。第四位是選擇一個的「短」/「長」。 您可以看到您的imul指令在表格中是完好的,正好在69的位置(hu ..; J)。

對於許多指令而言,「短/長」位之前的位是對操作數的順序進行編碼:在ModR/M字節中編碼的兩個寄存器中的哪一個是源,哪一個是目的地這適用於具有兩個寄存器操作數的指令)。

至於ModR/M字節的尋址模式字段,這裏是如何解釋它:

  • 11是最簡單的:它編碼登記到寄存器傳輸。一個寄存器由接下來的三個位(reg字段)編碼,另一個寄存器由該字節的另外三個位(R/M字段)編碼。
  • 01表示在該字節之後,會出現一個字節的位移。
  • 10意味着相同,但是使用的位移是四字節(在32位CPU上)。
  • 00是最棘手的:它意味着間接尋址或簡單位移,具體取決於R/M字段的內容。

如果SIB字節存在時,它是通過在R/M比特100比特模式信號發送。還有一個代碼爲101的32位全位移模式,它根本不使用SIB字節。

這裏是所有這些尋址模式的總結:

Mod R/M 
11 rrr = register-register (one encoded in `R/M` bits, the other one in `reg` bits). 
00 rrr = [ register ]  (except SP and BP, which are encoded in `SIB` byte) 
00 100 = SIB byte present 
00 101 = 32-bit displacement only (no `SIB` byte required) 
01 rrr = [ rrr + disp8 ] (8-bit displacement after the `ModR/M` byte) 
01 100 = SIB + disp8 
10 rrr = [ rrr + disp32 ] (except SP, which means that the `SIB` byte is used) 
10 100 = SIB + disp32 

現在讓我們進行解碼您imul

69是它的操作碼。它編碼imul的版本,該版本不簽名擴展8位操作數。 6B版本確實對它們進行了簽名擴展。 (如果有人問,它們在操作碼中的位1不同)。

62RegR/M字節。在二進制文件中,它是0110 001001 100 010。前兩個字節(Mod字段)表示間接尋址模式,位移將爲8位。接下來的三位(reg字段)爲100,並將SP寄存器(在這種情況下爲ESP,因爲我們處於32位模式)編碼爲目標寄存器。最後三位是R/M字段,我們在那裏有010,它將D寄存器(在這種情況下爲EDX)編碼爲使用的其他(源)寄存器。

現在我們預計會有一個8位的位移。那裏是:2f是位移,一個正數(十進制數字+47)。

最後一部分是imul指令要求的立即數常量的四個字節。在你的情況下,這是6c 64 2d 6c在little-endian是$6c2d646c

這就是cookie崩潰的方式; -J