计算机系统基础实验 BombLab

本内容是本人的作业备份,仅作参考,不可抄袭!

实验任务概述:

本实验通过要求你使用课程所学知识拆除一个“binary bombs来增强对程序的机器级表示、汇编语言、调试器和逆向工程等方面原理与技能的掌握。一个“binary bombs32位二进制炸弹,下文将简称为炸弹)是一个Linux可执行程序,包含了7个阶段(或层次、关卡)。炸弹运行的每个阶段要求你输入一个特定字符串,你的输入符合程序预期的输入,该阶段的炸弹就被拆除引信即解除了,否则炸弹“爆炸”打印输出 “BOOM!!!。实验的目标是拆除尽可能多的炸弹层次。

每个炸弹阶段考察了机器级程序语言的一个不同方面,难度逐级递增:

阶段1:字符串比较

阶段2:循环

阶段3:条件/分支

阶段4:递归调用和栈

阶段5:指针

阶段6:链表/指针/结构

另外还有一个隐藏阶段(树型结构),只有当你在第4阶段的解后附加一特定字符串后才会出现。

为完成二进制炸弹拆除任务,你需要使用gdb调试器和objdump来反汇编炸弹的可执行文件并跟踪调试每一阶段的机器代码,从中理解每一汇编语言代码的行为或作用,进而设法推断拆除炸弹所需的目标字符串。比如在每一阶段的开始代码前和引爆炸弹的函数前设置断点。

实验步骤:

1.分析main函数

分析bomb.c

    分析bomb.c可知,炸弹的运行机制:由read_line获取输入得到input,然后再将input传递给phase_1等函数,然后执行phase_1,如果执行正确,则拆弹成功,进入phase_2.

2.阶段一

分析phase_1的反汇编代码,可以发现在(8048d17)处调用了<strings_not_equal>,这个函数的功能是比较两个字符串是否相等,因此猜测这段代码是要输入一个字符串A,与其内部字符串B比较看是否一致,如果一样就通过。这里输入“rrr”进行测试。

    而(8048d0d)处为调用<strings_not_equal>函数前进行的入栈,并且传入了一个地址,猜测入栈的eax,就是题目要求的字符串B,而另一个入栈的0x8(%ebp)猜测就是输入的A

  1. b *(0x8048d17) 在此处设立断点,去看eax中的值
  2. x/s $ebp+0x8 这里将($ebp+0x8)中的内容入栈了,通过x/s查看其中的内容,发现是我们之前输入的“rrr”,可以得知返回的就是A,说明将A入栈了
  3. x/s $eax 以字符串的形式去看eax中存放的东西,得到字符串I turned the moon into something I call a Death Star.  ,可知这就是我们要的字符串B
  4. 重新输入A= I turned the moon into something I call a Death Star. 顺利通过

 

 3.阶段2

观察发现,phase_2在(8048d5d)处调用了一个函数<read_six_numbers>,猜测是读取6个数,并按照一定的规律进行输出。需要输入6个数A B C D E F,这里输入0 1 1 2 3 4进行测试。

(1)b*(0x8048d66)观察在调用函数<read_six_numbers>后内存的存放情况

(2)x/16wx $ebp-0x24查看从($ebp-0x24)开始,16个32位数的存放。可以发现从($ebp-0x24)~($ebp)存放的是输入的A B C D E F:0 1 1 2 3 4

 

(3)而分析(8048d66)和(8048d68)可以得知如果eax不等于0就会爆炸,而eax中存放的是($ebp-0x24)也就是A,则A必须为0

(4)b*(0x8048d6d) 这里有个cmp,如果eax!=1,就会爆炸。而根据(8048d6a)可知eax中存放的是($ebp-0x20)也就是B,则B必须为1

(5)b *(0x8048d9b) 可以得知6个数除了前两个数A,B,后面的数要通过前两个数相加得到。即A=0 , B=1 ,C=A+B,D=B+C ……,即最后的输出按照累加进行输出

(6)所以可得ABCDEF分别的:0 1 1 2 3 5

成功!

完整代码:

 4.阶段3

 

