BUPT lab2二进制炸弹(实验一到实验五)

一、实验目的


1.理解C语言程序的机器级表示。
2.初步掌握GDB调试器的用法。
3.阅读C编译器生成的x86-64机器代码,理解不同控制结构生成的基本指令模式,过程的实现。


二、实验环境


1.SecureCRT(10.120.11.12)
2.Linux
3.Objdump命令反汇编
4.GDB调试工具
5.Xshell 软件


三、实验内容


登录bupt1服务器,在home目录下可以找到Evil博士专门为你量身定制的一个bomb,当运行时,它会要求你输入一个字符串,如果正确,则进入下一关,继续要求你输入下一个字符串;否则,炸弹就会爆炸,输出一行提示信息并向计分服务器提交扣分信息。因此,本实验要求你必须通过反汇编和逆向工程对bomb执行文件进行分析,找到正确的字符串来解除这个的炸弹。
本实验通过要求使用课程所学知识拆除一个“binary bombs”来增强对程序的机器级表示、汇编语言、调试器和逆向工程等方面原理与技能的掌握。 “binary bombs”是一个Linux可执行程序,包含了5个阶段(或关卡)。炸弹运行的每个阶段要求你输入一个特定字符串,你的输入符合程序预期的输入,该阶段的炸弹就被拆除引信;否则炸弹“爆炸”,打印输出 “BOOM!!!”。炸弹的每个阶段考察了机器级程序语言的一个不同方面,难度逐级递增。
为完成二进制炸弹拆除任务,需要使用gdb调试器和objdump来反汇编bomb文件,可以单步跟踪调试每一阶段的机器代码,也可以阅读反汇编代码,从中理解每一汇编语言代码的行为或作用,进而设法推断拆除炸弹所需的目标字符串。

四。实验步骤及实验分析

1.准备工作:

登录bupt1服务器,使用ls命令,发现炸弹文件压缩包bomb322.tar。使用tar -xvf bomb322.tar解压文件压缩包,得到一个目录bomb322。使用cd bomb322命令进入目录,使用ls命令,可看到三个文件bomb,bomb.c,README 。(answer文件为我自己建立的文本文件,用于存储答案),我的压缩包已经解压过了,所以没有演示该步骤

输入cat README命令,阅读README文件内容,确认炸弹bomb322属于学生2022211270。

2.观察bomb.c文件

发现实验分为六步,分别位于phase_1,phase_2,phase_3,phase_4,phase_5,phase_6六个函数中。其中还有一个隐藏关卡7位于sectet_phase

2.开始拆弹

注:如果不想在试错过程中错误的引爆炸弹,可以在每一次run程序前键入break explode_bomb,在爆炸函数这里设置断点,这样的话一旦输错即跳转到该断点处,此时再键入kill 终止程序,这样的话,就可以既知道输入的测试样例为错误答案又能不引爆炸弹

示例:

如果出现BOOM!想要及时退出程序,按住ctrl c 即可退出!

实验一

键入gdb bomb 进入调试窗口

键入b phase_1在phase_1函数处设置断点。
键入命令disas phase_1,观察phase_1的代码,根据<phase_1+9>处的代码,发现该关卡应输入一字符串,并与对应的字符串相互比较,二者一致则拆除炸弹通过关。

同时观察代码,根据<phasse_1+4>处的代码,可发现从键盘输入的字符串应与存储在0x402740处的字符串相互比较。故键入x /s 0x402670,以字符串形式显示该地址处的数据。得到一串字符串

因此第一关的答案为字符串:I am the mayor. I can do anything I want.

键入命令r运行程序,输入该字符串

随后使用crtl+C命令和kill命令安全退出调试程序。

打开answer文件,存入第一关答案I am the mayor. I can do anything I want.保存并退出。键入cat answer,检查字符串是否已存入文件answer。

重新进入gdb调试工具,键入r answer ,关卡一通过,说明正确答案已正确存入文件answer。

关卡一通过。

实验二

键入b phase_2在phase_2函数处设置断点

键入命令disas phase_2,观察phase_2的代码,根据<phase_2+25>处的代码,可知我们应输入六个数字。

键入disas read_six_numbers,通过观察<__isoc99_sscanf@plt>上边寄存器%esi的内容发现输入的必须是6个整数

并且如果输入的数字大于5,才不会引爆炸弹。

继续观察phase_2函数,汇编代码分析:

