/ Computer Science

Frank 干员潜入 Bomb Lab 拆弹记

Dr. Evil's Insidious Bomb

Bomb Lab 是 《深入理解计算机系统》 (Computer Systems A Programmer's perspective) 中对应第三章内容:程序的机器级表示的lab。主要内容为提供一个二进制对象文件bomb,当运行时,它会要求用户输入六个字符串,如果其中的任何一个不正确,炸弹就会爆炸,输出一行错误信息。

学生必须通过反汇编和逆向工程来找到六个正确的字符串来解除自己的炸弹

理论上每个人的炸弹答案都不同, 本文章仅供参考

拆弹工具

  • objdump - 用于反汇编二进制对象文件

  • gedit - 用于查看反汇编后的结果与文本文件的编写

  • gdb - 用于运行时单步调试与查看运行时内存与寄存器信息

开始拆弹

第一阶段

 08048b90 <phase_1>:

 8048b90:   83 ec 1c                sub    $0x1c,%esp

 8048b93:   c7 44 24 04 3c a1 04    movl   $0x804a13c,0x4(%esp)

 8048b9a:   08 

 8048b9b:   8b 44 24 20             mov    0x20(%esp),%eax

 8048b9f:   89 04 24                mov    %eax,(%esp)

 8048ba2:   e8 73 04 00 00          call   804901a <strings_not_equal>

 8048ba7:   85 c0                   test   %eax,%eax

 8048ba9:   74 05                   je     8048bb0 <phase_1+0x20>

 8048bab:   e8 75 05 00 00          call   8049125 <explode_bomb>

 8048bb0:   83 c4 1c                add    $0x1c,%esp

 8048bb3:   c3                      ret    


① 从call 804901a <strings_not_equal>这里在比较字符串应该可以猜到第一关要我们输入的是一串字符,如果输入的字符和它要求 的字符串一样的话,就ok,不然就会boom。

首先,在进入bomb函数前先设置断点,先找到call 8049125 <explode_bomb>,输入b *0x8049125

②然后输入r,运行

③找到movl $0x804a13c,0x4(%esp),输入p (char*)0x804a13c打印出存放的内容

④退出gdb,输入./bomb,输入刚刚输出的字符串,即可成功拆掉第一弹。

第二阶段


08048bb4 <phase_2>:

 8048bb4:   53                      push   %ebx

 8048bb5:   83 ec 38                sub    $0x38,%esp

 8048bb8:   8d 44 24 18             lea    0x18(%esp),%eax

 8048bbc:   89 44 24 04             mov    %eax,0x4(%esp)

 8048bc0:   8b 44 24 40             mov    0x40(%esp),%eax

 8048bc4:   89 04 24                mov    %eax,(%esp)

 8048bc7:   e8 80 05 00 00          call   804914c <read_six_numbers>

 8048bcc:   83 7c 24 18 00          cmpl   $0x0,0x18(%esp)

 8048bd1:   79 22                   jns    8048bf5 <phase_2+0x41>

 8048bd3:   e8 4d 05 00 00          call   8049125 <explode_bomb>

 8048bd8:   eb 1b                   jmp    8048bf5 <phase_2+0x41>

 8048bda:   89 d8                   mov    %ebx,%eax

 8048bdc:   03 44 9c 14             add      0x14(%esp,%ebx,4),%eax

 8048be0:   39 44 9c 18             cmp    %eax,0x18(%esp,%ebx,4)

 8048be4:   74 05                   je     8048beb <phase_2+0x37>

 8048be6:   e8 3a 05 00 00          call   8049125 <explode_bomb>

 8048beb:   83 c3 01                add    $0x1,%ebx

 8048bee:   83 fb 06                cmp    $0x6,%ebx

 8048bf1:   75 e7                   jne    8048bda <phase_2+0x26>

 8048bf3:   eb 07                   jmp    8048bfc <phase_2+0x48>

 8048bf5:   bb 01 00 00 00          mov    $0x1,%ebx

 8048bfa:   eb de                   jmp    8048bda <phase_2+0x26>

 8048bfc:   83 c4 38                add    $0x38,%esp

 8048bff:   5b                      pop    %ebx

 8048c00:   c3                      ret    
 
  1. 首先我们观察8048bc7这一行,它调用了read_six_numbers这个方法读取六个数字。所以我们要输入六个数字

  2. 炸弹的触发条件很明确,一旦%eax和参数不相等,那么炸弹便会爆炸。否则继续拆弹循环。

  3. 在第一个循环时,%eax和parameter(i+1)做比较,以此类推。

  4. 而%eax=%ebx+parameter(i),同时我们可以很惊奇的发现8048beb 每次都对%ebx 自增1, 我们可以把 ebx 看作为 for 循环时的迭代数字。 所以整个问题便迎刃而解了。只要满足parameter(i+1)= i + parameter(i)就不会引爆炸弹

  5. 同时由开头的代码处(8048bd1) 可以看出,第一个数字必须大于等于0,否则立马引爆炸弹。我觉得使用1作为第一个数字。

  6. 而从最后的代码可以看出这个循环会进行5次,也就是对输入的前6个参数进行比较。所以答案可以是

所以阶段2的答案为1 2 4 7 11 16.

第三阶段


08048c01 <phase_3>:

 8048c01:   83 ec 2c                sub    $0x2c,%esp

 8048c04:   8d 44 24 1c             lea    0x1c(%esp),%eax

 8048c08:   89 44 24 0c             mov    %eax,0xc(%esp)

 8048c0c:   8d 44 24 18             lea    0x18(%esp),%eax

 8048c10:   89 44 24 08             mov    %eax,0x8(%esp)

 8048c14:   c7 44 24 04 db a2 04    movl   $0x804a2db,0x4(%esp)

 8048c1b:   08 

 8048c1c:   8b 44 24 30             mov    0x30(%esp),%eax

 8048c20:   89 04 24                mov    %eax,(%esp)

 8048c23:   e8 38 fc ff ff          call   8048860 <__isoc99_sscanf@plt>

 8048c28:   83 f8 01                cmp    $0x1,%eax

 8048c2b:   7f 05                   jg     8048c32 <phase_3+0x31>

 8048c2d:   e8 f3 04 00 00          call   8049125 <explode_bomb>

 8048c32:   83 7c 24 18 07          cmpl   $0x7,0x18(%esp)

 8048c37:   77 3c                   ja     8048c75 <phase_3+0x74>

 8048c39:   8b 44 24 18             mov    0x18(%esp),%eax

 8048c3d:   ff 24 85 9c a1 04 08    jmp    *0x804a19c(,%eax,4)

 8048c44:   b8 5d 02 00 00          mov    $0x25d,%eax

 8048c49:   eb 3b                   jmp    8048c86 <phase_3+0x85>

 8048c4b:   b8 af 00 00 00          mov    $0xaf,%eax

 8048c50:   eb 34                   jmp    8048c86 <phase_3+0x85>

 8048c52:   b8 14 01 00 00          mov    $0x114,%eax

 8048c57:   eb 2d                   jmp    8048c86 <phase_3+0x85>

 8048c59:   b8 dd 01 00 00          mov    $0x1dd,%eax

 8048c5e:   eb 26                   jmp    8048c86 <phase_3+0x85>

 8048c60:   b8 59 01 00 00          mov    $0x159,%eax

 8048c65:   eb 1f                   jmp    8048c86 <phase_3+0x85>

 8048c67:   b8 4f 00 00 00          mov    $0x4f,%eax

 8048c6c:   eb 18                   jmp    8048c86 <phase_3+0x85>

 8048c6e:   b8 95 03 00 00          mov    $0x395,%eax

 8048c73:   eb 11                   jmp    8048c86 <phase_3+0x85>

 8048c75:   e8 ab 04 00 00          call   8049125 <explode_bomb>

 8048c7a:   b8 00 00 00 00          mov    $0x0,%eax

 8048c7f:   eb 05                   jmp    8048c86 <phase_3+0x85>

 8048c81:   b8 19 01 00 00          mov    $0x119,%eax

 8048c86:   3b 44 24 1c             cmp    0x1c(%esp),%eax

 8048c8a:   74 05                   je     8048c91 <phase_3+0x90>

 8048c8c:   e8 94 04 00 00          call   8049125 <explode_bomb>

 8048c91:   83 c4 2c                add    $0x2c,%esp

 8048c94:   c3                      ret    

  1. 下一步调用了库函数sscanf,我们想到sscanf中的参数中需要一个格式化字符串,那么esp中的这个地址值就很有可能存放了这个字符串,我们同样使用gdb在运行时查看这个字符串:

https://ws1.sinaimg.cn/large/006tKfTcgy1frj3lybs9sj30bq058glg.jpg

  1. 下面第x行将eax与1进行比较,eax一般用于存放函数返回值,而sscanf 的返回值是成功读入的数值个数,也就是说这几行将成功读入的个数与1进行比较,如果大于1则跳过引爆的代码。

  2. 下面第x行将esp+0x18中存放的值与0x7进行比较,如果大于则跳到8048c75的位置,我们看这个地址的指令:

8048c75:   e8 ab 04 00 00          call   8049125 <explode_bomb>

假设读入的第一个数为x,看到所有分支最后都跳转到了8048c86 这行判断 (cmp) 中,将eax中的值与esp+0x1c也就是我们读入的第二个数进行判断,如果相等的话跳过引爆代码。

  1. 结果详细分析,我们得出了等价的伪代码
function phase_3(input) {

   var a, b :Int;

    val qty :Int = sscanf(input, "%d %d", &a, &b); // Side effect with a b

    if (qty <= 1) {explode_bomb();}

    switch (a) {

    case 0: a = 605; break;

    case 1: a = 281; break;

    case 2: a = 175; break;

    case 3: a = 276; break;

    case 4: a = 477; break;

    case 5: a = 345; break;

    case 6: a = 79; break;

    case 7: a = 917; break;

    default: {
        explode_bomb(); // 0<a || a >7 is boom!!
        a = 0; 
        break;
    } 

    }

    if (a != b) {explode_bomb();}

}


  1. 通过以上等价代码也就是说我们只要根据不同的第一个参数的值读入对应的第二个参数就可以了,所以我们可以随意选择一个x值,这里我选择x=1,对应的第二个参数为0x119换成十进制是281,所以第3阶段的答案为:
A的值 B的值
0 605
1 281
2 175
3 276
4 477
5 345
6 79
7 917