可以发现phase_4调用了<__isoc99_sscanf@plt>,并且在(8048df8)与(8048dfc)将两个有效地址入栈,可以猜测需要输入两个数分别是A,B而(8048e78)猜测是将A,B根据跳转表,进行相应的计算后得到的数字与特定值进行比较,如果相等即通过.这里输入“4 0”进行测试。

  1. b*(0x8048e15) 调用<__isoc99_sscanf@plt>后,eax=输入的数字个数,这段代码表示如果eax<=1则爆炸,则输入的数字个数需要大于1。
  2. p/x $eax $eax=2,输入两个数顺利通过
  3. b*(0x8048e23) 这里将eax和7进行了比较,需要eax<=7.而这里eax=($ebp-0x1c),查看后发现就是第一个输入A,则A<=7
  4. p/x $eax $eax=4,发现eax等于输入的第一个数字A=4,而eax是被($ebp-0x1c)赋值的,下一步对($ebp-0x1c)进行查看
  5. x/4wx $ebp-0x1c  对从($ebp-0x1c)开始的内存查看四个32位的数据,可以发现从($ebp-0x1c)~( $ebp)分别存放的是:(A B C 输入的数字个数),而查看发现C=0,也就是(4 0 0 2)

 

 

  1. b *(0x 8048e78)将($ebp-0x1c)与5进行比较,而($ebp-0x1c)中存放的是第一个输入A,如果A>5就爆炸,所以A<=5
  2. b *(0x 8048e80)可以发现这里将经过L14~L22后得到的($ebp-0x14),与eax进行比较,而eax=($ebp-0x18)=B,所以这里需要将第二个输入B与C比较,如果B=C,则phase_3通过
  3. 那么如何得到C的值呢,通过观察发现(0x8048e34)处跳转指令的跳转地点与第一个输入有关,是一个跳转表的结构。
  4. b*(0x8048e34) 设置断点后,开始跟踪,发现不同的A会得到不同的C,如果输入A=4,则在L22结束后会输出C=0,这时再让输入B=0,即可通过。

完整代码:

 5.阶段4

  1. b *(0x8048f51) 这里调用函数<__isoc99_sscanf@plt>
  2. b*(0x8048f5c) 这里如果($ebp-0x18)=2,则通过,否则会爆炸。而通过测试,发现($ebp-0x18)=输入的数字个数,所以可知要输入2个数A,B,这里输入“13 3”进行测试

  1. b *(0x8048f65) 这里如果eax=0则爆炸,而eax=($ebp-0x20)
  2. x/4wx ($ebp-0x20) 观察从($ebp-0x20)地址开始的4个32位的内容,发现    (($ebp-0x20)~($ebp-0x14))内存放的是                          (输入的A,B,输入的数字个数,未知D),

    也就是“13,3,2,0”,所以eax!=0,则A!=0

b *(0x8048f6c) 比较($ebp-0x20)和0xe,如果($ebp-0x20)<14,才可通过,也就是A<14

  1. b *(0x8048f76) 为未知D赋值3,D=3

  1. b *(0x8048f88) 调用<func4>函数并且将(第一个输入A,0,0xe)送入栈
  2. 进入func4函数发现内部其实是个递归调用,其中有两次调用入口,分别是X口:(8048ee2)和Y口:(8048f06)
  3. b *(0x8048ecd) 这里比较eax与0x8(%ebp),
  4. p/x $eax发现eax=7,而0x8(%ebp)=第一个输入A,如果eax<A则调转到Y口,从Y口进下一轮递归,如果eax>A,则从X口进,如果eax=A,则直接跳转到(8048f15)
  5. b *(0x8048f15) 可以发现这里是给eax赋0,而测试可发现这里的eax即是func4的返回参数。
  6. b *(0x8048f96) 这里将-0x10(%ebp), -0x14(%ebp)进行比较,即需要将-0x10(%ebp)与D进行比较,由(6)知,D=3,而-0x10(%ebp)=eax=<func4>返回的参数E,所以如果E!=3,则爆炸,所以返回的参数需要等于3,E=3
  7. 如何让返回参数eax=E=3,观察func4函数发现,(0x8048f15)会给eax赋0,所以要避免走(0x8048f15),而只有A=eax时会进入(0x8048f15)。
  8. b *(0x8048ecd) 测试发现eax的值随着A的不同而变化,这里我们输入的A=0xd,这时发现eax进行三次分别值为7 b d,用ni的指令依次往下读

第一次eax=7,A>7,进入Y口

第二次eax=b,A>b,进入Y口

第三次eax=d,A=d,进入(0x8048f15)赋值eax=0

  1. 然后第一次从Y口返回,b*(0x8048f10) 将eax=1
  2. 第二次从Y口返回,b*(0x8048f0e) 将eax=2,b*( 8048f10) eax=eax+1=3
  3. b *(0x8048f96) 返回参数E=3,顺利通过
  4. b *(0x8048f9e) -0x1c(%ebp)= -0x14(%ebp),也就是B=D=3
  5. 得知输入A=13,B=3

 

 

 

 6.阶段5

  1. b *(0x8048fe8) 这里调用了<string_length>函数
  2. b *(0x8048ff3) 这里将$0x6,-0x18(%ebp)比较,x/wx $ebp-0x18 得到-0x18(%ebp)=输入的个数,所以这里要求输入的字符个数为6
  3. b *(0x8049046) 这里调用了<strings_not_equal>,可知使要比较输入字符串S与key字符串是否一致,这里输入字符串“rrrrrr”
  4. x/s $ebx-0x2484 可以看到这里存放“flyers”,猜测与key有关
  5. x/s $ebp-0x13 这里存放“dddddd”,这里本来应该是我们输入的“rrrrrr”,现在变成了dddddd,说明字符串发生了某种变换,比较对应关系
  6. b *(0x80490x18),读取0x140(%ebx,%eax,1)= m a d u i e r s n f o t v b y l,而观察两两对应关系,只需要可以发现“flyers”在其中的顺序为”9,15,4,5,6,7”, 说明输入字符串中对应位的字符的最低4位的数值等于"9,f,e,5,6,7",即可通过这一关。查看ASCII值,得到ionefg

 7.阶段6

  1. b *(0x804937b) 这里调用了<read_six_numbers>,可知是需要输入6个数字
  2. b*(0x80490xb5) 用ni指令一步一步看代码,发现这是一处(0x80490b5—0x8049105)循环,目的是判断6个数字彼此之间并不相等 ,功能指令在b *(0x80490ea),这里将eax,edx做了比较,
  3. b *(0x80490c7) 这里将eax与6比较,p/x $eax eax=输入的数字,说明输入要小于6所以需要输入6个小于6的数字,b *(0x80490be) 这里要求eax>0,所以要输入6个小于6大于0的数字
  4. b *(0x8049161) 这段循环将每个地址赋值分别对应输入数字所对应的代码
  5. b *(0x804919a) 这里将-0x4c(%ebp)地址里的内容赋值给%edx,将-0x4c(%ebp)+8地址的内容赋值给%eax,x/4wx $ebp-0x4c 查看可以发现更改后,

