20155236 《信息安全系統設計基礎》第十四周學習總
20145221 《信息安全系統設計基礎》第5周學習總結
程序的機器級表示
歷史觀點
- Intel處理器系列:俗稱x86,開始時是第一代單芯片、16位微處理器之一。
- DOS時代的平坦模式,不區分用戶空間和內核空間,很不安全;
- 8086的分段模式;
- IA32的帶保護模式的平坦模式
- 每個後繼處理器的設計都是後向兼容的,可以保證較早版本上編譯的代碼在較新的處理器上運行。
程序編碼
- GCC將源代碼轉化為可執行代碼的步驟:
- C預處理器——擴展源代碼-生成.i文件
- 編譯器——產生兩個源代碼的匯編代碼-——生成.s文件
- 匯編器——將匯編代碼轉化成二進制目標代碼——生成.o文件
- 鏈接器——產生可執行代碼文件
- 兩種抽象:
- 指令集結構ISA:是機器級程序的格式和行為,定義了處理器狀態、指令的格式,以及每條指令對狀態的影響。
- 機器級程序使用的存儲器地址是虛擬地址,看上去是一個非常大的字節數組,實際上是將多個硬件存儲器和操作系統軟件組合起來。
- C預處理器插入宏和頭文件:
gcc -E xxx.c -o xxx.i
編譯器產生源代碼的匯編代碼:gcc -S xxx.i -o xxx.s
匯編器化成二進制目標代碼:gcc -c xxx.s -o xxx.o
鏈接器生成最終可執行文件:gcc xxx. -o xxx
用objdump -d xxx.o -o
反匯編
3.3數據格式
- 大多數GCC生成的匯編代碼指令都有一個字符後綴,表明操作數的大小。
- C語言數據類型在X86-64中的大小。在64位機器中,指針長8字節。
C聲明 | Intel數據類型 | 匯編代碼後綴 | 大小(字節) |
---|---|---|---|
char | 字節 | b | 1 |
short | 字 | w | 2 |
int | 雙字 | l | 4 |
long | 四字 | q | 8 |
char* | 四字 | q | 8 |
float | 單精度 | s | 4 |
double | 雙精度 | l | 8 |
3.4訪問信息
寄存器
一個IA32中央處理單元(CPU)包含一組8個存儲32位值的寄存器。用來存儲整數數據和指針。
%eax %ax (%ah %al) 通用寄存器 %ecx %cx (%ch %cl) 通用寄存器 %edx %dx (%dh %dl) 通用寄存器 %ebx %bx (%bh %bl) 通用寄存器 %esi %si 用來操縱數組 %edi %di 用來操縱數組 %esp %sp 操縱棧幀 %ebp %bp 操縱棧幀
- 對於整數寄存器
- esi edi可以用來操縱數組
- esp ebp用來操縱棧幀
32位的eax,16位的ax,8位的ah,al都是獨立的
操作數格式(s=1, 2, 4, 8)
格式 | 操作數值 |
---|---|
Imm(rb, ri, s) | M[Imm+R[rb]+R[ri]·s] |
- MOV相當於賦值
- 不能從內存直接MOV到另一個內存,要用寄存器中轉
MOV指令示例(源操作數,目的操作數)
- MOV指令大小匹配,MOVZ和MOVS將較小的源值復制到較大的目的
MOVZ類將目的中剩余的字節填充為0
MOVS類通過符號擴展來填充,把源操作數的最高位進行賦值
註意
對於32位的eax,16位的ax,8位的ah,al都是獨立的,我們通過下面例子說明:
假定當前是32位x86機器,eax寄存器的值為0x8226,執行完addw $0x8266 ,%ax指令後eax的值是多少? 解析:0x8226+0x826=0x1044c, ax是16位寄存器,出現溢出,最高位的1會 丟掉,剩下0x44c,不要以為eax是32位的不會發生溢出.
尋址方式
- 根據操作數的不同類型,尋址方式可分為以下三種:
- 立即數尋址方式:操作數為常數值,寫作$後加一個整數。
- 寄存器尋址方式:操作數為某個寄存器中的內容。
- 存儲器尋址方式:根據計算出來的地址訪問某個存儲器的位置。
尋址模式:一個立即數偏移Imm,一個基址寄存器Eb,一個變址寄存器Ei,一個比例因子s(必須為1,2,4,8)有效地址計算為:
Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s
數據傳送指令
- MOV相當於C語言的賦值‘=‘
mov S,D S中的字節傳送到D中
註意
- ATT格式中的方向;
- 不能從內存地址直接MOV到另一個內存地址,要用寄存器中轉一下;
- 區分MOV,MOVS(符號擴展),MOVZ(零擴展)
push和pop:
pushl S R[%esp] ← R[%esp]-4 M[R[%esp]] ← S popl D D ← M[R[%esp]] R[%esp] ← R[%esp]+4
註意
- 棧頂元素的地址是所有棧中元素地址中最低的,後進先出;
- 指針就是地址;局部變量保存在寄存器中。
算術和邏輯操作
加載有效地址
- leal,從存儲器讀數據到寄存器,而從存儲器引用的過程實際上是將有效地址寫入到目的操作數。
目的操作數必須是一個寄存器。
一元操作和二元操作
- 一元操作:只有一個操作數,既是源又是目的,可以是一個寄存器或者存儲器。
二元操作:第二個操作數既是源又是目的,兩個操作數不能同時是存儲器。
移位
- 先給出位移量,然後是位移的數值,可進行算數和邏輯右移。
移位操作移位量可以是立即數或%cl中的數。
控制
條件碼
描述最近的算數或者邏輯操作的屬性,可以檢測這些寄存器來執行條件分支指令。
- CF:進位標誌,最近操作使高位產生進位,用來檢測無符號操作數的溢出
- ZF:零標誌,最近操作得出的結果為0
- SF:符號標誌,最近操作得到的結果為負數
OF:溢出標誌,最近操作導致一個補碼溢出-正溢出或負溢出。
註意
- leal不改變條件碼寄存器
- CMP與SUB的區別:CMP也是根據兩個操作數之差設置條件碼,但只設置條件碼而不更新目標寄存器
有條件跳轉的條件看狀態寄存器(教材上叫條件碼寄存器)
訪問條件碼的讀取方式
- 根據條件碼的某個組合,將一個字節設置成0或1;
- 跳轉到程序某個其他的部分;
- 有條件的傳送數據。
SET指令根據t=a-b的結果設置條件碼
跳轉指令及其編碼
- 控制中最核心的是跳轉語句:
- 有條件跳轉(實現if,switch,while,for)
- 無條件跳轉jmp(實現goto)
當執行PC相關的尋址時,程序計數器的值是跳轉指令後面那條指令的地址,而不是跳轉指令本身的地址。
翻譯條件分支
- 將條件和表達式從C語言翻譯成機器代碼,最常用的方式是結合有條件和無條件跳轉。
C語言中if-else語句的通用形式:
if(test-expr) then-statement else else-statement
匯編結構:
t=test-expr; if!(t) goto false; then-statement goto done; false: else-statement done:
循環
- do-while循環
C語言中do-while語句的通用形式:
do body-statement while(test-expr);
匯編結構:
loop: body-statement t=test-expr; if(t) goto loop;
- while循環
C語言中while語句的通用形式:
while(test-expr) body-statement
匯編結構:
t=test-expr; if(!t) goto done; loop: body-statement t=test-expr; if(t) goto loop; done:
- for循環
C語言中for語句的通用形式:
for(init-expr;test-expr;update-expr) body-statement
匯編結構
init-expr t=test-expr; if(!t) goto done; loop: body-statement update-expr; t=test-expr; if(t) goto loop; done:
- switch語句
- 根據一個整數索引值進行多重分支,執行switch語句的關鍵步驟是通過跳轉表來訪問代碼位置,使結構更加高效。
過程
- 數據傳遞、局部變量的分配和釋放通過操縱程序棧來實現。
C語言過程調用機制使用了棧數據結構提供的後進先出的內存管理原則。
- 函數調用的棧幀結構用來傳遞參數、存儲返回地址、保存寄存器及本地存儲
P(調用者)調用Q(被調用者),則Q的參數放在P的棧幀中。調用Q是P的返回地址被壓入棧中,形成P的棧幀的末尾。返回地址是程序從Q返回時應該繼續執行的地方。Q的棧幀從保存的幀指針如%ebp開始,後面是保存的寄存器值。
棧幀結構
- 為單個過程分配的棧叫做棧幀,寄存器%ebp為幀指針,而寄存器指針%esp為棧指針,程序執行時棧指針移動,大多數信息的訪問都是相對於幀指針。
棧向低地址方向增長,而棧指針%esp指向棧頂元素。
轉移控制
- call:目標是指明被調用過程起始的指令地址,效果是將返回地址入棧,並跳轉到被調用過程的起始處。
- ret:從棧中彈出地址,並跳轉到這個位置。
- 函數返回值存在%eax中
call和ret指令的一般形式:
指令 | 描述 |
---|---|
call Label | 過程調用 |
call **Operand* | 過程調用 |
ret | 從過程調用中返回 |
尋址方式
- 根據操作數的不同類型,尋址方式可分為以下三種:
- 立即數尋址方式:操作數為常數值,寫作$後加一個整數。
- 寄存器尋址方式:操作數為某個寄存器中的內容。
- 存儲器尋址方式:根據計算出來的地址訪問某個存儲器的位置。
尋址模式:一個立即數偏移Imm,一個基址寄存器Eb,一個變址寄存器Ei,一個比例因子s(必須為1,2,4,8)有效地址計算為:
Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s
數據傳送指令
- MOV相當於C語言的賦值‘=‘
mov S,D S中的字節傳送到D中
機器級代碼
- 指令集結構ISA
- 是機器級程序的格式和行為,定義了處理器狀態、指令的格式,以及每條指令對狀態的影響
- 機器級程序使用的存儲器地址是虛擬地址
- 看上去是一個非常大的字節數組,實際上是將多個硬件存儲器和操作系統軟件組合起來
- 一些通常對C語言程序員隱藏的機器代碼在IA32中是可見的:
- 程序計數器(在IA32中,通常稱為“PC”,用%eip表示)指示將要執行的下一條指令在存儲器中的地址。
- 整數寄存器:包含8個命名的位置,分別存儲32位的數值,這些寄存器可以存儲地址(對應C語言的指針)或整數數據,有的寄存器被用來記錄某些重要的程序狀態,其他的寄存器用來保存臨時數據,例如過程的局部變量和函數的返回值。
- 條碼寄存器:保存著最近執行的算術或邏輯指令的狀態信息,他們用來實現控制或數據流中的條件變化。
- 浮點寄存器:一組浮點寄存器存放浮點數據
- 一條機器指令只執行一個非常基本的操作。機器代碼只是簡單地將存儲器看成一個很大的、按字節尋址的數組。
代碼示例
- 課本107頁代碼如下
int accum = 0;
int sum(int x, int y)
{
int t = x + y;
accum += t;
return t;
}
- 反匯編結果
gcc -c code.c
: - 匯編之後的代碼
gcc -S code.c
(已刪除“.”後的部分): - 前兩條:
- pushl %ebp 將寄存器%ebp的內容壓入程序棧
- movl %esp,%ebp 得到新棧低,將當前棧頂賦予棧低
- 後兩條:
- popl %ebp過程調用結束,恢復舊棧低
- ret 子程序的返回指令
- 二進制文件可以用
od
命令查看,也可以用gdb的x命令查看。有些輸出內容過多,可以使用more或less命令結合管道查看,也可以使用輸出重定向來查看。od code.o | more
od code.o > code.txt
數據格式
C聲明 | 匯編代碼後綴 | 大小(字節) |
---|---|---|
char | b- 字節 | 1 |
short | w- 字 | 2 |
int | l- 雙字 | 4 |
long | l- 雙字 | 4 |
long long int | - | 8 |
char * | l- 雙字 | 4 |
float | s- 單精度 | 4 |
double | l- 雙精度 | 8 |
long double | t- 擴展精度 | 10/12 |
- IA32不支持64位整數運算
- 大多數GCC生成的匯編代碼指令都有一個字符後綴,表明操作數的大小。
訪問信息
- 一個IA32的中央處理器單元包含一組8個存儲32位數值的寄存器。所有八個寄存器都可以作為16位(字)或32位(雙字)來訪問:
- %esi,%edi可以用來操縱數組
- %esp,%ebp用來操縱棧幀
- 64位的%rax,32位的%eax,16位的%ax,8位的%ah,%al都是獨立的
- 操作數三種類型:
- 立即數,即常數值
- 寄存器,表示某個寄存器的內容
- 存儲器,根據計算出來的地址(有效地址)訪問某個存儲器位置
有效地址的計算方式:
Imm(Eb,Ei,s) = Imm + R[Eb] + R[Ei]*s
控制
設置條件碼
- 有兩類指令(有8、16和32位形式),它們只設置條件碼而不改變任何其他寄存器
CMP
指令根據它們的兩個操作數之差來設置條件碼TEST
指令的行為與AND
指令一樣,除了它們只設置條件碼而不改變目的寄存器的值。典型的用法是,兩個操作數是一樣的,或其中的一個操作數是一個掩碼,用來指示哪些位應該被測試。
跳轉指令及其編碼
- JUMP指令,同樣是匯編中常用的指令,根據不同的條件和符號位進行不同的跳轉動作,具體見書128頁。
- 跳轉指令有幾種不同的編碼,最常用的是PC(程序計數器)相關的。
- 需要註意的是,jump分為直接跳轉和間接跳轉:
- 直接跳轉:後面跟標號作為跳轉目標
- 間接跳轉:*後面跟一個操作數指示符
- 當執行與PC相關的尋址時,程序計數器的值是跳轉指令後面的那條指令的地址,而不是跳轉指令本身的地址。
過程
棧幀結構:
- IA32程序用程序棧來支持過程調用。它包括將數據(參數和返回值)和控制從代碼的一部分傳到另一部分,另外還包括進入時為過程的局部變量分配空間,並在退出時釋放空間。一般地,機器只提供轉移控制到過程和從過程中轉移出控制的簡單指令,數據傳遞、局部變量的分配和釋放必然通過程序棧實現。
- 機器用棧來傳遞過程參數、存儲返回信息、保存寄存器用於以後恢復,以及本地存儲。為單個過程分配的那部分棧稱為棧幀。最頂端的棧幀以兩個指針界定,寄存器%ebp為幀指針,寄存器%esp為棧指針。
- 棧指針%esp可以移動,幀指針%esp不變化,因而大多數信息的訪問都是相對於幀指針的。如下圖所示:
- 支持過程調用和返回的指令
leave指令:為返回準備棧,它等價於如下代碼:
movl %ebp,%esp popl %ebp //恢復已保存的%ebp寄存器
ret指令:從過程調用中返回。指的是從棧中彈出地址,並跳轉到這個位置
- 寄存器使用慣例
- 調用者調用被調用者時,要求被調用者不能覆蓋某個調用者稍後會使用的寄存器值。根據慣例,寄存器%eax、%edx、%ecx稱為調用者保存寄存器。P調用Q時,Q可以覆蓋這些寄存器而不會破壞任何P需要的數據(因為會恢復)。另一方面,寄存器%ebx、%esi等被劃分為被調用者保存寄存器。要求Q必須在覆蓋這些寄存器值之前,先把它們保存到棧中,並在返回前恢復它們。
轉移控制:
- call指令:call指令有一個目標,即指明被調用過程起始的指令地址; call指令的效果是將返回地址入棧。並跳轉到被調用過程的起始處。
- ret指令:ret指從棧中彈出地址,並跳轉到這個位置;ret指令返回到call指令後的那條指令。
- leave指令:leave指令可以使棧做好返回的準備
- 寄存器使用慣例:%eax,%edx,%ecx 調用者保存寄存器;%ebx,%esi,%edi 被調用者保存寄存器;%ebp,%esp 保持寄存器。
- 保存某值的兩種方式:由調用者保存,在調用之前就壓進棧;由被調用者保存,在剛被調用的時候就壓進棧,並在返回之前恢復。
函數調用棧信息的GDB命令
- backtrace/bt n
- n是一個正整數,表示只打印棧頂上n層的棧信息。
- n表一個負整數,表示只打印棧底下n層的棧信息。
- frame n
- n是一個從0開始的整數,是棧中的層編號。比如:frame 0,表示棧頂,frame 1,表示棧的第二層。
- 這個指令的意思是移動到n指定的棧幀中去,並打印選中的棧的信息。如果沒有n,則打印當前幀的信息。
- up n
- 表示向棧的上面移動n層,可以不打n,表示向上移動一層。
- down n
- 表示向棧的下面移動n層,可以不打n,表示向下移動一層。
教材學習中的問題和解決過程
逆向工程的簡單運用
- 分析如下:
- A題:數組本身其實沒有二維三維之說,任何數組都可以說是線性結構,可以看成是從數組首位開始往後的線性排布,例如對於
A[R][S][T],A[i][j][k] 的位置是 A(,i*S*T+j*T+k,4)
。(A數組是int型所以占4個字節)。第一題,較為簡單,暫時不涉及逆向工程。但這一題是求解第二題的關鍵所在 - B題:題目要求運用逆向工程技術,根據匯編代碼,確定R、S和T的值。
- 首先,我們要根據匯編代碼來求解此題,所以看懂匯編代碼是關鍵。
其次,我們通過
movl $2772, %eax
可以得出,數組A占有R*S*T*4 = 2772
個字節,這是第一個等式。movl 8(%ebp), %ecx ;get i ... movl %ecx, %edx ;i -> %edx sall $6, %edx ;i^6即64*i subl %ecx, %edx ;64i-i=63i
上述幾行匯編代碼,因為我已經註釋了,結合第一題答案,可以清楚的發現第二個等式:
S*T = 63
movl 12(%ebp), %eax ;get j leal (%eax, %eax, 8), %eax ;8*j+j -> j,最後 %eax = 9*j
- 上述幾行匯編代碼,因為我已經註釋了,結合第一題答案,可以清楚的發現第三個等式:
T = 9
綜上,三個等式,可以解得該題的結果為:
R=11, S=7, T=9
其他(感悟、思考等,可選)
- 首先,我覺得本周的學習任務是對上學期匯編的一個鞏固,在原有我們所學的8086匯編知識的基礎上得到了拓寬,這次我們所學的匯編主要是在32位機器上的。在寄存器個數沒有變化的前提下,所能支持的位數翻了一倍,引入了
%eax, %ebx, %ebp...
等寄存器,操作指令也由原來的向左賦值變為向右賦值。雖然變化挺多,但本質是不變的一條機器指令只執行一個非常基本的操作以及匯編語言是近似機器語言,其造作指令與機器碼一一對應(這在我們反匯編的實踐中體現的很好)。 - 其次,在課後的練習以及家庭作業中,首次接觸到了逆向工程的概念,即通過閱讀匯編代碼,得出原有C語言文件裏的相關知識的工作。我也用該方法成功解決了幾道題目,讓我再次感受到了匯編語言的巨大魅力,雖然匯編語言可移植性不強,但專門用來對付某臺機器上的C語言可是綽綽有余,可以幫我們挖掘到更多的信息。
參考資料
- 《深入理解計算機系統》
- Linux 基礎入門(新版)
- Vim編輯器
- Linux系統編程
- 2017-2018-1 《信息安全系統設計基礎》教學進程
20155236 《信息安全系統設計基礎》第十四周學習總