type
status
date
slug
summary
tags
category
icon
password
Catagory
Materials
Retired
Retired
Due date
Jun 15, 2024 12:43 PM
Status
Belong in
前言
張大衛老師著作【Windows 軟體安全實務 - 緩衝區溢位攻擊】筆記
環境:Windows xp SP3,little-endian ;compiler:mingw32
使用Dev-C++使用 mingw32-gcc.exe 編譯器來compile範例程式,沒有像ASLR、DEP等保護機制
MSVCRT or UCRT runtime library? Traditionally the MinGW-w64 compiler used MSVCRT as runtime library, which is available on all versions of Windows. Since Windows 10 Universal C Runtime (UCRT) is available as an alternative to MSVCRT. Universal C Runtime can also be installed on earlier versions of Windows (see: Update for Universal C Runtime in Windows). Unless you are targetting older versions of Windows, UCRT as runtime library is the better choice, as it was written to better support recent Windows versions as well as provide better standards conformance (see also: Upgrade your code to the Universal CRT).
OllyDbg
- 每一欄16進位記憶體位址 32bits ⇒ 4bytes
- $ 符號:OllyDbg特別標示 Function Prologue位置
- 程式會從00401220記憶體位址開始執行,是因為程式的PE(Portable Executable)header中定義的程式起始位置(Entry Point) ⇒ 通常不是main函式
- 在執行main前,會先執行一系列動作,包含初始化程序、執行緒、程式的參數等資訊。
- F2設定Breakpoint;F9執行到Breakpoint;F7逐行執行
x86 Register
x86架構有8個通用暫存器(General Purpose Register,GPR)、6段暫存器、1個標誌暫存器和指令指標。 64位元的x86有附加的暫存器。
- EAX, Accumulator Register:可用來做加減乘除運算、邏輯運算、字串運算,或是I/O處理。
- ECX, Counter Register:可用來進行資料運算、存放迴圈次數、字串處理的重複次數、資料位移或是旋轉的次數。
- EDX, Data Register:可用來進行資料運算、乘除運算,或是拿來存放I/O的位址。
- EBX, Base Register:可用來進行資料運算,或是加強索引及定址功能。可以直接當作記憶體位址的基底運算元。
- ESP, Stack Pointer:永遠指向堆疊頂端的最新資料儲存位址。當堆疊資料有進(PUSH)出(PUP)時,SP的位址也會隨之加減更動。
- EBP, Base Pointer:可以指向堆疊的任何位置,也能用來做間接定址、運輸和運算。
- ESI, Source Index:作為資料來源的記憶區索引。
- EDI, Destination Index:作為資料目的地的記憶區索引。
- EIP, Instruction Pointer:用來指向指令的所在位置,一旦CPU執行完IP所指的指令後,IP便會再指向到下一個指令,讓CPU去讀取執行。負責掌管CPU執行程式的流程。
以buffer overflow的攻擊角度而言,除了ESP、EBP、EIP以外,其餘暫存器均可能拿來做任何運用。
TL;DR
Dev-C++使用 mingw32-gcc.exe 編譯器和由 Visual C++ 編譯出來的程式,其內部偵錯資訊格式 (symbol format) 不同
gdb 的 disassemble 指令吃兩個參數,第一個是起始位址,第二個是結束位址。它會自動將起始位址到結束位址中間的記憶體內容進行反組譯。,
disassemble func func+1等同於反組譯function位置0x401290到0x401291間一個byte的組合命令。
in main caller,準備要call strcpy function
function prologue(function的初始狀態)
通常PUSH EBP至下行 MOV EBP,ESP會被稱為function Prologue,不過若是將function寫成inline的格式就不會有此特徵。
下圖strcpy function 0x401290 - 0x401291以及main function 0x4012aa - 0x4012ab的位置即是function prologue
strcpy function prologue
Q:callee function 要如何存取到參數及return address?
Ans:
- return address:EBP+4(此例0022FF4C)
- 參數:EBP+8(此例0022FF50)
strcpy function
準備參數的方向為由右至左,先準備參數值或位址儲存進暫存器中在push in stack
const char * source argument
00x401296 MOV EAX,DWORD PTR SS:[EBP+8],EBP+8 pointer(003E249F)移動至EAX中
00401299 的 MOV DWORD PTR SS:[ESP+4],EAX,將EAX位址複製到ESP+4(0022FF04)
char * destination argument
0040129D的LEA EAX,DWORD PTR SS:[EBP-28] 這一行是把 EBP-28 (0022FF20位置) 的結果存進暫存器 EAX 裡面,所以執行完後 EAX 會等於 EBP-28 指標所指位址
由上述可知 mingw compiler提供40 bytes的空間給buffer變數,而不是原始code所呼叫的 buffer[24],原因在於24(base10) = 2X10的1次方 + 4X10的0次方;40(轉換成base16) = 2X16的1次方 + 4X16的0次方 所以=40
004012A0的MOV DWORD PTR SS:[ESP],EAX 是再把 EAX 的值存到 ESP 指向的空間,為下一行呼叫strcpy參數使用
msvcrt.strcopy(未完)
004012A3 <JMP.&msvcrt.strcpy>,return address儲存至stack(ESP-4)中(004012A8),jump strcpy的位址
function epilogue
【有坑待挖】
改變程式執行流程
在char * destination argument 我們知道compiler配置EBP-28位址共40bytes空間給buffer變數。return address 會放在EBP+4的位址,而此值最終會被POP至EIP中以作為回到caller function的下一行要執行的指令,可以透過覆蓋return address(也就是EBP+4的地方),來執行我們想要的程式所在位址。
如果我們可以修改 EBP+4 的內容,便可以修改回到 main 函式之後,程序會從哪邊繼續下去執行。下列範例從004012EB平移到004012FE來跳過印出"x is 1\n" 的字串,
利用前述提到buffer[24]complier配置40Bytes空間EBP-28的特性,又EBP+4位址儲存RETRUN ADDRESS
初試Buffer Overflow
針對BoF漏洞並不一定存在,針對
先前的例子中可知buffer[24]中會預留40bytes的記憶體(EBP-28),EBP指標位址及EIP指標位址(EBP+8)均被AAAA覆蓋。由此可知,44bytes的A char會覆蓋掉EBP;48Bytes會覆蓋掉EIP
由下例來驗證前述論述,透過將EIP改成BBBB來獲取EIP的entry
當我們要重現透過EIP跳到想要的指令,跳過”x is 1”字串呈現”x is 0”之結果。將想要跳過去的function 位址寫進eip中,以本例來說,把004012FE寫在EIP即可。
因為windows是little-endian故要反過來寫如下圖
因為語系是中文的關係導致,系統會去解析是否為multibytes,EIP在解析的過程中將
\xFE\x12
字元放在一起解讀成中文字,若解析不出來會出現?符號,而?符號轉換成ASCII code即上圖的3F
。從
\xFE\x12\x40\x00
變成\x00\x3F\x40\x00
,又\x00為Null;最前面的位元組本來就是00,故只覆蓋到後面3個bytes(\x3F\x40\x00),little-edian排序最後EIP解析出來結果變成0000403F
Questions
- 在string buffer_overflow中最後一個位元組是null(Bad Characters),當解析的過程會導致後面無法新增其他的字元在後面
- 前述的範例不適用其他支援multibytes語系的程式,因為會被解析成?符號
- 目前使用絕對的記憶體位址來跳至我們想執行的程式段落,因為mingw compiler原則上不會變更記憶體位址,除非由OS強制使用ASLR(Address Space Layout Randomization)
- simplec001.exe執行完會當掉,若是不想被victim發現該怎麼做?
初試Shell Code
前導
[register]用中括號表示取暫存器的值;register表示暫存器的位址
stack pivot:程式執行的流程放在stack中;常用的組語有
CALL ESP
、JMP ESP
、PUSH ESP
/RETN
simplec001.exe參數準備
A*40+B*4+C*4+X*4
,還沒執行strcopy function前尚未BOF時,ESP=0022FF00;EBP=0022FF40;[EBP+4]=004012EB(return address);[EBP+8]=003E249F(named parameters)執行完strcpy function執行緒來到004012A8尚未執行,從這裡可以看到EBP尚在0022FF48,stack range[EBP-4]到[EBP-28]共40Bytes覆蓋41(A的16進位);[EBP]覆蓋4個42(B的16進位);[EBP+4]覆蓋4個43(C的16進位);[EBP+8]覆蓋4個58(X的16進位)。
執行004012A8
RETN
opcode時,會將整個callee stack popup(0022FF00~0022FF40),並將前述的[EBP+4](4個C)寫進EIP;找不到43434343
這個opcode無法繼續執行此時會發生error(58585858
不會執行到)。Q1.Solution - shellcode v0.1
綜上可知,[EIP]要修改成有意義的opcode(eg. JMP ESP、CALL ESP、PUSH ESP/RETN etc..)引導到stack上執行
語系英文環境
使用windbg載入simplec001.exe執行檔,從下圖可以看到一開始載入的模組有image00400000(simplec001本身的程式碼)及3個dll模組ntdll.dll(7c900000~7c9b2000)、kernel32.dll(7c800000~7c8f6000)、msvcrt.dll(77c10000~77c68000),一開始停留在7c90120e位址,對照上面模組範圍即停留在ntdll.dll模組範圍內
輸入
a
assemble 在7c90120e輸入指令push esp
,opcode 54
;7c90120f輸入指令retn
,opcode c3
;breakpoint opcode cc
輸入
u
disassemble 前面7c90120e的位址。指令
s
搜尋從77c10000~77c68000(msvcrt.dll範圍)是否有 54 c3
之opcode,下圖紅框之位址有其指令,挑個77c35459位址原本前導示範的shellcode設定成A*40+4*B+4*C+4*X,EIP設為4個C字元,當執行到EIP時,因無法解析成看得懂的opcode而無法執行下去,故下段instructions 4個X字元並不會執行
[EIP]內容改成77c35459位址(其opcode為push esp,retn),當執行完可以看到EIP停留在0022FF50的位址,回頭看記憶體從0022FF50~0022FF53設定成4個breakpoint的opcode
語系中文環境
image00400000(simplec001本身的程式碼)及3個dll模組ntdll.dll(7c920000~7c9b7000)、kernel32.dll(7c800000~7c91f000)、msvcrt.dll(77be0000~77c38000),一開始停留在7c90120e位址,對照上面模組範圍即停留在ntdll.dll模組範圍內
Q2.Solutions - shellcode v0.1.1
Q2必須要解決在multibyte的環境下不能夠直接將\xFE\x12\x40\x00當作參數透過system()傳遞給simplec001
Ans. 為避免\xFE\x12\字元出現在EIP,可以利用其他暫存器計算⇒找出bad chars
作者先幫我們找到\x77是安全的字元,下面利用0x77777777 xor 0x77376589 = 0x4012FE(”x is 0”字串位置)計算完成後 JMP EAX,來獲得EIP
由上圖發現到0022FF60位只有解析錯誤的狀況,原本預期
\x33\xc1\xff\xe0
(XOR EAX,ECX#JMP EAX)被解析成\x33\x3F
表示\xc1
、\xff
、\xe0
字元均有可能是bad char。作者建議可以在第三及第四句後面加上
\x42
以ASCII code來說是字母B,以opcode來說是INC EDX(即EDX值+1)在此範例中沒有意義,有時候加上一些無意義的指令,可以躲過bad char解析。Q4.Solutions - shellcode v0.1.2
Q4必須要解決因EBP被覆蓋掉而導致回到main function會發生error,故要將EBP還原成原本的值(0022FF48)
將前面的breakpoint
\xcc
拿掉,順利克服上述的問題成功執行Q:How to prevent an attacker to execute code?
Ans:
- code singing
- executable space protection
Reference
‣
- x86 opcode
- Windbg