CSAPP:Boom

本文详细记录了使用GDB调试爆破程序的六个阶段,从读取字符串到拆弹,深入解析汇编代码,逐步解密每个阶段的逻辑。涉及到的知识点包括字符串比较、循环、条件判断和函数调用。通过对汇编代码的逐行分析,找出正确的输入值,最终成功解除所有炸弹。
摘要由CSDN通过智能技术生成

Boom!!!

要干啥:

顾名思义,拆炸弹,一共有6关,你要在每一关输入指定的字符串来拆除炸弹。

怎么干:

gdb基础:

具体使用看官网或者技术博客,我只在这里介绍本实验用到的gdb命令。

quit是退出gdb调试,可简写为q run让程序跑起来,一般是设置断点后输入该命令进行调试,可简写为r break设置断点,break后面跟着的可以是行,可以是函数名称,可以是地址,可简写为b step若为函数则进入函数,但是在本例中我使用sstep的简写情况)则直接跳过了进入函数步骤,到达主函数的下一条语句中。所以也导致了我第一颗雷爆炸了。T.T disas查看反汇编代码,后面可以跟着地址,可以跟着函数名称,也是本实验的主要命令。 print查看各种变量的命令,后面可以跟着寄存器名称如$rip,可以跟着地址,可以跟着变量名等等你也可以查看变量的16进制形式输出,10进制输出,2进制输出。 list查看代码。

查看汇编代码:

  1. objdump -d ./bomb >> bomb_ass.txt来反编译可执行性文件,查看所有的汇编代码。

  2. gdb根据list我们可以知道作者自定义的函数有initialize_bomb()read_line()phase_i其中i为1到6,phase_defused(),我是使用disas命令查看其汇编代码。 本实验使用第二种方法来查看汇编代码:

本实验使用注视与文字相结合的方式来解析问题

main

没用。

initialize_bomb

也没用但程序较少贴出来看看把。

(gdb) disas initialize_bomb
Dump of assembler code for function initialize_bomb:
   0x00000000004013a2 <+0>:     sub    $0x8,%rsp
   0x00000000004013a6 <+4>:     mov    $0x4012a0,%esi
   0x00000000004013ab <+9>:     mov    $0x2,%edi
   0x00000000004013b0 <+14>:    call   0x400b90 <signal@plt>
   0x00000000004013b5 <+19>:    add    $0x8,%rsp
   0x00000000004013b9 <+23>:    ret    
End of assembler dump.

read_line

没用,不用看。

phase_1

(gdb) disas phase_1
Dump of assembler code for function phase_1:
   0x0000000000400ee0 <+0>:     sub    $0x8,%rsp
   0x0000000000400ee4 <+4>:     mov    $0x402400,%esi# %rdi为调用phase_1的第一个参数也作为调用strings_not_equal的第一个参数。
   0x0000000000400ee9 <+9>:     call   0x401338 <strings_not_equal>
   0x0000000000400eee <+14>:    test   %eax,%eax #%eax为0才跳过炸弹。
   0x0000000000400ef0 <+16>:    je     0x400ef7 <phase_1+23>
   0x0000000000400ef2 <+18>:    call   0x40143a <explode_bomb>
   0x0000000000400ef7 <+23>:    add    $0x8,%rsp
   0x0000000000400efb <+27>:    ret    
End of assembler dump.

phase_1解决步骤:

  1. phase_1code中我们可以知道只有strings_not_equal返回0才会跳过炸弹启动代码。所以我们进入strings_not_equal

strings_not_equal

