IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    The Hardware/Software Interface Lab2 bomb

    klion26发表于 2014-08-25 14:13:21
    love 0

    这个 bomb 是 Couresa 上面的一门课  The Hardware/Software Interface 中第四章的一个实验。同时也是 CSAPP   里面的一个作业。花了1天时间把这个做了。期间主要是用到了 gdb,objdump 的一些知识,当然还有一些汇编的基础知识,比如说在 64 位系统下,参数通过 rdi, rsi, rdx, rcx, r8, r9 传递,返回值在 rax中。其他的利用 gdb 差不多就可以完成了[这里只讲前五个关卡,不包括后面的附加关卡和隐藏]。

    首先我们不知道任何有关 bomb 的输入,所以直接 gdb 运行即可,随便输入看看程序需要什么[下面所有红色的斜体字表示命令]。

    一: 运行 gdb bomb。然后在 gdb 的命令行里面执行 b phase_1。然后运行程序,会发现程序停在那,等你输入,这个时候随便输入一些字符即可。然后发现程序执行到了 phase_1 处,利用 gdb 的命令 disas 反汇编指令查看 phase_1 函数的汇编语句,如下所示

    => 0x0000000000400e70 <+0>:    sub    $0x8,%rsp
    0x0000000000400e74 <+4>:    mov    $0x401af8,%esi
    0x0000000000400e79 <+9>:    callq  0x40123d <strings_not_equal>
    0x0000000000400e7e <+14>:    test   %eax,%eax
    0x0000000000400e80 <+16>:    je     0x400e87 <phase_1+23>
    0x0000000000400e82 <+18>:    callq  0x40163d <explode_bomb>
    0x0000000000400e87 <+23>:    add    $0x8,%rsp
    0x0000000000400e8b <+27>:    retq

    发现调用了一个叫做 string_not_equal 的函数,用 stepi 执行到第三行,然后根据函数返回结果(函数返回结果在 rax 中,eax 是 rax 的低 32 位)。判断是否 explode_bomb。那么我们利用 stepi 指令运行到 callq  0x40123d  这一行,利用 x /s $rdi 和 x /s $rsi 来查看 string_not_equal 函数的两个参数。发现  x /s $rsi 的输出是 "Science isn't about why, it's about why not?",x /s $rdi 的输出就是你输入的东西,也就是说我们需要输入的是这个字符串,这样就两个参数就相等了。也就是说,第一关我们需要输入的就是这个字符串"Science isn't about why, it's about why not?". 接下来低二关

    二:  在等待输入的时候,继续随便输入一些字符(我们只是用这些字符来调试的,从而得到正确的答案)。

    => 0x0000000000400e8c <+0>: mov %rbx,-0x20(%rsp)
     0x0000000000400e91 <+5>: mov %rbp,-0x18(%rsp)
     0x0000000000400e96 <+10>: mov %r12,-0x10(%rsp)
     0x0000000000400e9b <+15>: mov %r13,-0x8(%rsp)
     0x0000000000400ea0 <+20>: sub $0x48,%rsp
     0x0000000000400ea4 <+24>: mov %rsp,%rsi
     0x0000000000400ea7 <+27>: callq 0x401743 
     0x0000000000400eac <+32>: mov %rsp,%rbp
     0x0000000000400eaf <+35>: lea 0xc(%rsp),%r13
     0x0000000000400eb4 <+40>: mov $0x0,%r12d
     0x0000000000400eba <+46>: mov %rbp,%rbx
     0x0000000000400ebd <+49>: mov 0xc(%rbp),%eax
     0x0000000000400ec0 <+52>: cmp %eax,0x0(%rbp)
     0x0000000000400ec3 <+55>: je 0x400eca 
     0x0000000000400ec5 <+57>: callq 0x40163d 
     0x0000000000400eca <+62>: add (%rbx),%r12d
     0x0000000000400ecd <+65>: add $0x4,%rbp
     0x0000000000400ed1 <+69>: cmp %r13,%rbp
     0x0000000000400ed4 <+72>: jne 0x400eba 
     0x0000000000400ed6 <+74>: test %r12d,%r12d
     0x0000000000400ed9 <+77>: jne 0x400ee0 
     0x0000000000400edb <+79>: callq 0x40163d 
     0x0000000000400ee0 <+84>: mov 0x28(%rsp),%rbx
     0x0000000000400ee5 <+89>: mov 0x30(%rsp),%rbp
     0x0000000000400eea <+94>: mov 0x38(%rsp),%r12
     0x0000000000400eef <+99>: mov 0x40(%rsp),%r13
     0x0000000000400ef4 <+104>: add $0x48,%rsp
     0x0000000000400ef8 <+108>: retq

    在上面的额汇编代码中,我们看到首先,是会调用一个叫做 read_six_numbers 的函数,也就是说需要读入的是6个数字。然后接下来我们发现12行中把 0xc($rbp) 所对应的内存中的数据赋值给 %eax, 然后用 %eax 和 0x0($rbp) 做比较,如果不相等就爆炸,也就是说我们输入的6个数字中第1个数字和第4个数字必须相等. 我用的是数字 4. 从第11行到第19行,是一个循环,表示输入的这 6 个数的前4个都要相等表示第1个数和第4个数相等,第2个数和第5个数相等,第3个数和第6个数相等(相差3个位置)。谢谢网友@zxd 指出。在这里我用的是4个4,然后测试 $12d 是否为0, 这个 $12d 是前4个数字的和,不等于0就行了,否则就会爆炸了。接下来到了第3关

    三: 继续输入无关字符,我们停在 phase_3 处,得到如下汇编代码

    => 0x0000000000400ef9 <+0>: sub $0x18,%rsp
     0x0000000000400efd <+4>: lea 0x8(%rsp),%rcx
     0x0000000000400f02 <+9>: lea 0xc(%rsp),%rdx
     0x0000000000400f07 <+14>: mov $0x401ebe,%esi
     0x0000000000400f0c <+19>: mov $0x0,%eax
     0x0000000000400f11 <+24>: callq 0x400ab0 <__isoc99_sscanf@plt>
     0x0000000000400f16 <+29>: cmp $0x1,%eax
     0x0000000000400f19 <+32>: jg 0x400f20 
     0x0000000000400f1b <+34>: callq 0x40163d 
     0x0000000000400f20 <+39>: cmpl $0x7,0xc(%rsp)
     0x0000000000400f25 <+44>: ja 0x400f63 
     0x0000000000400f27 <+46>: mov 0xc(%rsp),%eax
     0x0000000000400f2b <+50>: jmpq *0x401b60(,%rax,8)
     0x0000000000400f32 <+57>: mov $0x217,%eax
     0x0000000000400f37 <+62>: jmp 0x400f74 
     0x0000000000400f39 <+64>: mov $0xd6,%eax
     0x0000000000400f3e <+69>: jmp 0x400f74 
     0x0000000000400f40 <+71>: mov $0x153,%eax
     0x0000000000400f45 <+76>: jmp 0x400f74 
     0x0000000000400f47 <+78>: mov $0x77,%eax
     0x0000000000400f4c <+83>: jmp 0x400f74 
     0x0000000000400f4e <+85>: mov $0x160,%eax
    ---Type  to continue, or q  to quit---
     0x0000000000400f53 <+90>: jmp 0x400f74 
     0x0000000000400f55 <+92>: mov $0x397,%eax
     0x0000000000400f5a <+97>: jmp 0x400f74 
     0x0000000000400f5c <+99>: mov $0x19c,%eax
     0x0000000000400f61 <+104>: jmp 0x400f74 
     0x0000000000400f63 <+106>: callq 0x40163d 
     0x0000000000400f68 <+111>: mov $0x0,%eax
     0x0000000000400f6d <+116>: jmp 0x400f74 
     0x0000000000400f6f <+118>: mov $0x39e,%eax
     0x0000000000400f74 <+123>: cmp 0x8(%rsp),%eax
     0x0000000000400f78 <+127>: je 0x400f7f 
     0x0000000000400f7a <+129>: callq 0x40163d 
     0x0000000000400f7f <+134>: add $0x18,%rsp
     0x0000000000400f83 <+138>: retq

    我们看到第6行调用 sscanf,然后第7行对 sscanf 的返回结果做判断,也就是说我们必须输入至少两个数字(或字符串),否则就爆炸了。然后跳到第10行,用我们输入的的第一个数字和7比较,不能大于7,否则就爆炸了。接下来需要知道13行中的代码表示是一个 switch 语句。其中 *0x401b60 表示 jump table 的地址,后面的 rax 表示第几个,8表示数据类型。由于我一开始输入的数字是 2,然后跳转到相应的位置(我们可以用 print *0x401b60 来查看 jump table 的起始位置,其中 gdb 的 print 命令用来输出值, x 命令用来显示相应位置的的内存内容,通俗的说 print 可以看成一个值,x 看成一个指针。)跳到第16行。然后把 $eax 和 第二个输入的数值做对比($eax 是在前面第 16 行进行的赋值,0xd6),所以我们的第二个参数设置位 0xd6(214) 就行了.然后到了第四关

    四:来到第四关,我们得到如下汇编代码

    => 0x0000000000400fc1 <+0>: sub $0x18,%rsp
     0x0000000000400fc5 <+4>: lea 0xc(%rsp),%rdx
     0x0000000000400fca <+9>: mov $0x401ec1,%esi
     0x0000000000400fcf <+14>: mov $0x0,%eax
     0x0000000000400fd4 <+19>: callq 0x400ab0 <__isoc99_sscanf@plt>
     0x0000000000400fd9 <+24>: cmp $0x1,%eax
     0x0000000000400fdc <+27>: jne 0x400fe5 
     0x0000000000400fde <+29>: cmpl $0x0,0xc(%rsp)
     0x0000000000400fe3 <+34>: jg 0x400fea 
     0x0000000000400fe5 <+36>: callq 0x40163d 
     0x0000000000400fea <+41>: mov 0xc(%rsp),%edi
     0x0000000000400fee <+45>: callq 0x400f84 
     0x0000000000400ff3 <+50>: cmp $0x37,%eax
     0x0000000000400ff6 <+53>: je 0x400ffd 
     0x0000000000400ff8 <+55>: callq 0x40163d 
     0x0000000000400ffd <+60>: add $0x18,%rsp
     0x0000000000401001 <+64>: retq

    首先看到 sscanf 函数,然后判断 eax 是否等于1,也就说说这里有且只有一个输入,然后在第8行把这个参数和0比较,必须大于0,否则爆炸。然后把这个输入作为参数调用 func4 。下面得到的是 func4 的汇编代码

    => 0x0000000000400f84 <+0>: mov %rbx,-0x10(%rsp)
     0x0000000000400f89 <+5>: mov %rbp,-0x8(%rsp)
     0x0000000000400f8e <+10>: sub $0x18,%rsp
     0x0000000000400f92 <+14>: mov %edi,%ebx
     0x0000000000400f94 <+16>: mov $0x1,%eax
     0x0000000000400f99 <+21>: cmp $0x1,%edi
     0x0000000000400f9c <+24>: jle 0x400fb2 
     0x0000000000400f9e <+26>: lea -0x1(%rbx),%edi
     0x0000000000400fa1 <+29>: callq 0x400f84 
     0x0000000000400fa6 <+34>: mov %eax,%ebp
     0x0000000000400fa8 <+36>: lea -0x2(%rbx),%edi
     0x0000000000400fab <+39>: callq 0x400f84 
     0x0000000000400fb0 <+44>: add %ebp,%eax
     0x0000000000400fb2 <+46>: mov 0x8(%rsp),%rbx
     0x0000000000400fb7 <+51>: mov 0x10(%rsp),%rbp
     0x0000000000400fbc <+56>: add $0x18,%rsp
     0x0000000000400fc0 <+60>: retq
    End of assembler dump.

    这份代码一开始的时候还是有点绕的,这个函数是一个递归函数。带回我们就可以看到这个函数的原函数了。
    首先我们看到,如果这个函数的参数小于等于1的话,那么直接返回(第7,8行的比较和跳转),设置的返回值是1(第6行,记着我们的返回值存在 $rax 中,$eax 是 $rax 的低位)。如果大于1的话,那么就调用两次改函数(调用自己),第一次的参数是 $rdi-1(这里的 $rdi 是函数传入的参数), 第二次的参数是 $rdi-2,其中第一个在第9行设置成 $rdi-1, 第二个函数在第12行,这里的 $rbx 是保存的 $rdi,然后把两个函数的结果相加得到改函数的返回结果,也就是变成了如下的原函数

    int func4(int x)
    {
      if(x<=1)
        return 1;
       return func4(x-1)+ func4(x-2);
    }

    接下来我们用这个原函数来计算相应的值,我们需要得到的结果等于 0x37.这个是在第四关的第13行。得到的是 9.到此我们第四关完成了,接下来是第无关

    五:第无关来了,得到如下的汇编代码

    => 0x0000000000401002 <+0>: sub $0x18,%rsp
     0x0000000000401006 <+4>: lea 0x8(%rsp),%rcx
     0x000000000040100b <+9>: lea 0xc(%rsp),%rdx
     0x0000000000401010 <+14>: mov $0x401ebe,%esi
     0x0000000000401015 <+19>: mov $0x0,%eax
     0x000000000040101a <+24>: callq 0x400ab0 <__isoc99_sscanf@plt>
     0x000000000040101f <+29>: cmp $0x1,%eax
     0x0000000000401022 <+32>: jg 0x401029 
     0x0000000000401024 <+34>: callq 0x40163d 
     0x0000000000401029 <+39>: mov 0xc(%rsp),%eax
     0x000000000040102d <+43>: and $0xf,%eax
     0x0000000000401030 <+46>: mov %eax,0xc(%rsp)
     0x0000000000401034 <+50>: cmp $0xf,%eax
     0x0000000000401037 <+53>: je 0x401065 
     0x0000000000401039 <+55>: mov $0x0,%ecx
     0x000000000040103e <+60>: mov $0x0,%edx
     0x0000000000401043 <+65>: add $0x1,%edx
     0x0000000000401046 <+68>: cltq
     0x0000000000401048 <+70>: mov 0x401ba0(,%rax,4),%eax
     0x000000000040104f <+77>: add %eax,%ecx
     0x0000000000401051 <+79>: cmp $0xf,%eax
     0x0000000000401054 <+82>: jne 0x401043 
     0x0000000000401056 <+84>: mov %eax,0xc(%rsp)
     0x000000000040105a <+88>: cmp $0xc,%edx
     0x000000000040105d <+91>: jne 0x401065 
     0x000000000040105f <+93>: cmp 0x8(%rsp),%ecx
     0x0000000000401063 <+97>: je 0x40106a 
     0x0000000000401065 <+99>: callq 0x40163d 
     0x000000000040106a <+104>: add $0x18,%rsp
     0x000000000040106e <+108>: retq
    End of assembler dump.

    同样我们看到 sscanf,然后判断返回值,必须大于1个参数,然后把输入的第一个参数与上 0xf。也就是把这个参数调整到 [1,15] 这个范围内,接下来17-22行一个循环,我们可以还原成一个函数,如下

    int a[] = {a, 2, e, 7, 8, c, f, b, 0, 4, 1, d, 3, 9, 6, 5};//16进制
    ecx = 0
    edx = 1;
    eax = a[eax];
    ecx += eax;
    while(eax != f)
    {
        ++edx;
        eax = a[eax];
        ecx += eax;
    }

    然后把 edx 和7比较,也就是说 我们必须让 edx =7.然后把 ecx 和设置的值做比较(也就是说我们输入的第二个参数),我们可以用反推出来的函数计算结果。最后就行了。最后就完全完成了。至此无关完全完成。 Oh,yeah!

    您可能也喜欢:

    用 GDB 调式 Nginx

    VMware 安装RedHat9时光盘无法挂载

    几个简单的数学题

    另类UX让你输入强口令

    网络流sap算法
    无觅


沪ICP备19023445号-2号
友情链接