(3) 19c  (4) b3  (5) d  (6) 3bb  (1) 14c  (2) a8

  1. b *(0x80491a7) 这里将eax,与edx比较,需要将数字所赋值的值从大到小进行排列
  2. 所以输出值为 6 3 1 4 2 5

9.隐藏阶段

首先寻找隐藏关卡的打开入口,发现只有在<phase_defused>中调用了<secret_phase>,而<phase_defused>出现在main()中,由main调用于是从main函数开始看起。

  1. b phase_defused 在<phase_defused>函数上打断点
  2. b *(0x80499db) 这里将eax与0x6进行比较,如果eax=6才可通过,p/x $eax而经过测试发现,每通过一关,eax的数量就会加1,所以这里意思是只有在第6关通过后,才可打开

  1. b*(8049a07)处调用<__isoc99_sscanf@plt>,先看需要输入什么类型的数据。可以发现在调用前将两个特殊地址入栈,可能是需要输入的数据,可以查看一下。
  2. x/s $ebx-0x2231  得$0x804adcf=“%d %d %s”,即输入两个数字加一个字符串
  3. x/s $eax+0xf0 得$0x804d4f0=“13,3” ,即是在第4关的输入后面加一个字符串,这里我们输入“rrr”试一下

  1. b *(0x8049a12) 经过测试发现,-0x60(%ebp)如果输入两个数字就是2,再输入字符串就是3,所以这里是测试是否按“%d %d %s”格式输入。
  2. b *(0x8049a26) 这里调用了<strings_not_equal>,说明要比较输入字符串是否与内部字符串key相同。
  3. x/s $ebp-0x5c 查看结果可以发现 得到“rrr”说明这是我们输入的字符串,
  4. x/s $ebx-0x2228 得到“DrEvil”,这就是内部字符串key,在第四关输入的时候,输入“13 3 DrEvil”,即可进入第七关

  1. 这里进入secret_phase b *(0x8049272) 将0x0与-0x10(%ebp)比较
  2. x/wx ($ebp-0x10) 得到这里的-0x10(%ebp)是指输入的数字
  3. b *(0x8049278) 这里将$0x3e9与-0x10(%ebp),如果输入<=1001,才可通过,这里输入一个数A:7
  4. b *(0x8049293) 这里调用了<func7>函数

  1. 进入<func7>函数,b*0x80491ea 这里将0x0,0x8(%ebp)比较
  2. b *(0x80491fc)这里将%eax,0xc(%ebp)比较,如果eax<=($ebp+0xc),则进入(0x804921a),如果eax>($ebp+0xc),则进入下一轮递归
  3. x/4wx $ebp+0xc 发现$ebp+0xc=7,是输入A。
  4. p/x $eax 发现eax=24
  5. 所以如果eax>A,则进入递归,eax=24>A=7,所以进入下一轮递归

  1.  c指令跳转到(0x80491fc)  p/x $eax  发现eax=8,eax=8>A=7,进入下一轮递归
  2. c指令跳转到(0x80491fc)  p/x $eax  发现eax=6,eax=6<A=7, 进入(0x804921a),经过测试发现eax每轮的值为24、8、6、1

  1. b *(0x804921f)这里将%eax,与0xc(%ebp)比较,如果相等则将eax=0
  2. p/x $eax eax=6 发现eax的值等于比A小的那个数,也就是A过不去的那个数,也就是如果eax=A,则将eax=0,而由b *(0x804929e)可知 这里将<func7>的输出值与4比较,如果不等就爆炸,所以我们知道通过<func7>要得到一个4的返回值,而这个返回值存放在eax里,也就是要使eax=4
  3. 如果要使eax=4,则应该避免让eax归0,所以eax!=A,所以A不能等于24、8、6、1这里(eax=6)!=(A=7),可以
  4. b*(0x8049242) 这里将eax=eax+1
  5. b*(0x49240)eax=eax+eax,可以发现如果递归三次从出口出,可以刚好使eax=4,即A=7可以使eax等于4

实验答案:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值