介紹
GDB(GNU Debugger)是Richard Stallman為了GNU所開發的其中一個套件
debug format
目前linux elf的debug format是DWARF所以我們先只探討DWARF,
當編譯的時候加-g代表就是塞入debug資訊到執行檔
 |  | 
GDB指令
環境準備
安裝gdb-dashboard
接下來實驗搭配gdb-dashboard套件比較美觀請先安裝
 |  | 
準備要實驗的程式
 |  | 
啟動GDB
gdb executablenamegdb --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開始執行/重新執行,會一直跑下去直到程式中斷或是結束跑完catchsyscallwritecatch 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 ↩︎