0000000000401338 <strings_not_equal>: 
# 在 phase_1 中 :
# 第一个实参是%rdi也是输入的字符串的值,第二个形参是%rsi是一个固定值:$0x402400
# return 必须为0才能不爆炸,即%rax必须为0
  401338:   41 54                   push   %r12
  40133a:   55                      push   %rbp
  40133b:   53                      push   %rbx
  40133c:   48 89 fb                mov    %rdi,%rbx # %rbx = %rdi(这个字符串的地址值,第一个实参)
  40133f:   48 89 f5                mov    %rsi,%rbp # %rbp = %rsi($0x402400,第二个实参)
  401342:   e8 d4 ff ff ff          call   40131b <string_length>
  401347:   41 89 c4                mov    %eax,%r12d # %r12d = string_length(%rdi)
  40134a:   48 89 ef                mov    %rbp,%rdi # %rdi = %rbp($0x402400)
  40134d:   e8 c9 ff ff ff          call   40131b <string_length> # %rax = string_length(%rdi),这里的%rdi就是$0x402400
  # 通过 (gdb) x /3sb 0x402400 可以得到其值:"Border relations with Canada have never been better."如果是中文则改变一个编码的长度也就是将b变为h
  401352:   ba 01 00 00 00          mov    $0x1,%edx # %rdx = 1
  401357:   41 39 c4                cmp    %eax,%r12d # %r12d = %r12d - %rax ,其中原来的%r12d是401347的值
  40135a:   75 3f                   jne    40139b <strings_not_equal+0x63> # 如果两次调用string_length的返回值不相同就 to 40139B
  40135c:   0f b6 03                movzbl (%rbx),%eax # %rax = 输入的字符串的值
  40135f:   84 c0                   test   %al,%al
  401361:   74 25                   je     401388 <strings_not_equal+0x50> # to 401388
  401363:   3a 45 00                cmp    0x0(%rbp),%al
  401366:   74 0a                   je     401372 <strings_not_equal+0x3a> # to 401372
  401368:   eb 25                   jmp    40138f <strings_not_equal+0x57> # to 40138F
  40136a:   3a 45 00                cmp    0x0(%rbp),%al
  40136d:   0f 1f 00                nopl   (%rax) # 虽然带有参数,但该指令什么都不做
  401370:   75 24                   jne    401396 <strings_not_equal+0x5e> # to 401396
  401372:   48 83 c3 01             add    $0x1,%rbx
  401376:   48 83 c5 01             add    $0x1,%rbp
  40137a:   0f b6 03                movzbl (%rbx),%eax # %rax = M[R[%rbx]], 因为是0扩展,所以近似于赋值给%rax
  40137d:   84 c0                   test   %al,%al #
  40137f:   75 e9                   jne    40136a <strings_not_equal+0x32> # to 40136A # %rax非0则跳转,若为0到下一条
  401381:   ba 00 00 00 00          mov    $0x0,%edx # 走这也行。
  401386:   eb 13                   jmp    40139b <strings_not_equal+0x63> # to 40139B
  401388:   ba 00 00 00 00          mov    $0x0,%edx # 走这也行
  40138d:   eb 0c                   jmp    40139b <strings_not_equal+0x63> # to 40139B
  40138f:   ba 01 00 00 00          mov    $0x1,%edx
  401394:   eb 05                   jmp    40139b <strings_not_equal+0x63> # to 40139B
  401396:   ba 01 00 00 00          mov    $0x1,%edx
  40139b:   89 d0                   mov    %edx,%eax # %rax = %rdx, 所以要想返回值为0,%rdx必须为0
  40139d:   5b                      pop    %rbx
  40139e:   5d                      pop    %rbp
  40139f:   41 5c                   pop    %r12
  4013a1:   c3                      ret    
  1. strings_not_equal有很多条线可以走,你要耐心的仔细的找,我是从后往前推导知道了走哪条路可以走到正确的结果,避免了无效的尝试。

  2. 通过分析代码我们可以知道问题的关键是string_length程序处,在strings_not_equal中在40134240134d分别调用了string_length,并且在401357进行比较,这是关键点。如果二者相同程序返回0退出,进而拆弹成功。(如果你害怕作者用strings_not_equal的名字骗你的话,去看一下strings_not_equal的源程序,我是这样做的...以防万一嘛^_^)

  3. 看两次调用string_length我们就可以知道,进行比较的分别是%rdi,%rsi所以从phase_1我们找到了,与输入值进行比较的字符串所在的地址$0x402400,通过x /3sb 0x402400 可以得到其值:"Border relations with Canada have never been better."即为答案。

第一问的疑问与思考:(一些废话)

  1. strings_not_equal仅仅是比较长度这么简单?那我把"Border relations with Canada have never been better."换成同样长度的字符可不可以,答案是肯定不可以啊。好吧这也是我没认真看汇编+爱玩的代价,又是一颗雷。为什么呢?

  2. 因为40135a的位置判断的是jne,如果长度相同肯定进行下一步的每个字符进行比较的操作了,那块就是我所说的比较嘈杂的部分了,如果你比较懒那你惟一的选择就是相信作者: Dr. Evil的代码和函数名实现的是一个功能喽。

phase_defused

可能的挖掘彩蛋的点

phase_2

(gdb) 
0000000000400efc <phase_2>:
# 就一个参数,存在%rdi中
# 进入函数后%rsp要-0x8
  400efc:   55                      push   %rbp # 被调用者保存 %rsp -= 8
  400efd:   53                      push   %rbx # 被调用者保存 %rsp -= 8
  400efe:   48 83 ec 28             sub    $0x28,%rsp # 申请一段栈空间
  400f02:   48 89 e6                mov    %rsp,%rsi # %rsp作为调用read_six_numbers的第二个参数为:7fffffffd5c0
  400f05:   e8 52 05 00 00          call   40145c <read_six_numbers> # read_six_numbers(%rdi, %rsi),其中%rsi = %rsp
  # read_six_numbers(%rdi, %rsi)返回 1
  400f0a:   83 3c 24 01             cmpl   $0x1,(%rsp) # M[R[%rsp]] - 1
  400f0e:   74 20                   je     400f30 <phase_2+0x34> # M[R[%rsp]]为1则跳转到400f30
  400f10:   e8 25 05 00 00          call   40143a <explode_bomb> # M[R[%rsp]]不为1则触发炸弹
  400f15:   eb 19                   jmp    400f30 <phase_2+0x34>
  400f17:   8b 43 fc                mov    -0x4(%rbx),%eax # %eax = M[R[%rbx] - 0x4] # %eax = 1
  400f1a:   01 c0                   add    %eax,%eax # %rax = 2 * %rax  # 从400f2c频繁的跳到这来,进行*2操作,并且每次循环R[%rbx]的位置+0x4
  400f1c:   39 03                   cmp    %eax,(%rbx) # M[R[%rbx]] - %rax #由此判断7fffffffd5c4存的数为2
  400f1e:   74 05                   je     400f25 <phase_2+0x29> # 若二者相等则跳转到400f25
  400f20:   e8 15 05 00 00          call   40143a <explode_bomb> # 二者不相等则触发炸弹
  400f25:   48 83 c3 04             add    $0x4,%rbx # %rbx = %rbx + 0x4 # %rbx = 7fffffffd5c8
  400f29:   48 39 eb                cmp    %rbp,%rbx # %rbx - %rbp 
  400f2c:   75 e9                   jne    400f17 <phase_2+0x1b> # 二者不相等则跳转到400f17,否则
  400f2e:   eb 0c                   jmp    400f3c <phase_2+0x40> # 二者相等跳转到400f3c,炸弹2解除
  400f30:   48 8d 5c 24 04          lea    0x4(%rsp),%rbx # %rbx = %rsp + 0x4 # %rbx = 7fffffffd5c4
  400f35:   48 8d 6c 24 18          lea    0x18(%rsp),%rbp # %rbp = %rsp + 0x18 # %rbp = 7fffffffd5d8,若以4为步长则%rbx与%rbp差距4步,与进入循环前的已知的两个数相加正好6个数字
  400f3a:   eb db                   jmp    400f17 <phase_2+0x1b>
  400f3c:   48 83 c4 28             add    $0x28,%rsp # 终点
  400f40:   5b                      pop    %rbx
  400f41:   5d                      pop    %rbp
  400f42:   c3                      ret    

