-
课程实验报告
课
程
名
称:
计算机系统原理实验
实验项目名称:
BombLab
专
业
班
级:
计科
1501
姓
名:
马子垚
学
号:
2
完
成
时
间:
2017
年
4
月
19
日
实验目的
理解汇编语言,学会使用调试器
实验原理
二进制炸弹是作为一个目标
代码文件提供给学生们的程序,运行时,
它提示用户输入
6
p>
个不同的字符串。
如果其中任何一个不正确,
炸弹
就会“爆炸”:打印出一条错误信息。学生通过反汇编和逆向工程来
确定是哪六个字符串,从而解除他们各自炸弹的雷管。
实验步骤及体会
一、
实验准备
1
、
首先,
由于虚拟机操作系统与
windows
系统相互独立,
所
以首先将
Bomb.
c
及其相关文件存至百度云盘,然后在
ubantu
操作系统内下载至文件系统目录下的下载文件夹
里面:
2
、
输入<
/p>
./bomb
试运行
bomb.c
文件时会提示权限不够:
所以需要输入
chmod+x+
文件名的命令
于
改变文件或目录
的访问权限。
用它控制文件或目录的访问权限。
在经过操
作之后,获
得了权限,程序可以正常运行了:
3
、
由于<
/p>
bomb.c
文件并没有蕴含许多能破解的信息,所以需
要将其进行反汇编,详细操作如下:
输入
objdump
–
d bomb >
将汇编代码输出到
自
动生成一个
的文件里面,方便我们查
看与分析
代码:
二、
具体实验步骤及其分析
试查看导出的
,我发现总的文本里面分为很多段,其中就
有
Phase_1~Phase_6
、
Phase_defuse
、
Phase_secret<
/p>
以及其他相关函
数的代码,所以我猜测每一段
Phase
代码就是我们需要破解的关卡,
所以我将它们分
别导出新建
text
文件,逐段分析。
Phase_1
< br>及其破解过程
:
知识点:
p>
string
,函数调用,栈
反汇编代码及其分析:
08048f61
8048f61:
55
push %ebp
//
压栈
ebp
为栈指针
esp
为栈指针。
push
指令可以理解为两个步骤:
1. subl $$4 , %esp
–栈底向下移一位
2. movl
%ebp
,(
%esp
)
将
ebp
的值存入
p>
esp
中
8048f62:
89 e5
mov %esp,%ebp
//
把
esp
赋值给
ebp
8048f64:
83 ec 18
sub $$0x18,%esp
//esp
指针下移
0x18
个字节
8048f67:
c7 44 24 04 5c a1
04
movl $$0x804a15c,0x4(%esp)
//
取
0x804a15c
处
的内容存入
*
(
%esp+4
)
//
前一段总的分
析:
初始化栈,
push
是压栈指令,
ebp
寄存器中的内容是栈底指针。
e
sp
寄存器里面存的是栈顶地址。
通过
sub
指令,
将栈顶固定在第十八个存储单元
< br>里面。
字符串的内存地址为
0804a15c
,
四个字节。
一个存储单元存放
< br>8
个
bit
两个
十六进制数字,栈顶及后三个单元存栈顶地址。故用
mov
指令将字符串地址存在
栈顶后的第四个字节处。
8048f6e:
08
8048f6f:
8b 45 08
mov 0x8(%ebp),%eax
//
取用户输入的内容存入
%ebp
8048f72:
89 04 24
mov %eax,(%esp)
//eax
的值赋值给
*esp
8048f75:
e8 31 00 00 00
call 8048fab
//
调用字符串比较函数,
string_no
t_equal
就提示需要比较两个字符串,
通过
gcc
函数调用规范知道压入堆栈的两个参数分别为输入字符串与程序内部字
符串。
函数的时候,
必须返回
%eax
为
0
,而这个函数的作用就是
判断字符串是否相等。
8048f7a:
85 c0
test %eax,%eax
//
< br>test
执行的就是
and
的指
令,
不会保存
and
执行的结果,
p>
而是根据
and
的结果设置
flags
寄存器的各种标志。
8048f7c:
74 05
je 8048f83
// Je
指令:当
ZF
等于零的时候跳转,也就是相等的时候跳转。判断
%eax
< br>是否为
0
,为
0
跳转,不为
0
引爆
8048f7e:
e8 4e
01 00 00
call 80490d1
8048f83:
c9
leave
//
为结束函数做准备
8048f84:
c3
ret
//
返回。
ret
:不带任何参数时,用于在子程序的结束位置,被调用的子程序必
须有
ret
指令,
否则调用没
有
ret
指令的子程序会导致自陷,
子
程序执行完之后
处于失控状态。带参数
ret n
表示子程序返回主程序的同时,堆栈弹出
n
个字<
/p>
节(栈顶指针减
n
)
具体操作:
读取
0x804a15c
内存的字符串(密码),
并设置断点。
成功读取到字符串,作为密码输入:
第一关成功破解
程序流程:
1
、取内存地址
0x8049a04
处的内容;
2
、取用户输入的内容(即密码);
3
、比较两者的值,相等则
%eax<
/p>
置为
0
,进入下一关,不相等则调用引爆
程序引
爆炸弹。
< br>Phase_2
及其破解过程
:
知识点:循环语句,数组
08048d6a
8048d6a:
55
push %ebp
8048d6b:
89 e5
mov
%esp,%ebp
8048d6d:
56
push %esi
8048d6e:
53
push
%ebx
8048d6f:
83 ec 30
sub $$0x30,%esp
8048d72:
8d 45 e0
lea
-0x20(%ebp),%eax
//
lea
:
取偏移地址将一个数的内存单元的偏移地址,
送入寄存器中存
储,
与
mov
不同的是
mov
是将该数当作一个偏移地址存入寄存器,没有寻址的功能。
8048d75:
89 44 24 04
mov %eax,0x4(%esp)
//
取用户输入的内容,存入
%ebp
8048d79:
8b 45 08
mov 0x8(%ebp),%eax
8048d7c:
89 04 24
mov %eax,(%esp)
//
前面几行均为初始化栈操作
8048d7f:
e8 87 03 00 00
call 804910b
//
调用函数
read_six_numbers
(说明密码是
6
个数字)
8048d84:
83 7d e0 00
cmpl
$$0x0,-0x20(%ebp)
//
比较第一个输入的的数是否为
0
8048d88:
75 06
jne 8048d90
//
若不为
0
(
< br>jne
看出)则跳转下一步(爆炸)
//
比较第二个输入的的数是否为
1
8048d8a:
83 7d e4 01
cmpl $$0x1,-0x1c(%ebp)
//
p>
若为
1
(
je
p>
)则跳转下一步(
8048d95
)
8048d8e:
74 05
je 8048d95
8048d90:
e8 3c 03 00 00
call 80490d1
8048d95:
8d 5d e8
lea -0x18(%ebp),%ebx
8048d98:
8d 75 f8
lea -0x8(%ebp),%esi
8048d9b:
8b 43 fc
mov -0x4(%ebx),%eax
//
下一个数必须是前两个数之和
8048d9e:
03 43
f8
add -0x8(%ebx),%eax
8048da1:
39 03
cmp %eax,(%ebx)
//
若为前两个数之和则跳转下一步(
8048daa
),
否则爆炸
8048da3:
74
05
je 8048daa
8048da5:
e8
27 03 00 00
call 80490d1
8048daa:
83
c3 04
add $$0x4,%ebx
8048dad:
39 f3
cmp %esi,%ebx
//
回到
8048d9b
,相当于一个循环
,
esi
和
edx
都是地址寄存器,首先将两个地址比
较,当他们相同的时候,就一直向下,否
则跳转。
8048daf:
75
ea
jne 8048d9b
8048db1:
83
c4 30
add $$0x30,%esp
8048db4:
5b
pop %ebx
8048db5:
5e
pop
%esi
8048db6:
5d
pop %ebp
8048db7:
c3
ret
总体上看,这一关让我们输入的
是以
0
为首项的
Fibonacc
p>
数列。
密码
:
0 1 1 2 3 5
故在第一个的基础上:
程序流程:
1.
读取用户输入内容(为
6
个数字);
2.
判断输入的第一个值是否为
0
,不是则引爆炸弹;
3.
判断输入的第一个值是否为
1
,不是则引爆炸弹;
3.
做一个
6
次循环,判断后一个数是否等于前两个数之和,不是则引爆炸弹;
4.
六个数字判断相等结束后,进入下一关
Phase_3
p>
及其破解过程
:
知识点:
switch
语句
08048ea1
8048ea1:
55
push %ebp
8048ea2:
89 e5
mov
%esp,%ebp
8048ea4:
83 ec 28
sub $$0x28,%esp
//
初始化栈的操作
8048ea7:
8d 45 f0
lea -0x10(%ebp),%eax
//
p>
用户输入的参数
2
存在
*
(
%ebp-10
)
8048eaa:
89 44 24 0c
mov %eax,0xc(%esp)
//
用户输入的参数
1
存在
*
(
%ebp-c
)
< br>
8048eae:
8d
45 f4
lea
-0xc(%ebp),%eax
8048eb1:
89
44 24 08
mov
%eax,0x8(%esp)
//
取出地址
0x804a23e
中的内容
8048eb5:
c7 44 24 04 3e a2
04
movl $$0x804a23e,0x4(%esp)
8048ebc:
08
8048ebd:
8b 45 08
mov 0x8(%ebp),%eax
8048ec0:
89 04 24
mov %eax,(%esp)
8048ec3:
e8 78 f9 ff ff
call 8048840 <__isoc99_sscanf@plt>
p>
//
调用
sscanf
函数,传入输入参数,
scanf
函数调用之后可能导致<
/p>
eax
里面的保存的
数据发生改变。
p>
8048ec8:
83 f8 01
cmp $$0x1,%eax
//
scanf
读入数据流之后,
%eax
寄存的是读入数据的个数。
8048ecb:
7f 05
jg
8048ed2
8048ecd:
e8 ff 01 00 00
call
80490d1
8048ed2:
83 7d f4 07
cmpl
$$0x7,-0xc(%ebp)
//
必须
-0xc(%ebp)
的值小于
7
,否则爆炸
8048ed6:
77 6b
ja
8048f43
8048ed8:
8b 45 f4
mov
-0xc(%ebp),%eax
//
相当于
switch(a)
8048edb:
ff 24
85 a0 a1 04 08
jmp
*0x804a1a0(,%eax,4)
//
跳转到以地址
*0x804a1a0
为基址的跳转表中
8048ee2:
b8 00 00 00 00
mov $$0x0,%eax
8048ee7:
eb 53
jmp
8048f3c
8048ee9:
b8 00 00 00 00
mov
$$0x0,%eax
8048eee:
66 90
xchg %ax,%ax
8048ef0:
eb 45
jmp
8048f37
8048ef2:
b8 00 00 00 00
mov
$$0x0,%eax
8048ef7:
eb 39
jmp 8048f32
8048ef9:
b8 00 00 00 00
mov $$0x0,%eax
8048efe:
66 90
xchg
%ax,%ax
8048f00:
eb 2b
jmp 8048f2d
8048f02:
b8 00 00 00 00
mov $$0x0,%eax
8048f07:
eb 1f
jmp
8048f28
8048f09:
b8 00 00 00 00
mov
$$0x0,%eax
8048f0e:
66 90
xchg %ax,%ax
8048f10:
eb 11
jmp
8048f23
8048f12:
b8 14 03 00 00
mov
$$0x314,%eax
8048f17:
eb 05
jmp 8048f1e
8048f19:
b8 00 00 00 00
mov $$0x0,%eax
8048f1e:
2d 5a 03 00 00
sub
$$0x35a,%eax
8048f23:
05 ef
02 00 00
add $$0x2ef,%eax
8048f28:
2d 16 02 00 00
sub $$0x216,%eax
8048f2d:
05 16 02 00 00
add
$$0x216,%eax
8048f32:
2d 16
02 00 00
sub $$0x216,%eax
8048f37:
05 16 02 00 00
add $$0x216,%eax
8048f3c:
2d 16 02 00 00
sub
$$0x216,%eax
8048f41:
eb 0a
jmp 8048f4d
8048f43:
e8 89 01 00 00
call 80490d1
8048f48:
b8 00 00 00 00
mov $$0x0,%eax
//
输入的第一个数字必须小于
5,
否则爆炸
< br>
8048f4d:
83
7d f4 05
cmpl
$$0x5,-0xc(%ebp)
8048f51:
7f
05
jg 8048f58
//
表示输入的第二个数字必
须等于第一个数经过数次运算的结果
8048f53:
3b 45 f0
cmp
-0x10(%ebp),%eax
//
成功跳出
8048f56:
74 05
je 8048f5d
8048f58:
e8 74 01 00 00
call 80490d1
8048f5d:
c9
leave
8048f5e:
66 90
xchg
%ax,%ax
8048f60:
c3
ret
帧堆栈数据记录表:
具体分析:
由
movl $$0x804a23e,0x4(%esp)
,用
gdb
查看
0x804a23e
的值
发现这个应该是要求输入两个数字
jmp *0x804a1a0(,%eax,4)
首
先,
看到这个语句不知道是什么意思,
搜索也搜索不到这段地址
。
后来通过百
度,发现这是一个基于跳转表的
< br>switch
语句的汇编描述,后来决定自己编写一
个<
/p>
switch
语句去运行,理解了
swi
tch
语句在汇编中的表达方式。
①
当第一个数输入
< br>0
时
去查看
< br>*0x804a1a0+4*0=*0x804a1a0
的值
所以会跳转到
0x8048f
12
那行代码中
则后面的运算为
p>
0x(314-35a+2ef-216).
转换为十进制为
147
。故输入
0 147
得到结果(接第二个炸弹)
同理:
②
输入
1
运算得到
1 -641
③
输入
2
运算得到
2
217
(运行结果略)
④
输入
3
运算得到
3
534
(运行结果略)
⑤
输入
4
运算得到
4
0
(运行结果略)
程序流程:
1.
读取输入参数
1
和参数
2
,调用
ssanf
函数传入两个参数;
2.
比较参数
1
p>
与
7
的大小,小于等于
7
继续,大于
7
引爆;
3.
根据参数
1
的值来搜索跳转地址,计算得到最终的
%eax
;
4.
比较参数
< br>1
与
5
的大小,小于等于
5
继续,大于
5
引爆
;
5.
比较参数
2
与计算得到的
%eax
是否相等
,
相等则进入下一关,
不相等则引爆。
Phase_4
及其破解过程
:
知识点:递归
08048e2e
8048e2e:
55
8048e2f:
89 e5
8048e31:
83 ec 28
8048e34:
8d 45 f0
8048e37:
89 44 24 0c
8048e3b:
8d 45 f4
8048e3e:
89 44 24 08
8048e42:
c7 44 24 04 3e a2
04
8048e49:
08
8048e4a:
8b 45 08
8048e4d:
89 04 24
//
调用
sscanf
函数,传入输入参数
8048e50:
e8 eb f9 ff ff
8048e55:
83 f8 02
//
输入的为两个数据,否则引爆
push %ebp
mov %esp,%ebp
sub $$0x28,%esp
lea
-0x10(%ebp),%eax
mov %eax,0xc(%esp)
lea -0xc(%ebp),%eax
mov
%eax,0x8(%esp)
movl
$$0x804a23e,0x4(%esp)
mov
0x8(%ebp),%eax
mov %eax,(%esp)
call 8048840 <__isoc99_sscanf@plt>
cmp $$0x2,%eax
8048e58:
75 0c
jne
8048e66
8048e5a:
8b 45 f4
mov
-0xc(%ebp),%eax
8048e5d:
85
c0
test %eax,%eax
//
第一个数字为负数则跳转至爆炸
8048e5f:
78 05
js 8048e66
8048e61:
83 f8 0e
cmp $$0xe,%eax
//
第一个数字必须小于
14
则跳转,否则继续运行至爆炸
p>
8048e64:
7e 05
jle
8048e6b
8048e66:
e8 66 02 00 00
call
80490d1
8048e6b:
c7 44 24 08 0e 00 00
movl
$$0xe,0x8(%esp)
8048e72:
00
8048e73:
c7 44 24 04 00 00
00
movl $$0x0,0x4(%esp)
8048e7a:
00
8048e7b:
8b 45 f4
mov
-0xc(%ebp),%eax
8048e7e:
89
04 24
mov %eax,(%esp) <
/p>
//
调用
func4
函数
8048e81:
e8
da fc ff ff
call 8048b60
//
当
phase_
4
调用
func4
时,
phase_4
中的返回地址被压入栈中,形成
Pha
se4
的栈
帧的末尾。
Func4
p>
从保存栈指针的值开始。
8048e86:
83 f8 01
cmp $$0x1,%eax
8048e89:
75 06
jne 8048e91
8048e8b:
83 7d f0 01
cmpl $$0x1,-0x10(%ebp)
//
p>
第二个数不为
1
则跳转
8048e91
(爆炸)
//<
/p>
为
1
则成功跳出
8048e8f:
74 0c
je 8048e9d
8048e91:
8d b4 26 00 00 00
00
lea 0x0(%esi,%eiz,1),%esi
8048e98:
e8 34 02 00 00
call 80490d1
8048e9d:
c9
leave
8048e9e:
66 90
xchg
%ax,%ax
8048ea0:
c3
ret
与之前一样
由
movl $$0x804a23e,0x4(%esp)
句来得到要求,要求输入两个数字
分析题目
设要求输入的两个数字为
x,y
,则
0
<=x<14,y=1.
调用
fu
nc4
函数,故查看
func4
的代码
:
08048b60
8048b60:
55
8048b61:
89 e5
8048b63:
83 ec 18
8048b66:
89 5d f8
push %ebp
mov %esp,%ebp
sub $$0x18,%esp
mov
%ebx,-0x8(%ebp)
8048b69:
89
75 fc
mov
%esi,-0x4(%ebp)
//
%ebx
、
%esi
是被调用者保存寄存器,
func4
函数在使用这些寄存器的值之前,
必须把他
们保存到栈中,
并且在返回前回复他们,
在本题中保存的这些值
没有实
际用途。
8048b6c:
8b 55 08
mov
0x8(%ebp),%edx
8048b6f:
8b
45 0c
mov
0xc(%ebp),%eax
8048b72:
8b
5d 10
mov
0x10(%ebp),%ebx
8048b75:
89
d9
mov %ebx,%ecx
8048b77:
29 c1
sub %eax,%ecx
8048b79:
89 ce
mov
%ecx,%esi
//
shr
是逻辑右移指令
8048b7b:
c1 ee 1f
shr $$0x1f,%esi
8048b7e:
8d 0c 0e
lea
(%esi,%ecx,1),%ecx
8048b81:
d1 f9
sar
%ecx
//sar
是算数右移指令,
只移位一位,算术右移补最高位
8048b83:
01 c1
add %eax,%ecx
8048b85:
39 d1
cmp
%edx,%ecx
8048b87:
7e 17
jle 8048ba0
8048b89:
83 e9 01
sub $$0x1,%ecx
8048b8c:
89 4c 24 08
mov
%ecx,0x8(%esp)
8048b90:
89
44 24 04
mov
%eax,0x4(%esp)
8048b94:
89
14 24
mov %edx,(%esp)
8048b97:
e8 c4 ff ff ff
call 8048b60
8048b9c:
01 c0
add %eax,%eax
8048b9e:
eb 20
jmp
8048bc0
8048ba0:
b8 00 00 00 00
mov
$$0x0,%eax
8048ba5:
39 d1
cmp %edx,%ecx
8048ba7:
7d 17
jge
8048bc0
8048ba9:
89 5c 24 08
mov
%ebx,0x8(%esp)
8048bad:
83
c1 01
add $$0x1,%ecx
8048bb0:
89 4c 24 04
mov %ecx,0x4(%esp)
8048bb4:
89 14 24
mov %edx,(%esp)
//
递归调用函数
func4
8048bb7:
e8 a4
ff ff ff
call 8048b60
8048bbc:
8d 44 00 01
lea 0x1(%eax,%eax,1),%eax
8048bc0:
8b 5d f8
mov -0x8(%ebp),%ebx
8048bc3:
8b 75 fc
mov -0x4(%ebp),%esi
8048bc6:
89 ec
mov %ebp,%esp
8048bc8:
5d
pop
%ebp
8048bc9:
c3
ret
帧堆栈数据记录表:
Phase_4:
Fun4:
第一次调用
fun4()
,
edx
为输入的
x
p>
值,
eax=0,ebx=14;
根据整个
fun4()
函数,
当
x<=7
时,调换
ebx
与<
/p>
esi
的值,返回到第四关中,
eax!
=1
,直接跳转最后爆
炸。所以,
x>
7
。当输入为
8
,
ecx=7
减去
1
变为
6
,然后自己调用自己,即为递
归函数,此时新的
edx=8
不变,
eax=0
不变,
ebx=6
。根据跟随代入法,最后可<
/p>
以总结成
C
语言代码:
< br>
分析过程:
转化成的
C
代码:
将机器语言转化为
C
代码我们可以更直观的来判断和推导未知数
的值,
的出的结
论是这是一个二分法求值。
当且仅当下端和中值相等的时候才退出递归过程,
只
可能出
现在上半区,这题有多解。
由此可见,当输入
8
1
或者
9
1
或者
11
1
时,可避开炸弹。
主程序流程:
1.
读取参数,调用
sscanf
函数传入参数;
2.
比较输入参数与
0
的大小,小于等于则引爆;
3.
把输入参数传入函数
func4
,并调用函数
func4
;
4.
若第二个数等于
1
,则返回,进入下一关,否则引爆炸
弹。