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

notion image
notion image
  • 每一欄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的組合命令。
notion image
in main caller,準備要call strcpy function
執行緒到00x4012E3,尚未執行指令
執行緒到00x4012E3,尚未執行指令
執行00x4012E3指令MOV DWORD PTR SS:[ESP],EAX,傳遞argv[1],將參數位址push到stack中(ESP)。執行緒來到00x4012E6位址尚未執行call simplec0.00401290指令
執行00x4012E3指令MOV DWORD PTR SS:[ESP],EAX,傳遞argv[1],將參數位址push到stack中(ESP)。執行緒來到00x4012E6位址尚未執行call simplec0.00401290指令
執行004012E6 CALL SimpleC0.00401290指令後,將00x4012EB return address push到stack中。下一行EIP則進入00x401290 strcpy function prologue
執行004012E6 CALL SimpleC0.00401290指令後,將00x4012EB return address push到stack中。下一行EIP則進入00x401290 strcpy function prologue

function prologue(function的初始狀態)

通常PUSH EBP至下行 MOV EBP,ESP會被稱為function Prologue,不過若是將function寫成inline的格式就不會有此特徵。
下圖strcpy function 0x401290 - 0x401291以及main function 0x4012aa - 0x4012ab的位置即是function prologue
notion image

strcpy function prologue