解决:

在执行phase_2前运行gdb查看rsp的值(每次运行值可能结果不同但思路相通)

(gdb) p $rsp
$1 = (void *) 0x7fffffffd600

然后进入函数,以此为基础可以计算出其他的寄存器值,具体的看我的注释。注意:你的寄存器的数字和我的不一样是正常的,注释只是将汇编语言进行翻译和分析。我们到达400f05并且可以求出所有的参数值,进入read_six_numbers

read_six_numbers

000000000040145c <read_six_numbers>:
# 最终改动:不知道咋回事,如果第二个参数输入的是%rsp作为%rsi的值,那么,他存的的6个数字是连续的从调用read_six_numbers时的M[R[%rsp]+xx]可以访问到他们
# phase_2所调用的函数
# 第一个实参是输入的字符串地址,存在%rdi里,第二个实参是进入phase_2时%rsp值-0x28存在%rsi里
# %rsp = 7fffffffd5c0 - 8 = 7fffffffd5b8
  40145c:   48 83 ec 18             sub    $0x18,%rsp # 申请了3个0x8的空间,用来存数据,执行完之后%rsp = 7fffffffd5c0
  401460:   48 89 f2                mov    %rsi,%rdx # %rdx = %rsi(第二个实参)7fffffffd5c0
  401463:   48 8d 4e 04             lea    0x4(%rsi),%rcx # %rcx = %rsi + 4 所以 %rcx = 7fffffffd5c4
  401467:   48 8d 46 14             lea    0x14(%rsi),%rax # %rax = %rsi + 0x14 所以%rax = 7fffffffd5d8
  40146b:   48 89 44 24 08          mov    %rax,0x8(%rsp) # M[R[%rsp]+0x8] = %rax # 存的第二个,可能这一个用了16字节的空间
  401474:   48 89 04 24             mov    %rax,(%rsp) # M[R[%rsp]] = %rax # 存的第一个,大小为0x8,这里更改了以%rsp为地址的值:
  401478:   4c 8d 4e 0c             lea    0xc(%rsi),%r9 # %r9 = %rsi + 0xc # %r9 = 7fffffffd5cc
  40147c:   4c 8d 46 08             lea    0x8(%rsi),%r8 # %r8 = %rsi + 0x8 # %r8 = 7fffffffd5c8
  401480:   be c3 25 40 00          mov    $0x4025c3,%esi # %rsi = 0x4025c3
  401485:   b8 00 00 00 00          mov    $0x0,%eax # %rax = 0
  40148a:   e8 61 f7 ff ff          call   400bf0 <__isoc99_sscanf@plt> # 调用sscanf(%rdi, %rsi, %rdx, %rcx, %r8, %r9, %rsp + 0x8, %rsp+0xc) #第一个参数是输入的字符串地址
# (gdb) x /1sb 0x4025c3
# 0x4025c3:       "%d %d %d %d %d %d"
# 完毕之后输入值依次被存放到:(以%d的形式)
# 0x7fffffffd5c0
# 7fffffffd5c4
# 7fffffffd5c8
# 7fffffffd5cc
# 7fffffffd5c8
# 7fffffffd5d8
# 再次之前:7fffffffd5c0里存放着:7fffffffd5d8, 7fffffffd5c8里存放着也是:7fffffffd5d8
  40148f:   83 f8 05                cmp    $0x5,%eax # %rax - 0x5 #所以该函数return 1
  401492:   7f 05                   jg     401499 <read_six_numbers+0x3d> # rax大于5则跳过炸弹,说明sscanf成功读入了5个变量
  401494:   e8 a1 ff ff ff          call   40143a <explode_bomb>
  401499:   48 83 c4 18             add    $0x18,%rsp
  40149d:   c3                      ret    

根据401480可以得到%rsi的值,这正好是401485调用sscanf的第二个参数,使用(gdb) x /1sb 0x4025c3进行查看可以得到:0x4025c3: "%d %d %d %d %d %d"说明一共有6个数字从输入读入变量中,所以sscanf一共有8个变量,说明有两个变量是存在栈上的,即sscanf(%rdi, %rsi, %rdx, %rcx, %r8, %r9, %rsp + 8, %rsp+16) 其返回值是成功读入的数量为6,40148f401492可以最终确定,读入的数字是6个(6个以上的后面再说),然后从read_six_numbers跳转出来之后,400f0e400f10我们可以知道第一个数字是1,从400f1a400f1c可以知道第二个数字为2,然后会进入一个小循环,循环次数由%rbx%rbp之间的差值决定。而他们存的值正好对应着sscanf中存数的地址。而每一次程序都会比较这个值是否是上个值的二倍,如果是则进行下一次循环,如果不是则起爆炸弹。所以答案就是1 2 4 8 16 32

该问题的思考:(一些废话)

比较有意思的是,虽然0x4025c3存了6个%d但是如果我输入7个行不行呢?

Phase 1 defused. How about the next one?
1 2 4 8 16 32 hhh
That's number 2.  Keep going!

