## NB The default assembly codes are generated by GDB in runtime. Those generated by Objdump would be commited with `# OBJDUMP` If it's connected to a grading system (eg. when this app is sent by your professor) remember to set a breakpoint for ALL ``, so that your GPA won't be affected :) English version will be available soon 👍😍❤️ _ov4 ``` ➜ bomblab git:(main) ✗ ./bomb ans.txt Welcome to my fiendish little bomb. You have 6 phases with which to blow yourself up. Have a nice day! Phase 1 defused. How about the next one? That's number 2. Keep going! Halfway there! So you got that one. Try this one. Good work! On to the next... Curses, you've found the secret phase! But finding it and solving it are quite different... Wow! You've defused the secret stage! Congratulations! You've defused the bomb! ➜ bomblab git:(main) ✗ ``` # commands `set disassembly-flavor att` nano ~/.gdbinit `b phase_1` 设置断点 `x/s` 访问内存 string `si` 步入 `ni` 步过 `x/6dw $rsp` - /6 6个单位 - d 十进制 - w 4字节 `x/8a` - a 作为地址(0x+偏移量) `x/5i` - i 作为指令 `checkpoint`: 创建当前时刻的快照。 `info checkpoints`: 查看所有已创建的快照。 `restart `: 回滚(恢复)到指定的快照 ID。 `delete checkpoint `: 删除快照。 `objdump -s -j .rodata bomb` 查看静态只读数据 # Phase 1 直接在寄存器里面找字符串 ```asm 0000000000400ee0 : 400ee0: 48 83 ec 08 sub $0x8,%rsp # 栈对齐 400ee4: be 00 24 40 00 mov $0x402400,%esi # %edi存储的是phase_1被调用的参数,%esi就是存储第二个比较的参数 400ee9: e8 4a 04 00 00 call 401338 400eee: 85 c0 test %eax,%eax # 检测返回值是不是0 400ef0: 74 05 je 400ef7 # 如果返回值是0 400ef2: e8 43 05 00 00 call 40143a 400ef7: 48 83 c4 08 add $0x8,%rsp # 恢复rsp 400efb: c3 ret ``` # Phase 2 `ni` 单步步过 随便输入一些根本不可能是答案的数字 遇到了不跳转,然后下面就是bomb的 答案肯定在cmp的其中一个操作数里面 # Phase 3 %esi对应的是格式字符 `0x4025cf: "%d %d"` 需要输入2个整数 ```asm 400f51: be cf 25 40 00 mov $0x4025cf,%esi 400f56: b8 00 00 00 00 mov $0x0,%eax 400f5b: e8 90 fc ff ff call 400bf0 <__isoc99_sscanf@plt> ``` ```c int sscanf(const char *str, const char *format, ...); ``` 之后找到cmp,答案就在寄存器里面 # Phase 4 func返回值需要是0,才能通关 如果第一次就(mid <= input) 并且 (mid >= input),明显只有7满足,那就会直接返回0 如果第一次不是7,那么要确保每次都走左边 如果最深层的right - left = 1,那么range在400fe6为0,400fee操作之后也保持0 最终最外层的func4也会返回0 func4运行完之后还会检测第二个数字,别被坑了(第二个很简单的) ```asm 0x401048 call 0x400fce 0x40104d test %eax, %eax 0x40104f jne 0x401058 TAKEN [Reason: !Z] ↳ 0x401058 call 0x40143a ``` 另见 `phase4.asm` # Phase 5 `phase5.asm` 字符转码表 `4024b0 maduiersnfotvbyl` 答案 `40245e flyers` 简单的for循环,遍历每个字符,然后查表转换之后放到stack # Phase 6 汇编太长了,自己去`phase6.asm`看,有完整的注释 1. 输入`6`个数组,检测是否在`[1, 6]`,并且没有重复 2. 假设原来的输入是`x`,将它转换成`7-x`,存储在`[rsp, rsp+20]` (`rsp+23`是最后一个占用的字节) 3. 按coded input的顺序,将地址提取到`rsp+32`开始的连续内存;如果coded input是`4 5 1 2 3`,那么rsp+32开始的指针对应的就是`node 4, 5, 2, 1, 3`的地址 4. 更新`node.next`,指向重新连接之后的下一个`node`,使得`next`的顺序与`[rsp+32, rsp+72]`存储的顺序相符(ie. 不要了`rsp+32`之后的内容,也知道重新连接之后的链表的顺序) 5. 检测顺序(要求是降序);很明显 ,`node 3, 4, 5, 6, 1, 2`排序时刚好降序,答案自然就出来了 ```c // 16 bytes each struct idk { int num; // 4 bytes int index; // 4 bytes idk *next; // 8 bytes } node[6]; ``` ``` gef➤ x/24dwx 0x6032d0 0x6032d0 : 0x0000014c 0x00000001 0x006032e0 0x00000000 0x6032e0 : 0x000000a8 0x00000002 0x006032f0 0x00000000 0x6032f0 : 0x0000039c 0x00000003 0x00603300 0x00000000 0x603300 : 0x000002b3 0x00000004 0x00603310 0x00000000 0x603310 : 0x000001dd 0x00000005 0x00603320 0x00000000 0x603320 : 0x000001bb 0x00000006 0x00000000 0x00000000 ``` # Secret `phase_defused`不是简单的函数!! 如果在phase 4的时候匹配到3个有效输入(不是2个吗?自己去看看汇编就知道了),并且第三个输入和预设的相符,那么就会在phase 6之后开启隐藏关 带有注释的汇编 `secret.asm` 二叉树 `tree.txt` 自己看看注释就知道怎么做了(~~其实是太困了不想写了,心态也崩了~~) # Solution See `ans.txt`