// 对bomb可执行文件进行调试
gdb bomb
// 通过disas指令可以通过反汇编操作查看phase_1这个函数的汇编代码
disas phase_1
// 或者可以直接通过这个指令生成整个反汇编文件
objdump -d bomb > bomb.s
400e32: e8 67 06 00 00 callq 40149e
400e37: 48 89 c7 mov %rax,%rdi
400e3a: e8 a1 00 00 00 callq 400ee0
x/s 0x402400
即可获得这个字符串Dump of assembler code for function phase_1:
// 函数调用时的压栈操作
0x0000000000400ee0 <+0>: sub $0x8,%rsp
// 将0x402400这个地址放入esi寄存器
// 作为strings_not_equal的一个参数
0x0000000000400ee4 <+4>: mov $0x402400,%esi
0x0000000000400ee9 <+9>: callq 0x401338
// 将eax和eax寄存器进行按位与操作,并把结果放入标志寄存器
// eax是上面那个函数的返回值,如果eax为0,说明这两个字符是相同的
0x0000000000400eee <+14>: test %eax,%eax
// 如果零标志位为1,则je成功运行,即躲过explode_bomb
// 也就是说零标志位如果是0,代表eax不是0,那么就会爆炸
0x0000000000400ef0 <+16>: je 0x400ef7
// 爆炸
0x0000000000400ef2 <+18>: callq 0x40143a
0x0000000000400ef7 <+23>: add $0x8,%rsp
0x0000000000400efb <+27>: retq
End of assembler dump.
反汇编代码如下
这个phase的大意是通过sccanf读取我们输入的6个数字,如果能够满足是1 2 4 8 16 32的话,那就可以通过。
结合着汇编代码去看,应该不难理解这个phase的意思。
0000000000400efc :
400efc: 55 push %rbp
400efd: 53 push %rbx
400efe: 48 83 ec 28 sub $0x28,%rsp
400f02: 48 89 e6 mov %rsp,%rsi
400f05: e8 52 05 00 00 callq 40145c
400f0a: 83 3c 24 01 cmpl $0x1,(%rsp) // 栈指针指向的内存的值应该是1
400f0e: 74 20 je 400f30
400f10: e8 25 05 00 00 callq 40143a
400f15: eb 19 jmp 400f30
400f17: 8b 43 fc mov -0x4(%rbx),%eax // 将rbx-4的位置的内存值给到eax
400f1a: 01 c0 add %eax,%eax // eax乘2
400f1c: 39 03 cmp %eax,(%rbx) // 即eax乘2后,要等于rbx指向的值,eax实际上是rbx下面的一个值
400f1e: 74 05 je 400f25
400f20: e8 15 05 00 00 callq 40143a
400f25: 48 83 c3 04 add $0x4,%rbx // 再给rbx+4
400f29: 48 39 eb cmp %rbp,%rbx
400f2c: 75 e9 jne 400f17 // 重复
400f2e: eb 0c jmp 400f3c //结束当前程序
400f30: 48 8d 5c 24 04 lea 0x4(%rsp),%rbx // 栈顶指针+4,给到rbx
400f35: 48 8d 6c 24 18 lea 0x18(%rsp),%rbp // 栈顶指针+24,给到rbp,即栈底指针
400f3a: eb db jmp 400f17
400f3c: 48 83 c4 28 add $0x28,%rsp
400f40: 5b pop %rbx
400f41: 5d pop %rbp
400f42: c3 retq
但是有个问题就是,我们输入的应该是正序还是倒序的?这个需要结合read_six_numbers来看了。
int sscanf(const char *str, const char *format, ...)
,其中后面的省略号就是通过str解析出来的结果,可能有多个,在这里,就是6个数字。000000000040145c :
40145c: 48 83 ec 18 sub $0x18,%rsp
401460: 48 89 f2 mov %rsi,%rdx // 第3个参数,也是被解析的第一个值
401463: 48 8d 4e 04 lea 0x4(%rsi),%rcx // 第4个参数,也是被解析的第二个值
401467: 48 8d 46 14 lea 0x14(%rsi),%rax
40146b: 48 89 44 24 08 mov %rax,0x8(%rsp)
401470: 48 8d 46 10 lea 0x10(%rsi),%rax
401474: 48 89 04 24 mov %rax,(%rsp)
401478: 4c 8d 4e 0c lea 0xc(%rsi),%r9
40147c: 4c 8d 46 08 lea 0x8(%rsi),%r8
401480: be c3 25 40 00 mov $,%esi
401485: b8 00 00 00 00 mov $0x0,%eax
40148a: e8 61 f7 ff ff callq 400bf0 <__isoc99_sscanf@plt>
40148f: 83 f8 05 cmp $0x5,%eax
401492: 7f 05 jg 401499
401494: e8 a1 ff ff ff callq 40143a
401499: 48 83 c4 18 add $0x18,%rsp
40149d: c3 retq
这个phase会让你输入两个数字
0000000000400f43 :
400f43: 48 83 ec 18 sub $0x18,%rsp // 减小栈指针,挪出24字节的空间
400f47: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx // 栈指针+12 给rcx
400f4c: 48 8d 54 24 08 lea 0x8(%rsp),%rdx // 栈指针+8 给rdx
400f51: be cf 25 40 00 mov $0x4025cf,%esi // %d %d,sscanf的参数
400f56: b8 00 00 00 00 mov $0x0,%eax
400f5b: e8 90 fc ff ff callq 400bf0 <__isoc99_sscanf@plt>
400f60: 83 f8 01 cmp $0x1,%eax // 匹配成功的数目要大于1,即大于等于2,应该是2
400f63: 7f 05 jg 400f6a
400f65: e8 d0 04 00 00 callq 40143a
400f6a: 83 7c 24 08 07 cmpl $0x7,0x8(%rsp)
400f6f: 77 3c ja 400fad // 7如果大于rsp+8指向的值,则直接爆炸
400f71: 8b 44 24 08 mov 0x8(%rsp),%eax // rsp+8指向的值给eax
400f75: ff 24 c5 70 24 40 00 jmpq *0x402470(,%rax,8) // 间接寻址0x402470+8*rax,其实这里就是个switch操作,根据你输入的第一个数字是0-6,给你传送到下面对应的mov
400f7c: b8 cf 00 00 00 mov $0xcf,%eax // 将eax置为0xcf,即11001111 15+64+128=207
400f81: eb 3b jmp 400fbe
400f83: b8 c3 02 00 00 mov $0x2c3,%eax
400f88: eb 34 jmp 400fbe
400f8a: b8 00 01 00 00 mov $0x100,%eax
400f8f: eb 2d jmp 400fbe
400f91: b8 85 01 00 00 mov $0x185,%eax
400f96: eb 26 jmp 400fbe
400f98: b8 ce 00 00 00 mov $0xce,%eax
400f9d: eb 1f jmp 400fbe
400f9f: b8 aa 02 00 00 mov $0x2aa,%eax
400fa4: eb 18 jmp 400fbe
400fa6: b8 47 01 00 00 mov $0x147,%eax
400fab: eb 11 jmp 400fbe
400fad: e8 88 04 00 00 callq 40143a
400fb2: b8 00 00 00 00 mov $0x0,%eax
400fb7: eb 05 jmp 400fbe
400fb9: b8 37 01 00 00 mov $0x137,%eax
400fbe: 3b 44 24 0c cmp 0xc(%rsp),%eax // 比较eax是否等于rsp+12指向的值
400fc2: 74 05 je 400fc9 // 等于的话,直接退出程序
400fc4: e8 71 04 00 00 callq 40143a // 不等于的话,就爆炸
400fc9: 48 83 c4 18 add $0x18,%rsp
400fcd: c3 retq
phase_4反汇编的代码如下
要求我们两个数字,其中第一个数字要小于等于14,第二个数字必须为0。其中第一个数字要通过func4函数,并且使其返回0。
000000000040100c :
40100c: 48 83 ec 18 sub $0x18,%rsp // 栈指针下移,挪出24字节的位置
401010: 48 8d 4c 24 0c lea 0xc(%rsp),%rcx // 将rcx置为栈顶指针+12
401015: 48 8d 54 24 08 lea 0x8(%rsp),%rdx // 将rdx置为栈顶指针+8
40101a: be cf 25 40 00 mov $0x4025cf,%esi //
40101f: b8 00 00 00 00 mov $0x0,%eax
401024: e8 c7 fb ff ff callq 400bf0 <__isoc99_sscanf@plt>
401029: 83 f8 02 cmp $0x2,%eax
40102c: 75 07 jne 401035 // 如果输入参数个数不是两个,直接爆炸
40102e: 83 7c 24 08 0e cmpl $0xe,0x8(%rsp) // 将14 和 栈顶指针+8指向的值 比较
401033: 76 05 jbe 40103a // 如果小于等于,则跳过炸弹继续执行
401035: e8 00 04 00 00 callq 40143a // 如果大于则直接爆炸
40103a: ba 0e 00 00 00 mov $0xe,%edx // 将edx置为14,func4的第3个参数
40103f: be 00 00 00 00 mov $0x0,%esi // 将esi置为0,func4的第2个参数
401044: 8b 7c 24 08 mov 0x8(%rsp),%edi // 将rsp+8指向的值给edi,func4的第1个参数
401048: e8 81 ff ff ff callq 400fce // 调用一个函数
40104d: 85 c0 test %eax,%eax // 如果eax不为0,则直接爆炸
40104f: 75 07 jne 401058 // eax不为0,跳到炸弹那里去
401051: 83 7c 24 0c 00 cmpl $0x0,0xc(%rsp) // 比较0和rsp+12指向的值
401056: 74 05 je 40105d // 如果rsp+12指向的值就是0,则通过
401058: e8 dd 03 00 00 callq 40143a
40105d: 48 83 c4 18 add $0x18,%rsp
401061: c3 retq
func4函数的反汇编的代码
0000000000400fce :
400fce: 48 83 ec 08 sub $0x8,%rsp
400fd2: 89 d0 mov %edx,%eax // eax被置为14
400fd4: 29 f0 sub %esi,%eax // eax被置为14
400fd6: 89 c1 mov %eax,%ecx // ecx被置为14
400fd8: c1 e9 1f shr $0x1f,%ecx // ecx被置为0
400fdb: 01 c8 add %ecx,%eax // eax被置为14 0b1110
400fdd: d1 f8 sar %eax // eax被置为0b111,即7
400fdf: 8d 0c 30 lea (%rax,%rsi,1),%ecx // ecx被置为7
400fe2: 39 f9 cmp %edi,%ecx // x和7比较
400fe4: 7e 0c jle 400ff2 // 如果x<=7,跳转
400fe6: 8d 51 ff lea -0x1(%rcx),%edx
400fe9: e8 e0 ff ff ff callq 400fce
400fee: 01 c0 add %eax,%eax
400ff0: eb 15 jmp 401007
400ff2: b8 00 00 00 00 mov $0x0,%eax // 将eax置为0
400ff7: 39 f9 cmp %edi,%ecx // 将x和7比较
400ff9: 7d 0c jge 401007 // 如果大于等于7,则直接跳出程序
400ffb: 8d 71 01 lea 0x1(%rcx),%esi // 将esi置为8
400ffe: e8 cb ff ff ff callq 400fce // 递归
401003: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax
401007: 48 83 c4 08 add $0x8,%rsp
40100b: c3 retq
我刚开始做phase4的时候,陷入了func4这个函数里,完全搞不清楚它在干啥。这也有了一个教训,看汇编代码的时候,不要陷进去汇编的细节,要能够提取它的作用,用C语言的形式去想一下,或者想一想它具体在完成什么动作。
这个炸弹很有意思
0000000000401062 :
// 将fs:0x28的值放到rsp+24字节的位置
401062: 53 push %rbx
401063: 48 83 ec 20 sub $0x20,%rsp // 给栈挪出32字节的位置
401067: 48 89 fb mov %rdi,%rbx // 将我们的输入给rbx
40106a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax // 将段寄存器偏移40字节的值给rax
401071: 00 00
401073: 48 89 44 24 18 mov %rax,0x18(%rsp) // 将rax给栈顶指针+24字节的位置
// 判断输入字符串的长度是否是6,如果不是6则直接爆炸
401078: 31 c0 xor %eax,%eax // eax清零
40107a: e8 9c 02 00 00 callq 40131b // 获取我们输入的字符串的长度
40107f: 83 f8 06 cmp $0x6,%eax // 判断是否是6
401082: 74 4e je 4010d2 // 如果是6的话,则跳过炸弹
401084: e8 b1 03 00 00 callq 40143a // 如果不是6,则直接爆炸
401089: eb 47 jmp 4010d2
// 这一段就是把我们输入的字符串通过一些奇怪的处理,放置在栈上rsp+16开始的位置,16-21
// 这个奇怪的处理应该是根据我们输入的字符,用它们的ascii码为偏移,去0x4024b0地址取字符放到rsp+16开始的位置
40108b: 0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx // 将rbx+rax指向的值放入ecx,这里的rbx就是我们的输入,这里应该就是依次把字符给ecx
40108f: 88 0c 24 mov %cl,(%rsp) // 将ecx的值为栈顶指向的位置
401092: 48 8b 14 24 mov (%rsp),%rdx // 把栈顶的值给edx
401096: 83 e2 0f and $0xf,%edx // 用0b1111与edx按位与
401099: 0f b6 92 b0 24 40 00 movzbl 0x4024b0(%rdx),%edx // 将0x4024b0+edx指向的值给edx
4010a0: 88 54 04 10 mov %dl,0x10(%rsp,%rax,1) // 将edx的值放到16+rsp+rax指向的位置
4010a4: 48 83 c0 01 add $0x1,%rax // 给rax+1
4010a8: 48 83 f8 06 cmp $0x6,%rax // 6和rax比较
4010ac: 75 dd jne 40108b // 如果不同,则继续循环
// 判断我们放在栈上的字符串是否和0x40245e这个地址的字符一样
4010ae: c6 44 24 16 00 movb $0x0,0x16(%rsp) // 将rsp+22指向的值置为0
4010b3: be 5e 24 40 00 mov $0x40245e,%esi // 给esi一个地址0x40245e // "flyers"
4010b8: 48 8d 7c 24 10 lea 0x10(%rsp),%rdi // 给rdi一个地址rsp+16 // "maduiersnfotvbylSo you think you can stop the bomb with ctrl-c, do you?"
4010bd: e8 76 02 00 00 callq 401338 // 比较这两个地址指向的字符串
4010c2: 85 c0 test %eax,%eax
4010c4: 74 13 je 4010d9 // 如果字符串相同,则跳过炸弹
4010c6: e8 6f 03 00 00 callq 40143a // 如果字符串不相同,则爆炸
4010cb: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
4010d0: eb 07 jmp 4010d9
4010d2: b8 00 00 00 00 mov $0x0,%eax // 将eax置0
4010d7: eb b2 jmp 40108b // 跳转
// 比较%fs:0x2和栈上0x18(%rsp)开始的值是否一样,一样则成功。这个测试正常来说不会有问题。
4010d9: 48 8b 44 24 18 mov 0x18(%rsp),%rax // 将rsp+24指向的值给rax
4010de: 64 48 33 04 25 28 00 xor %fs:0x28,%rax // 将rax和一个地址的值异或
4010e5: 00 00
4010e7: 74 05 je 4010ee // 如果等于0,则跳过下面这个函数,成功返回
4010e9: e8 42 fa ff ff callq 400b30 <__stack_chk_fail@plt>
4010ee: 48 83 c4 20 add $0x20,%rsp
4010f2: 5b pop %rbx
4010f3: c3 retq
这个lab也很有意思啊
00000000004010f4 :
// 准备操作
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 // 开辟了80个字节的栈空间
401100: 49 89 e5 mov %rsp,%r13 // 将栈顶指针给r13
401103: 48 89 e6 mov %rsp,%rsi // 将栈顶指针给了rsi,即函数的第二个参数
401106: e8 51 03 00 00 callq 40145c // 将我们输入的6个数字读入栈中
40110b: 49 89 e6 mov %rsp,%r14 // 把栈指针给了r14
40110e: 41 bc 00 00 00 00 mov $0x0,%r12d // 将r12置为0
// 下面这一波就是判断,是否六个数字都不同,并且六个数字都小于等于6
401114: 4c 89 ed mov %r13,%rbp // 把r13给rbp,rbp是栈底指针啊
401117: 41 8b 45 00 mov 0x0(%r13),%eax // 把r13指向的东西赋给了eax,r13现在是栈底,应该就是我们输入的第一个数字
40111b: 83 e8 01 sub $0x1,%eax // 将输入的第一个数字减1
40111e: 83 f8 05 cmp $0x5,%eax // 5和输入的数字进行比较
401121: 76 05 jbe 401128 // 输入的数字减1后小于等于5,则跳过炸弹,否则爆炸
401123: e8 12 03 00 00 callq 40143a
401128: 41 83 c4 01 add $0x1,%r12d // 给r12加1,r12在这之前刚被置为0
40112c: 41 83 fc 06 cmp $0x6,%r12d // 6和r12对比,如果一样则跳转,第一次到这里应该是不会跳转
401130: 74 21 je 401153
401132: 44 89 e3 mov %r12d,%ebx // 将r12给ebx,即ebx置为1
// 下面这一段是判断输入的第2到第6数是否都不和第1个数相同,如果存在相同的,则直接爆炸
401135: 48 63 c3 movslq %ebx,%rax // 将ebx给rax,rax=1
401138: 8b 04 84 mov (%rsp,%rax,4),%eax // eax=rsp+4*rax指向的值,第一次运行到这里应该是rsp+4,访问的是输入的第2个数字
40113b: 39 45 00 cmp %eax,0x0(%rbp) // eax和rbp指向的值比较,即第一个数和第二个数进行比较
40113e: 75 05 jne 401145 // 如果不同,则跳转,否则就爆炸
401140: e8 f5 02 00 00 callq 40143a
401145: 83 c3 01 add $0x1,%ebx // ebx+=1
401148: 83 fb 05 cmp $0x5,%ebx // ebx和5进行对比,如果ebx小于等于5,则跳转
40114b: 7e e8 jle 401135
// 给r13加4
40114d: 49 83 c5 04 add $0x4,%r13
401151: eb c1 jmp 401114
401153: 48 8d 74 24 18 lea 0x18(%rsp),%rsi // 将rsi指向rsp+24的位置
401158: 4c 89 f0 mov %r14,%rax // 把r14给rax,第一次运行到这里的时候r14这时候就是rsp
40115b: b9 07 00 00 00 mov $0x7,%ecx // ecx置为7
// 这是一个小循环,循环6次,将我们输入的数,都换成7-x
401160: 89 ca mov %ecx,%edx // edx置为7
401162: 2b 10 sub (%rax),%edx // 7-rsp指向的值
401164: 89 10 mov %edx,(%rax) // 再将7-rsp指向的值放到rsp指向的位置
401166: 48 83 c0 04 add $0x4,%rax // rax+4
40116a: 48 39 f0 cmp %rsi,%rax // rsi和rax比较
40116d: 75 f1 jne 401160 // 如果不一样,则继续循环
// 现在栈顶的6个数字分别是,6 5 4 3 2 1
// 下面的操作是根据栈顶的六个数字,去一个链表里取结点
40116f: be 00 00 00 00 mov $0x0,%esi // esi置为0
401174: eb 21 jmp 401197 // 跳转
401176: 48 8b 52 08 mov 0x8(%rdx),%rdx // rdx+8指向的值放到rdx中
40117a: 83 c0 01 add $0x1,%eax // eax+1
40117d: 39 c8 cmp %ecx,%eax // ecx中存放的是我们输入的数字,
40117f: 75 f5 jne 401176 // 如果不同的话,就不断循环
401181: eb 05 jmp 401188
401183: ba d0 32 60 00 mov $0x6032d0,%edx
401188: 48 89 54 74 20 mov %rdx,0x20(%rsp,%rsi,2) // 6304544 6304528 6304512 6304496 6304480 0x6032d0
40118d: 48 83 c6 04 add $0x4,%rsi
401191: 48 83 fe 18 cmp $0x18,%rsi
401195: 74 14 je 4011ab
401197: 8b 0c 34 mov (%rsp,%rsi,1),%ecx // 取出栈顶的元素放到ecx
40119a: 83 f9 01 cmp $0x1,%ecx // 元素和1相比
40119d: 7e e4 jle 401183 // 如果元素小于等于1,则跳转
40119f: b8 01 00 00 00 mov $0x1,%eax // eax置为1
4011a4: ba d0 32 60 00 mov $0x6032d0,%edx // edx置为0x6032d0
4011a9: eb cb jmp 401176
// 上面的操作花了这么大牛劲,就是把6304544 6304528 6304512 6304496 6304480 0x6032d0这六个地址放在了rsp+32 +0 +8 +16 +24 +32 +40的位置 好像刚好放满了这个栈
4011ab: 48 8b 5c 24 20 mov 0x20(%rsp),%rbx // 把rbx置为rsp+32指向的值
4011b0: 48 8d 44 24 28 lea 0x28(%rsp),%rax // 把rax置为rsp+40,即上述操作栈的第二个元素的位置
4011b5: 48 8d 74 24 50 lea 0x50(%rsp),%rsi // 把rsi置为rsp+80,即整个栈的终点
4011ba: 48 89 d9 mov %rbx,%rcx // 把rcx置为第一个值
// 这里好像是循环着把一个链表给串起来
4011bd: 48 8b 10 mov (%rax),%rdx // 把rdx置为第二个值
4011c0: 48 89 51 08 mov %rdx,0x8(%rcx) // 把第二个值放到rcx+8指向的位置
4011c4: 48 83 c0 08 add $0x8,%rax // rax+=1,即指向下一个值
4011c8: 48 39 f0 cmp %rsi,%rax // 比较是否到了终点
4011cb: 74 05 je 4011d2 // 到了终点则跳转
4011cd: 48 89 d1 mov %rdx,%rcx // 否则将rcx变成rdx,即变成第二个点
4011d0: eb eb jmp 4011bd
// 将链表最后一个节点的next指针置为0
4011d2: 48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx)
4011d9: 00
// 链表降序排序 443 477 691 924 168 332
4011da: bd 05 00 00 00 mov $0x5,%ebp // ebp置为5
4011df: 48 8b 43 08 mov 0x8(%rbx),%rax // 此时rbx是第一个结点的地址,因此rax是第二个结点的地址
4011e3: 8b 00 mov (%rax),%eax // 将eax置为第二个结点
4011e5: 39 03 cmp %eax,(%rbx) // 比较第二个结点和第一个结点的值
4011e7: 7d 05 jge 4011ee // 第一个如果要大于等于第二个,那么就跳过炸弹,否则就爆炸
4011e9: e8 4c 02 00 00 callq 40143a
4011ee: 48 8b 5b 08 mov 0x8(%rbx),%rbx
4011f2: 83 ed 01 sub $0x1,%ebp
4011f5: 75 e8 jne 4011df
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 retq