答案是可以的,因为sscanf返回的是成功写入的个数,而返回后phase_2并没有对%rax有任何限制。

学到的东西:

display /i $pc+si进行汇编级别的调试。 display /[] $[] 进行对寄存器值进行查看,以任意的方式 x /1sb <addr>进行地址数据查看,可以调整查看的元数据的长度单位,格式,查看数量等。

phase_3

(gdb) disas phase_3
0000000000400f43 <phase_3>:
  400f43:   48 83 ec 18             sub    $0x18,%rsp # 执行完之后%rsp = 0x7fffffffd880
  400f47:   48 8d 4c 24 0c          lea    0xc(%rsp),%rcx # %rcx = %rsp + 0xc(4字节)
  400f4c:   48 8d 54 24 08          lea    0x8(%rsp),%rdx # %rdx = %rsp + 0x8(4字节)
  400f51:   be cf 25 40 00          mov    $0x4025cf,%esi # %rsi = 0x4025cf
  400f56:   b8 00 00 00 00          mov    $0x0,%eax # %rax = 0
  400f5b:   e8 90 fc ff ff          call   400bf0 <__isoc99_sscanf@plt>
  400f60:   83 f8 01                cmp    $0x1,%eax # %rax - 1
  400f63:   7f 05                   jg     400f6a <phase_3+0x27> # 说明只有2个变量被读入,我们将第一个被读入的变量为x1,第二个为x2
# (gdb) x /1sb 0x4025cf
# 0x4025cf:       "%d %d"
  400f65:   e8 d0 04 00 00          call   40143a <explode_bomb>
  400f6a:   83 7c 24 08 07          cmpl   $0x7,0x8(%rsp) # M[R[%rsp] + 0x8 ] - 0x7,x1-0x7
  400f6f:   77 3c                   ja     400fad <phase_3+0x6a> # 如果x1 > 7 则引爆炸弹
  400f71:   8b 44 24 08             mov    0x8(%rsp),%eax # %rax = M[R[%rsp] + 0x8], 将x1赋值给rax
  400f75:   ff 24 c5 70 24 40 00    jmp    *0x402470(,%rax,8) # 跳转到8*%rax + (*0x402470),其中(*0x402470) = 0x 400f7c
  400f7c:   b8 cf 00 00 00          mov    $0xcf,%eax # %rax = $0xcf x1 = 0跳转到这里, rax = (207)_10
  400f81:   eb 3b                   jmp    400fbe <phase_3+0x7b> 
  400f83:   b8 c3 02 00 00          mov    $0x2c3,%eax # %rax = $0x2c3
  400f88:   eb 34                   jmp    400fbe <phase_3+0x7b>
  400f8a:   b8 00 01 00 00          mov    $0x100,%eax # %rax = $0x100
  400f8f:   eb 2d                   jmp    400fbe <phase_3+0x7b>
  400f91:   b8 85 01 00 00          mov    $0x185,%eax # %rax = $0x185
  400f96:   eb 26                   jmp    400fbe <phase_3+0x7b>
  400f98:   b8 ce 00 00 00          mov    $0xce,%eax # %rax = $0xce
  400f9d:   eb 1f                   jmp    400fbe <phase_3+0x7b>
  400f9f:   b8 aa 02 00 00          mov    $0x2aa,%eax # %rax = $0x2aa
  400fa4:   eb 18                   jmp    400fbe <phase_3+0x7b> # 跳转到这里可以正好对齐指令,这里对应的%rax = 5,即第一个输入为5
  400fa6:   b8 47 01 00 00          mov    $0x147,%eax # %rax = $0x147
  400fab:   eb 11                   jmp    400fbe <phase_3+0x7b>
  400fad:   e8 88 04 00 00          call   40143a <explode_bomb>
  400fb2:   b8 00 00 00 00          mov    $0x0,%eax # %rax = 0
  400fb7:   eb 05                   jmp    400fbe <phase_3+0x7b>
  400fb9:   b8 37 01 00 00          mov    $0x137,%eax # %rax = $0x137
  400fbe:   3b 44 24 0c             cmp    0xc(%rsp),%eax # rax - M[R[%rsp] + 0xc],说明第二个数字为%rax不同地方跳转过来的答案也就不同,从x1 = 0过来则x2 = 207
  400fc2:   74 05                   je     400fc9 <phase_3+0x86>
  400fc4:   e8 71 04 00 00          call   40143a <explode_bomb>
  400fc9:   48 83 c4 18             add    $0x18,%rsp
  400fcd:   c3                      ret   
End of assembler dump.

解决:

比较简单的一道题:

(gdb) x /1sb 0x4025cf
0x4025cf:       "%d %d"

实在看不懂,看一遍我标注的注释就可以看懂了。

本题的思考:

由于本题的答案不唯一,我得到了2个答案,其中跳转到400fac的情况是%rax = 5时,这时在运行到400fbe%rax的值都没有被改变,所以第一个答案就是5 5,但是这个答案却引爆了炸弹,这个问题我还没有解决,但是走%rax = 0这条路可以走通,即0 207

phase_4

(gdb) disas phase_4
000000000040100c <phase_4>:
  40100c:	48 83 ec 18          	sub    $0x18,%rsp
  401010:	48 8d 4c 24 0c       	lea    0xc(%rsp),%rcx # %rcx = %rsp + 0xc(4字节)
  401015:	48 8d 54 24 08       	lea    0x8(%rsp),%rdx # %rdx = %rsp + 0x8(4字节)
  40101a:	be cf 25 40 00       	mov    $0x4025cf,%esi # %rsi = $0x4025cf