Dump of assembler code for function phase_2:
   0x0000000000400f49 <+0>:     push   %rbp
   0x0000000000400f4a <+1>:     push   %rbx
   0x0000000000400f4b <+2>:     sub    $0x28,%rsp
   0x0000000000400f4f <+6>:     mov    %fs:0x28,%rax
   0x0000000000400f58 <+15>:    mov    %rax,0x18(%rsp)
   0x0000000000400f5d <+20>:    xor    %eax,%eax
   0x0000000000400f5f <+22>:    mov    %rsp,%rsi
   0x0000000000400f62 <+25>:    callq  0x40179f <read_six_numbers>
   0x0000000000400f67 <+30>:    cmpl   $0x1,(%rsp)
   0x0000000000400f6b <+34>:    je     0x400f72 <phase_2+41>
//第一个数等于一,跳过爆炸函数,不会引爆炸弹
   0x0000000000400f6d <+36>:    callq  0x401769 <explode_bomb>
   0x0000000000400f72 <+41>:    mov    %rsp,%rbx
//bx存放第一个数,bx==1
   0x0000000000400f75 <+44>:    lea    0x14(%rsp),%rbp
//bp存放(%rsp+0x14),转化为十进制是%rsp+20,(20/4==5)即bp存放输入的第6个整数
   0x0000000000400f7a <+49>:    mov    (%rbx),%eax//ax==bx==1
   0x0000000000400f7c <+51>:    add    %eax,%eax
//关键语句:ax==ax+ax==2*ax
   0x0000000000400f7e <+53>:    cmp    %eax,0x4(%rbx)
//后一个数与前一个数的2倍进行比较,如果相同则不引爆,不相同则引爆,
说明后一个整数是前一个整数的二倍,
说明输入的6个整数应该是以 1开头的以2为公比的等比数列,即1 2 4 8 16 32
   0x0000000000400f81 <+56>:    je     0x400f88 <phase_2+63>
   0x0000000000400f83 <+58>:    callq  0x401769 <explode_bomb>
   0x0000000000400f88 <+63>:    add    $0x4,%rbx
//第一个整数移动到第二个整数,以此类推
   0x0000000000400f8c <+67>:    cmp    %rbp,%rbx
/循环结束的条件是(%rbx+0x4)==%rbp,前一个数的2倍==第6个数时,循环结束。
   0x0000000000400f8f <+70>:    jne    0x400f7a <phase_2+49>
//若不相等则进入循环,跳转到<+49>步
   0x0000000000400f91 <+72>:    mov    0x18(%rsp),%rax
//与栈保护相关
   0x0000000000400f96 <+77>:    xor    %fs:0x28,%rax
   0x0000000000400f9f <+86>:    je     0x400fa6 <phase_2+93>
   0x0000000000400fa1 <+88>:    callq  0x400b90 <__stack_chk_fail@plt>
   0x0000000000400fa6 <+93>:    add    $0x28,%rsp
   0x0000000000400faa <+97>:    pop    %rbx
   0x0000000000400fab <+98>:    pop    %rbp
--Type <RET> for more, q to quit, c to continue without paging--c
   0x0000000000400fac <+99>:    retq

因此,这道题的答案是1 2 4 8 16 32

键入命令r answer运行程序,输入1 2 4 8 16 32

随后使用crtl+C命令和kill命令安全退出调试程序。
打开answer文件,存入第二关答案0 1 1 2 3 5.保存并退出。键入cat answer,检查字符串是否已存入文件answer。
重新进入gdb调试工具,键入r answer ,关卡通过,说明正确答案已正确存入文件answer。

实验三

键入b phase_3在phase_3函数处设置断点。

键入命令disas phase_3,观察phase_3的代码,发现,输入函数的参数与0x402796有关,

使用x /s 0x402796函数进行观察,发现本次需要输入两个整数与一个字符。

同时发现输入的内容>2个才不会引爆炸弹。

继续向后观察,发现对第一个输入数字的判断,第一个数字以无符号比较不可大于7

<+318>为炸弹函数

继续向后观察,发现后续代码中存在一个switch语句,跳转位置取决于输入的第一个数字。

%eax中放第一个输入的数字

jmpq   *0x4027b0(,%rax,8):*表示间接引用,8是指针存储为8字节的四字,用%rax*8才能到对应的跳转位置。

查看switch各分支的位置

由于第一个输入的数字<=7,且跳转表有8个跳转位置,因此有case0 case1.... case7

对应关系为:

(gdb) x /20xg 0x4027b0
0x4027b0:     case0: 0x0000000000400ffa    case1:0x000000000040101c
0x4027c0:     case2:  0x000000000040103e    case3: 0x0000000000401060
0x4027d0:     case4:  0x000000000040107f    case5: 0x000000000040109a
0x4027e0:     case6:  0x00000000004010b5    case7: 0x00000000004010d0

