PC平臺逆向破解實驗報告(待補充)
PC平臺逆向破解實驗報告(待補充)
實踐目標
本次實踐的對象是一個名為pwn1的linux可執行文件。
該程序正常執行流程是:main調用foo函數,foo函數會簡單回顯任何用戶輸入的字符串。
該程序同時包含另一個代碼片段,getShell,會返回一個可用Shell。正常情況下這個代碼是不會被運行的。我們實踐的目標就是想辦法運行這個代碼片段。我們將學習兩種方法運行這個代碼片段,然後學習如何註入運行任何Shellcode。
實踐內容
- 手工修改可執行文件,改變程序執行流程,直接跳轉到getShell函數。
- 利用foo函數的Bof漏洞,構造一個攻擊輸入字符串,覆蓋返回地址,觸發getShell函數。
- 註入一個自己制作的shellcode並運行這段shellcode。
1 預備知識
linux下的破解自然需要熟悉linux的命令與操作,這裏給出一些linux下有關二進制的一些知識。
1.1 匯編語言基礎
一些同學已經匯編語言程序設計課程學習過匯編語言了,我們使用的語法是Intel的。而在linux下用gdb和objdump得到的匯編代碼的語法是AT&T的(嚴格來說有些區別),這兩者之間存在一些區別。
我們學的還是8086的實模式下的16位匯編語言,還需要了解32位寄存器。這個轉換還是比較容易的。
1.1.1 Intel語法與AT&T語法的區別
區別 | Intel語法 | AT&T語法 | 備註 |
---|---|---|---|
源操作數和目的操作數的位置 | MOV EAC, ECX | movl %ecx, %eax | 將ECX的值存入EAX |
常量和寄存器的表示 | MOV EAX, 12h | movl $0x12, %eax | 將0x12存入EAX |
尋址方式的表示 | MOV EAX, [EBX+20h] | movl 0x20(%ebx), %eax | Intel語法中[base+index * scale+disp]相當於AT&T中的disp(base, index, scale) |
MOV EAX, [EBX+ECX * 4h -20h] | movl -0x20h(%ebx, %ecx, 0x04),%eax | ||
指令後綴 | MOV EAX, dword ptr [EBX] | movl (%ebx), %eax |
AT&T語法中,指令後綴l, w, b分別對應long, word, byte, 在Intel語法裏就是dword ptr, word ptr, byte ptr。
1.1.2 x86寄存器與常用指令
x86處理器有一下的一些通用寄存器:
- %eax (許多函數的返回值默認存在這個寄存器裏)
- %ebx
- %ecx
- %edx
- %esi
- %edi
還有幾個專用寄存器:
- %ebp
- %esp (指向棧頂)
- %eip (保存下一條指令的地址)
- %eflag (各種標誌位都在這裏)
常用匯編指令
指令 | 機器碼 | 說明 | 示例 |
---|---|---|---|
NOP | 0x90 | 空指令,啥也不做 | |
JNE | 0x75 | 條件轉移指令,標誌位ZF==0 則跳轉 | |
JE | 0x74 | 條件轉移指令,標誌位ZF==1 則跳轉 | |
JMP | 0xe9 後面是四個字節的偏移量 | 無條件轉移指令 | |
0xeb 後面是兩個字節的偏移量 | |||
0xff25 後面是四個字節的地址 | |||
CMP | 比較指令,相當於減法,可以改變標誌位 | ||
call | 0xe8 後面是四個字節的偏移量 | 函數調用,可以認為是先push下一條指令的地址,再jmp到對應的地址 |
1.1.3 其他內容
一般來說,我們看到的匯編語言函數的結構是這個樣子的:
<function_name>:
pushl %ebp
movl %esp, %ebp
subl 4*N, %esp # N為局部變量個數
# 函數主體
leave
ret
leave
指令相當於
movl %ebp, %esp
popl %ebp
於是調用函數的時候,看到的匯編語言指令可能是這個樣子的
# 函數: <function_name>
# 函數參數: <p1> <p2> <p3>...<pn>
pushl <pn>
...
...
pushl <p2>
pushl <p1>
call <function_name> # 執行call命令時,會將<next_code>的地址壓棧
<next_code>:
此時,棧看起來就是這個樣子的:
低地址
+--------------+
| 局部變量2 <--- -8(%ebp)
+--------------+
| 局部變量1 <--- -4(%ebp)
+--------------+
| 舊%ebp <--- (%ebp)
+--------------+
| RET地址 <--- 4(%ebp)
+--------------+
| 參數1 <--- 8(%ebp)
+--------------+
| 參數2 <--- 12(%ebp)
+--------------+
| ... ...
+--------------+
| 參數N <--- N*4+4(%ebp)
+--------------+
高地址
1.2 gdb的常用命令
下面給出常用的幾個gdb命令,並不全面。在以後的實驗中慢慢學習gdb就可以了,不用強求。
gdb命令 | 參數 | 含義 | 示例 |
---|---|---|---|
break | 地址 | 設置斷點,簡寫為b,地址類型包括:函數名,行號,*內存地址 | break main; break 12; break *0x08048373 |
run | 命令行參數 | 運行程序,可簡寫為r | |
clear | 地址 | 清除斷點,與break相反 | |
info | break | 顯示斷點信息 | |
continue | 繼續執行程序 | ||
attach | 進程號 | 調試已經運行的程序 | |
quit | 退出 |
1.3 objdump的常見用法
objdump可以快速方便地反編譯簡單的、未被篡改的二進制文件,它可以讀取所有常用的ELF類型的文件。下面給出objdump常見的用法。
- 查看ELF文件中所有節的數據或代碼
objdump -D <elf_object>
- 只查看ELF文件中的程序代碼
objdump -d <elf_object>
- 查看所有符號
objdump -tT <elf_object>
2 實驗過程
2.1 直接修改程序機器指令,改變程序執行流程
2.1.1 思路
第一個實驗的思路非常簡單,原本的getShell函數是程序中的“死代碼”,正常情況下永遠也不會執行。
我們通過修改main函數中的call指令,使得原本執行foo函數的程序轉而去執行getShell函數。總結下來就是下面兩步:
- 找到getShell函數的位置
- 修改main函數中,call指令的參數,使得程序調用getShell函數
2.1.2 過程
先用objdump反匯編目標程序,輸入指令objdump -d 20155110pwn1 | less
用less分頁顯示比more更方便,可以像使用vim那樣用“/”查找字符串。
然後再看看main函數
我們發現,在main函數中,按照正常流程,call指令會調用foo函數,e8是call指令的機器碼,後面跟著四字節的偏移量ff ff ff d7(小端序,補碼)。這裏的偏移量是怎麽求的呢?
call指令在執行時,會EIP當前的值,也就是下一條指令的地址——0x080484ba壓棧,然後修改寄存器EIP,EIP+偏移量= 0x080484b + 0xffffffd7 = 0x08048491(32位的有符號數運算),將EIP指向foo函數的起始地址。
我們需要修改call指令的偏移量,根據“目的地址=EIP(call的下一條指令的地址)+偏移量”,新的偏移量 = 0x0804847d(getShell函數的起始地址) - 0x080484ba = 0xffffffc3(補碼運算)
接著用十六進制編輯器,或者vim來修改目標程序即可。這裏使用vim,輸入:%!xxd
進入十六進制模式,修改偏移量。
修改後,得到
運行修改後的程序,得到shell
未完待續
PC平臺逆向破解實驗報告(待補充)