# (gdb) x /1sb 0x4025cf
# 0x4025cf:       "%d %d"
  40101f:	b8 00 00 00 00       	mov    $0x0,%eax # %rax = 0
  401024:	e8 c7 fb ff ff       	call   400bf0 <__isoc99_sscanf@plt>
  401029:	83 f8 02             	cmp    $0x2,%eax # %rax - 2 
  40102c:	75 07                	jne    401035 <phase_4+0x29> # 从这里或者参数数量都可以推出输入了两个值
  40102e:	83 7c 24 08 0e       	cmpl   $0xe,0x8(%rsp) # M[R[%rsp]+0x8] - 0xe
  401033:	76 05                	jbe    40103a <phase_4+0x2e> # 如果输入的第一个变量<=0xe则跳过炸弹
  401035:	e8 00 04 00 00       	call   40143a <explode_bomb>
  40103a:	ba 0e 00 00 00       	mov    $0xe,%edx # %rdx = 0xe
  40103f:	be 00 00 00 00       	mov    $0x0,%esi # %rsi = 0
  401044:	8b 7c 24 08          	mov    0x8(%rsp),%edi # %rdi = M[R[%rsp] + 0x8] # 将输入的第一个变量赋值给%rdi
  401048:	e8 81 ff ff ff       	call   400fce <func4> # fun4(%rdi, %rsi, %rdx, %rcx)
  40104d:	85 c0                	test   %eax,%eax
  40104f:	75 07                	jne    401058 <phase_4+0x4c> # 如果返回值不等于0则触发炸弹
  401051:	83 7c 24 0c 00       	cmpl   $0x0,0xc(%rsp) # M[R[%rsp]+0xc] - 0
  401056:	74 05                	je     40105d <phase_4+0x51> # 如果M[R[%rsp]+0xc] 为 0则不触发炸弹,也就是输入的第二个值为0
  401058:	e8 dd 03 00 00       	call   40143a <explode_bomb>
  40105d:	48 83 c4 18          	add    $0x18,%rsp
  401061:	c3                   	ret    

End of assembler dump.

解决1:

第二个值在40105d可以看出是0,第一个值决定了func4的返回值,并且在401058可以得到返回值必须为0,由于func4后两个参数固定,第一个参数指定了范围,所以我们可以不去读懂func4通过试一遍就可以得到正确答案。 运用call获取特定参数下的函数返回值

call (int)func4(i, 0, 14)

其中i0-0xe

(gdb) call (int)func4(1, 0, 14)
$1 = 0
(gdb) call (int)func4(2, 0, 14) 
$2 = 4
(gdb) call (int)func4(3, 0, 14) 
$3 = 0
(gdb) call (int)func4(4, 0, 14) 
$4 = 2
(gdb) call (int)func4(5, 0, 14) 
$5 = 2
(gdb) call (int)func4(6, 0, 14)
$6 = 6
(gdb) call (int)func4(7, 0, 14)
$7 = 0
(gdb) call (int)func4(8, 0, 14)
$8 = 1
(gdb) call (int)func4(9, 0, 14)
$9 = 1
(gdb) call (int)func4(10, 0, 14)
$10 = 5
(gdb) call (int)func4(11, 0, 14)
$11 = 1
(gdb) call (int)func4(12, 0, 14)
$12 = 3
(gdb) call (int)func4(13, 0, 14)
$13 = 3
(gdb) call (int)func4(14, 0, 14)
$14 = 7

所以答案为(1,0) (3,0) (7,0),对还有(0,0)

解决2:

func4转化为c语言程序:

#include<stdio.h>
int func4(int x1,int x2,int x3){
    int rax = x3 - x2;
    int x4 = rax >> 31;//逻辑
    rax = (rax + x4) >> 1;//算数
    x4 = x2 + rax;
    if (x4 <= x1){
        rax = 0;
        if (x4 >= x1){
            return rax;
        }
        else{
            x2 = x4 + 1;
            //printf("func4(%d, %d, %d)\n", x1, x2, x3);
            return 2*func4(x1, x2, x3) + 1;
        }
    }else{
        x3 = x4 -1;
        //printf("func4(%d, %d, %d)\n", x1, x2, x3);
        return  2 * func4(x1, x2, x3);
    }

}
int main(){
    //int i = 1;
    for(int i = 0; i <=14; i++)
        printf("func4(%d, 0, 14)\t:%d\n",i, func4(i, 0, 14));
    return 0;
}

结果为:

func4(0, 0, 14) :0
func4(1, 0, 14) :0
func4(2, 0, 14) :4
func4(3, 0, 14) :0
func4(4, 0, 14) :2
func4(5, 0, 14) :2
func4(6, 0, 14) :6
func4(7, 0, 14) :0
func4(8, 0, 14) :1
func4(9, 0, 14) :1
func4(10, 0, 14)        :5
func4(11, 0, 14)        :1
func4(12, 0, 14)        :3
func4(13, 0, 14)        :3
func4(14, 0, 14)        :7

所以结果为:(0,0)(1,0)(3,0)(7,0)

学到的东西:

call (int)func4(i, 0, 14):可以对函数进行调试

phase_5