故,以第一个输入数字为1为例:

查看对应的汇编代码:

   0x0000000000401017 <+106>:   jmpq   0x4010f5 <phase_3+328>
   0x000000000040101c <+111>:   mov    $0x68,%eax
//101c对应的跳转位置,ax==0x68,第一个整数为1
   0x0000000000401021 <+116>:   cmpl   $0x201,0x14(%rsp)
//第二个整数为0x201,即256*2+1=513,不相等则直接bomb
   0x0000000000401029 <+124>:   je     0x4010f5 <phase_3+328>
//跳转到<+328>位置
   0x000000000040102f <+130>:   callq  0x401769 <explode_bomb>
   0x0000000000401034 <+135>:   mov    $0x68,%eax

   0x00000000004010f5 <+328>:   cmp    0xf(%rsp),%al
//al与ax寄存器的值相同,说明要找0x68对应的字符,即字符h
   0x00000000004010f9 <+332>:   je     0x401100 <phase_3+339>
   0x00000000004010fb <+334>:   callq  0x401769 <explode_bomb>
//下面与栈保护有关
   0x0000000000401100 <+339>:   mov    0x18(%rsp),%rax
   0x0000000000401105 <+344>:   xor    %fs:0x28,%rax
   0x000000000040110e <+353>:   je     0x401115 <phase_3+360>
   0x0000000000401110 <+355>:   callq  0x400b90 <__stack_chk_fail@plt>
   0x0000000000401115 <+360>:   add    $0x28,%rsp
   0x0000000000401119 <+364>:   retq

acsII码转换表

由分析可知,其中的一组答案为1 h 513,其他跳转过程同理。

随后使用crtl+C命令和kill命令安全退出调试程序。
打开answer文件,存入第三关答案1 h 513保存并退出。键入cat answer,检查字符串是否已存入文件answer。
重新进入gdb调试工具,键入r answer ,关卡通过,说明正确答案已正确存入文件answer。
至此,关卡三已通过。

实验四

键入b phase_4在phase_4函数处设置断点。

键入命令disas phase_4,观察phase_4的代码,同关卡三,我们可发现本题也是输入两个整数

%eax-0x2>=0,并且%eax-2<=2,由此可知

输入的数字n1 n2,有一个数字的范围在2-4(初步理解为n1>=2&&n1<=4)

观察phase_4代码可知,首先,%edi的值赋为6进入func4递归函数,%eax保存着递归函数返回值,与你输入的第二个数进行比对,如果%eax的值与你输入的第二个值相同,则不引爆炸弹,否则就会bomb。

因此,我们要输入的值:一个在2-4范围中,一个是初始值为6且经过func4函数的返回值,记为n1 n2

下面输入disas func4,观察其汇编代码

(gdb) disas func4
Dump of assembler code for function func4:
   0x000000000040111a <+0>:     test   %edi,%edi                
//判断%edi的值与0的关系
   0x000000000040111c <+2>:     jle    0x401149 <func4+47>   
 //如果<=0,则跳转到<+47>
   0x000000000040111e <+4>:     mov    %esi,%eax                
//如果%edi>0,则ax=si,初始时si==n1
   0x0000000000401120 <+6>:     cmp    $0x1,%edi//di与1比较
   0x0000000000401123 <+9>:     je     0x401153 <func4+57>
//di==1,跳转到<+57>
   0x0000000000401125 <+11>:    push   %r12
   0x0000000000401127 <+13>:    push   %rbp
   0x0000000000401128 <+14>:    push   %rbx
//入栈,引入参数,同时说明递归时用的是另外的寄存器,递归的参数为func4(edi,esi,eax)
   0x0000000000401129 <+15>:    mov    %esi,%ebp
//bp=si
   0x000000000040112b <+17>:    mov    %edi,%ebx
//bx=di
   0x000000000040112d <+19>:    lea    -0x1(%rdi),%edi
//di--
   0x0000000000401130 <+22>:    callq  0x40111a <func4>
//递归回到开头
   0x0000000000401135 <+27>:    lea   0x0(%rbp,%rax,1),%r12d
//r12d=ax+bp
   0x000000000040113a <+32>:    lea    -0x2(%rbx),%edi
//di=bx-2==di-2
   0x000000000040113d <+35>:    mov    %ebp,%esi
//si=bp
   0x000000000040113f <+37>:    callq  0x40111a <func4>
