反匯編
- 反匯編:把目標代碼轉為匯編代碼的過程。 通常,編寫程序是利用高級語言如C,Pascal等高級語言進行編程的,然后再經過編譯程序生成可以被計算機系統直接執行的文件。反匯編即是指將這些執行文件反編譯還原成匯編語言或其他高級語言。但通常反編譯出來的程序與原程序會存在許多不同,雖然執行效果相同.
- gdb相關操作:
b linenumber 設置斷點
run 運行
disassemble 獲取匯編代碼
用i(nfo) r(egisters)查看各寄存器的值
用x查看內存地址中的值 - 具體步驟:
設置斷點在main函數調用f函數的位置gdb> b main
gdb> run運行
gdb> disassemble反匯編
display /i $pc
i r
x查看內存中的內容
si執行下一條匯編 - 反匯編:
使用gcc - g example.c -o example -m32指令在64位的機器上產生32位匯編,然后使用gdb example指令進入gdb調試器: - 用gcc在64位機器上編譯一個32位的程序,遇到報錯,具體如下圖:
- 這是因為編譯64位Linux版本32位的二進制文件,需要安裝一個庫,使用指令
sudo apt-get install libc6-dev-i386
- 進入之后先在main函數處設置一個斷點,再run一下,使用disassemble指令獲取匯編代碼,用i(info) r(registers)指令查看各寄存器的值:
- 可見此時主函數的棧基址為0xffffd1e8,用x(examine)指令查看內存地址中的值,但目前%esp所指堆棧內容為0,%ebp所指內容也為0
以上為入門小練習,接下來讓我們直接進入f函數中,查看f函數的每一步匯編代碼
“display /i $pc”
其中 $pc 代表當前匯編指令,/i 表示以十六進行顯示。當需要關心匯編代碼時,此命令相當有用。這樣在每次執行下一條匯編語句時,都會顯示出當前執行的語句。下面展示每一步時%esp、%ebp、%eip、%eax和堆棧內容的變化:
- movl 指令:把值0x1b存在%ebp-0x4中
- 將上一個函數的地址入棧,以%ebp+0x8作為基址 待考證
- call指令將下一條指令的地址入棧
- endbr32
這是Intel 為 CONTROL-FLOW ENFORCEMENT TECHNOLOGY 新加的指令:
The ENDBRANCH (see Section 73 for details) is a new instruction that is used to mark valid jump target addresses of indirect calls and jumps in the program. This instruction opcode is selected to be one that is a NOP on legacy machines such that programs compiled with ENDBRANCH new instruction continue to function on old machines without the CET enforcement. On processors that support CET the ENDBRANCH is still a NOP and is primarily used as a marker instruction by the processor pipeline to detect control flow violations. The CPU implements a state machine that tracks indirect jmp and call instructions. When one of these instructions is seen, the state machine moves from IDLE to WAIT_FOR_ENDBRANCH state. In WAIT_FOR_ENDBRANCH state the next instruction in the program stream must be an ENDBRANCH. If an ENDBRANCH is not seen the processor causes a control protection exception (#CP), else the state machine moves back to IDLE state.
給技術要求相對跳轉的目標地址一定是一條 endbr32 或 endbr64 指令,否則就會異常。該指令並不執行任何操作,只是用於驗證目標地址是期望的跳轉目標。
- 將上一個函數的基址入棧,從當前%esp開始作為新基址
- 此處棧底地址改變,下圖是新地址與原始地址的對比圖:
- call下一條指令入棧
- 將%esp指向的地址中的值賦給%eax寄存器
- ret指令將棧頂彈給%eip
將$0x2e23寄存器中的值與%eax寄存器中的值相加,存在$0x2e23寄存器中
- 將%eax寄存器中的值存到0x8+%ebp地址中,也就是堆棧中
- %eax寄存器的值與$0x3寄存器的值相加后的值存到$0x3寄存器中
- pop %ebp指令將棧頂彈到%ebp中,同時%esp增加4字節
-
ret指令將棧頂彈給%eip
-
leave
在32位匯編下相當於:
mov esp,ebp;//將ebp指向(ebp內部應當保存一個地址,所謂指向即這個地址對應的空間)的值賦給esp
pop ebp/* leave指令將EBP寄存器的內容復制到ESP寄存器中,
以釋放分配給該過程的所有堆棧空間。然后,它從堆棧恢復EBP寄存器的舊值。*/