介紹
GDB(GNU Debugger)是Richard Stallman
為了GNU所開發的其中一個套件
debug format
目前linux elf的debug format是DWARF
所以我們先只探討DWARF
,
當編譯的時候加-g
代表就是塞入debug資訊到執行檔
|
|
GDB指令
環境準備
安裝gdb-dashboard
接下來實驗搭配gdb-dashboard套件比較美觀請先安裝
|
|
準備要實驗的程式
|
|
啟動GDB
gdb executablename
gdb --args executablename arg1 arg2 arg3
打開並且跟蹤程式,如果有參數可以塞入- 因為要被你追蹤的程式還沒啟動所以需要這些指令:
r
,start
,starti
sudo gdb --pid 123
跟蹤已經執行的程式利用pid,因為跟蹤其他程式所以需要root權限
進入GDB後
最原始版本gdb可以使用的指令
進入gdb後
start
開始並且中斷在main函數,沒有symbol table
無法使用starti
在沒有symbol table
之下,我們只能從頭看組合語言,所以我們需要starti
從第一行組合語言開始並且中斷n
執行下一行原始碼,遇到function call不會中斷,等到function call執行完成才中斷ni
執行下一行組合語言,遇到function call不會中斷,等到function call執行完成才中斷s
執行下一行原始碼,遇到function call會進入並且中斷si
執行下一行組合語言,遇到function call會進入並且中斷b main
設定中斷在main
函數(main
名稱是紀錄在elf的symbol tablereadelf -s 執行檔名
),如果gcc設定-s
代表刪除程式所有symbol table
,你就無法找到main的位置b *0x123
設定中斷在0x123
記憶體位置j *0x123
跳到0x123
記憶體位置並且會馬上
繼續執行c
持續執行等到下一個中斷b
出現info b
列出已經設定哪些中斷d 1
刪除Num 1的中斷r
開始執行/重新執行,會一直跑下去直到程式中斷或是結束跑完catch
syscall
write
catch syscall write
可以暫停當你呼叫write syscall之後
q
離開gdbhandle SIGTSTP nostop nopass print
這裡SIGTSTP
可以改成你要設定的訊號(Control+C發送SIGINT
,Control+Z發送SIGTSTP
),或是all
全部signal一起設定stop
/nostop
設定收到SIGTSTP訊號gdb要停止嗎?pass
/nopass
設定收到SIGTSTP訊號gdb要轉傳給被追蹤的程式嗎?print
/noprint
設定收到SIGTSTP訊號gdb要顯示嗎?
info signals
顯示目前signals的設定set disable-randomization off
這個指令可以gdb隨機載入執行檔到不同記憶體位置
python
在gdb裡面啟動python,用end
來關閉,目的是可以拿來擴展gdb功能
原始碼進入點都是main?
在debug此檔案go-elf
到底要中斷在哪裡才能進入主程式.
這個範例希望讓大家體會這個main
名稱只是約定好的,不同程式語言它可能自己定義
提示
readelf -s go-elf
有趣的程式
- 希望學到簡單c inline assambly
- 希望了解某些暫存器在system v abi的規範下有特殊用途
- 不用main
- 不用printf libary
- 輸出hello, world!
找找主程式的進入點?
|
|
gcc -nostdlib main.c -no-pie
handle signal範例
- 了解到被追蹤的程式的signal,會被追蹤者掌控
- 對程式按
CTRL+Z
默認的行為是發送(SIGTSTP
)
|
|
1.編譯
|
|
2.在signal的終端機 執行 signal
|
|
出現pid/tid: 123 ppid: 321
123就是signal程式的pid等等會用到(這裡的數字不會跟我一樣)
3.開啟另一個終端機讓gdb去追蹤signal程式
|
|
handle
)signal.c
收到CTRL+Z
(SIGTSTP
)訊號的時候GDB不停止(nostop
)不轉傳給signal.c
(nopass
)但是要在GDB介面輸出訊息(print
)1.在gdb的終端機輸入
|
|
2.在gdb的終端機輸入c
讓signal.c
繼續執行
|
|
3.在signal的終端機按下CTRL+Z觀察
如果上面nopass
改成pass
會有什麼差別?
gdb-dashboard
dashboard後按tab兩下可以看到有哪些指令可以用
“查詢0x123後10bytes的記憶體”
dashboard memory watch 0x123 10
Debug Python by GDB
剛才的指令我們已經學會如何動態分析編譯語言(Compiled language
)接下來會教學如何分析直譯語言(Interpreted language
)我們以python為範例
python有各種直譯器的實作pypy
(python-JIT),Jython
(java),cpython
(C),IronPython
(c#)
一般人用的python是cpython
也就是吉多·范羅蘇姆(Guido van Rossum
)python之父所開發的
接下來的範例只探討利用gdb注入cpython
利用查閱(Python/C API Reference Manual)學會如何控制python直譯器(python直譯器教學),進而利用gdb注入python程式
到執行中的python程序
裏面
範例
引用來源Extending GDB with Python - Lisa Roach
此實驗的架構圖
創建main.py並且放入此程式
|
|
1.啟動main.py會看到pid的數值這裡舉例pid:123
(你的pid會跟我不一樣)
python main.py
2.開啟另一個終端機輸入下面指令去追蹤pid:123
程序
|
|
告訴python直譯器的GIL要給我用資源
|
|
告訴python直譯器我想執行的程式
|
|
告訴python直譯器的GIL還你資源拉
|
|
觀察看看發生什麼事??
Extending GDB with Python
介紹
gdb支持Python
與Guile
兩種程式來強化gdb,我想很少人會Guile
吧??
可以看到有許多gdb的擴展套件他們都是用python寫的
-pwndbg
-peda
-gef
下圖可以看到目前大部份強化GDB的工具都是利用python開發所以我們只教python擴展gdb
如何載入擴展
- GDB啟動的時候會載入
~/.gdbinit
- gdb -x 我的套件.py
- 進入gdb打python
GDB原理
ptrace
gdb底層是利用ptrace完成(strace也是利用ptrade完成
3 )接下來簡單聊一下ptrace systemcall
linux kernel裡面稱被追蹤的程式叫作tracee
,追蹤人的程式叫tracer
點選ptrace syscall可以看到ptrace實作的地方,有個叫SYSCALL_DEFINE4
的macro
SYSCALL_DEFINE4->經過編譯器前置處理->__x64_sys_ptrace
在x64 linux v5.10的ptrace的名稱
__x64_sys_ptrace(long request, long pid, unsigned long addr, unsigned long data)
PTRACE_ATTACH
跟作業系統報備哪個程式要被追蹤
ptrace(PTRACE_ATTACH, pid, NULL, NULL)
細節請看ptrace.c
- 確保 tracee 不是kernel thread
- 不讓你追蹤核心與暫停核心程式,如果真要這樣搞,要用User-mode Linux (UML)參考jserv教學
- 確保 tracee 與tracer不是相同thread group
- 因為linux同thread group會共用Signal handlers所以tracer無法處理Signal行為
- 確保 tracer對tracee有權限
- 確保tracee沒有死掉或是變僵屍(EXIT_ZOMBIE/EXIT_DEAD)
- 都死了還要追蹤?
- 確保tracee沒有被其他程式追蹤
- 紀錄tracee已經被追蹤
- 更改tracer為tracee繼父
- 發送
SIGSTOP
給tracee - 這裡不保證tracee已經暫停-PTRACE_ATTACH
一開始程式繼父與生父是同一人,但
PTRACE_ATTACH
完後tracer
會變為tracee
的繼父,並且當小孩(tracee
)死掉當然是繼父(tracer
)被通知(SIGCHLD
)負責收屍(wait
),但是如果小孩死掉繼父沒有收屍,小孩就變成僵屍-sched.h僵屍範例
複製下面dummy-loop-prog.c的程式執行
PTRACE_ATTACH完成後需使用waitpid()
確保被ptrace追蹤的程式已經停止了
以上滿足才能開始ptrace動作,讀取暫存器
,讀寫記憶體
,跳到某行
…
正常駭客要解決
- W^X policy
- 目前作業系統都有
W^X policy
所以一塊記憶體只能是寫或是執行不能同時擁有
- 目前作業系統都有
- ASLR(Address space layout randomization)
- gdb會關閉ASLR讓你以為位置固定,但正常程式會啟用並且保護
- 隨機記憶體位置,駭客就不知道要跳到哪執行某些函數
cat /proc/[pid]/maps
breakpoint
??
範例程式
範例PTRACE_ATTACH的過程
sequenceDiagram
autonumber
loop 迴圈
dummy-loop-prog.c->>dummy-loop-prog.c: TASK_RUNNING(R)
end
injector.c->>作業系統: PTRACE_ATTACH dummy-loop-prog.c
作業系統->>dummy-loop-prog.c: SIGSTOP
Note left of 作業系統: dummy-loop-prog.c變為TASK_TRACED狀態
Note right of injector.c: waitpid()
loop 迴圈
dummy-loop-prog.c->>dummy-loop-prog.c: TASK_TRACED(t)
end
injector.c
dummy-loop-prog.c
這個版本是作最簡單while迴圈程式
|
|
這個版本利用PTRACE_TRACEME
讓tracee
直接被生父追蹤,所以其他程序無法再追蹤他了,來避免被其他程式動態分析
|
|
編譯
|
|
|
|
實驗
- 開啟終端機啟動要被注入的程式
|
|
- 開啟新的終端機,這個範例dummy-loop-prog 的
pid
為123
sudo給injector權限注入pid
為123的程式
|
|
- 在
dummy-loop-prog
終端機下
|
|
這個78是injector注入的
問題?
- 希望把78改成其他數字
利用ftrace了解ptrace
先學學ftrace基礎
我們可以利用剛才的程式配合ftrace我們可以感受程式流進去核心後做了什麼事
DWARF
這裡聊ELF的debug section與GDB如何讀取ELF的DWARF資訊並debug
debug section壓縮
編譯main.c
輸出debug資訊(-g
)在link階段(-Wl
)放入(--compress-debug-sections=zlib-gnu
)參數讓debug壓縮gcc -Wl,--compress-debug-sections=zlib-gnu -g main.c
- --compress-debug-sections=
- zlib
- 與zlib-gabi一樣
- zlib-gnu
- 壓縮DWARF並且更改debug section名稱
.debug_xxx
->.zdebug_xxx
- 壓縮DWARF並且更改debug section名稱
- zlib-gabi
- 壓縮DWARF並且不會更改debug section名稱
- zlib
引用
trace基本Linux系統呼叫
jserv-以 ptrace 系統呼叫來追蹤/修改行程
jserv-ptrace / SIGTRAP / int3 的關聯
Infecting Running Processes
MAKE STACK EXECUTABLE AGAIN
ptrace() Tutorial
Command set-permission(mprotect)
strace 是如何工作的
How does strace work
Linux task_struct parent 和 real_parent 的区别
How debuggers work: Part 2 - Breakpoints
position-independent code ↩︎
position-independent executable ↩︎
Intercepting and Emulating Linux System Calls with Ptrace ↩︎