//递归回到开头
   0x0000000000401144 <+42>:    add    %r12d,%eax
//核心部分:ax=r12d+ax,又r12d=ax+bp,bp=si,ax=ax+si+ax==2*ax+n1;
   0x0000000000401147 <+45>:    jmp    0x40114f <func4+53>
//跳转至<+53>,出栈
   0x0000000000401149 <+47>:    mov    $0x0,%eax
   0x000000000040114e <+52>:    retq
//return 0
   0x000000000040114f <+53>:    pop    %rbx
   0x0000000000401150 <+54>:    pop    %rbp
   0x0000000000401151 <+55>:    pop    %r12
   0x0000000000401153 <+57>:    repz retq
End of assembler dump.

看着有些乱,整合一下:

(gdb) disas func4
Dump of assembler code for function func4:
   0x000000000040111a <+0>:     test   %edi,%edi
   0x000000000040111c <+2>:     jle    0x401149 <func4+47>
//if(edi<=0) return 0
   0x0000000000401149 <+47>:    mov    $0x0,%eax
   0x000000000040114e <+52>:    retq
   0x000000000040114f <+53>:    pop    %rbx
   0x0000000000401150 <+54>:    pop    %rbp
   0x0000000000401151 <+55>:    pop    %r12
   0x0000000000401153 <+57>:    repz retq

//else if(edi>0)
   0x000000000040111e <+4>:     mov    %esi,%eax
   0x0000000000401120 <+6>:     cmp    $0x1,%edi

//if(edi>0&&edi==1) return n1,结束递归的条件。
   0x0000000000401123 <+9>:     je     0x401153 <func4+57>
   0x0000000000401153 <+57>:    repz retq

//if(edi>0&&edi!=1)
   0x0000000000401125 <+11>:    push   %r12
   0x0000000000401127 <+13>:    push   %rbp
   0x0000000000401128 <+14>:    push   %rbx
   0x0000000000401129 <+15>:    mov    %esi,%ebp//ebp=esi=n1
   0x000000000040112b <+17>:    mov    %edi,%ebx
   0x000000000040112d <+19>:    lea    -0x1(%rdi),%edi
   0x0000000000401130 <+22>:    callq  0x40111a <func4>
//func4(edi--,esi,eax),esi=n1,并且%eax中存储func4函数的返回值
   0x0000000000401135 <+27>:    lea    0x0(%rbp,%rax,1),%r12d
//%r12d=func4(edi--,esi,eax)+n1
   0x000000000040113a <+32>:    lea    -0x2(%rbx),%edi//edi-2
   0x000000000040113d <+35>:    mov    %ebp,%esi
   0x000000000040113f <+37>:    callq  0x40111a <func4>
//%eax=func4(edi-2,esi,eax),esi=n1
   0x0000000000401144 <+42>:    add    %r12d,%eax

//(计算前)eax==func4(edi-2,esi,eax)
//r12d==func4(edi--,esi,eax)+n1
//(计算后)eax==func4(edi--,esi,eax)+func4(edi-2,esi,eax)+n1

//核心:func4(edi,esi,eax)func4(edi--,esi,eax)+func4(edi-2,esi,eax)+n1
   0x0000000000401147 <+45>:    jmp    0x40114f <func4+53>
  
End of assembler dump.

由此,若n1==2时

func4(1)==func4(0)+func4(-1)+n1==0+0+2==2

func4(2)==func4(1)+func4(0)+n1==2+0+2==4

func4(3)==func4(2)+func4(1)+n1==4+2+2==8

func4(4)==func4(3)+func4(2)+n1==8+4+2==14

func4(5)==func4(4)+func4(3)+n1==14+8+2==24

func4(6)==func4(5)+func4(4)+n1==24+14+2==40

因此 n1==2,n2==40为一组答案,(但不知道为啥,这里输入40 2才算过,输入2 40反而不行)

将答案输入程序进行测试,成功拆弹,后将答案存入answer文件。

实验五

键入b phase_5在phase_5函数处设置断点。
键入命令disas phase_5,观察phase_5的代码

观察代码,发现本次需要输入一个长度为6的字符串

(gdb) disas phase_5
Dump of assembler code for function phase_5:
   0x00000000004011c2 <+0>:     push   %rbx
   0x00000000004011c3 <+1>:     sub    $0x10,%rsp
   0x00000000004011c7 <+5>:     mov    %rdi,%rbx
   0x00000000004011ca <+8>:     mov    %fs:0x28,%rax
   0x00000000004011d3 <+17>:    mov    %rax,0x8(%rsp)
   0x00000000004011d8 <+22>:    xor    %eax,%eax
   0x00000000004011da <+24>:    callq  0x401477 <string_length>