notion image
執行00x401290指令PUSH EBP(儲存舊的EBP位址,以記錄離開strcpy function後要回到原本的main function's stack),從原本的0022FF4C(-4)後變成0022FF48
執行00x401290指令PUSH EBP(儲存舊的EBP位址,以記錄離開strcpy function後要回到原本的main function's stack),從原本的0022FF4C(-4)後變成0022FF48
notion image
執行00x401291指令MOV EBP,ESP,準備callee function的stack空間,EIP下一個指令來到00x401293
執行00x401291指令MOV EBP,ESP,準備callee function的stack空間,EIP下一個指令來到00x401293
notion image
執行00x401293指令SUB ESP,48,ESP原本在0022FF48(-48)⇒0022FF00
執行00x401293指令SUB ESP,48,ESP原本在0022FF48(-48)⇒0022FF00
0022FF4C(EBP+4)可以存取main function return address 00x4012EB;0022FF50(EBP+8)儲存位址可存取參數值
0022FF4C(EBP+4)可以存取main function return address 00x4012EB;0022FF50(EBP+8)儲存位址可存取參數值
Q:callee function 要如何存取到參數及return address?
Ans:
  • return address:EBP+4(此例0022FF4C)
  • 參數:EBP+8(此例0022FF50)

strcpy function

準備參數的方向為由右至左,先準備參數值或位址儲存進暫存器中在push in stack
notion image

const char * source argument

00x401296 MOV EAX,DWORD PTR SS:[EBP+8],EBP+8 pointer(003E249F)移動至EAX中
 
notion image
notion image
00401299 的 MOV DWORD PTR SS:[ESP+4],EAX,將EAX位址複製到ESP+4(0022FF04)
notion image
notion image

char * destination argument

0040129D的LEA EAX,DWORD PTR SS:[EBP-28] 這一行是把 EBP-28 (0022FF20位置) 的結果存進暫存器 EAX 裡面,所以執行完後 EAX 會等於 EBP-28 指標所指位址
notion image
notion image
💡
由上述可知 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參數使用
notion image
notion image

msvcrt.strcopy(未完)

004012A3 <JMP.&msvcrt.strcpy>,return address儲存至stack(ESP-4)中(004012A8),jump strcpy的位址
notion image

function epilogue

取消callee function’s stack

Callee function結束時,瞬間將此function內消耗的stack也要取消。pop EBP(原caller function’s stack base),而原先ESP(in function prologue第一個步驟PUSH EBP)也要加回來
LEAVE 和 RETN 被稱作 function epilogue
notion image
執行緒到004012A8尚未執行指令。ESP 0022FF00儲存毫不相關的資料;EBP 0022FF48儲存main functionEBP位址(0022F78)
執行緒到004012A8尚未執行指令。ESP 0022FF00儲存毫不相關的資料;EBP 0022FF48儲存main functionEBP位址(0022F78)
執行004012A8 LEAVE,恢復到原main function的EBP位址(0022FF78);因為要返回main function,故ESP要指向Return address(EBP+4)0022FF4C
執行004012A8 LEAVE,恢復到原main function的EBP位址(0022FF78);因為要返回main function,故ESP要指向Return address(EBP+4)0022FF4C
notion image
RETN ⇒ POP return address to EIP
notion image
notion image

【有坑待挖】
notion image

改變程式執行流程

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" 的字串,
notion image
利用前述提到buffer[24]complier配置40Bytes空間EBP-28的特性,又EBP+4位址儲存RETRUN ADDRESS
notion image
notion image

初試Buffer Overflow

先調整OllyDBG just in time debugger
先調整OllyDBG just in time debugger
參數設定48個A
參數設定48個A
如圖EIP pointer會被A覆蓋掉表示存在bof漏洞
如圖EIP pointer會被A覆蓋掉表示存在bof漏洞
💡
針對BoF漏洞並不一定存在,針對
AttackSimpleC001會去呼叫SimpleC001,並提供48bytes的參數給simplec001
AttackSimpleC001會去呼叫SimpleC001,並提供48bytes的參數給simplec001
先前的例子中可知buffer[24]中會預留40bytes的記憶體(EBP-28),EBP指標位址及EIP指標位址(EBP+8)均被AAAA覆蓋。由此可知,44bytes的A char會覆蓋掉EBP;48Bytes會覆蓋掉EIP
由下例來驗證前述論述,透過將EIP改成BBBB來獲取EIP的entry
由此可知EIP被414141(B之16進位)覆蓋,EIP的entry會落在offset 44 bytes
由此可知EIP被414141(B之16進位)覆蓋,EIP的entry會落在offset 44 bytes
當我們要重現透過EIP跳到想要的指令,跳過”x is 1”字串呈現”x is 0”之結果。將想要跳過去的function 位址寫進eip中,以本例來說,把004012FE寫在EIP即可。
notion image
因為windows是little-endian故要反過來寫如下圖
notion image
notion image
notion image
因為語系是中文的關係導致,系統會去解析是否為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

  1. 在string buffer_overflow中最後一個位元組是null(Bad Characters),當解析的過程會導致後面無法新增其他的字元在後面
  1. 前述的範例不適用其他支援multibytes語系的程式,因為會被解析成?符號
  1. 目前使用絕對的記憶體位址來跳至我們想執行的程式段落,因為mingw compiler原則上不會變更記憶體位址,除非由OS強制使用ASLR(Address Space Layout Randomization)
  1. simplec001.exe執行完會當掉,若是不想被victim發現該怎麼做?

初試Shell Code

前導

👉🏻
[register]用中括號表示取暫存器的值;register表示暫存器的位址
🌐
stack pivot:程式執行的流程放在stack中;常用的組語有CALL ESPJMP ESPPUSH 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)
執行完004012A0,執行緒來到004012A3尚未執行
執行完004012A0,執行緒來到004012A3尚未執行
執行完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進位)。
notion image
notion image
執行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模組範圍內
notion image
輸入a assemble 在7c90120e輸入指令push esp ,opcode 54 ;7c90120f輸入指令retn ,opcode c3 ;breakpoint opcode cc
輸入 u disassemble 前面7c90120e的位址。
notion image
指令 s 搜尋從77c10000~77c68000(msvcrt.dll範圍)是否有 54 c3 之opcode,下圖紅框之位址有其指令,挑個77c35459位址
notion image
原本前導示範的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
notion image

語系中文環境

image00400000(simplec001本身的程式碼)及3個dll模組ntdll.dll(7c920000~7c9b7000)、kernel32.dll(7c800000~7c91f000)、msvcrt.dll(77be0000~77c38000),一開始停留在7c90120e位址,對照上面模組範圍即停留在ntdll.dll模組範圍內
notion image
notion image
notion image
notion image

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
notion image
由上圖發現到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解析。
notion image

Q4.Solutions - shellcode v0.1.2

Q4必須要解決因EBP被覆蓋掉而導致回到main function會發生error,故要將EBP還原成原本的值(0022FF48)
用windbg來找opcode
用windbg來找opcode
看起來均有解析完整,沒有出現其他的bad char
看起來均有解析完整,沒有出現其他的bad char
將前面的breakpoint\xcc 拿掉,順利克服上述的問題成功執行
notion image

Q:How to prevent an attacker to execute code?

Ans:
  • code singing
  • executable space protection

Reference