必备知识:
x86-64寄存器
Linux x64环境下,根据调用约定,传参顺序为 rdi, rsi, rdx, rcx, r8, r9。剩下的参数通过栈传递,从右至左顺序入栈。
汇编指令
phase_1
(gdb) disas phase_1 Dump of assembler code for function phase_1: 0x00000000000015e7 <+0>: endbr64 # 控制流保护指令,防止返回导向攻击(ROP) 0x00000000000015eb <+4>: sub $0x8,%rsp # 将栈指针 %rsp 减少 0x8 字节,为局部变量分配空间 0x00000000000015ef <+8>: lea 0x1b56(%rip),%rsi # 0x314c # 计算字符串常量地址并加载到寄存器 %rsi 0x00000000000015f6 <+15>: call 0x1b6c <strings_not_equal> # 调用函数 strings_not_equal 并传递两个参数 0x00000000000015fb <+20>: test %eax,%eax # 测试 %eax 中的值是否为零 0x00000000000015fd <+22>: jne 0x1604 <phase_1+29> # 如果 %eax 不为零,则跳转到偏移量 0x1604 处执行 explode_bomb 函数 0x00000000000015ff <+24>: add $0x8,%rsp # 将栈指针 %rsp 增加 0x8 字节,恢复之前的栈状态 0x0000000000001603 <+28>: ret # 返回到调用者 0x0000000000001604 <+29>: call 0x1c80 <explode_bomb> # 调用 explode_bomb 函数终止程序 0x0000000000001609 <+34>: jmp 0x15ff <phase_1+24> # 无条件跳转到偏移量 0x15ff 处重新检查栈帧指针 End of assembler dump
|
x
: 执行 examine
(检查)命令,用于查看指定内存地址的内容。
/s
: 指定格式化方式,这里是 s
,表示将内存数据解释为以空字符(\0
)结束的字符串。
0x314c
: 要查看的内存地址。
作为第二个参数传入,观察第二行我们可以发现用来比较的字符串存在0x314c里,直接查看
phase_2
(gdb) disas phase_2 Dump of assembler code for function phase_2: 0x000000000000160b <+0>: endbr64 # 控制流保护指令,防止返回导向攻击(ROP) 0x000000000000160f <+4>: push %rbp # 保存基址指针 %rbp 到栈上 0x0000000000001610 <+5>: push %rbx # 保存寄存器 %rbx 到栈上 0x0000000000001611 <+6>: sub $0x28,%rsp # 将栈指针 %rsp 减少 0x28 字节,为局部变量分配空间 0x0000000000001615 <+10>: mov %fs:0x28,%rax # 从线程本地存储中读取一个值到寄存器 %rax。这个值通常是栈帧指针的旧值 0x000000000000161e <+19>: mov %rax,0x18(%rsp) # 将刚刚读取的值保存到栈上偏移量为 0x18 的位置 0x0000000000001623 <+24>: xor %eax,%eax # 清零寄存器 %eax 0x0000000000001625 <+26>: mov %rsp,%rsi # 将栈指针 %rsp 的值复制到寄存器 %rsi 0x0000000000001628 <+29>: call 0x1cac <read_six_numbers> # 调用函数 read_six_numbers 并传递栈指针 %rsi 作为参数 0x000000000000162d <+34>: cmpl $0x0,(%rsp) # 比较栈顶第一个整数是否小于 0 0x0000000000001631 <+38>: js 0x163d <phase_2+50> # 如果小于 0,则跳转到偏移量 0x163d 处执行 explode_bomb 函数 0x0000000000001633 <+40>: mov %rsp,%rbp # 将栈指针 %rsp 的值复制到基址指针 %rbp 0x0000000000001636 <+43>: mov $0x1,%ebx # 将 1 加载到寄存器 %ebx 0x000000000000163b <+48>: jmp 0x1650 <phase_2+69> # 无条件跳转到偏移量 0x1650 处开始循环 0x000000000000163d <+50>: call 0x1c80 <explode_bomb> # 调用 explode_bomb 函数终止程序 0x0000000000001642 <+55>: jmp 0x1633 <phase_2+40> # 无条件跳转到偏移量 0x1633 处重新检查第一个整数 0x0000000000001644 <+57>: add $0x1,%ebx # 将 %ebx 增加 1 0x0000000000001647 <+60>: add $0x4,%rbp # 将 %rbp 增加 4(指向下一个整数) 0x000000000000164b <+64>: cmp $0x6,%ebx # 比较 %ebx 是否等于 6 0x000000000000164e <+67>: je 0x1661 <phase_2+86> # 如果等于 6,则跳转到偏移量 0x1661 处结束循环 0x0000000000001650 <+69>: mov %ebx,%eax # 将 %ebx 的值加载到 %eax 0x0000000000001652 <+71>: add 0x0(%rbp),%eax # 将当前整数加到 %eax 0x0000000000001655 <+74>: cmp %eax,0x4(%rbp) # 比较 %eax 和下一个整数 0x0000000000001658 <+77>: je 0x1644 <phase_2+57> # 如果相等,则跳转到偏移量 0x1644 处继续循环 0x000000000000165a <+79>: call 0x1c80 <explode_bomb> # 否则调用 explode_bomb 函数终止程序 0x000000000000165f <+84>: jmp 0x1644 <phase_2+57> # 无条件跳转到偏移量 0x1644 处继续循环 0x0000000000001661 <+86>: mov 0x18(%rsp),%rax # 将之前保存的栈帧指针的旧值从栈上加载到 %rax 0x0000000000001666 <+91>: sub %fs:0x28,%rax # 从 %rax 中减去线程本地存储中的值 0x000000000000166f <+100>: jne 0x1678 <phase_2+109> # 如果结果不为零,则跳转到偏移量 0x1678 处执行 __stack_chk_fail 函数 0x0000000000001671 <+102>: add $0x28,%rsp # 将栈指针 %rsp 增加 0x28 字节,恢复之前的栈状态 0x0000000000001675 <+106>: pop %rbx # 从栈上弹出寄存器 %rbx 的值 0x0000000000001676 <+107>: pop %rbp # 从栈上弹出基址指针 %rbp 的值 0x0000000000001677 <+108>: ret # 返回到调用者 0x0000000000001678 <+109>: call 0x1250 <__stack_chk_fail@plt> # 调用 __stack_chk_fail 函数处理栈溢出检测失败的情况 End of assembler dump.
|
总体逻辑
调用 read_six_numbers
0x0000000000001625 <+26>: mov %rsp,%rsi 0x0000000000001628 <+29>: call 0x1cac <read_six_numbers>
|
- 将当前栈指针
%rsp
作为参数传递给 read_six_numbers
,数字会存储在栈上。
- 调用
read_six_numbers
函数,要求用户输入 6 个整数。
第一关数字必须为0
0x000000000000162d <+34>: cmpl $0x0,(%rsp)
|
反汇编<read_six_numbers>
:
int sscanf(const char *str, const char *format, ...);
|
数字位置 |
寄存器 |
内存地址 |
第一个数 |
%rsi |
%rsi |
第二个数 |
%rcx |
%rsi + 0x4 |
第三个数 |
%r8 |
%rsi + 0x8 |
第四个数 |
%r9 |
%rsi + 0xc |
第五个数 |
栈顶 (从 %rax 压栈) |
%rsi + 0x10 |
第六个数 |
栈顶 (从 %rax 压栈) |
%rsi + 0x14 |
(这个函数共有6个参数,传入了6个指针,其中4个存放在寄存器中,2个存放在的栈中。)
Linux x64环境下,根据调用约定,传参顺序为 rdi, rsi, rdx, rcx, r8, r9。剩下的参数通过栈传递,从右至左顺序入栈。
回到第二关:
核心验证逻辑从 <+40>
开始。
phase_3
(gdb) disas phase_3 Dump of assembler code for function phase_3: 0x000000000000167d <+0>: endbr64 0x0000000000001681 <+4>: sub $0x28,%rsp 0x0000000000001685 <+8>: mov %fs:0x28,%rax 0x000000000000168e <+17>: mov %rax,0x18(%rsp) 0x0000000000001693 <+22>: xor %eax,%eax 0x0000000000001695 <+24>: lea 0xf(%rsp),%rcx 0x000000000000169a <+29>: lea 0x10(%rsp),%rdx 0x000000000000169f <+34>: lea 0x14(%rsp),%r8 0x00000000000016a4 <+39>: lea 0x1abf(%rip),%rsi # 0x316a 0x00000000000016ab <+46>: call 0x1300 [__isoc99_sscanf@plt](mailto:__isoc99_sscanf@plt) 0x00000000000016b0 <+51>: cmp $0x2,%eax 0x00000000000016b3 <+54>: jle 0x16d5 <phase_3+88> 0x00000000000016b5 <+56>: cmpl $0x7,0x10(%rsp) 0x00000000000016ba <+61>: ja 0x17ca <phase_3+333> 0x00000000000016c0 <+67>: mov 0x10(%rsp),%eax 0x00000000000016c4 <+71>: lea 0x1ab5(%rip),%rdx # 0x3180 0x00000000000016cb <+78>: movslq (%rdx,%rax,4),%rax 0x00000000000016cf <+82>: add %rdx,%rax 0x00000000000016d2 <+85>: notrack jmp *%rax 0x00000000000016d5 <+88>: call 0x1c80 <explode_bomb> 0x00000000000016da <+93>: jmp 0x16b5 <phase_3+56> 0x00000000000016dc <+95>: mov $0x71,%eax 0x00000000000016e1 <+100>: cmpl $0x38b,0x14(%rsp) 0x00000000000016e9 <+108>: je 0x17d4 <phase_3+343> 0x00000000000016ef <+114>: call 0x1c80 <explode_bomb> 0x00000000000016f4 <+119>: mov $0x71,%eax 0x00000000000016f9 <+124>: jmp 0x17d4 <phase_3+343> 0x00000000000016fe <+129>: mov $0x73,%eax 0x0000000000001703 <+134>: cmpl $0x6b,0x14(%rsp) 0x0000000000001708 <+139>: je 0x17d4 <phase_3+343> 0x000000000000170e <+145>: call 0x1c80 <explode_bomb> 0x0000000000001713 <+150>: mov $0x73,%eax 0x0000000000001718 <+155>: jmp 0x17d4 <phase_3+343> 0x000000000000171d <+160>: mov $0x70,%eax 0x0000000000001722 <+165>: cmpl $0xe3,0x14(%rsp) 0x000000000000172a <+173>: je 0x17d4 <phase_3+343> 0x0000000000001730 <+179>: call 0x1c80 <explode_bomb> 0x0000000000001735 <+184>: mov $0x70,%eax 0x000000000000173a <+189>: jmp 0x17d4 <phase_3+343> 0x000000000000173f <+194>: mov $0x71,%eax 0x0000000000001744 <+199>: cmpl $0x1e7,0x14(%rsp) 0x000000000000174c <+207>: je 0x17d4 <phase_3+343> 0x0000000000001752 <+213>: call 0x1c80 <explode_bomb> 0x0000000000001757 <+218>: mov $0x71,%eax 0x000000000000175c <+223>: jmp 0x17d4 <phase_3+343> 0x000000000000175e <+225>: mov $0x6e,%eax --Type <RET> for more, q to quit, c to continue without paging-- 0x0000000000001763 <+230>: cmpl $0x1df,0x14(%rsp) 0x000000000000176b <+238>: je 0x17d4 <phase_3+343> 0x000000000000176d <+240>: call 0x1c80 <explode_bomb> 0x0000000000001772 <+245>: mov $0x6e,%eax 0x0000000000001777 <+250>: jmp 0x17d4 <phase_3+343> 0x0000000000001779 <+252>: mov $0x74,%eax 0x000000000000177e <+257>: cmpl $0x176,0x14(%rsp) 0x0000000000001786 <+265>: je 0x17d4 <phase_3+343> 0x0000000000001788 <+267>: call 0x1c80 <explode_bomb> 0x000000000000178d <+272>: mov $0x74,%eax 0x0000000000001792 <+277>: jmp 0x17d4 <phase_3+343> 0x0000000000001794 <+279>: mov $0x64,%eax 0x0000000000001799 <+284>: cmpl $0x318,0x14(%rsp) 0x00000000000017a1 <+292>: je 0x17d4 <phase_3+343> 0x00000000000017a3 <+294>: call 0x1c80 <explode_bomb> 0x00000000000017a8 <+299>: mov $0x64,%eax 0x00000000000017ad <+304>: jmp 0x17d4 <phase_3+343> 0x00000000000017af <+306>: mov $0x6f,%eax 0x00000000000017b4 <+311>: cmpl $0x30c,0x14(%rsp) 0x00000000000017bc <+319>: je 0x17d4 <phase_3+343> 0x00000000000017be <+321>: call 0x1c80 <explode_bomb> 0x00000000000017c3 <+326>: mov $0x6f,%eax 0x00000000000017c8 <+331>: jmp 0x17d4 <phase_3+343> 0x00000000000017ca <+333>: call 0x1c80 <explode_bomb> 0x00000000000017cf <+338>: mov $0x66,%eax 0x00000000000017d4 <+343>: cmp %al,0xf(%rsp) 0x00000000000017d8 <+347>: jne 0x17ef <phase_3+370> 0x00000000000017da <+349>: mov 0x18(%rsp),%rax 0x00000000000017df <+354>: sub %fs:0x28,%rax 0x00000000000017e8 <+363>: jne 0x17f6 <phase_3+377> 0x00000000000017ea <+365>: add $0x28,%rsp 0x00000000000017ee <+369>: ret 0x00000000000017ef <+370>: call 0x1c80 <explode_bomb> 0x00000000000017f4 <+375>: jmp 0x17da <phase_3+349> 0x00000000000017f6 <+377>: call 0x1250 [__stack_chk_fail@plt](mailto:__stack_chk_fail@plt) End of assembler dump.
|
0x0000000000001695 <+24>: lea 0xf(%rsp),%rcx 0x000000000000169a <+29>: lea 0x10(%rsp),%rdx 0x000000000000169f <+34>: lea 0x14(%rsp),%r8
|
0x00000000000016b5 <+56>: cmpl $0x7,0x10(%rsp) 0x00000000000016ba <+61>: ja 0x17ca <phase_3+333>
|
mov 0x10(%rsp), %eax # 取入 eax lea 0x1ab5(%rip), %rdx # 加载表基址到 rdx -> 0x3180 movslq (%rdx,%rax,4), %rax # 从表中取地址
|
跳转到: 0x000000000000171d <+160>: mov $0x70,%eax 这行代码将 0x70(即 112,对应字符 'p')存储到 eax 寄存器中。
|
**判断第三个的是否和值 0xe3(227)匹配** 0x1722: cmpl $0xe3,0x14(%rsp) 也就是判断0x14是否和0xe3(227)是否相等
|
0x000000000000172a <+173>: je 0x17d4 <phase_3+343> 第三个值等于时跳转至0x17d4 <phase_3+343>
|
跳转到: 0x00000000000017d4 <+343>: cmp %al,0xf(%rsp) 0x00000000000017d8 <+347>: jne 0x17ef <phase_3+370> 这里开始比较al寄存器的值与栈中偏移0xf(%rsp)处的值是否相等。%al是eax寄存器的低8位,这里存储了一个字符。 0x00000000000017ef <+370>: call 0x1c80 <explode_bomb> 如果cmp指令比较的值不相等就跳到上面这行代码,触发explode_bomb
|
phase_4
(gdb) disas phase_4 Dump of assembler code for function phase_4: 0x0000000000001836 <+0>: endbr64 # 控制流保护指令,防止返回导向攻击(ROP) 0x000000000000183a <+4>: sub $0x18,%rsp # 将栈指针 %rsp 减少 0x18 字节,为局部变量分配空间 0x000000000000183e <+8>: mov %fs:0x28,%rax # 从线程本地存储中读取一个值到寄存器 %rax。这个值通常是栈帧指针的旧值 0x0000000000001847 <+17>: mov %rax,0x8(%rsp) # 将刚刚读取的值保存到栈上偏移量为 0x8 的位置 0x000000000000184c <+22>: xor %eax,%eax # 清零寄存器 %eax 0x000000000000184e <+24>: mov %rsp,%rcx # 将栈指针 %rsp 的值复制到寄存器 %rcx 0x0000000000001851 <+27>: lea 0x4(%rsp),%rdx # 计算栈上偏移量为 0x4 的地址,并将其加载到寄存器 %rdx 0x0000000000001856 <+32>: lea 0x1aba(%rip),%rsi # 0x3317 # 计算字符串常量 "%d %d" 在内存中的地址,并将其加载到寄存器 %rsi 0x000000000000185d <+39>: call 0x1300 <__isoc99_sscanf@plt> # 调用函数 __isoc99_sscanf 来解析输入字符串,期望两个整数参数,并将结果存储在栈上的指定位置 0x0000000000001862 <+44>: cmp $0x2,%eax # 比较 %eax 中的值(即成功解析的整数数量)是否等于 2 0x0000000000001865 <+47>: jne 0x1872 <phase_4+60> # 如果不等于 2,则跳转到偏移量 0x1872 处执行 explode_bomb 函数 0x0000000000001867 <+49>: mov (%rsp),%eax # 将第一个解析得到的整数值从栈上加载到 %eax 0x000000000000186a <+52>: sub $0x2,%eax # 从 %eax 中减去 2 0x000000000000186d <+55>: cmp $0x2,%eax # 比较 %eax 是否小于或等于 2 0x0000000000001870 <+58>: jbe 0x1877 <phase_4+65> # 如果 %eax 小于或等于 2,则跳转到偏移量 0x1877 处继续执行 0x0000000000001872 <+60>: call 0x1c80 <explode_bomb> # 否则调用 explode_bomb 函数终止程序 0x0000000000001877 <+65>: mov (%rsp),%esi # 将第一个解析得到的整数值从栈上加载到 %esi 0x000000000000187a <+68>: mov $0x8,%edi # 将常量 8 加载到 %edi 0x000000000000187f <+73>: call 0x17fb <func4> # 调用函数 func4 并传递两个参数 0x0000000000001884 <+78>: cmp %eax,0x4(%rsp) # 比较 func4 返回的结果与第二个解析得到的整数值 0x0000000000001888 <+82>: jne 0x189f <phase_4+105> # 如果两者不相等,则跳转到偏移量 0x189f 处执行 explode_bomb 函数 0x000000000000188a <+84>: mov 0x8(%rsp),%rax # 将之前保存的栈帧指针的旧值从栈上加载到 %rax 0x000000000000188f <+89>: sub %fs:0x28,%rax # 从 %rax 中减去线程本地存储中的值 0x0000000000001898 <+98>: jne 0x18a6 <phase_4+112> # 如果结果不为零,则跳转到偏移量 0x18a6 处执行 __stack_chk_fail 函数 0x000000000000189a <+100>: add $0x18,%rsp # 将栈指针 %rsp 增加 0x18 字节,恢复之前的栈状态 0x000000000000189e <+104>: ret # 返回到调用者 0x000000000000189f <+105>: call 0x1c80 <explode_bomb> # 调用 explode_bomb 函数终止程序 0x00000000000018a4 <+110>: jmp 0x188a <phase_4+84> # 无条件跳转到偏移量 0x188a 处重新检查栈帧指针 0x00000000000018a6 <+112>: call 0x1250 <__stack_chk_fail@plt> # 调用 __stack_chk_fail 函数处理栈溢出检测失败的情况 End of assembler dump.
|
0x000000000000184e <+24>: mov %rsp,%rcx //第二个参数 0x0000000000001851 <+27>: lea 0x4(%rsp),%rdx //第一个参数 **Linux x64环境下,根据调用约定,传参顺序为 rdi, rsi, rdx, rcx, r8, r9。剩下的参数通过栈传递,从右至左顺序入栈。**
|
查看传入格式 0x0000000000001856 <+32>: lea 0x1aba(%rip),%rsi # 0x3317
|
0x0000000000001867 <+49>: mov (%rsp),%eax //将第一个整数加载到 %esi 中。 0x000000000000186a <+52>: sub $0x2,%eax //%eax 是 x 的值,减去 2 后结果仍存入 %eax 0x000000000000186d <+55>: cmp $0x2,%eax //将 (x - 2) 与 2 比较 0x0000000000001870 <+58>: jbe 0x1877 <phase_4+65> //jbe 指令跳转的条件是 (x - 2) <= 2,即:0 ≤ x−2 ≤ 2 ⟹ 2 ≤ x ≤ 4
|
(gdb) disas func4 Dump of assembler code for function func4: 0x00000000000017fb <+0>: endbr64 0x00000000000017ff <+4>: mov $0x0,%eax # 初始化 eax 为 0 0x0000000000001804 <+9>: test %edi,%edi # 测试 edi 是否为 0 0x0000000000001806 <+11>: jle 0x1835 <func4+58> # 如果 edi <= 0, 直接返回 0 0x0000000000001808 <+13>: push %r12 # 保存 r12 寄存器 0x000000000000180a <+15>: push %rbp # 保存 rbp 寄存器 0x000000000000180b <+16>: push %rbx # 保存 rbx 寄存器 0x000000000000180c <+17>: mov %edi,%ebx # 将 edi (第一个参数) 复制到 ebx 0x000000000000180e <+19>: mov %esi,%ebp # 将 esi (第二个参数) 复制到 ebp 0x0000000000001810 <+21>: mov %esi,%eax # 将 esi (第二个参数) 复制到 eax 0x0000000000001812 <+23>: cmp $0x1,%edi # 比较 edi 是否等于 1 0x0000000000001815 <+26>: je 0x1830 <func4+53> # 如果 edi == 1, 跳转到结束部分 0x0000000000001817 <+28>: lea -0x1(%rdi),%edi # 将 edi 减 1 0x000000000000181a <+31>: call 0x17fb <func4> # 递归调用 func4 0x000000000000181f <+36>: lea (%rax,%rbp,1),%r12d # 计算 rax + rbp 并存储在 r12d 0x0000000000001823 <+40>: lea -0x2(%rbx),%edi # 将 ebx 减 2 并存储在 edi 0x0000000000001826 <+43>: mov %ebp,%esi # 将 rbp 复制到 esi 0x0000000000001828 <+45>: call 0x17fb <func4> # 递归调用 func4 0x000000000000182d <+50>: add %r12d,%eax # 将 r12d 加到 eax 0x0000000000001830 <+53>: pop %rbx # 恢复 rbx 寄存器 0x0000000000001831 <+54>: pop %rbp # 恢复 rbp 寄存器 0x0000000000001832 <+55>: pop %r12 # 恢复 r12 寄存器 0x0000000000001834 <+57>: ret # 返回 0x0000000000001835 <+58>: ret # 如果 edi <= 0, 直接返回 0 End of assembler dump.
|
函数入口 0x00000000000017fb <+0>: mov $0x0,%eax #初始化 eax 为 0 0x0000000000001804 <+9>: test %edi,%edi #测试 edi 是否为 0 0x0000000000001806 <+11>: jle 0x1835 <func4+58> #如果 edi <= 0, 直接返回 0 如果输入参数 x 小于等于 0,则直接返回 0。
|
递归逻辑 0x0000000000001817 <+28>: lea -0x1(%rdi),%edi # 将 edi 减 1 0x000000000000181a <+31>: call 0x17fb <func4> # 递归调用 func4
|
在 phase_4 的最后阶段: 0x0000000000001877 <+65>: mov (%rsp),%esi 0x000000000000187a <+68>: mov $0x8,%edi 0x000000000000187f <+73>: call 0x17fb <func4> 0x0000000000001884 <+78>: cmp %eax,0x4(%rsp) 0x0000000000001888 <+82>: jne 0x189f <phase_4+105>
|
phase_5
(gdb) disas phase_5 Dump of assembler code for function phase_5: 0x00000000000018ab <+0>: endbr64 # 控制流保护指令,防止返回导向攻击(ROP) 0x00000000000018af <+4>: push %rbx # 保存寄存器 %rbx 到栈上 0x00000000000018b0 <+5>: mov %rdi,%rbx # 将输入字符串指针 %rdi 复制到 %rbx 0x00000000000018b3 <+8>: call 0x1b4b <string_length> # 调用函数 string_length 获取输入字符串的长度 0x00000000000018b8 <+13>: cmp $0x6,%eax # 比较 string_length 的返回值是否等于 6 0x00000000000018bb <+16>: jne 0x18e9 <phase_5+62> # 如果不等于 6,则跳转到偏移量 0x18e9 处执行 explode_bomb 函数 0x00000000000018bd <+18>: mov %rbx,%rax # 将输入字符串指针 %rbx 复制到 %rax 0x00000000000018c0 <+21>: lea 0x6(%rbx),%rdi # 计算输入字符串末尾地址并加载到 %rdi 0x00000000000018c4 <+25>: mov $0x0,%ecx # 将累加器 %ecx 初始化为 0 0x00000000000018c9 <+30>: lea 0x18d0(%rip),%rsi # 0x31a0 <array.0> # 计算数组 array.0 的基址并加载到 %rsi 0x00000000000018d0 <+37>: movzbl (%rax),%edx # 从当前字符地址 %rax 加载一个字节到 %edx 并清零高 24 位 0x00000000000018d3 <+40>: and $0xf,%edx # 取 %edx 的低 4 位 0x00000000000018d6 <+43>: add (%rsi,%rdx,4),%ecx # 根据低 4 位索引访问数组 array.0 并将其值加到 %ecx 0x00000000000018d9 <+46>: add $0x1,%rax # 将 %rax 增加 1,指向下一个字符 0x00000000000018dd <+50>: cmp %rdi,%rax # 比较 %rax 和 %rdi 是否相等 0x00000000000018e0 <+53>: jne 0x18d0 <phase_5+37> # 如果不相等,则跳转到偏移量 0x18d0 处继续循环 0x00000000000018e2 <+55>: cmp $0x39,%ecx # 比较累加器 %ecx 是否等于 57 (0x39) 0x00000000000018e5 <+58>: jne 0x18f0 <phase_5+69> # 如果不等于 57,则跳转到偏移量 0x18f0 处执行 explode_bomb 函数 0x00000000000018e7 <+60>: pop %rbx # 从栈上弹出寄存器 %rbx 的值 0x00000000000018e8 <+61>: ret # 返回到调用者 0x00000000000018e9 <+62>: call 0x1c80 <explode_bomb> # 调用 explode_bomb 函数终止程序 0x00000000000018ee <+67>: jmp 0x18bd <phase_5+18> # 无条件跳转到偏移量 0x18bd 处重新检查字符串长度 0x00000000000018f0 <+69>: call 0x1c80 <explode_bomb> # 调用 explode_bomb 函数终止程序 0x00000000000018f5 <+74>: jmp 0x18e7 <phase_5+60> # 无条件跳转到偏移量 0x18e7 处恢复寄存器并返回 End of assembler dump.
|
要求传入一个长度为6的字符串 0x00000000000018b3 <+8>: call 0x1b4b <string_length> # 调用函数 string_length 获取输入字符串的长度 0x00000000000018b8 <+13>: cmp $0x6,%eax # 比较 string_length 的返回值是否等于 6 0x00000000000018bb <+16>: jne 0x18e9 <phase_5+62> # 如果不等于 6,则跳转到偏移量 0x18e9 处执行 explode_bomb 函数
|
核心部分如下: 0x00000000000018c9 <+30>: lea 0x18d0(%rip),%rsi # 0x31a0 <array.0> # 计算数组 array.0 的基址并加载到 %rsi 0x00000000000018d0 <+37>: movzbl (%rax),%edx # 从当前字符地址 %rax 加载一个字节到 %edx 并清零高 24 位 0x00000000000018d3 <+40>: and $0xf,%edx # 取 %edx 的低 4 位 0x00000000000018d6 <+43>: add (%rsi,%rdx,4),%ecx # 根据低 4 位索引访问数组 array.0 并将其值加到 %ecx 0x00000000000018d9 <+46>: add $0x1,%rax # 将 %rax 增加 1,指向下一个字符 0x00000000000018dd <+50>: cmp %rdi,%rax # 比较 %rax 和 %rdi 是否相等 0x00000000000018e0 <+53>: jne 0x18d0 <phase_5+37> # 如果不相等,则跳转到偏移量 0x18d0 处继续循环 验证总和是否等于 57 0x00000000000018e2 <+55>: cmp $0x39,%ecx # 比较累加器 %ecx 是否等于 57 (0x39) 0x00000000000018e5 <+58>: jne 0x18f0 <phase_5+69> # 如果不等于 57,则跳转到偏移量 0x18f0 处执行 explode_bomb 函数
|
(gdb) x/16x 0x31a0 0x31a0 <array.0>: 0x00000002 0x0000000a 0x00000006 0x00000001 0x31b0 <array.0+16>: 0x0000000c 0x00000010 0x00000009 0x00000003 0x31c0 <array.0+32>: 0x00000004 0x00000007 0x0000000e 0x00000005 0x31d0 <array.0+48>: 0x0000000b 0x00000008 0x0000000f 0x0000000d
|
phase_6
(gdb) disas phase_6 Dump of assembler code for function phase_6: 0x00000000000018f7 <+0>: endbr64 0x00000000000018fb <+4>: push %r14 #push 指令:将 %r14寄存器的值保存在栈中 0x00000000000018fd <+6>: push %r13 #push 指令:将 %r13寄存器的值保存在栈中 0x00000000000018ff <+8>: push %r12 #push 指令:将 %r12寄存器的值保存在栈中 0x0000000000001901 <+10>: push %rbp #push 指令:将 %rbp寄存器的值保存在栈中 0x0000000000001902 <+11>: push %rbx #push 指令:将 %rbx寄存器的值保存在栈中 0x0000000000001903 <+12>: sub $0x60,%rsp #为本地变量分配 96 字节的栈空间 0x0000000000001907 <+16>: mov %fs:0x28,%rax #用于设置栈保护(stack canary) 0x0000000000001910 <+25>: mov %rax,0x58(%rsp) #用于设置栈保护(stack canary) 0x0000000000001915 <+30>: xor %eax,%eax #将 %eax 寄存器清零 0x0000000000001917 <+32>: mov %rsp,%r13 #将栈指针 %rsp 的值复制到 %r13 中 0x000000000000191a <+35>: mov %r13,%rsi #将 %r13 的值复制到 %rsi 中,作为参数传递给接下来的 read_six_numbers 函数。 0x000000000000191d <+38>: call 0x1cac <read_six_numbers> #调用 read_six_numbers 函数 0x0000000000001922 <+43>: mov $0x1,%r14d #将 %r14d 寄存器初始化为 1,用作循环计数器 0x0000000000001928 <+49>: mov %rsp,%r12 #将栈指针 %rsp 复制到 %r12 寄存器中,可能用于指向输入缓冲区。 0x000000000000192b <+52>: jmp 0x1955 <phase_6+94> #跳转到地址 0x1955(跳过后面的部分,直接进入比较代码)。 0x000000000000192d <+54>: call 0x1c80 <explode_bomb> 0x0000000000001932 <+59>: jmp 0x1964 <phase_6+109> 0x0000000000001934 <+61>: add $0x1,%rbx #增加 %rbx 0x0000000000001938 <+65>: cmp $0x5,%ebx # 并与 5 进行比较,可能是循环计数器。这里控制着一个 5 次的循环。 0x000000000000193b <+68>: jg 0x194d <phase_6+86> 0x000000000000193d <+70>: mov (%r12,%rbx,4),%eax 0x0000000000001941 <+74>: cmp %eax,0x0(%rbp) #比较 %eax 中的值与 %rbp 中存储的某个值。 0x0000000000001944 <+77>: jne 0x1934 <phase_6+61>#如果不相等,继续执行循环。 0x0000000000001946 <+79>: call 0x1c80 <explode_bomb> 0x000000000000194b <+84>: jmp 0x1934 <phase_6+61> 0x000000000000194d <+86>: add $0x1,%r14 0x0000000000001951 <+90>: add $0x4,%r13 0x0000000000001955 <+94>: mov %r13,%rbp #将 %r13 寄存器的值复制到 %rbp 寄存器中 0x0000000000001958 <+97>: mov 0x0(%r13),%eax #将 %r13 寄存器所指向的内存地址处的值加载到 %eax 寄存器中 0x000000000000195c <+101>: sub $0x1,%eax #将 %eax 寄存器中的值减去 1 0x000000000000195f <+104>: cmp $0x5,%eax #将 %eax 与 5 比较。 0x0000000000001962 <+107>: ja 0x192d <phase_6+54> #如果大于 5,则跳转到前面的代码部分,引爆炸弹 0x0000000000001964 <+109>: cmp $0x5,%r14d 0x0000000000001968 <+113>: jg 0x196f <phase_6+120> 0x000000000000196a <+115>: mov %r14,%rbx 0x000000000000196d <+118>: jmp 0x193d <phase_6+70> 0x000000000000196f <+120>: mov $0x0,%esi 0x0000000000001974 <+125>: mov (%rsp,%rsi,4),%ecx 0x0000000000001977 <+128>: mov $0x1,%eax 0x000000000000197c <+133>: lea 0x388d(%rip),%rdx # 0x5210 <node1> 0x0000000000001983 <+140>: cmp $0x1,%ecx 0x0000000000001986 <+143>: jle 0x1993 <phase_6+156> 0x0000000000001988 <+145>: mov 0x8(%rdx),%rdx --Type <RET> for more, q to quit, c to continue without paging-- 0x000000000000198c <+149>: add $0x1,%eax 0x000000000000198f <+152>: cmp %ecx,%eax 0x0000000000001991 <+154>: jne 0x1988 <phase_6+145> 0x0000000000001993 <+156>: mov %rdx,0x20(%rsp,%rsi,8) 0x0000000000001998 <+161>: add $0x1,%rsi 0x000000000000199c <+165>: cmp $0x6,%rsi 0x00000000000019a0 <+169>: jne 0x1974 <phase_6+125> 0x00000000000019a2 <+171>: mov 0x20(%rsp),%rbx 0x00000000000019a7 <+176>: mov 0x28(%rsp),%rax 0x00000000000019ac <+181>: mov %rax,0x8(%rbx) 0x00000000000019b0 <+185>: mov 0x30(%rsp),%rdx 0x00000000000019b5 <+190>: mov %rdx,0x8(%rax) 0x00000000000019b9 <+194>: mov 0x38(%rsp),%rax 0x00000000000019be <+199>: mov %rax,0x8(%rdx) 0x00000000000019c2 <+203>: mov 0x40(%rsp),%rdx 0x00000000000019c7 <+208>: mov %rdx,0x8(%rax) 0x00000000000019cb <+212>: mov 0x48(%rsp),%rax 0x00000000000019d0 <+217>: mov %rax,0x8(%rdx) 0x00000000000019d4 <+221>: movq $0x0,0x8(%rax) 0x00000000000019dc <+229>: mov $0x5,%ebp 0x00000000000019e1 <+234>: jmp 0x19ec <phase_6+245> 0x00000000000019e3 <+236>: mov 0x8(%rbx),%rbx 0x00000000000019e7 <+240>: sub $0x1,%ebp 0x00000000000019ea <+243>: je 0x19fd <phase_6+262> 0x00000000000019ec <+245>: mov 0x8(%rbx),%rax 0x00000000000019f0 <+249>: mov (%rax),%eax 0x00000000000019f2 <+251>: cmp %eax,(%rbx) 0x00000000000019f4 <+253>: jge 0x19e3 <phase_6+236> 0x00000000000019f6 <+255>: call 0x1c80 <explode_bomb> 0x00000000000019fb <+260>: jmp 0x19e3 <phase_6+236> 0x00000000000019fd <+262>: mov 0x58(%rsp),%rax 0x0000000000001a02 <+267>: sub %fs:0x28,%rax 0x0000000000001a0b <+276>: jne 0x1a1a <phase_6+291> 0x0000000000001a0d <+278>: add $0x60,%rsp 0x0000000000001a11 <+282>: pop %rbx 0x0000000000001a12 <+283>: pop %rbp 0x0000000000001a13 <+284>: pop %r12 0x0000000000001a15 <+286>: pop %r13 0x0000000000001a17 <+288>: pop %r14 0x0000000000001a19 <+290>: ret 0x0000000000001a1a <+291>: call 0x1250 [__stack_chk_fail@plt](mailto:__stack_chk_fail@plt) End of assembler dump.
|
call read_six_numbers: 调用函数读取 6 个用户输入的数字,存储在 rsp 指向的栈空间中。 0x000000000000191d <+38>: call 0x1cac <read_six_numbers>
|
检查输入范围,要求输入数字减1后在0-5之间,也就是输入范围为1-6 0x0000000000001958 <+97>: mov 0x0(%r13),%eax ; 将 r13 地址指向的值加载到 eax 中 0x000000000000195c <+101>: sub $0x1,%eax ; eax -= 1 0x000000000000195f <+104>: cmp $0x5,%eax ; 比较 eax 和 5 0x0000000000001962 <+107>: ja 0x192d <phase_6+54> ; 如果 eax > 5,跳转到 0x192d,即炸弹爆炸
|
去重检查,遍历用户输入的 6 个数字,检查是否存在重复。 0x000000000000192b <+52>: jmp 0x1955 <phase_6+94> ... 0x000000000000193d <+70>: mov (%r12,%rbx,4),%eax 0x0000000000001941 <+74>: cmp %eax,0x0(%rbp) 0x0000000000001944 <+77>: jne 0x1934 <phase_6+61> 0x0000000000001946 <+79>: call 0x1c80 <explode_bomb>
|
构建链表,户输入的数字被用作偏移量,遍历链表以找到对应的节点地址。 0x0000000000001974 <+125>: mov (%rsp,%rsi,4),%ecx 0x000000000000197c <+133>: lea 0x388d(%rip),%rdx # 0x5210 <node1> 0x0000000000001983 <+140>: cmp $0x1,%ecx 0x0000000000001986 <+143>: jle 0x1993 <phase_6+156> 0x0000000000001988 <+145>: mov 0x8(%rdx),%rdx 0x000000000000198c <+149>: add $0x1,%eax 0x0000000000001991 <+154>: jne 0x1988 <phase_6+145>
|
根据节点的值(可能是 node->value),按降序重新链接链表。(值从大到小) 0x00000000000019a2 <+171>: mov 0x20(%rsp),%rbx 0x00000000000019a7 <+176>: mov 0x28(%rsp),%rax 0x00000000000019ac <+181>: mov %rax,0x8(%rbx) ... 0x00000000000019cb <+212>: mov 0x48(%rsp),%rax 0x00000000000019d0 <+217>: mov %rax,0x8(%rdx) 0x00000000000019d4 <+221>: movq $0x0,0x8(%rax)
|
以 32 个双字 (word, 4 字节) 的形式,从地址 0x5210
开始显示内存内容。
(gdb) x/32dw 0x5210 0x5210 <node1>: 286 1 21024 0 0x5220 <node2>: 809 2 21040 0 0x5230 <node3>: 776 3 21056 0 0x5240 <node4>: 383 4 21072 0 0x5250 <node5>: 761 5 20752 0 0x5260 <host_table>: 13169 0 13195 0 0x5270 <host_table+16>: 13221 0 0 0 0x5280 <host_table+32>: 0 0 0 0
|
地址 |
名称 |
字段1(值) |
字段2(序号) |
字段3(指针/偏移量) |
字段4(保留) |
0x5210 |
node1 |
286 |
1 |
0x5220 (21024) |
0 |
0x5220 |
node2 |
809 |
2 |
0x5230 (21040) |
0 |
0x5230 |
node3 |
776 |
3 |
0x5240 (21056) |
0 |
0x5240 |
node4 |
383 |
4 |
0x5250 (21072) |
0 |
0x5250 |
node5 |
761 |
5 |
0x5120 (20752) |
0 |
可以得知node5的字段30x5120
(20752)指向node6,所以再查看20752这个地址得到node6
(gdb) x/32dw 20752 0x5110 <node6>: 315 6 0 0 0x5120 <bomb_id>: -2053338326 0 0 0 0x5130 <n1>: 36 0 20816 0 0x5140 <n1+16>: 20848 0 0 0 0x5150 <n21>: 8 0 20944 0 0x5160 <n21+16>: 20880 0 0 0 0x5170 <n22>: 50 0 20912 0 0x5180 <n22+16>: 20976 0 0 0
|
地址 |
名称 |
字段1(值) |
字段2(序号) |
字段3(指针/偏移量) |
字段4(保留) |
0x5210 |
node1 |
286 |
1 |
0x5220 (21024) |
0 |
0x5220 |
node2 |
809 |
2 |
0x5230 (21040) |
0 |
0x5230 |
node3 |
776 |
3 |
0x5240 (21056) |
0 |
0x5240 |
node4 |
383 |
4 |
0x5250 (21072) |
0 |
0x5250 |
node5 |
761 |
5 |
0x5120 (20752) |
0 |
0x5110 |
node6 |
315 |
6 |
0 |
0 |
作为一个web手,对于汇编和逆向确实无从下手,只能一点一点跟着学,学校布置的这个bomblab和网上的还不一样,是改过了,最后phase_6硬着头皮做了两天都没做出来,最后隔了两天,周日洗完澡突然有了思路,直接秒了,说实话这实验做的有点浪费时间,不过还是学到了很多。