(gdb) disas phase_5
0000000000401062 <phase_5>:
  401062:	53                   	push   %rbx
  401063:	48 83 ec 20          	sub    $0x20,%rsp
  401067:	48 89 fb             	mov    %rdi,%rbx # %rbx = %rdi # %rdi 为存放输入字符串的地址
  40106a:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax # %rax = 0x933adc9274865d00
  401071:	00 00 
  401073:	48 89 44 24 18       	mov    %rax,0x18(%rsp) # M[R[%rsp]+0x18] = %rax
  401078:	31 c0                	xor    %eax,%eax # %rax = %rax ^ %rax
  40107a:	e8 9c 02 00 00       	call   40131b <string_length>
  40107f:	83 f8 06             	cmp    $0x6,%eax # %rax - 6
  401082:	74 4e                	je     4010d2 <phase_5+0x70> # 这里就要求输入的字符串长度为6了
  401084:	e8 b1 03 00 00       	call   40143a <explode_bomb>
  401089:	eb 47                	jmp    4010d2 <phase_5+0x70>
  40108b:	0f b6 0c 03          	movzbl (%rbx,%rax,1),%ecx # %rcx = %rax+ %rbx
  40108f:	88 0c 24             	mov    %cl,(%rsp) # M[R[%rsp]] = %rcx
  401092:	48 8b 14 24          	mov    (%rsp),%rdx # %rdx = M[R[%rsp]] # %rdx = 输入的字符串的某个字符
  401096:	83 e2 0f             	and    $0xf,%edx # %rdx = %rdx & %0xf # 只将最后4位保留,作为后面寻址的偏移量
  401099:	0f b6 92 b0 24 40 00 	movzbl 0x4024b0(%rdx),%edx # %rdx = M[R[%rdx] + 0x4024b0]
  4010a0:	88 54 04 10          	mov    %dl,0x10(%rsp,%rax,1) # M[R[%rax]+R[%rsp]+0x10] = %rdx
  4010a4:	48 83 c0 01          	add    $0x1,%rax # %rax = %rax + 0x1
  4010a8:	48 83 f8 06          	cmp    $0x6,%rax # %rax - 0x6
  4010ac:	75 dd                	jne    40108b <phase_5+0x29> # 如果%rax~=6则跳转到 40108b (从0开始循环到6结束跳出循环)
  4010ae:	c6 44 24 16 00       	movb   $0x0,0x16(%rsp) # M[R[%rsp]+0x16] = 0 # 后面的截断,只比较0x10-0x15这六个字符
  4010b3:	be 5e 24 40 00       	mov    $0x40245e,%esi # %rsi = $0x40245e :存的是“flyers”
  4010b8:	48 8d 7c 24 10       	lea    0x10(%rsp),%rdi # %rdi = %rsp + 0x10
  4010bd:	e8 76 02 00 00       	call   401338 <strings_not_equal>
  4010c2:	85 c0                	test   %eax,%eax
  4010c4:	74 13                	je     4010d9 <phase_5+0x77> # 如果 二者不相等则触发炸弹
  4010c6:	e8 6f 03 00 00       	call   40143a <explode_bomb>
  4010cb:	0f 1f 44 00 00       	nopl   0x0(%rax,%rax,1) # 跳过
  4010d0:	eb 07                	jmp    4010d9 <phase_5+0x77> # 跳转
  4010d2:	b8 00 00 00 00       	mov    $0x0,%eax # %rax = 0
  4010d7:	eb b2                	jmp    40108b <phase_5+0x29>
  4010d9:	48 8b 44 24 18       	mov    0x18(%rsp),%rax # %rax = M[R[%rsp] + 0x18]
  4010de:	64 48 33 04 25 28 00 	xor    %fs:0x28,%rax # %rax = %rax ^ M[R[%fs]+0x28]
  4010e5:	00 00 
  4010e7:	74 05                	je     4010ee <phase_5+0x8c> # 若二者相等则退出,炸弹成功解除
  4010e9:	e8 42 fa ff ff       	call   400b30 <__stack_chk_fail@plt>
  4010ee:	48 83 c4 20          	add    $0x20,%rsp
  4010f2:	5b                   	pop    %rbx
  4010f3:	c3                   	ret    
End of assembler dump.

解决:

具体步骤看注释即可了解

(gdb) x /1sb 0x40245e
0x40245e:       "flyers"
(gdb) x /1sb 0x4024b0
0x4024b0 <array.3449>:  "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"

其中,偏移量是取输入值的二进制后四位,所以范围也就被限定在了0-f

maduiersnfotvbylSo
0123456789abcdef

进而得到每个字符的偏移量: 9,f,e,5,6,7 但是着还不够,由于他读取输入的字符获取ASCII值,然后截取后四位得到偏移量,然后进而根据偏移量截取0x4024b0位置的字符,并与0x40245e字符进行比较,所以本题答案不唯一,只要满足其ASCII的后四位与9,f,e,5,6,7对应即可。

phase_6