//输入字符串,6个字符
   0x00000000004011df <+29>:    cmp    $0x6,%eax
   0x00000000004011e2 <+32>:    je     0x4011e9 <phase_5+39>
   0x00000000004011e4 <+34>:    callq  0x401769 <explode_bomb>

   0x00000000004011e9 <+39>:    mov    $0x0,%eax
//eax=0
   0x00000000004011ee <+44>:    movzbl (%rbx,%rax,1),%edx
//dx=bx+ax
   0x00000000004011f2 <+48>:    and    $0xf,%edx
//edx=edx&0xf
   0x00000000004011f5 <+51>:    movzbl 0x4027f0(%rdx),%edx
//dx=dx+0x4027f0
   0x00000000004011fc <+58>:    mov    %dl,(%rsp,%rax,1)
   0x00000000004011ff <+61>:    add    $0x1,%rax
//ax=ax+1
   0x0000000000401203 <+65>:    cmp    $0x6,%rax
   0x0000000000401207 <+69>:    jne    0x4011ee <phase_5+44>
//存在循环,rax记录当前的字符的位置,如果rax!=6,则继续循环
   0x0000000000401209 <+71>:    movb   $0x0,0x6(%rsp)
//6个字符后面赋值为0
   0x000000000040120e <+76>:    mov    $0x40279f,%esi
//esi=0x40279f
   0x0000000000401213 <+81>:    mov    %rsp,%rdi
//rdi放首字符地址
   0x0000000000401216 <+84>:    callq  0x401495 <strings_not_equal>
   0x000000000040121b <+89>:    test   %eax,%eax
   0x000000000040121d <+91>:    je     0x401224 <phase_5+98>
//判断字符串是否匹配
   0x000000000040121f <+93>:    callq  0x401769 <explode_bomb>
   0x0000000000401224 <+98>:    mov    0x8(%rsp),%rax
   0x0000000000401229 <+103>:   xor    %fs:0x28,%rax
   0x0000000000401232 <+112>:   je     0x401239 <phase_5+119>
--Type <RET> for more, q to quit, c to continue without paging--c
   0x0000000000401234 <+114>:   callq  0x400b90 <__stack_chk_fail@plt>
   0x0000000000401239 <+119>:   add    $0x10,%rsp
   0x000000000040123d <+123>:   pop    %rbx
   0x000000000040123e <+124>:   retq
End of assembler dump.

 观察到两个奇怪的地址,用x  /s 指令查看内容,发现是两个字符串,而且0x40279f对应的刚好是6个字符,再返回去观察汇编代码

可以推理出:edx=edx$0xf,存放的是每一次输入的字符的十六进制值与0xf按位与的结果

按照这个结果作为位置索引,在maduiersnfotvbylSo字符串中找到对应的字符,最终组成的字符串按顺序与bruins作比较,相同则通过,不相同则引爆炸弹。

示例  设未知数x,它的低四位与1111按位与后结果是它本身,这个结果如果算出13,根据13这个下标在maduiersnfotvbylSo找到“b”,构成“bruins”的第一个字符,而x本身的低四位是D,根据ASCII表找出m符合条件。

b在“maduiersnfotvbylSo”中为第14个,对应的下标为13,ASCII中低四位二进制为13的字符有m
r在“maduiersnfotvbylSo”中为第7个,对应的下标为6,ASCII中低四位二进制为6的字符有f
u在“maduiersnfotvbylSo”中为第4个,对应的下标为3,ASCII中低四位二进制为3的字符有c
i在“maduiersnfotvbylSo”中为第5个,对应的下标为4,ASCII中低四位二进制为4的字符有d,t
n在“maduiersnfotvbylSo”中为第9个,对应的下标为8,ASCII中低四位二进制为8的字符有h,x 
s在“maduiersnfotvbylSo”中为第8个,对应的下标为7,ASCII中低四位二进制为7的字符有g,w

因此,一组结果为mfcdhg。

将答案输入程序进行测试,成功拆弹,后将答案存入answer文件。

心得体会

1.汇编语言中$加操作数表示立即数

2.分析函数时不要只看某几条语句,看到不懂的地方就先向下看,根据后面的逻辑反推前面的逻辑。
3、 大胆的猜测也是一个很重要的策略。
4、递归函数和循环往往很复杂,这种时候好的转换为C语言伪代码的能力就变得很重要

5.有时候没必要分析每一句的含义,学会抓大放小。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值