(gdb) disas phase_6
00000000004010f4 <phase_6>:
  4010f4:	41 56                	push   %r14
  4010f6:	41 55                	push   %r13
  4010f8:	41 54                	push   %r12
  4010fa:	55                   	push   %rbp
  4010fb:	53                   	push   %rbx 
  4010fc:	48 83 ec 50          	sub    $0x50,%rsp
  # ---------------------(1-start)------------------------------------
  401100:	49 89 e5             	mov    %rsp,%r13 # %r13 = %rsp
  401103:	48 89 e6             	mov    %rsp,%rsi # %rsi = %rsp
  401106:	e8 51 03 00 00       	call   40145c <read_six_numbers> # read_six_numbers(%rdi, %rsi) 存读个数字
  # 数字存在M[R[%rsp]], M[R[%rsp]+0x4], M[R[%rsp]+0x8], M[R[%rsp]+0xc], M[R[%rsp]+0x10], M[R[%rsp]+0x14]中
  40110b:	49 89 e6             	mov    %rsp,%r14 # %r14 = %rsp
  40110e:	41 bc 00 00 00 00    	mov    $0x0,%r12d # %r12d = 0
  401114:	4c 89 ed             	mov    %r13,%rbp # %rbp = %r13
  401117:	41 8b 45 00          	mov    0x0(%r13),%eax # %rax = M[R[%r13]] # %rax是输入的第一个数字
  40111b:	83 e8 01             	sub    $0x1,%eax # %rax = %rax - 0x1
  40111e:	83 f8 05             	cmp    $0x5,%eax # %rax - 0x5
  401121:	76 05                	jbe    401128 <phase_6+0x34> # 如果M[R[%r13]]的值小鱼等于6的话,避开炸弹,这句话表示每个值都要xi<6
  401123:	e8 12 03 00 00       	call   40143a <explode_bomb>
  # ------------------------------(1-end,2-start)--------------------------
  401128:	41 83 c4 01          	add    $0x1,%r12d # %r12d = %r12d + 0x1(计数器)
  40112c:	41 83 fc 06          	cmp    $0x6,%r12d # %r12d - 0x6
  401130:	74 21                	je     401153 <phase_6+0x5f> # 如果%r12d 等于6则跳转,否则进入循环
  401132:	44 89 e3             	mov    %r12d,%ebx # %rbx = %r12d
  401135:	48 63 c3             	movslq %ebx,%rax # %rax = %rbx
  401138:	8b 04 84             	mov    (%rsp,%rax,4),%eax # %rax = M[4*R[%rax] + R[%rsp]]
  40113b:	39 45 00             	cmp    %eax,0x0(%rbp) # M[R[%rbp]] - %rax,M[R[%rbp]]里面存的是第一个输入的数字
  40113e:	75 05                	jne    401145 <phase_6+0x51> # M[R[%rbp]] 与 %rax 不相等则跳过炸弹,即要求每个数字与第一个数字不相等
  401140:	e8 f5 02 00 00       	call   40143a <explode_bomb>
  401145:	83 c3 01             	add    $0x1,%ebx # %rbx = %rbx + 0x1
  401148:	83 fb 05             	cmp    $0x5,%ebx # %rbx - 0x5
  40114b:	7e e8                	jle    401135 <phase_6+0x41> # 若 %rbx <= 0x5则 继续循环
  40114d:	49 83 c5 04          	add    $0x4,%r13 # %r13 = %r13 + 0x4
  401151:	eb c1                	jmp    401114 <phase_6+0x20> # 跳转
  # -------------------------------(2-end,3-start)------------------------------------
  # (以上是个双层循环,通过改变%r12d值,就是要和其他数字进行比较的值,与其他数字进行比较也是一层循环)
  401153:	48 8d 74 24 18       	lea    0x18(%rsp),%rsi # %rsi = %rsp + 0x18
  401158:	4c 89 f0             	mov    %r14,%rax # %rax = %r14(%rsp)
  40115b:	b9 07 00 00 00       	mov    $0x7,%ecx # %rcx = 0x7
  401160:	89 ca                	mov    %ecx,%edx # %rdx = %rcx
  401162:	2b 10                	sub    (%rax),%edx # %rdx = %rdx - M[R[%rax]]
  401164:	89 10                	mov    %edx,(%rax) # M[R[%rax]] = %rdx
  401166:	48 83 c0 04          	add    $0x4,%rax # %rax = %rax + 0x4
  40116a:	48 39 f0             	cmp    %rsi,%rax # %rax - %rsi
  40116d:	75 f1                	jne    401160 <phase_6+0x6c> # 如果%rax 与 %rsi 不相等则进入循环,直到相等退出循环
  # -------------------(3-end,4-start)-------------------------(a[i] = 7 - a[i])
  40116f:	be 00 00 00 00       	mov    $0x0,%esi # %rsi = 0
  401174:	eb 21                	jmp    401197 <phase_6+0xa3> # 跳转
  401176:	48 8b 52 08          	mov    0x8(%rdx),%rdx # %rdx = M[R[%rdx]+0x8]
  40117a:	83 c0 01             	add    $0x1,%eax # %rax = %rax + 1
  40117d:	39 c8                	cmp    %ecx,%eax # %rax - %rcx
  40117f:	75 f5                	jne    401176 <phase_6+0x82> # 如果%rax与%rcx不等则继续循环,否则跳出循环,出循环的时候,rax与读入的>1的数要一致
  401181:	eb 05                	jmp    401188 <phase_6+0x94> # 进入循环a
  401183:	ba d0 32 60 00       	mov    $0x6032d0,%edx # %rdx = 0x6032d0
  401188:	48 89 54 74 20       	mov    %rdx,0x20(%rsp,%rsi,2) # M[R[%rsi]*2+R[%rsp]+0x20] = %rdx
  40118d:	48 83 c6 04          	add    $0x4,%rsi # %rsi = %rsi + 0x4
  401191:	48 83 fe 18          	cmp    $0x18,%rsi # %rsi - 0x18(6次循环)
  401195:	74 14                	je     4011ab <phase_6+0xb7> # 如果 %rsi == 0x18则跳转--》出循环a的方式
  401197:	8b 0c 34             	mov    (%rsp,%rsi,1),%ecx # %rcx = M[R[%rsi] + R[%rsp]] # 对存的数字依次进行读取
  40119a:	83 f9 01             	cmp    $0x1,%ecx # %rcx - 0x1
  40119d:	7e e4                	jle    401183 <phase_6+0x8f> # 如果%rcx<=1则跳转
  40119f:	b8 01 00 00 00       	mov    $0x1,%eax # %rax = 0x1
  4011a4:	ba d0 32 60 00       	mov    $0x6032d0,%edx # %rdx = 0x6032d0 (x/24 0x6032d0)
  4011a9:	eb cb                	jmp    401176 <phase_6+0x82> # 跳转进入循环b
  # ----------------------(4-end,5-start)-----------------------------
  4011ab:	48 8b 5c 24 20       	mov    0x20(%rsp),%rbx # %rbx = M[R[%rsp]+0x20]
  4011b0:	48 8d 44 24 28       	lea    0x28(%rsp),%rax # %rax = %rsp + 0x28
  4011b5:	48 8d 74 24 50       	lea    0x50(%rsp),%rsi # %rsi = %rsp + 0x50
  4011ba:	48 89 d9             	mov    %rbx,%rcx # %rcx = %rbx
  # -----------------------(5-end,6-start)---------------------(上面是变量初始化)
  4011bd:	48 8b 10             	mov    (%rax),%rdx # %rdx = M[R[%rax]]
  4011c0:	48 89 51 08          	mov    %rdx,0x8(%rcx) # M[R[%rcx] + 0x8] = %rdx
  4011c4:	48 83 c0 08          	add    $0x8,%rax # %rax = %rax + 0x8
  4011c8:	48 39 f0             	cmp    %rsi,%rax # %rax - %rsi
  4011cb:	74 05                	je     4011d2 <phase_6+0xde> # 如果%rax与%rsi相等则跳出循环c
  4011cd:	48 89 d1             	mov    %rdx,%rcx # 否则 %rcx = %rdx
  4011d0:	eb eb                	jmp    4011bd <phase_6+0xc9> # 继续循环c
  # ---------------------------------------(6-end,7-start)----------------------------
  4011d2:	48 c7 42 08 00 00 00 	movq   $0x0,0x8(%rdx) # M[R[%rdx]+0x8] = 0 # 链表最后一个元素指针域为NULL
  4011d9:	00 
  4011da:	bd 05 00 00 00       	mov    $0x5,%ebp # %rbp = 0x5
  4011df:	48 8b 43 08          	mov    0x8(%rbx),%rax # %rax = M[R[%rbx]+0x8] # rax代表M[R[%rsp]+0x20]的指针域
  4011e3:	8b 00                	mov    (%rax),%eax # %rax = M[R[%rax]] # 指针值所指元素的值,即指针所在元素的下一个元素的值
  4011e5:	39 03                	cmp    %eax,(%rbx) # M[R[%rbx]] - %rax
  4011e7:	7d 05                	jge    4011ee <phase_6+0xfa> # 如果M[R[%rbx]] >= %rax 则跳过炸弹,说明一个比一个小,链表中的元素
  4011e9:	e8 4c 02 00 00       	call   40143a <explode_bomb>
  4011ee:	48 8b 5b 08          	mov    0x8(%rbx),%rbx # %rbx = M[R[%rbx]+ 0x8] # 这是链表?以此来遍历所有的元素
  4011f2:	83 ed 01             	sub    $0x1,%ebp # %rbp = %rbp - 0x1
  4011f5:	75 e8                	jne    4011df <phase_6+0xeb> # 如果 %rbp 不等于 0则继续循环c,否则退出函数,拆弹成功
  4011f7:	48 83 c4 50          	add    $0x50,%rsp
  4011fb:	5b                   	pop    %rbx
  4011fc:	5d                   	pop    %rbp
  4011fd:	41 5c                	pop    %r12
  4011ff:	41 5d                	pop    %r13
  401201:	41 5e                	pop    %r14
  401203:	c3                   	ret    

代码解释:

第1模块:读入输入的索引,并且约束索引值不超过6 第2模块:约束输入的索引两两不等 第3模块:a[i] = 7 - a[i],输入的索引进行转换 第4模块:根据索引,读取内置的链表中相应位置的值,并将其读入自己的链表中。 第5模块:初始化变量,用来控制链表指针域的构造。 第6模块:构造链表指针域。 第7模块:约束自己创造的链表的值必须依次不增(即相等或递减)。

解决:

从代码的第四部分,我们知道,我们输入的数字其实就是索引,用来截取0x6032d0开始的24个字节的数据,所以我们首先用gdb进行查看数据:

(gdb) x/24 0x6032d0
0x6032d0 <node1>:       332     1       6304480 0
0x6032e0 <node2>:       168     2       6304496 0
0x6032f0 <node3>:       924     3       6304512 0
0x603300 <node4>:       691     4       6304528 0
0x603310 <node5>:       477     5       6304544 0
0x603320 <node6>:       443     6       0       0

作者自定的链表:

(gdb) p /d *6304480
$6 = 168
(gdb) p /d *6304496
$7 = 924
(gdb) p /d *6304512
$8 = 691
(gdb) p /d *6304528
$9 = 477
(gdb) p /d *6304544
$10 = 443

由于4011e7表明,链表中值一个比一个小,所以,正确的索引是:3,4,5,6,1,2,在由于,代码第三模块表明,输入值进行了转换:a[i] = 7 - a[i]所以,正确的输入索引是:7-3,7-4,7-5,7-6,7-1,7-24,3,2,1,6,5

草稿(这块有很多错误,本模块的唯一作用是为了笔者自己回忆用)

思考与感想:

这个实验终于完成了,我知道肯定有很多彩蛋没有挖掘出来,因为在阅读汇编代码的过程中有很多函数并没有用到,这就等我有时间在挖掘吧,总的来说,着6个实验很不容易,锻炼了我的gdb调试能力与耐心,一步步的走,总的来说,感谢作者以及Dr. Evil,使一个单身青年即使在深夜也会因为拆掉一颗雷而兴奋不已。

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值