鸣谢
Bomb二进制炸弹拆弹实验 | DDqiuqiu (dingdingqiuqiu.github.io)
tyc的小窝
真诚的感谢这两位大佬
一些前置指令
cd /home/belly/Desktop #导到当前目录
gedit ./bomb.s #用gedit查看bomb文件
gdb bomb #调试并运行当前文件
set args "./b.txt" #(gdb)使用b文件进行测试
b phrase_1 #(gdb)相应函数打断点 默认1,2,3
r #(gdb)运行
quit #(gdb)放弃gdb模式
vim b.txt #打开b
阶段1:字符串比较
通过phase_1的反汇编代码找出要输入的字符串。
汇编注解
8048b20: 55 push %ebp ; 保存旧的基指针
8048b21: 89 e5 mov %esp,%ebp ; 将新的栈帧基指针设置为当前栈顶
8048b23: 83 ec 08 sub $0x8,%esp ; 为局部变量分配空间
8048b26: 8b 45 08 mov 0x8(%ebp),%eax ; 将函数的第一个参数(存储在ebp+8的位置)移动到eax寄存器
8048b29: 83 c4 f8 add $0xfffffff8,%esp ; 为即将进行的函数调用准备栈空间
8048b2c: 68 c0 97 04 08 push $0x80497c0 ; 将第二个参数压入栈中
8048b31: 50 push %eax ; 将第一个参数压入栈中
8048b32: e8 f9 04 00 00 call 8049030 <strings_not_equal> ; 调用strings_not_equal函数
8048b37: 83 c4 10 add $0x10,%esp ; 清理栈
8048b3a: 85 c0 test %eax,%eax ; 检查eax的值(strings_not_equal的返回值)
8048b3c: 74 05 je 8048b43 <phase_1+0x23> ; 如果eax为0(即字符串相等),则跳转到8048b43
8048b3e: e8 b9 09 00 00 call 80494fc <explode_bomb> ; 否则,调用explode_bomb函数
8048b43: 89 ec mov %ebp,%esp ; 恢复栈指针
8048b45: 5d pop %ebp ; 恢复基指针
8048b46: c3 ret ; 返回到调用者
8048b47: 90 nop ; 无操作指令,通常用于填充或对齐
答案
8048b2c: 68 c0 97 04 08 push $0x80497c0
8048b31: 50 push %eax
8048b32: e8 f9 04 00 00 call 8049030 <strings_not_equal>
输入项与位于0x80497c0比较结果,判断答案在0x80497c0里
x/s后 得知答案为 Public speaking is very easy.
伪代码
void phase_1(char* input) {
if (strings_not_equal(input, "0x80497c0")) {
explode_bomb();
}
}
阶段2 循环
通过phase_2的反汇编代码推断第二阶段要输入的数据
汇编注解
8048b48: 55 push %ebp ; 保存旧的基指针
8048b49: 89 e5 mov %esp,%ebp ; 将新的栈帧基指针设置为当前栈顶
8048b4b: 83 ec 20 sub $0x20,%esp ; 为局部变量分配空间
8048b4e: 56 push %esi ; 保存寄存器esi的值
8048b4f: 53 push %ebx ; 保存寄存器ebx的值
8048b50: 8b 55 08 mov 0x8(%ebp),%edx ; 将函数的第一个参数(存储在ebp+8的位置)移动到edx寄存器
8048b53: 83 c4 f8 add $0xfffffff8,%esp ; 为即将进行的函数调用准备栈空间
8048b56: 8d 45 e8 lea -0x18(%ebp),%eax ; 将地址ebp-0x18加载到eax寄存器
8048b59: 50 push %eax ; 将eax压入栈中
8048b5a: 52 push %edx ; 将edx压入栈中
8048b5b: e8 78 04 00 00 call 8048fd8 <read_six_numbers> ; 调用read_six_numbers函数
8048b60: 83 c4 10 add $0x10,%esp ; 清理栈
8048b63: 83 7d e8 01 cmpl $0x1,-0x18(%ebp) ; 比较nums[0]和1
8048b67: 74 05 je 8048b6e <phase_2+0x26> ; 如果nums[0]等于1,则跳转到8048b6e
8048b69: e8 8e 09 00 00 call 80494fc <explode_bomb> ; 否则,调用explode_bomb函数
8048b6e: bb 01 00 00 00 mov $0x1,%ebx ; 将1移动到ebx寄存器
8048b73: 8d 75 e8 lea -0x18(%ebp),%esi ; 将地址ebp-0x18加载到esi寄存器
8048b76: 8d 43 01 lea 0x1(%ebx),%eax ; 将ebx+1的结果加载到eax寄存器
8048b79: 0f af 44 9e fc imul -0x4(%esi,%ebx,4),%eax ; 将nums[i]和nums[i-1]相乘,结果存储在eax中
8048b7e: 39 04 9e cmp %eax,(%esi,%ebx,4) ; 比较nums[i]和eax
8048b81: 74 05 je 8048b88 <phase_2+0x40> ; 如果nums[i]等于eax,则跳转到8048b88
8048b83: e8 74 09 00 00 call 80494fc <explode_bomb> ; 否则,调用explode_bomb函数
8048b88: 43 inc %ebx ; 将ebx加1
8048b89: 83 fb 05 cmp $0x5,%ebx ; 比较ebx和5
8048b8c: 7e e8 jle 8048b76 <phase_2+0x2e> ; 如果ebx小于等于5,则跳转到8048b76
8048b8e: 8d 65 d8 lea -0x28(%ebp),%esp ; 将地址ebp-0x28加载到esp寄存器
8048b91: 5b pop %ebx ; 恢复ebx的值
8048b92: 5e pop %esi ; 恢复esi的值
8048b93: 89 ec mov %ebp,%esp ; 恢复栈指针
8048b95: 5d pop %ebp ; 恢复基指针
8048b96: c3 ret ; 返回到调用者
8048b97: 90 nop ; 无操作指令,通常用于填充或对齐
答案
8048b56: 8d 45 e8 lea -0x18(%ebp),%eax
8048b59: 50 push %eax
8048b5a: 52 push %edx
判断ebp-0x18是指针位置
8048b63: 83 7d e8 01 cmpl $0x1,-0x18(%ebp)
8048b67: 74 05 je 8048b6e <phase_2+0x26>
8048b69: e8 8e 09 00 00 call 80494fc <explode_bomb>
8048b6e: bb 01 00 00 00 mov $0x1,%ebx
判断1是否等于numbers[0],否则爆炸
8048b6e: bb 01 00 00 00 mov $0x1,%ebx
8048b73: 8d 75 e8 lea -0x18(%ebp),%esi
8048b76: 8d 43 01 lea 0x1(%ebx),%eax
8048b79: 0f af 44 9e fc imul -0x4(%esi,%ebx,4),%eax
8048b7e: 39 04 9e cmp %eax,(%esi,%ebx,4)
8048b81: 74 05 je 8048b88 <phase_2+0x40>
8048b83: e8 74 09 00 00 call 80494fc <explode_bomb>
8048b88: 43 inc %ebx
8048b89: 83 fb 05 cmp $0x5,%ebx
8048b8c: 7e e8 jle 8048b76 <phase_2+0x2e>
比较numbers[ebx] != (ebx + 1) * numbers[ebx - 1]
ebx++;
截至条件是ebx>5
根据上述条件,可列出答案 1 2 6 24 120 720
伪代码
void phase_2(int* numbers) {
read_six_numbers(numbers);
if (numbers[0] != 1) {
explode_bomb();
}
int ebx = 1;
do {
if (numbers[ebx] != (ebx + 1) * numbers[ebx - 1]) {
explode_bomb();
}
ebx++;
} while (ebx <= 5);
}
阶段3 条件/分支
通过phase_3的反汇编代码推断第三阶段要输入的数据
汇编注解
8048b98: 55 push %ebp // 将基指针压入堆栈
8048b99: 89 e5 mov %esp,%ebp // 将堆栈指针的值移动到基指针
8048b9b: 83 ec 14 sub $0x14,%esp // 从堆栈指针中减去20(0x14)
8048b9e: 53 push %ebx // 将ebx寄存器的值压入堆栈
8048b9f: 8b 55 08 mov 0x8(%ebp),%edx // 将ebp+8的值移动到edx寄存器
8048ba2: 83 c4 f4 add $0xfffffff4,%esp // 将esp加上-12(0xfffffff4)
8048ba5: 8d 45 fc lea -0x4(%ebp),%eax // 将ebp-4的地址加载到eax寄存器
8048ba8: 50 push %eax // 将eax寄存器的值压入堆栈
8048ba9: 8d 45 fb lea -0x5(%ebp),%eax // 将ebp-5的地址加载到eax寄存器
8048bac: 50 push %eax // 将eax寄存器的值压入堆栈
8048bad: 8d 45 f4 lea -0xc(%ebp),%eax // 将ebp-12的地址加载到eax寄存器
8048bb0: 50 push %eax // 将eax寄存器的值压入堆栈
8048bb1: 68 de 97 04 08 push $0x80497de // 将值0x80497de压入堆栈
8048bb6: 52 push %edx // 将edx寄存器的值压入堆栈
8048bb7: e8 a4 fc ff ff call 8048860 <sscanf@plt> // 调用函数sscanf
8048bbc: 83 c4 20 add $0x20,%esp // 将esp加上32(0x20)
8048bbf: 83 f8 02 cmp $0x2,%eax // 将eax与2进行比较
8048bc2: 7f 05 jg 8048bc9 <phase_3+0x31>// 如果eax大于2,则跳转到8048bc9
8048bc4: e8 33 09 00 00 call 80494fc <explode_bomb>// 调用函数explode_bomb
8048bc9: 83 7d f4 07 cmpl $0x7,-0xc(%ebp) // 将ebp-12与7进行比较
8048bcd: 0f 87 b5 00 00 00 ja 8048c88 <phase_3+0xf0>// 如果ebp-12大于7,则跳转到8048c88
8048bd3: 8b 45 f4 mov -0xc(%ebp),%eax // 将ebp-12的值移动到eax寄存器
8048bd6: ff 24 85 e8 97 04 08 jmp *0x80497e8(,%eax,4) // 跳转到0x80497e8+eax*4的地址
8048c94: e8 63 08 00 00 call 80494fc <explode_bomb> // 调用函数explode_bomb
8048c99: 8b 5d e8 mov -0x18(%ebp),%ebx // 将ebp-24的值移动到ebx寄存器
8048c9c: 89 ec mov %ebp,%esp // 将基指针的值移动到堆栈指针
8048c9e: 5d pop %ebp // 弹出堆栈顶部的值到基指针
8048c9f: c3 ret // 返回
答案
8048bb7: e8 a4 fc ff ff call 8048860 <sscanf@plt>
8048bbc: 83 c4 20 add $0x20,%esp
8048bbf: 83 f8 02 cmp $0x2,%eax
8048bc2: 7f 05 jg 8048bc9 <phase_3+0x31>
8048bc4: e8 33 09 00 00 call 80494fc <explode_bomb>
调用函数sscanf
8048ba5: 8d 45 fc lea -0x4(%ebp),%eax // 将ebp-4的地址加载到eax寄存器
8048ba8: 50 push %eax // 将eax寄存器的值压入堆栈
8048ba9: 8d 45 fb lea -0x5(%ebp),%eax // 将ebp-5的地址加载到eax寄存器
8048bac: 50 push %eax // 将eax寄存器的值压入堆栈
8048bad: 8d 45 f4 lea -0xc(%ebp),%eax // 将ebp-12的地址加载到eax寄存器
8048bb0: 50 push %eax // 将eax寄存器的值压入堆栈
8048bb1: 68 de 97 04 08 push $0x80497de // 将值0x80497de压入堆栈
8048bb6: 52 push %edx // 将edx寄存器的值压入堆栈
得到参数列表,将字符串s按照"%d %c %d"的格式分别读取到三个变量v3, v4, v5中,返回值存放在eax寄存器中。-
将esp寄存器加上32,恢复堆栈指针的位置。
将eax寄存器的值与2进行比较,如果大于2,则跳转到8048bc9继续执行,否则调用函数explode_bomb,引爆炸弹。
看如下跳转表:
.text:08048BD6 jmp ds:jpt_8048BD6[eax*4] ; switch jump
.text:08048BD6 ; ---------------------------------------------------------------------------
.text:08048BDD align 10h
.text:08048BE0
.text:08048BE0 loc_8048BE0: ; CODE XREF: phase_3+3E↑j
.text:08048BE0 ; DATA XREF: .rodata:jpt_8048BD6↓o
.text:08048BE0 mov bl, 71h ; 'q' ; jumptable 08048BD6 case 0
.text:08048BE2 cmp [ebp+var_4], 309h
.text:08048BE9 jz loc_8048C8F
.text:08048BEF call explode_bomb
.text:08048BF4 ; ---------------------------------------------------------------------------
.text:08048BF4 jmp loc_8048C8F
.text:08048BF4 ; ---------------------------------------------------------------------------
.text:08048BF9 align 10h
.text:08048C00
.text:08048C00 loc_8048C00: ; CODE XREF: phase_3+3E↑j
.text:08048C00 ; DATA XREF: .rodata:jpt_8048BD6↓o
.text:08048C00 mov bl, 62h ; 'b' ; jumptable 08048BD6 case 1
.text:08048C02 cmp [ebp+var_4], 0D6h
.text:08048C09 jz loc_8048C8F
.text:08048C0F call explode_bomb
.text:08048C14 ; ---------------------------------------------------------------------------
.text:08048C14 jmp short loc_8048C8F
.text:08048C16 ; ---------------------------------------------------------------------------
.text:08048C16
.text:08048C16 loc_8048C16: ; CODE XREF: phase_3+3E↑j
.text:08048C16 ; DATA XREF: .rodata:jpt_8048BD6↓o
.text:08048C16 mov bl, 62h ; 'b' ; jumptable 08048BD6 case 2
.text:08048C18 cmp [ebp+var_4], 2F3h
.text:08048C1F jz short loc_8048C8F
.text:08048C21 call explode_bomb
.text:08048C26 ; ---------------------------------------------------------------------------
.text:08048C26 jmp short loc_8048C8F
.text:08048C28 ; ---------------------------------------------------------------------------
.text:08048C28
.text:08048C28 loc_8048C28: ; CODE XREF: phase_3+3E↑j
.text:08048C28 ; DATA XREF: .rodata:jpt_8048BD6↓o
.text:08048C28 mov bl, 6Bh ; 'k' ; jumptable 08048BD6 case 3
.text:08048C2A cmp [ebp+var_4], 0FBh
.text:08048C31 jz short loc_8048C8F
.text:08048C33 call explode_bomb
.text:08048C38 ; ---------------------------------------------------------------------------
.text:08048C38 jmp short loc_8048C8F
.text:08048C38 ; ---------------------------------------------------------------------------
.text:08048C3A align 10h
.text:08048C40
.text:08048C40 loc_8048C40: ; CODE XREF: phase_3+3E↑j
.text:08048C40 ; DATA XREF: .rodata:jpt_8048BD6↓o
.text:08048C40 mov bl, 6Fh ; 'o' ; jumptable 08048BD6 case 4
.text:08048C42 cmp [ebp+var_4], 0A0h
.text:08048C49 jz short loc_8048C8F
.text:08048C4B call explode_bomb
.text:08048C50 ; ---------------------------------------------------------------------------
.text:08048C50 jmp short loc_8048C8F
.text:08048C52 ; ---------------------------------------------------------------------------
.text:08048C52
.text:08048C52 loc_8048C52: ; CODE XREF: phase_3+3E↑j
.text:08048C52 ; DATA XREF: .rodata:jpt_8048BD6↓o
.text:08048C52 mov bl, 74h ; 't' ; jumptable 08048BD6 case 5
.text:08048C54 cmp [ebp+var_4], 1CAh
.text:08048C5B jz short loc_8048C8F
.text:08048C5D call explode_bomb
.text:08048C62 ; ---------------------------------------------------------------------------
.text:08048C62 jmp short loc_8048C8F
.text:08048C64 ; ---------------------------------------------------------------------------
.text:08048C64
.text:08048C64 loc_8048C64: ; CODE XREF: phase_3+3E↑j
.text:08048C64 ; DATA XREF: .rodata:jpt_8048BD6↓o
.text:08048C64 mov bl, 76h ; 'v' ; jumptable 08048BD6 case 6
.text:08048C66 cmp [ebp+var_4], 30Ch
.text:08048C6D jz short loc_8048C8F
.text:08048C6F call explode_bomb
.text:08048C74 ; ---------------------------------------------------------------------------
.text:08048C74 jmp short loc_8048C8F
.text:08048C76 ; ---------------------------------------------------------------------------
.text:08048C76
.text:08048C76 loc_8048C76: ; CODE XREF: phase_3+3E↑j
.text:08048C76 ; DATA XREF: .rodata:jpt_8048BD6↓o
.text:08048C76 mov bl, 62h ; 'b' ; jumptable 08048BD6 case 7
.text:08048C78 cmp [ebp+var_4], 20Ch
.text:08048C7F jz short loc_8048C8F
.text:08048C81 call explode_bomb
.text:08048C86 ; ---------------------------------------------------------------------------
.text:08048C86 jmp short loc_8048C8F
.text:08048C88 ; ---------------------------------------------------------------------------
.text:08048C88
.text:08048C88 def_8048BD6: ; CODE XREF: phase_3+35↑j
.text:08048C88 mov bl, 78h ; 'x' ; jumptable 08048BD6 default case
.text:08048C8A call explode_bomb
.text:08048C8F ; ---------------------------------------------------------------------------
.text:08048C8F
.text:08048C8F loc_8048C8F: ; CODE XREF: phase_3+51↑j
.text:08048C8F ; phase_3+5C↑j ...
.text:08048C8F cmp bl, [ebp+var_5]
.text:08048C92 jz short loc_8048C99
.text:08048C94 call explode_bomb
.text:08048C99 ; ---------------------------------------------------------------------------
.text:08048C99
.text:08048C99 loc_8048C99: ; CODE XREF: phase_3+FA↑j
.text:08048C99 mov ebx, [ebp+var_18]
.text:08048C9C mov esp, ebp
.text:08048C9E pop ebp
.text:08048C9F retn
.text:08048C9F phase_3 endp
以下为上述答案列表
0 q 777
1 b 214
2 b 755
3 k 251
4 o 160
5 t 458
6 v 780
7 b 524
伪代码
int __cdecl phase_3(char *s)
{
int result; // eax
char v2; // bl
int v3; // [esp+Ch] [ebp-Ch] BYREF
char v4; // [esp+13h] [ebp-5h] BYREF
int v5; // [esp+14h] [ebp-4h] BYREF
if ( sscanf(s, "%d %c %d", &v3, &v4, &v5) <= 2 )
explode_bomb();
result = v3;
switch ( v3 )
{
case 0:
v2 = 113;
if ( v5 != 777 )
explode_bomb();
return result;
case 1:
v2 = 98;
if ( v5 != 214 )
explode_bomb();
return result;
case 2:
v2 = 98;
if ( v5 != 755 )
explode_bomb();
return result;
case 3:
v2 = 107;
if ( v5 != 251 )
explode_bomb();
return result;
case 4:
v2 = 111;
if ( v5 != 160 )
explode_bomb();
return result;
case 5:
v2 = 116;
if ( v5 != 458 )
explode_bomb();
return result;
case 6:
v2 = 118;
if ( v5 != 780 )
explode_bomb();
return result;
case 7:
v2 = 98;
if ( v5 != 524 )
explode_bomb();
return result;
default:
explode_bomb();
return result;
}
if ( v2 != v4 )
explode_bomb();
return result;
}
阶段4 递归调用和栈
通过phase_4以及func4的反汇编代码推断第四阶段要输入的数据
汇编注解
08048ca0 <func4>:
8048ca0: 55 push %ebp ; 函数调用开始,保存当前的基址指针
8048ca1: 89 e5 mov %esp,%ebp ; 设置新的基址指针为当前栈指针
8048ca3: 83 ec 10 sub $0x10,%esp ; 为局部变量分配16字节的栈空间
8048ca6: 56 push %esi ; 保存esi寄存器的值
8048ca7: 53 push %ebx ; 保存ebx寄存器的值
8048ca8: 8b 5d 08 mov 0x8(%ebp),%ebx ; 将第一个参数(在ebp+8处)存入ebx寄存器
8048cab: 83 fb 01 cmp $0x1,%ebx ; 比较ebx寄存器的值和1
8048cae: 7e 20 jle 8048cd0 <func4+0x30> ; 如果小于等于1,跳转到func4+0x30处(返回1)
8048cb0: 83 c4 f4 add $0xfffffff4,%esp ; 分配4字节的栈空间
8048cb3: 8d 43 ff lea -0x1(%ebx),%eax ; 将ebx-1的值存入eax寄存器
8048cb6: 50 push %eax ; 将eax寄存器的值入栈
8048cb7: e8 e4 ff ff ff call 8048ca0 <func4> ; 递归调用func4函数
8048cbc: 89 c6 mov %eax,%esi ; 将返回值存入esi寄存器
8048cbe: 83 c4 f4 add $0xfffffff4,%esp ; 分配4字节的栈空间
8048cc1: 8d 43 fe lea -0x2(%ebx),%eax ; 将ebx-2的值存入eax寄存器
8048cc4: 50 push %eax ; 将eax寄存器的值入栈
8048cc5: e8 d6 ff ff ff call 8048ca0 <func4> ; 递归调用func4函数
8048cca: 01 f0 add %esi,%eax ; 将esi寄存器的值加到eax寄存器
8048ccc: eb 07 jmp 8048cd5 <func4+0x35> ; 跳转到func4+0x35处(函数结束前的清理)
8048cce: 89 f6 mov %esi,%esi ; 无操作,用于补全指令
8048cd0: b8 01 00 00 00 mov $0x1,%eax ; 返回值为1
8048cd5: 8d 65 e8 lea -0x18(%ebp),%esp ; 恢复栈指针到函数调用前的位置
8048cd8: 5b pop %ebx ; 恢复ebx寄存器的值
8048cd9: 5e pop %esi ; 恢复esi寄存器的值
8048cda: 89 ec mov %ebp,%esp ; 恢复栈指针到函数调用前的位置
8048cdc: 5d pop %ebp ; 恢复基址指针的值
8048cdd: c3 ret ; 函数返回
8048cde: 89 f6 mov %esi,%esi ; 无操作,用于补全指令
08048ce0 <phase_4>:
8048ce0: 55 push %ebp ; 函数调用开始,保存当前的基址指针
8048ce1: 89 e5 mov %esp,%ebp ; 设置新的基址指针为当前栈指针
8048ce3: 83 ec 18 sub $0x18,%esp ; 为局部变量分配24字节的栈空间
8048ce6: 8b 55 08 mov 0x8(%ebp),%edx ; 将第一个参数(在ebp+8处)存入edx寄存器
8048ce9: 83 c4 fc add $0xfffffffc,%esp ; 分配4字节的栈空间
8048cec: 8d 45 fc lea -0x4(%ebp),%eax ; 将ebp-4的地址存入eax寄存器
8048cef: 50 push %eax ; 将eax寄存器的值入栈
8048cf0: 68 08 98 04 08 push $0x8049808 ; 将字符串地址入栈
8048cf5: 52 push %edx ; 将第一个参数的值(edx寄存器)入栈
8048cf6: e8 65 fb ff ff call 8048860 <sscanf@plt> ; 调用sscanf函数解析字符串为整数
8048cfb: 83 c4 10 add $0x10,%esp ; 清理栈上的参数
8048cfe: 83 f8 01 cmp $0x1,%eax ; 比较返回值与1
8048d01: 75 06 jne 8048d09 <phase_4+0x29> ; 如果不等于1,跳转到phase_4+0x29处(爆炸)
8048d03: 83 7d fc 00 cmpl $0x0,-0x4(%ebp) ; 比较ebp-4处的值与0
8048d07: 7f 05 jg 8048d0e <phase_4+0x2e> ; 如果大于0,跳转到phase_4+0x2e处(爆炸)
8048d09: e8 ee 07 00 00 call 80494fc <explode_bomb> ; 爆炸
8048d0e: 83 c4 f4 add $0xfffffff4,%esp ; 分配4字节的栈空间
8048d11: 8b 45 fc mov -0x4(%ebp),%eax ; 将ebp-4处的值存入eax寄存器
8048d14: 50 push %eax ; 将eax寄存器的值入栈
8048d15: e8 86 ff ff ff call 8048ca0 <func4> ; 调用func4函数
8048d1a: 83 c4 10 add $0x10,%esp ; 清理栈上的参数
8048d1d: 83 f8 37 cmp $0x37,%eax ; 比较返回值与37
8048d20: 74 05 je 8048d27 <phase_4+0x47> ; 如果相等,跳转到phase_4+0x47处
8048d22: e8 d5 07 00 00 call 80494fc <explode_bomb> ; 爆炸
8048d27: 89 ec mov %ebp,%esp ; 恢复栈指针到函数调用前的位置
8048d29: 5d pop %ebp ; 恢复基址指针的值
8048d2a: c3 ret ; 函数返回
8048d2b: 90 nop ; 无操作,用于补全指令
答案
8048cec: 8d 45 fc lea -0x4(%ebp),%eax ; 将ebp-4的地址存入eax寄存器
8048cef: 50 push %eax ; 将eax寄存器的值入栈
确定sscanf参数只有一个%d
8048d03: 83 7d fc 00 cmpl $0x0,-0x4(%ebp) ; 比较ebp-4处的值与0
8048d07: 7f 05 jg 8048d0e <phase_4+0x2e> ; 如果大于0,跳转到phase_4+0x2e处(爆炸)
开始递归
8048d11: 8b 45 fc mov -0x4(%ebp),%eax ; 将ebp-4处的值存入eax寄存器
8048d14: 50 push %eax ; 将eax寄存器的值入栈
8048d15: e8 86 ff ff ff call 8048ca0 <func4> ; 调用func4函数
返回值不是37就爆炸
8048d1d: 83 f8 37 cmp $0x37,%eax ; 比较返回值与37
8048d20: 74 05 je 8048d27 <phase_4+0x47> ; 如果相等,跳转到phase_4+0x47处
8048d22: e8 d5 07 00 00 call 80494fc <explode_bomb> ; 爆炸
func:
8048cae: 7e 20 jle 8048cd0 <func4+0x30> ; 如果小于等于1,跳转到func4+0x30处(返回1)
递归func(ebx-1)
8048cb3: 8d 43 ff lea -0x1(%ebx),%eax ; 将ebx-1的值存入eax寄存器
8048cb6: 50 push %eax ; 将eax寄存器的值入栈
8048cb7: e8 e4 ff ff ff call 8048ca0 <func4> ; 递归调用func4函数
递归func(ebx-2)
8048cc1: 8d 43 fe lea -0x2(%ebx),%eax ; 将ebx-2的值存入eax寄存器
8048cc4: 50 push %eax ; 将eax寄存器的值入栈
8048cc5: e8 d6 ff ff ff call 8048ca0 <func4> ; 递归调用func4函数
8048cca: 01 f0 add %esi,%eax ; 将esi寄存器的值加到eax寄存器
返回func(ebx-1)+func(ebx-2)
8048cca: 01 f0 add %esi,%eax ; 将esi寄存器的值加到eax寄存器
8048ccc: eb 07 jmp 8048cd5 <func4+0x35> ; 跳转到func4+0x35处(函数结束前的清理)
易知:1、1、2、3、5、8、13、21、34、55,答案是9
int __cdecl func4(int a1)
{
int v1; // esi
if ( a1 <= 1 )
return 1;
v1 = func4(a1 - 1);
return v1 + func4(a1 - 2);
}
int __cdecl phase_4(char *s)
{
int result; // eax
int v2; // [esp+14h] [ebp-4h] BYREF
if ( sscanf(s, "%d", &v2) != 1 || v2 <= 0 )
explode_bomb();
result = func4(v2);
if ( result != 55 )
explode_bomb();
return result;
}
阶段5 指针
任务描述:通过phase_5的反汇编代码推断第五阶段要输入的数据
汇编注解
08048d2c <phase_5>:
8048d2c: 55 push %ebp ; 函数调用开始,保存当前的基址指针
8048d2d: 89 e5 mov %esp,%ebp ; 设置新的基址指针为当前栈指针
8048d2f: 83 ec 10 sub $0x10,%esp ; 为局部变量分配16字节的栈空间
8048d32: 56 push %esi ; 保存esi寄存器的值
8048d33: 53 push %ebx ; 保存ebx寄存器的值
8048d34: 8b 5d 08 mov 0x8(%ebp),%ebx ; 将第一个参数(在ebp+8处)存入ebx寄存器
8048d37: 83 c4 f4 add $0xfffffff4,%esp ; 分配4字节的栈空间
8048d3a: 53 push %ebx ; 将ebx寄存器的值入栈
8048d3b: e8 d8 02 00 00 call 8049018 <string_length> ; 调用string_length函数获取字符串长度
8048d40: 83 c4 10 add $0x10,%esp ; 清理栈上的参数
8048d43: 83 f8 06 cmp $0x6,%eax ; 比较字符串长度与6
8048d46: 74 05 je 8048d4d <phase_5+0x21> ; 如果相等,继续执行;否则跳转到phase_5+0x21处(爆炸)
8048d48: e8 af 07 00 00 call 80494fc <explode_bomb> ; 爆炸
8048d4d: 31 d2 xor %edx,%edx ; 清零edx寄存器
8048d4f: 8d 4d f8 lea -0x8(%ebp),%ecx ; 将ebp-8的地址存入ecx寄存器
8048d52: be 20 b2 04 08 mov $0x804b220,%esi ; 将字符串地址存入esi寄存器
8048d57: 8a 04 1a mov (%edx,%ebx,1),%al ; 将字符串中的字符存入al寄存器
8048d5a: 24 0f and $0xf,%al ; 取al寄存器的低4位
8048d5c: 0f be c0 movsbl %al,%eax ; 将al寄存器的值扩展为eax寄存器的值
8048d5f: 8a 04 30 mov (%eax,%esi,1),%al ; 从字符串中取一个字符
8048d62: 88 04 0a mov %al,(%edx,%ecx,1) ; 将取到的字符存入目标字符串中
8048d65: 42 inc %edx ; edx寄存器加1
8048d66: 83 fa 05 cmp $0x5,%edx ; 比较edx寄存器的值与5
8048d69: 7e ec jle 8048d57 <phase_5+0x2b> ; 如果小于等于5,继续循环;否则跳转到phase_5+0x2b处
8048d6b: c6 45 fe 00 movb $0x0,-0x2(%ebp) ; 将0存入ebp-2处
8048d6f: 83 c4 f8 add $0xfffffff8,%esp ; 分配8字节的栈空间
8048d72: 68 0b 98 04 08 push $0x804980b ; 将字符串地址入栈
8048d77: 8d 45 f8 lea -0x8(%ebp),%eax ; 将ebp-8的地址存入eax寄存器
8048d7a: 50 push %eax ; 将eax寄存器的值入栈
8048d7b: e8 b0 02 00 00 call 8049030 <strings_not_equal> ; 调用strings_not_equal函数比较两个字符串
8048d80: 83 c4 10 add $0x10,%esp ; 清理栈上的参数
8048d83: 85 c0 test %eax,%eax ; 测试比较的结果
8048d85: 74 05 je 8048d8c <phase_5+0x60> ; 如果相等,继续执行;否则跳转到phase_5+0x60处(爆炸)
8048d87: e8 70 07 00 00 call 80494fc <explode_bomb> ; 爆炸
8048d8c: 8d 65 e8 lea -0x18(%ebp),%esp ; 恢复栈指针到函数调用前的位置
8048d8f: 5b pop %ebx ; 恢复ebx寄存器的值
8048d90: 5e pop %esi ; 恢复esi寄存器的值
8048d91: 89 ec mov %ebp,%esp ; 恢复栈指针到函数调用前的位置
8048d93: 5d pop %ebp ; 恢复基址指针的值
8048d94: c3 ret ; 函数返回
8048d95: 8d 76 00 lea 0x0(%esi),%esi ; 无操作,用于补全指令
答案
输入字符串长度不为6,爆炸
8048d3b: e8 d8 02 00 00 call 8049018 <string_length> ; 调用string_length函数获取字符串长度
8048d40: 83 c4 10 add $0x10,%esp ; 清理栈上的参数
8048d43: 83 f8 06 cmp $0x6,%eax ; 比较字符串长度与6
8048d46: 74 05 je 8048d4d <phase_5+0x21> ; 如果相等,继续执行;否则跳转到phase_5+0x21处(爆炸)
循环 ebx是原数组 esi是array_123的地址
8048d5a: 24 0f and $0xf,%al ; 取al寄存器的低4位
8048d5c: 0f be c0 movsbl %al,%eax ; 将al寄存器的值扩展为eax寄存器的值
8048d5f: 8a 04 30 mov (%eax,%esi,1),%al ; 从字符串中取一个字符
8048d62: 88 04 0a mov %al,(%edx,%ecx,1) ; 将取到的字符存入目标字符串中
8048d65: 42 inc %edx ; edx寄存器加1
8048d66: 83 fa 05 cmp $0x5,%edx ; 比较edx寄存器的值与5
8048d69: 7e ec jle 8048d57 <phase_5+0x2b> ; 如果小于等于5,继续循环;否则跳转到phase_5+0x2b处
.data:0804B220 array_123 db 69h, 73h, 72h, 76h, 65h, 61h, 77h, 68h, 6Fh, 62h, 70h
.data:0804B220 ; DATA XREF: phase_5+26↑o
.data:0804B220 db 6Eh, 75h, 74h, 66h, 67h
69h -> ‘i’
73h -> ‘s’
72h -> ‘r’
76h -> ‘v’
65h -> ‘e’
61h -> ‘a’
77h -> ‘w’
68h -> ‘h’
6Fh -> ‘o’
62h -> ‘b’
70h -> ‘p’
6Eh -> ‘n’
75h -> ‘u’
74h -> ‘t’
66h -> ‘f’
67h -> ‘g’
在v3数组的末尾添加一个空字符,表示字符串的结束。
8048d6b: c6 45 fe 00 movb $0x0,-0x2(%ebp) ; 将0存入ebp-2处
与giants(8049030)比较,不同就炸
8048d7b: e8 b0 02 00 00 call 8049030 <strings_not_equal> ; 调用strings_not_equal函数比较两个字符串
.rodata:0804980B aGiants db 'giants',0 ; DATA XREF: phase_5+46↑o
当"a1"的内容为"O05KM1"时,我们可以将"a1"中的每个字符的ASCII值的低4位作为索引去查找array_123数组中的元素。具体来说:
'O’的ASCII值是79,二进制表示是1001111,所以其低4位是1111,即十进制的15。所以’O’被映射为array_123[15]。
'0’的ASCII值是48,二进制表示是110000,所以其低4位是0000,即十进制的0。所以’0’被映射为array_123[0]。
'5’的ASCII值是53,二进制表示是110101,所以其低4位是0101,即十进制的5。所以’5’被映射为array_123[5]。
'K’的ASCII值是75,二进制表示是1001011,所以其低4位是1011,即十进制的11。所以’K’被映射为array_123[11]。
'M’的ASCII值是77,二进制表示是1001101,所以其低4位是1101,即十进制的13。所以’M’被映射为array_123[13]。
'1’的ASCII值是49,二进制表示是110001,所以其低4位是0001,即十进制的1。所以’1’被映射为array_123[1]。
因此,你的输入字符串应该是O05KM1,这样才能生成目标字符串giants
O05KM1
伪代码
int __cdecl phase_5(int a1)
{
int i; // edx
int result; // eax
char v3[8]; // [esp+10h] [ebp-8h] BYREF
if ( string_length(a1) != 6 ) // 检查输入字符串的长度是否为6,如果不是,就调用explode_bomb()函数。
explode_bomb();
for ( i = 0; i <= 5; ++i ) // 遍历输入字符串的每个字符。
v3[i] = array_123[*(_BYTE *)(i + a1) & 0xF]; // 对于每个字符,取字符的ASCII值的低4位,并用这个值作为索引去查找array_123数组中的元素,然后将查找到的元素存储在v3数组中。
v3[6] = 0; // 在v3数组的末尾添加一个空字符,表示字符串的结束。
result = strings_not_equal(v3, "giants"); // 检查新生成的字符串v3是否等于"giants"。
if ( result ) // 如果不等于,就调用explode_bomb()函数。
explode_bomb();
return result; // 返回比较的结果。
}
int __cdecl string_length(_BYTE *a1)
{
_BYTE *v1; // edx
int result; // eax
v1 = a1; // 将v1设置为输入字符串的首地址。
for ( result = 0; *v1; ++result ) // 遍历输入字符串的每个字符,直到遇到空字符(字符串结束标志)。
++v1; // 将v1设置为下一个字符的地址。
return result; // 返回遍历的次数,即字符串的长度。
}
阶段6 链表/指针/结构
任务描述:通过phase_6的反汇编代码推断第六阶段要输入的数据
汇编注解
08048d98 <phase_6>:
8048d98: 55 push %ebp ; 函数调用开始,保存当前的基址指针
8048d99: 89 e5 mov %esp,%ebp ; 设置新的基址指针为当前栈指针
8048d9b: 83 ec 4c sub $0x4c,%esp ; 为局部变量分配76字节的栈空间
8048d9e: 57 push %edi ; 保存edi寄存器的值
8048d9f: 56 push %esi ; 保存esi寄存器的值
8048da0: 53 push %ebx ; 保存ebx寄存器的值
8048da1: 8b 55 08 mov 0x8(%ebp),%edx ; 将第一个参数(在ebp+8处)存入edx寄存器
8048da4: c7 45 cc 6c b2 04 08 movl $0x804b26c,-0x34(%ebp) ; 将固定值存入栈上变量
8048dab: 83 c4 f8 add $0xfffffff8,%esp ; 分配8字节的栈空间
8048dae: 8d 45 e8 lea -0x18(%ebp),%eax ; 将ebp-18的地址存入eax寄存器
8048db1: 50 push %eax ; 将eax寄存器的值入栈
8048db2: 52 push %edx ; 将edx寄存器的值入栈
8048db3: e8 20 02 00 00 call 8048fd8 <read_six_numbers> ; 调用read_six_numbers函数读取六个整数
8048db8: 31 ff xor %edi,%edi ; 清零edi寄存器
8048dba: 83 c4 10 add $0x10,%esp ; 清理栈上的参数
8048dbd: 8d 76 00 lea 0x0(%esi),%esi ; 将esi寄存器的值初始化为0
8048dc0: 8d 45 e8 lea -0x18(%ebp),%eax ; 将ebp-18的地址存入eax寄存器
8048dc3: 8b 04 b8 mov (%eax,%edi,4),%eax ; 将数组中的元素取出到eax寄存器
8048dc6: 48 dec %eax ; 将取出的值减1
8048dc7: 83 f8 05 cmp $0x5,%eax ; 比较是否小于等于5
8048dca: 76 05 jbe 8048dd1 <phase_6+0x39> ; 如果是,继续执行;否则跳转到phase_6+0x39处(爆炸)
8048dcc: e8 2b 07 00 00 call 80494fc <explode_bomb> ; 爆炸
8048dd1: 8d 5f 01 lea 0x1(%edi),%ebx ; 将edi寄存器的值加1,存入ebx寄存器
8048dd4: 83 fb 05 cmp $0x5,%ebx ; 比较ebx寄存器的值与5
8048dd7: 7f 23 jg 8048dfc <phase_6+0x64> ; 如果大于5,跳转到phase_6+0x64处
8048dd9: 8d 04 bd 00 00 00 00 lea 0x0(,%edi,4),%eax ; 计算数组元素在数组中的偏移
8048de0: 89 45 c8 mov %eax,-0x38(%ebp) ; 将偏移存入栈上变量
8048de3: 8d 75 e8 lea -0x18(%ebp),%esi ; 将esi寄存器初始化为ebp-18的地址
8048de6: 8b 55 c8 mov -0x38(%ebp),%edx ; 将偏移值存入edx寄存器
8048de9: 8b 04 32 mov (%edx,%esi,1),%eax ; 将数组元素的值取出到eax寄存器
8048dec: 3b 04 9e cmp (%esi,%ebx,4),%eax ; 比较两个数组元素的值
8048def: 75 05 jne 8048df6 <phase_6+0x5e> ; 如果不相等,跳转到phase_6+0x5e处(爆炸)
8048df1: e8 06 07 00 00 call 80494fc <explode_bomb> ; 爆炸
8048df6: 43 inc %ebx ; ebx寄存器加1
8048df7: 83 fb 05 cmp $0x5,%ebx ; 比较ebx寄存器的值与5
8048dfa: 7e ea jle 8048de6 <phase_6+0x4e> ; 如果小于等于5,继续循环;否则跳转到phase_6+0x4e处
8048dfc: 47 inc %edi ; edi寄存器加1
8048dfd: 83 ff 05 cmp $0x5,%edi ; 比较edi寄存器的值与5
8048e00: 7e be jle 8048dc0 <phase_6+0x28> ; 如果小于等于5,继续循环;否则跳转到phase_6+0x28处
8048e02: 31 ff xor %edi,%edi ; 清零edi寄存器
8048e04: 8d 4d e8 lea -0x18(%ebp),%ecx ; 将ebp-18的地址存入ecx寄存器
8048e07: 8d 45 d0 lea -0x30(%ebp),%eax ; 将ebp-30的地址存入eax寄存器
8048e0a: 89 45 c4 mov %eax,-0x3c(%ebp) ; 将ebp-30的地址存入栈上变量
8048e0d: 8d 76 00 lea 0x0(%esi),%esi ; 将esi寄存器初始化为0
8048e10: 8b 75 cc mov -0x34(%ebp),%esi ; 将栈上变量的值存入esi寄存器
8048e13: bb 01 00 00 00 mov $0x1,%ebx ; 将1存入ebx寄存器
8048e18: 8d 04 bd 00 00 00 00 lea 0x0(,%edi,4),%eax ; 计算数组元素在数组中的偏移
8048e1f: 89 c2 mov %eax,%edx ; 将eax寄存器的值存入edx寄存器
8048e21: 3b 1c 08 cmp (%eax,%ecx,1),%ebx ; 比较两个数组元素的值
8048e24: 7d 12 jge 8048e38 <phase_6+0xa0> ; 如果大于等于,跳转到phase_6+0xa0处
8048e26: 8b 04 0a mov (%edx,%ecx,1),%eax ; 将数组元素的值取出到eax寄存器
8048e29: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi ; 将esi寄存器初始化为0
8048e30: 8b 76 08 mov 0x8(%esi),%esi ; 将esi寄存器的值存入esi寄存器
8048e33: 43 inc %ebx ; ebx寄存器加1
8048e34: 39 c3 cmp %eax,%ebx ; 比较eax寄存器的值与ebx寄存器的值
8048e36: 7c f8 jl 8048e30 <phase_6+0x98> ; 如果小于,跳转到phase_6+0x98处
8048e38: 8b 55 c4 mov -0x3c(%ebp),%edx ; 将栈上变量的值存入edx寄存器
8048e3b: 89 34 ba mov %esi,(%edx,%edi,4) ; 将esi寄存器的值存入数组中
8048e3e: 47 inc %edi ; edi寄存器加1
8048e3f: 83 ff 05 cmp $0x5,%edi ; 比较edi寄存器的值与5
8048e42: 7e cc jle 8048e10 <phase_6+0x78> ; 如果小于等于5,继续循环;否则跳转到phase_6+0x78处
8048e44: 8b 75 d0 mov -0x30(%ebp),%esi ; 将栈上变量的值存入esi寄存器
8048e47: 89 75 cc mov %esi,-0x34(%ebp) ; 将esi寄存器的值存入栈上变量
8048e4a: bf 01 00 00 00 mov $0x1,%edi ; 将1存入edi寄存器
8048e4f: 8d 55 d0 lea -0x30(%ebp),%edx ; 将ebp-30的地址存入edx寄存器
8048e52: 8b 04 ba mov (%edx,%edi,4),%eax ; 将数组元素的值取出到eax寄存器
8048e55: 89 46 08 mov %eax,0x8(%esi) ; 将eax寄存器的值存入esi寄存器+8的地址
8048e58: 89 c6 mov %eax,%esi ; 将eax寄存器的值存入esi寄存器
8048e5a: 47 inc %edi ; edi寄存器加1
8048e5b: 83 ff 05 cmp $0x5,%edi ; 比较edi寄存器的值与5
8048e5e: 7e f2 jle 8048e52 <phase_6+0xba> ; 如果小于等于5,继续循环;否则跳转到phase_6+0xba处
8048e60: c7 46 08 00 00 00 00 movl $0x0,0x8(%esi) ; 将0存入esi寄存器+8的地址
8048e67: 8b 75 cc mov -0x34(%ebp),%esi ; 将栈上变量的值存入esi寄存器
8048e6a: 31 ff xor %edi,%edi ; 清零edi寄存器
8048e6c: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi ; 将esi寄存器初始化为0
8048e70: 8b 56 08 mov 0x8(%esi),%edx ; 将esi寄存器指向的地址的偏移为8的值存入edx寄存器
8048e73: 8b 06 mov (%esi),%eax ; 将esi寄存器指向的地址的值存入eax寄存器
8048e75: 3b 02 cmp (%edx),%eax ; 比较esi寄存器指向的地址和偏移为8的地址的值
8048e77: 7d 05 jge 8048e7e <phase_6+0xe6> ; 如果大于等于,跳转到phase_6+0xe6处
8048e79: e8 7e 06 00 00 call 80494fc <explode_bomb> ; 爆炸
8048e7e: 8b 76 08 mov 0x8(%esi),%esi ; 将esi寄存器指向的地址的偏移为8的值存入esi寄存器
8048e81: 47 inc %edi ; 将edi寄存器的值加1
8048e82: 83 ff 04 cmp $0x4,%edi ; 比较edi寄存器的值和4
8048e85: 7e e9 jle 8048e70 <phase_6+0xd8> ; 如果小于等于,跳转到phase_6+0xd8处
8048e87: 8d 65 a8 lea -0x58(%ebp),%esp ; 重置栈指针
8048e8a: 5b pop %ebx ; 恢复ebx寄存器的值
8048e8b: 5e pop %esi ; 恢复esi寄存器的值
8048e8c: 5f pop %edi ; 恢复edi寄存器的值
8048e8d: 89 ec mov %ebp,%esp ; 恢复栈指针
8048e8f: 5d pop %ebp ; 恢复基址指针
8048e90: c3 ret ; 返回
8048e91: 8d 76 00 lea 0x0(%esi),%esi ; 将esi寄存器的值初始化为0
i=esi;
这是一个循环:8048dc0-8048e00 循环6次
保证数组每个元素小于等于6
8048dbd: 8d 76 00 lea 0x0(%esi),%esi ; 将esi寄存器的值初始化为0
8048dc0: 8d 45 e8 lea -0x18(%ebp),%eax ; 将ebp-18的地址存入eax寄存器
8048dc3: 8b 04 b8 mov (%eax,%edi,4),%eax ; 将数组中的元素取出到eax寄存器
8048dc6: 48 dec %eax ; 将取出的值减1
8048dc7: 83 f8 05 cmp $0x5,%eax ; 比较是否小于等于5
8048dca: 76 05 jbe 8048dd1 <phase_6+0x39> ; 如果是,继续执行;否则跳转到phase_6+0x39处(爆炸)
8048dcc: e8 2b 07 00 00 call 80494fc <explode_bomb> ; 爆炸
这是一个循环 8048de6-8048de6 循环6-i次
保证后面的元素一定与前面的不一样
8048dd1: 8d 5f 01 lea 0x1(%edi),%ebx ; 将edi寄存器的值加1,存入ebx寄存器
8048dd4: 83 fb 05 cmp $0x5,%ebx ; 比较ebx寄存器的值与5
8048dd7: 7f 23 jg 8048dfc <phase_6+0x64> ; 如果大于5,跳转到phase_6+0x64处
8048dd9: 8d 04 bd 00 00 00 00 lea 0x0(,%edi,4),%eax ; 计算数组元素在数组中的偏移
8048de0: 89 45 c8 mov %eax,-0x38(%ebp) ; 将偏移存入栈上变量
8048de3: 8d 75 e8 lea -0x18(%ebp),%esi ; 将esi寄存器初始化为ebp-18的地址
8048de6: 8b 55 c8 mov -0x38(%ebp),%edx ; 将偏移值存入edx寄存器
8048de9: 8b 04 32 mov (%edx,%esi,1),%eax ; 将数组元素的值取出到eax寄存器
8048dec: 3b 04 9e cmp (%esi,%ebx,4),%eax ; 比较两个数组元素的值
8048def: 75 05 jne 8048df6 <phase_6+0x5e> ; 如果不相等,跳转到phase_6+0x5e处(爆炸)
8048df1: e8 06 07 00 00 call 80494fc <explode_bomb> ; 爆炸
8048df6: 43 inc %ebx ; ebx寄存器加1
8048df7: 83 fb 05 cmp $0x5,%ebx ; 比较ebx寄存器的值与5
8048dfa: 7e ea jle 8048de6 <phase_6+0x4e> ; 如果小于等于5,继续循环;否则跳转到phase_6+0x4e处
8048dfc: 47 inc %edi ; edi寄存器加1
8048dfd: 83 ff 05 cmp $0x5,%edi ; 比较edi寄存器的值与5
8048e00: 7e be jle 8048dc0 <phase_6+0x28> ; 如果小于等于5,继续循环;否则跳转到phase_6+0x28处
这是一个循环 8048e10-8048e42 循环6次
8048e30-8048e36 循环v15[k]-1
先看内循环
8048e30: 8b 76 08 mov 0x8(%esi),%esi ; 将esi寄存器的值存入esi寄存器
赋值给下个节点
外循环
8048e3b: 89 34 ba mov %esi,(%edx,%edi,4) ; 将esi寄存器的值存入数组中
结合循环可知:相当于用edi数组的元素作为这个新链表的索引
node4 > node2 > node6 > node3 > node1 > node5
8048e10: 8b 75 cc mov -0x34(%ebp),%esi ; 将栈上变量的值存入esi寄存器
8048e13: bb 01 00 00 00 mov $0x1,%ebx ; 将1存入ebx寄存器
8048e18: 8d 04 bd 00 00 00 00 lea 0x0(,%edi,4),%eax ; 计算数组元素在数组中的偏移
8048e1f: 89 c2 mov %eax,%edx ; 将eax寄存器的值存入edx寄存器
8048e21: 3b 1c 08 cmp (%eax,%ecx,1),%ebx ; 比较两个数组元素的值
8048e24: 7d 12 jge 8048e38 <phase_6+0xa0> ; 如果大于等于,跳转到phase_6+0xa0处
8048e26: 8b 04 0a mov (%edx,%ecx,1),%eax ; 将数组元素的值取出到eax寄存器
8048e29: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi ; 将esi寄存器初始化为0
8048e30: 8b 76 08 mov 0x8(%esi),%esi ; 将esi寄存器的值存入esi寄存器
8048e33: 43 inc %ebx ; ebx寄存器加1
8048e34: 39 c3 cmp %eax,%ebx ; 比较eax寄存器的值与ebx寄存器的值
8048e36: 7c f8 jl 8048e30 <phase_6+0x98> ; 如果小于,跳转到phase_6+0x98处
8048e38: 8b 55 c4 mov -0x3c(%ebp),%edx ; 将栈上变量的值存入edx寄存器
8048e3b: 89 34 ba mov %esi,(%edx,%edi,4) ; 将esi寄存器的值存入数组中
8048e3e: 47 inc %edi ; edi寄存器加1
8048e3f: 83 ff 05 cmp $0x5,%edi ; 比较edi寄存器的值与5
8048e42: 7e cc jle 8048e10 <phase_6+0x78> ; 如果小于等于5,继续循环;否则跳转到phase_6+0x78处
循环8048e52-8048e5e 循环5次
以ebp-30为首地址,建立动态链表
8048e52: 8b 04 ba mov (%edx,%edi,4),%eax ; 将数组元素的值取出到eax寄存器
8048e55: 89 46 08 mov %eax,0x8(%esi) ; 将eax寄存器的值存入esi寄存器+8的地址
8048e58: 89 c6 mov %eax,%esi ; 将eax寄存器的值存入esi寄存器
8048e5a: 47 inc %edi ; edi寄存器加1
8048e5b: 83 ff 05 cmp $0x5,%edi ; 比较edi寄存器的值与5
8048e5e: 7e f2 jle 8048e52 <phase_6+0xba> ; 如果小于等于5,继续循环;否则跳转到phase_6+0xba处
循环8048e70-8048e85 循环4次
前四个值edx与eax比较,要求前面必须比后面大
8048e70: 8b 56 08 mov 0x8(%esi),%edx ; 将esi寄存器指向的地址的偏移为8的值存入edx寄存器
8048e73: 8b 06 mov (%esi),%eax ; 将esi寄存器指向的地址的值存入eax寄存器
8048e75: 3b 02 cmp (%edx),%eax ; 比较esi寄存器指向的地址和偏移为8的地址的值
8048e77: 7d 05 jge 8048e7e <phase_6+0xe6> ; 如果大于等于,跳转到phase_6+0xe6处
8048e79: e8 7e 06 00 00 call 80494fc <explode_bomb> ; 爆炸
8048e7e: 8b 76 08 mov 0x8(%esi),%esi ; 将esi寄存器指向的地址的偏移为8的值存入esi寄存器
8048e81: 47 inc %edi ; 将edi寄存器的值加1
8048e82: 83 ff 04 cmp $0x4,%edi ; 比较edi寄存器的值和4
8048e85: 7e e9 jle 8048e70 <phase_6+0xd8> ; 如果小于等于,跳转到phase_6+0xd8处
满足以下条件即为答案
六个数字都在1到6的范围内。
六个数字都是唯一的,即没有重复的数字。
这些数字对应的节点在链表中的顺序必须是递增的。
node4 > node2 > node6 > node3 > node1 > node5
4 2 6 3 1 5
int __cdecl phase_6(char *s)
{
int v1; // ecx
int i; // edi
int j; // ebx
int k; // edi
_DWORD *v5; // esi
int l; // ebx
int v7; // esi
int m; // edi
int v9; // eax
int v10; // esi
int n; // edi
int result; // eax
int v13; // [esp+24h] [ebp-34h]
int v14[6]; // [esp+28h] [ebp-30h]
int v15[6]; // [esp+40h] [ebp-18h] BYREF
read_six_numbers(s, (int)v15); // 从字符串s中读取六个数字,并将这些数字存储在数组v15中。
for ( i = 0; i <= 5; ++i ) // 遍历数组v15中的每个元素。
{
if ( (unsigned int)(v15[i] - 1) > 5 ) // 如果元素的值不在1到6的范围内,
explode_bomb(v1); // 就调用explode_bomb()函数。
for ( j = i + 1; j <= 5; ++j ) // 对于每个元素,检查它是否与数组中的其他元素相等。
{
if ( v15[i] == v15[j] ) // 如果有相等的元素,
explode_bomb(v1); // 就调用explode_bomb()函数。
}
}
for ( k = 0; k <= 5; ++k ) // 遍历数组v15中的每个元素。
{
v5 = &node1; // 将v5设置为链表的头节点。
for ( l = 1; l < v15[k]; ++l ) // 根据元素的值,遍历链表中的节点。
v5 = (_DWORD *)v5[2]; // 将v5设置为下一个节点。
v14[k] = (int)v5; // 将节点的地址存储在数组v14中。
}
v7 = v14[0]; // 将v7设置为数组v14中的第一个元素(即第一个节点的地址)。
v13 = v14[0]; // 将v13设置为数组v14中的第一个元素(即第一个节点的地址)。
for ( m = 1; m <= 5; ++m ) // 遍历数组v14中的每个元素。
{
v9 = v14[m]; // 将v9设置为当前元素(即当前节点的地址)。
*(_DWORD *)(v7 + 8) = v9; // 将当前节点的下一个节点设置为v9指向的节点。
v7 = v9; // 将v7设置为v9(即将v7设置为当前节点的地址)。
}
*(_DWORD *)(v9 + 8) = 0; // 将最后一个节点的下一个节点设置为NULL,表示链表的结束。
v10 = v13; // 将v10设置为v13(即链表的头节点的地址)。
for ( n = 0; n <= 4; ++n ) // 遍历链表中的每个节点。
{
result = *(_DWORD *)v10; // 获取当前节点的值。
if ( *(_DWORD *)v10 < **(_DWORD **)(v10 + 8) ) // 如果当前节点的值小于下一个节点的值,
explode_bomb(v15); // 就调用explode_bomb()函数。
v10 = *(_DWORD *)(v10 + 8); // 将v10设置为下一个节点的地址。
}
return result; // 返回最后一个节点的值。
}
int __cdecl read_six_numbers(char *s, int a2)
{
int result; // eax
int v3; // ecx
result = sscanf(s, "%d %d %d %d %d %d", a2, a2 + 4, a2 + 8, a2 + 12, a2 + 16, a2 + 20); // 从字符串s中读取六个整数,并将这些整数存储在a2指向的内存中。
if ( result <= 5 ) // 如果无法读取到六个整数,
explode_bomb(v3); // 就调用explode_bomb()函数。
return result; // 返回读取到的整数的数量。
}
阶段7 隐藏阶段
任务描述:找出隐藏阶段开启方式并且拆除隐藏阶段的炸弹。
汇编注解
080489b0 <main>:
80489b0: 55 push %ebp ; 保存基址指针
80489b1: 89 e5 mov %esp,%ebp ; 设置基址指针
80489b3: 83 ec 14 sub $0x14,%esp ; 分配20字节的局部变量空间
80489b6: 53 push %ebx ; 保存ebx寄存器
80489b7: 8b 45 08 mov 0x8(%ebp),%eax ; 将第一个参数(argc)移入eax寄存器
80489ba: 8b 5d 0c mov 0xc(%ebp),%ebx ; 将第二个参数(argv)移入ebx寄存器
80489bd: 83 f8 01 cmp $0x1,%eax ; 比较argc和1
80489c0: 75 0e jne 80489d0 <main+0x20> ; 如果不等于1,跳转到80489d0处
80489c2: a1 48 b6 04 08 mov 0x804b648,%eax ; 将0x804b648的值移到eax寄存器
80489c7: a3 64 b6 04 08 mov %eax,0x804b664 ; 将eax寄存器的值存入0x804b664
80489cc: eb 62 jmp 8048a30 <main+0x80> ; 跳转到8048a30处
80489ce: 89 f6 mov %esi,%esi ; 无操作
80489d0: 83 f8 02 cmp $0x2,%eax ; 比较argc和2
80489d3: 75 3b jne 8048a10 <main+0x60> ; 如果不等于2,跳转到8048a10处
80489d5: 83 c4 f8 add $0xfffffff8,%esp ; 调整栈指针
80489d8: 68 20 96 04 08 push $0x8049620 ; 将字符串地址压入栈
80489dd: 8b 43 04 mov 0x4(%ebx),%eax ; 将argv[1]的值移到eax寄存器
80489e0: 50 push %eax ; 将eax寄存器的值压入栈
80489e1: e8 9a fe ff ff call 8048880 <fopen@plt> ; 调用fopen函数
80489e6: a3 64 b6 04 08 mov %eax,0x804b664 ; 将eax寄存器的值存入0x804b664
80489eb: 83 c4 10 add $0x10,%esp ; 清除栈
80489ee: 85 c0 test %eax,%eax ; 测试eax寄存器的值
80489f0: 75 3e jne 8048a30 <main+0x80> ; 如果不等于0,跳转到8048a30处
80489f2: 83 c4 fc add $0xfffffffc,%esp ; 调整栈指针
80489f5: 8b 43 04 mov 0x4(%ebx),%eax ; 将argv[1]的值移到eax寄存器
80489f8: 50 push %eax ; 将eax寄存器的值压入栈
80489f9: 8b 03 mov (%ebx),%eax ; 将argv[0]的值移到eax寄存器
80489fb: 50 push %eax ; 将eax寄存器的值压入栈
80489fc: 68 22 96 04 08 push $0x8049622 ; 将字符串地址压入栈
8048a01: e8 0a fe ff ff call 8048810 <printf@plt> ; 调用printf函数
8048a06: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048a09: 6a 08 push $0x8 ; 将8压入栈
8048a0b: e8 40 fe ff ff call 8048850 <exit@plt> ; 调用exit函数
8048a10: 83 c4 f8 add $0xfffffff8,%esp ; 调整栈指针
8048a13: 8b 03 mov (%ebx),%eax ; 将argv[0]的值移到eax寄存器
8048a15: 50 push %eax ; 将eax寄存器的值压入栈
8048a16: 68 3f 96 04 08 push $0x804963f ; 将字符串地址压入栈
8048a1b: e8 f0 fd ff ff call 8048810 <printf@plt> ; 调用printf函数
8048a20: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048a23: 6a 08 push $0x8 ; 将8压入栈
8048a25: e8 26 fe ff ff call 8048850 <exit@plt> ; 调用exit函数
8048a2a: 8d b6 00 00 00 00 lea 0x0(%esi),%esi ; 无操作
8048a30: e8 2b 07 00 00 call 8049160 <initialize_bomb> ; 调用initialize_bomb函数
8048a35: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048a38: 68 60 96 04 08 push $0x8049660 ; 将字符串地址压入栈
8048a3d: e8 ce fd ff ff call 8048810 <printf@plt> ; 调用printf函数
8048a42: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048a45: 68 a0 96 04 08 push $0x80496a0 ; 将字符串地址压入栈
8048a4a: e8 c1 fd ff ff call 8048810 <printf@plt> ; 调用printf函数
8048a4f: 83 c4 20 add $0x20,%esp ; 清除栈
8048a52: e8 a5 07 00 00 call 80491fc <read_line> ; 调用read_line函数
8048a57: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048a5a: 50 push %eax ; 将输入的字符串指针压入栈
8048a5b: e8 c0 00 00 00 call 8048b20 <phase_1> ; 调用phase_1函数
8048a60: e8 c7 0a 00 00 call 804952c <phase_defused> ; 调用phase_defused函数
8048a65: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048a68: 68 e0 96 04 08 push $0x80496e0 ; 将字符串地址压入栈
8048a6d: e8 9e fd ff ff call 8048810 <printf@plt> ; 调用printf函数
8048a72: 83 c4 20 add $0x20,%esp ; 清除栈
8048a75: e8 82 07 00 00 call 80491fc <read_line> ; 调用read_line函数
8048a7a: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048a7d: 50 push %eax ; 将输入的字符串指针压入栈
8048a7e: e8 c5 00 00 00 call 8048b48 <phase_2> ; 调用phase_2函数
8048a83: e8 a4 0a 00 00 call 804952c <phase_defused> ; 调用phase_defused函数
8048a88: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048a8b: 68 20 97 04 08 push $0x8049720 ; 将字符串地址压入栈
8048a90: e8 7b fd ff ff call 8048810 <printf@plt> ; 调用printf函数
8048a95: 83 c4 20 add $0x20,%esp ; 清除栈
8048a98: e8 5f 07 00 00 call 80491fc <read_line> ; 调用read_line函数
8048a9d: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048aa0: 50 push %eax ; 将输入的字符串指针压入栈
8048aa1: e8 f2 00 00 00 call 8048ce0 <phase_3> ; 调用phase_3函数
8048aa6: e8 81 0a 00 00 call 804952c <phase_defused> ; 调用phase_defused函数
8048aab: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048ab8: 68 3f 97 04 08 push $0x804973f ; 将字符串地址压入栈
8048abd: e8 58 fd ff ff call 8048810 <printf@plt> ; 调用printf函数
8048ac3: 83 c4 20 add $0x20,%esp ; 清除栈
8048ac6: e8 3c 07 00 00 call 80491fc <read_line> ; 调用read_line函数
8048acb: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048ace: 50 push %eax ; 将输入的字符串指针压入栈
8048acf: e8 17 02 00 00 call 8048ce0 <phase_4> ; 调用phase_4函数
8048ad4: e8 5e 0a 00 00 call 804952c <phase_defused> ; 调用phase_defused函数
8048ad9: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048adc: 68 60 97 04 08 push $0x8049760 ; 将字符串地址压入栈
8048ae1: e8 35 fd ff ff call 8048810 <printf@plt> ; 调用printf函数
8048ae6: 83 c4 20 add $0x20,%esp ; 清除栈
8048ae9: e8 19 07 00 00 call 80491fc <read_line> ; 调用read_line函数
8048aee: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048af1: 50 push %eax ; 将输入的字符串指针压入栈
8048af2: e8 40 02 00 00 call 8048d2c <phase_5> ; 调用phase_5函数
8048af4: 68 a0 97 04 08 push $0x80497a0 ; 将字符串地址压入栈
8048af9: e8 12 fd ff ff call 8048810 <printf@plt> ; 调用printf函数
8048afe: 83 c4 20 add $0x20,%esp ; 调整栈指针
8048b01: e8 f6 06 00 00 call 80491fc <read_line> ; 调用read_line函数
8048b06: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048b09: 50 push %eax ; 将输入的字符串地址压入栈
8048b0a: e8 89 02 00 00 call 8048d98 <phase_6> ; 调用phase_6函数
8048b0f: e8 18 0a 00 00 call 804952c <phase_defused> ; 调用phase_defused函数
8048b14: 31 c0 xor %eax,%eax ; 将eax寄存器清零
8048b16: 8b 5d e8 mov -0x18(%ebp),%ebx ; 将局部变量的值赋给ebx寄存器
8048b19: 89 ec mov %ebp,%esp ; 恢复栈指针
8048b1b: 5d pop %ebp ; 恢复基址指针
8048b1c: c3 ret ; 返回
8048b1d: 90 nop ; 无操作
8048b1e: 90 nop ; 无操作
8048b1f: 90 nop ; 无操作
0804952c <phase_defused>:
804952c: 55 push %ebp # 将基指针压入堆栈
804952d: 89 e5 mov %esp,%ebp # 将堆栈指针的值移动到基指针
804952f: 83 ec 64 sub $0x64,%esp # 从堆栈指针中减去100(0x64)
8049532: 53 push %ebx # 将ebx寄存器的值压入堆栈
8049533: 83 3d 80 b4 04 08 06 cmpl $0x6,0x804b480 # 将6与内存地址0x804b480中的值进行比较
804953a: 75 63 jne 804959f <phase_defused+0x73> # 如果上述比较不相等,则跳转到804959f
804953c: 8d 5d b0 lea -0x50(%ebp),%ebx # 将基指针减去80(0x50)的结果加载到ebx寄存器
804953f: 53 push %ebx # 将ebx寄存器的值压入堆栈
8049540: 8d 45 ac lea -0x54(%ebp),%eax # 将基指针减去84(0x54)的结果加载到eax寄存器
8049543: 50 push %eax # 将eax寄存器的值压入堆栈
8049544: 68 03 9d 04 08 push $0x8049d03 # 将0x8049d03压入堆栈
8049549: 68 70 b7 04 08 push $0x804b770 # 将0x804b770压入堆栈
804954e: e8 0d f3 ff ff call 8048860 <sscanf@plt> # 调用函数sscanf
8049553: 83 c4 10 add $0x10,%esp # 将堆栈指针增加16(0x10)
8049556: 83 f8 02 cmp $0x2,%eax # 将2与eax寄存器的值进行比较
8049559: 75 37 jne 8049592 <phase_defused+0x66> # 如果上述比较不相等,则跳转到8049592
804955b: 83 c4 f8 add $0xfffffff8,%esp # 将堆栈指针减去8
804955e: 68 09 9d 04 08 push $0x8049d09 # 将0x8049d09压入堆栈
8049563: 53 push %ebx # 将ebx寄存器的值压入堆栈
8049564: e8 c7 fa ff ff call 8049030 <strings_not_equal> # 调用函数strings_not_equal
8049569: 83 c4 10 add $0x10,%esp # 将堆栈指针增加16(0x10)
804956c: 85 c0 test %eax,%eax # 测试eax寄存器的值
804956e: 75 22 jne 8049592 <phase_defused+0x66> # 如果上述测试结果不为0,则跳转到8049592
8049570: 83 c4 f4 add $0xfffffff4,%esp # 将堆栈指针减去12
8049573: 68 20 9d 04 08 push $0x8049d20 # 将0x8049d20压入堆栈
8049578: e8 93 f2 ff ff call 8048810 <printf@plt> # 调用函数printf
804957d: 83 c4 f4 add $0xfffffff4,%esp # 将堆栈指针减去12
8049580: 68 60 9d 04 08 push $0x8049d60 # 将0x8049d60压入堆栈
8049585: e8 86 f2 ff ff call 8048810 <printf@plt> # 调用函数printf
804958a: 83 c4 20 add $0x20,%esp # 将堆栈指针增加32(0x20)
804958d: e8 56 f9 ff ff call 8048ee8 <secret_phase> # 调用函数secret_phase
8049592: 83 c4 f4 add $0xfffffff4,%esp # 将堆栈指针减去12
8049595: 68 a0 9d 04 08 push $0x8049da0 # 将0x8049da0压入堆栈
804959a: e8 71 f2 ff ff call 8048810 <printf@plt> # 调用函数printf
804959f: 8b 5d 98 mov -0x68(%ebp),%ebx # 将基指针减去104(0x68)的内存地址中的值移动到ebx寄存器
80495a2: 89 ec mov %ebp,%esp # 将基指针的值移动到堆栈指针
80495a4: 5d pop %ebp # 弹出堆栈顶部的值到基指针
80495a5: c3 ret # 返回
80495a6: 90 nop # 无操作
80495a7: 90 nop # 无操作
80495a8: 90 nop # 无操作
80495a9: 90 nop # 无操作
80495aa: 90 nop # 无操作
80495ab: 90 nop # 无操作
80495ac: 90 nop # 无操作
80495ad: 90 nop # 无操作
80495ae: 90 nop # 无操作
80495af: 90 nop # 无操作
8048ee8 <secret_phase>:
8048ee8: 55 push %ebp ; 保存基址指针
8048ee9: 89 e5 mov %esp,%ebp ; 设置基址指针
8048eeb: 83 ec 14 sub $0x14,%esp ; 分配20字节的局部变量空间
8048eee: 53 push %ebx ; 保存ebx寄存器
8048eef: e8 08 03 00 00 call 80491fc <read_line> ; 调用read_line函数,读取一行输入字符串
8048ef4: 6a 00 push $0x0 ; 将0压入栈作为strtol函数的第二个参数
8048ef6: 6a 0a push $0xa ; 将10压入栈作为strtol函数的第三个参数
8048ef8: 6a 00 push $0x0 ; 将0压入栈作为strtol函数的第四个参数
8048efa: 50 push %eax ; 将输入的字符串指针压入栈
8048efb: e8 f0 f8 ff ff call 80487f0 <__strtol_internal@plt> ; 调用strtol函数将字符串转换为长整型
8048f00: 83 c4 10 add $0x10,%esp ; 清除栈
8048f03: 89 c3 mov %eax,%ebx ; 将转换后的值存入ebx寄存器
8048f05: 8d 43 ff lea -0x1(%ebx),%eax ; ebx减去1,存入eax寄存器
8048f08: 3d e8 03 00 00 cmp $0x3e8,%eax ; 比较eax寄存器和1000
8048f0d: 76 05 jbe 8048f14 <secret_phase+0x2c> ; 如果小于等于,跳转到8048f14处
8048f0f: e8 e8 05 00 00 call 80494fc <explode_bomb> ; 爆炸
8048f14: 83 c4 f8 add $0xfffffff8,%esp ; 调整栈指针
8048f17: 53 push %ebx ; 将ebx寄存器的值压入栈
8048f18: 68 20 b3 04 08 push $0x804b320 ; 将字符串地址压入栈
8048f1d: e8 72 ff ff ff call 8048e94 <fun7> ; 调用fun7函数
8048f22: 83 c4 10 add $0x10,%esp ; 清除栈
8048f25: 83 f8 07 cmp $0x7,%eax ; 比较eax寄存器和7
8048f28: 74 05 je 8048f2f <secret_phase+0x47> ; 如果相等,跳转到8048f2f处
8048f2a: e8 cd 05 00 00 call 80494fc <explode_bomb> ; 爆炸
8048f2f: 83 c4 f4 add $0xfffffff4,%esp ; 调整栈指针
8048f32: 68 20 98 04 08 push $0x8049820 ; 将字符串地址压入栈
8048f37: e8 d4 f8 ff ff call 8048810 <printf@plt> ; 调用printf函数
8048f3c: e8 eb 05 00 00 call 804952c <phase_defused> ; 调用phase_defused函数
8048f41: 8b 5d e8 mov -0x18(%ebp),%ebx ; 将局部变量的值赋给ebx寄存器
8048f44: 89 ec mov %ebp,%esp ; 恢复栈指针
8048f46: 5d pop %ebp ; 恢复基址指针
8048f47: c3 ret ; 返回
8048f48: 90 nop
8048f49: 90 nop
8048f4a: 90 nop
8048f4b: 90 nop
8048f4c: 90 nop
8048f4d: 90 nop
8048f4e: 90 nop
8048f4f: 90 nop
08048e94 <fun7>:
8048e94: 55 push %ebp ; 将基指针寄存器的值压入堆栈
8048e95: 89 e5 mov %esp,%ebp ; 将堆栈指针寄存器的值移动到基指针寄存器
8048e97: 83 ec 08 sub $0x8,%esp ; 从堆栈指针寄存器中减去8
8048e9a: 8b 55 08 mov 0x8(%ebp),%edx ; 将基指针寄存器加8的值移动到数据寄存器edx
8048e9d: 8b 45 0c mov 0xc(%ebp),%eax ; 将基指针寄存器加c的值移动到累加器
8048ea0: 85 d2 test %edx,%edx ; 测试数据寄存器edx的值
8048ea2: 75 0c jne 8048eb0 <fun7+0x1c> ; 如果上一条测试指令的结果不为零,则跳转到8048eb0
8048ea4: b8 ff ff ff ff mov $0xffffffff,%eax ; 将-1移动到累加器
8048ea9: eb 37 jmp 8048ee2 <fun7+0x4e> ; 无条件跳转到8048ee2
8048eab: 90 nop ; 无操作指令
8048eac: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi ; 将源索引寄存器加0的值加载到源索引寄存器
8048eb0: 3b 02 cmp (%edx),%eax ; 比较数据寄存器edx和累加器的值
8048eb2: 7d 11 jge 8048ec5 <fun7+0x31> ; 如果上一条比较指令的结果大于或等于零,则跳转到8048ec5
8048eb4: 83 c4 f8 add $0xfffffff8,%esp ; 将堆栈指针寄存器加fffffff8
8048eb7: 50 push %eax ; 将累加器的值压入堆栈
8048eb8: 8b 42 04 mov 0x4(%edx),%eax ; 将数据寄存器加4的值移动到累加器
8048ebb: 50 push %eax ; 将累加器的值压入堆栈
8048ebc: e8 d3 ff ff ff call 8048e94 <fun7> ; 调用函数fun7
8048ec1: 01 c0 add %eax,%eax ; 将累加器的值加到累加器
8048ec3: eb 1d jmp 8048ee2 <fun7+0x4e> ; 无条件跳转到8048ee2
8048ec5: 3b 02 cmp (%edx),%eax ; 比较数据寄存器edx和累加器的值
8048ec7: 74 17 je 8048ee0 <fun7+0x4c> ; 如果上一条比较指令的结果等于零,则跳转到8048ee0
8048ec9: 83 c4 f8 add $0xfffffff8,%esp ; 将堆栈指针寄存器加fffffff8
8048ecc: 50 push %eax ; 将累加器的值压入堆栈
8048ecd: 8b 42 08 mov 0x8(%edx),%eax ; 将数据寄存器加8的值移动到累加器
8048ed0: 50 push %eax ; 将累加器的值压入堆栈
8048ed1: e8 be ff ff ff call 8048e94 <fun7> ; 调用函数fun7
8048ed6: 01 c0 add %eax,%eax ; 将累加器的值加到累加器
8048ed8: 40 inc %eax ; 累加器自增
8048ed9: eb 07 jmp 8048ee2 <fun7+0x4e> ; 无条件跳转到8048ee2
8048edb: 90 nop ; 无操作指令
8048edc: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi ; 将源索引寄存器加0的值加载到源索引寄存器
8048ee0: 31 c0 xor %eax,%eax ; 累加器异或累加器
8048ee2: 89 ec mov %ebp,%esp ; 将基指针寄存器的值移动到堆栈指针寄存器
8048ee4: 5d pop %ebp ; 弹出堆栈顶部的值到基指针寄存器
8048ee5: c3 ret ; 返回
8048ee6: 89 f6 mov %esi,%esi ; 将源索引寄存器的值移动到源索引寄存器
下面是个二叉树
读左右子树地址可知为小段输入
节点n1的值为24h
节点n21的值为8
节点n22的值为32h
节点n31的值为6
节点n32的值为16h
节点n33的值为2Dh
节点n34的值为6Bh
节点n41的值为1
节点n42的值为7
节点n43的值为14h
节点n44的值为23h
节点n45的值为28h
节点n46的值为2Fh
节点n47的值为63h
节点n48的值为3E9h
n数据段
.data:0804B278 n48 db 0E9h
.data:0804B279 db 3
.data:0804B27A db 0
.data:0804B27B db 0
.data:0804B27C db 0
.data:0804B27D db 0
.data:0804B27E db 0
.data:0804B27F db 0
.data:0804B280 db 0
.data:0804B281 db 0
.data:0804B282 db 0
.data:0804B283 db 0
.data:0804B284 public n46
.data:0804B284 n46 db 2Fh ; /
.data:0804B285 db 0
.data:0804B286 db 0
.data:0804B287 db 0
.data:0804B288 db 0
.data:0804B289 db 0
.data:0804B28A db 0
.data:0804B28B db 0
.data:0804B28C db 0
.data:0804B28D db 0
.data:0804B28E db 0
.data:0804B28F db 0
.data:0804B290 public n43
.data:0804B290 n43 db 14h
.data:0804B291 db 0
.data:0804B292 db 0
.data:0804B293 db 0
.data:0804B294 db 0
.data:0804B295 db 0
.data:0804B296 db 0
.data:0804B297 db 0
.data:0804B298 db 0
.data:0804B299 db 0
.data:0804B29A db 0
.data:0804B29B db 0
.data:0804B29C public n42
.data:0804B29C n42 db 7
.data:0804B29D db 0
.data:0804B29E db 0
.data:0804B29F db 0
.data:0804B2A0 db 0
.data:0804B2A1 db 0
.data:0804B2A2 db 0
.data:0804B2A3 db 0
.data:0804B2A4 db 0
.data:0804B2A5 db 0
.data:0804B2A6 db 0
.data:0804B2A7 db 0
.data:0804B2A8 public n44
.data:0804B2A8 n44 db 23h ; #
.data:0804B2A9 db 0
.data:0804B2AA db 0
.data:0804B2AB db 0
.data:0804B2AC db 0
.data:0804B2AD db 0
.data:0804B2AE db 0
.data:0804B2AF db 0
.data:0804B2B0 db 0
.data:0804B2B1 db 0
.data:0804B2B2 db 0
.data:0804B2B3 db 0
.data:0804B2B4 public n47
.data:0804B2B4 n47 db 63h ; c
.data:0804B2B5 db 0
.data:0804B2B6 db 0
.data:0804B2B7 db 0
.data:0804B2B8 db 0
.data:0804B2B9 db 0
.data:0804B2BA db 0
.data:0804B2BB db 0
.data:0804B2BC db 0
.data:0804B2BD db 0
.data:0804B2BE db 0
.data:0804B2BF db 0
.data:0804B2C0 public n41
.data:0804B2C0 n41 db 1
.data:0804B2C1 db 0
.data:0804B2C2 db 0
.data:0804B2C3 db 0
.data:0804B2C4 db 0
.data:0804B2C5 db 0
.data:0804B2C6 db 0
.data:0804B2C7 db 0
.data:0804B2C8 db 0
.data:0804B2C9 db 0
.data:0804B2CA db 0
.data:0804B2CB db 0
.data:0804B2CC public n45
.data:0804B2CC n45 db 28h ; (
.data:0804B2CD db 0
.data:0804B2CE db 0
.data:0804B2CF db 0
.data:0804B2D0 db 0
.data:0804B2D1 db 0
.data:0804B2D2 db 0
.data:0804B2D3 db 0
.data:0804B2D4 db 0
.data:0804B2D5 db 0
.data:0804B2D6 db 0
.data:0804B2D7 db 0
.data:0804B2D8 public n34
.data:0804B2D8 n34 db 6Bh ; k
.data:0804B2D9 db 0
.data:0804B2DA db 0
.data:0804B2DB db 0
.data:0804B2DC db 0B4h
.data:0804B2DD db 0B2h
.data:0804B2DE db 4
.data:0804B2DF db 8
.data:0804B2E0 db 78h ; x
.data:0804B2E1 db 0B2h
.data:0804B2E2 db 4
.data:0804B2E3 db 8
.data:0804B2E4 public n31
.data:0804B2E4 n31 db 6
.data:0804B2E5 db 0
.data:0804B2E6 db 0
.data:0804B2E7 db 0
.data:0804B2E8 db 0C0h
.data:0804B2E9 db 0B2h
.data:0804B2EA db 4
.data:0804B2EB db 8
.data:0804B2EC db 9Ch
.data:0804B2ED db 0B2h
.data:0804B2EE db 4
.data:0804B2EF db 8
.data:0804B2F0 public n33
.data:0804B2F0 n33 db 2Dh ; -
.data:0804B2F1 db 0
.data:0804B2F2 db 0
.data:0804B2F3 db 0
.data:0804B2F4 db 0CCh
.data:0804B2F5 db 0B2h
.data:0804B2F6 db 4
.data:0804B2F7 db 8
.data:0804B2F8 db 84h
.data:0804B2F9 db 0B2h
.data:0804B2FA db 4
.data:0804B2FB db 8
.data:0804B2FC public n32
.data:0804B2FC n32 db 16h
.data:0804B2FD db 0
.data:0804B2FE db 0
.data:0804B2FF db 0
.data:0804B300 db 90h
.data:0804B301 db 0B2h
.data:0804B302 db 4
.data:0804B303 db 8
.data:0804B304 db 0A8h
.data:0804B305 db 0B2h
.data:0804B306 db 4
.data:0804B307 db 8
.data:0804B308 public n22
.data:0804B308 n22 db 32h ; 2
.data:0804B309 db 0
.data:0804B30A db 0
.data:0804B30B db 0
.data:0804B30C db 0F0h
.data:0804B30D db 0B2h
.data:0804B30E db 4
.data:0804B30F db 8
.data:0804B310 db 0D8h
.data:0804B311 db 0B2h
.data:0804B312 db 4
.data:0804B313 db 8
.data:0804B314 public n21
.data:0804B314 n21 db 8
.data:0804B315 db 0
.data:0804B316 db 0
.data:0804B317 db 0
.data:0804B318 db 0E4h
.data:0804B319 db 0B2h
.data:0804B31A db 4
.data:0804B31B db 8
.data:0804B31C db 0FCh
.data:0804B31D db 0B2h
.data:0804B31E db 4
.data:0804B31F db 8
.data:0804B320 public n1
.data:0804B320 n1 dd 24h, 804B314h, 804B308h, 5 dup(0)
.data:0804B320 ; DATA XREF: secret_phase+30↑o
答案
打开隐藏关
由main函数可知,每个函数调用后总会调用一个函数
8048a5b: e8 c0 00 00 00 call 8048b20 <phase_1>
8048a60: e8 c7 0a 00 00 call 804952c <phase_defused>
查询这个函数phase_defused 可得
第一个if条件 调用0x804b480 查找得知 0x804b480是num_input_strings,即必须过六个关卡才能通过
(具体可见read_line()函数中,有.text:080492B0 inc num_input_strings)也就是说,每read_line()一次,便累加一次
8049533: 83 3d 80 b4 04 08 06 cmpl $0x6,0x804b480 # 将6与内存地址0x804b480中的值进行比较
观察知道 有两个变量 一个ebx50空间,一个eax4空间
804953c: 8d 5d b0 lea -0x50(%ebp),%ebx # 将基指针减去80(0x50)的结果加载到ebx寄存器
804953f: 53 push %ebx # 将ebx寄存器的值压入堆栈
8049540: 8d 45 ac lea -0x54(%ebp),%eax # 将基指针减去84(0x54)的结果加载到eax寄存器
8049543: 50 push %eax # 将eax寄存器的值压入堆栈
调用sscanf内容,注意到s为phase_4输入的内容,所以应在输入s时1输入内容,方能打开隐藏关
8049544: 68 03 9d 04 08 push $0x8049d03 # 将0x8049d03压入堆栈
8049549: 68 70 b7 04 08 push $0x804b770 # 将0x804b770压入堆栈
804954e: e8 0d f3 ff ff call 8048860 <sscanf@plt> # 调用函数sscanf
eax与0x8049d09比较,查0x8049d09得.rodata:08049D09 aAustinpowers db 'austinpowers',0 ; DATA XREF: phase_defused+32↑o
804955e: 68 09 9d 04 08 push $0x8049d09 # 将0x8049d09压入堆栈
8049563: 53 push %ebx # 将ebx寄存器的值压入堆栈
8049564: e8 c7 fa ff ff call 8049030 <strings_not_equal> # 调用函数strings_not_equal
综上 输入一个int再输入aAustinpowers,即可打开隐藏关
注:
在read_line函数中,为了避免调用explode_bomb函数,你需要满足以下条件:
skip函数的返回值不能为0。
如果输入文件是标准输入,那么skip函数的返回值也不能为0。
输入的字符串长度不能等于80。
在phase_defused函数中,为了避免调用secret_phase函数(这可能会导致调用explode_bomb函数),需要满足以下条件:
搜索可得 __strtol_internal可以将读取的行转换为整数
保证该数在1-1001以内,注意,是cmp
8048f05: 8d 43 ff lea -0x1(%ebx),%eax ; ebx减去1,存入eax寄存器
8048f08: 3d e8 03 00 00 cmp $0x3e8,%eax ; 比较eax寄存器和1000
8048f0d: 76 05 jbe 8048f14 <secret_phase+0x2c> ; 如果小于等于,跳转到8048f14处
调用fun7函数
24h:一个十六进制数,等于十进制的36。
804B314h和804B308h:这两个看起来像是内存地址,可能是指向其他数据或者函数的指针。
5 dup(0):这是汇编语言的一种语法,表示创建5个值为0的双字。
8048f17: 53 push %ebx ; 将ebx寄存器的值压入栈
8048f18: 68 20 b3 04 08 push $0x804b320 ; 将字符串地址压入栈
8048f1d: e8 72 ff ff ff call 8048e94 <fun7> ; 调用fun7函数
输出结果不为7时爆炸
8048f25: 83 f8 07 cmp $0x7,%eax ; 比较eax寄存器和7
8048f28: 74 05 je 8048f2f <secret_phase+0x47> ; 如果相等,跳转到8048f2f处
在secret_phase函数中,为了避免调用explode_bomb函数,需要满足以下条件:
输入的整数需要在1到1001之间(包括1和1001)。
fun7函数的返回值必须等于7。
接下来看fun7函数:
状况1:验证edx为0,返回-1
8048ea0: 85 d2 test %edx,%edx ; 测试数据寄存器edx的值
8048ea2: 75 0c jne 8048eb0 <fun7+0x1c> ; 如果上一条测试指令的结果不为零,则跳转到8048eb0
8048ea4: b8 ff ff ff ff mov $0xffffffff,%eax ; 将-1移动到累加器
状况2:edx与eax比较>=0
8048eb0: 3b 02 cmp (%edx),%eax ; 比较数据寄存器edx和累加器的值
8048eb2: 7d 11 jge 8048ec5 <fun7+0x31> ; 如果上一条比较指令的结果大于或等于零,则跳转到8048ec5
返回值:递归函数,输出add %eax,%eax
8048eb4: 83 c4 f8 add $0xfffffff8,%esp ; 将堆栈指针寄存器加fffffff8
8048eb7: 50 push %eax ; 将累加器的值压入堆栈
8048eb8: 8b 42 04 mov 0x4(%edx),%eax ; 将数据寄存器加4的值移动到累加器
8048ebb: 50 push %eax ; 将累加器的值压入堆栈
8048ebc: e8 d3 ff ff ff call 8048e94 <fun7> ; 调用函数fun7
8048ec1: 01 c0 add %eax,%eax ; 将累加器的值加到累加器
状况3:结合上次跳转,此处为edx与eax==
8048ec5: 3b 02 cmp (%edx),%eax ; 比较数据寄存器edx和累加器的值
8048ec7: 74 17 je 8048ee0 <fun7+0x4c> ; 如果上一条比较指令的结果等于零,则跳转到8048ee0
返回值
8048ee0: 31 c0 xor %eax,%eax ; 累加器异或累加器
状况4:都不符合
返回值,也是递归,返回 add %eax,%eax inc %eax
8048ec7: 74 17 je 8048ee0 <fun7+0x4c> ; 如果上一条比较指令的结果等于零,则跳转到8048ee0
8048ec9: 83 c4 f8 add $0xfffffff8,%esp ; 将堆栈指针寄存器加fffffff8
8048ecc: 50 push %eax ; 将累加器的值压入堆栈
8048ecd: 8b 42 08 mov 0x8(%edx),%eax ; 将数据寄存器加8的值移动到累加器
8048ed0: 50 push %eax ; 将累加器的值压入堆栈
8048ed1: e8 be ff ff ff call 8048e94 <fun7> ; 调用函数fun7
8048ed6: 01 c0 add %eax,%eax ; 将累加器的值加到累加器
8048ed8: 40 inc %eax ; 累加器自增
综上可以得知函数内容
请看上面数据段对其分析
易知:深度为4,要想达到数值为7,必定一直向右,向右支树深度搜索得,最大为03E9,即最终答案为03e9
或者
/*
* @Description:
* @Version: 2.0
* @Author: lbj
* @Date: 2023-11-13 12:07:17
* @LastEditors:
* @LastEditTime: 2023-12-07 19:18:00
*/
#include <iostream>
struct Node
{
int value;
Node *left;
Node *right;
};
int fun7(Node *a1, int a2)
{
if (!a1)
return -1;
if (a2 < a1->value)
return 2 * fun7(a1->left, a2);
if (a2 == a1->value)
return 0;
return 2 * fun7(a1->right, a2) + 1;
}
void testFun7(Node *root)
{
for (int i = 1; i <= 1001; ++i)
{
if (fun7(root, i) == 7)
std::cout << i << " ";
}
std::cout << std::endl;
}
int main()
{
Node n1 = {0x24, nullptr, nullptr};
Node n21 = {8, nullptr, nullptr};
Node n22 = {0x32, nullptr, nullptr};
Node n31 = {6, nullptr, nullptr};
Node n32 = {0x16, nullptr, nullptr};
Node n33 = {0x2D, nullptr, nullptr};
Node n34 = {0x6B, nullptr, nullptr};
Node n41 = {0x1, nullptr, nullptr};
Node n42 = {0x7, nullptr, nullptr};
Node n43 = {0x14, nullptr, nullptr};
Node n44 = {0x23, nullptr, nullptr};
Node n45 = {0x28, nullptr, nullptr};
Node n46 = {0x2F, nullptr, nullptr};
Node n47 = {0x63, nullptr, nullptr};
Node n48 = {0x3E9, nullptr, nullptr};
n1.left = &n21;
n1.right = &n22;
n21.left = &n31;
n21.right = &n32;
n22.left = &n33;
n22.right = &n34;
n31.left = &n41;
n31.right = &n42;
n32.left = &n43;
n32.right = &n44;
n33.left = &n45;
n33.right = &n46;
n34.left = &n47;
n34.right = &n48;
testFun7(&n1);
return 0;
}
输出结果也为1001
伪代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char *v4; // eax
char *v5; // eax
char *v6; // eax
int v7; // eax
char *v8; // eax
if ( argc == 1 )
{
infile = (_IO_FILE *)stdin;
}
else
{
if ( argc != 2 )
{
printf("Usage: %s [<input_file>]\n", *argv);
exit(8);
}
infile = fopen(argv[1], "r");
if ( !infile )
{
printf("%s: Error: Couldn't open %s\n", *argv, argv[1]);
exit(8);
}
}
initialize_bomb();
printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
printf("which to blow yourself up. Have a nice day!\n");
v3 = read_line();
phase_1(v3);
phase_defused();
printf("Phase 1 defused. How about the next one?\n");
v4 = (char *)read_line();
phase_2(v4);
phase_defused();
printf("That's number 2. Keep going!\n");
v5 = (char *)read_line();
phase_3(v5);
phase_defused();
printf("Halfway there!\n");
v6 = (char *)read_line();
phase_4(v6);
phase_defused();
printf("So you got that one. Try this one.\n");
v7 = read_line();
phase_5(v7);
phase_defused();
printf("Good work! On to the next...\n");
v8 = (char *)read_line();
phase_6(v8);
phase_defused();
return 0;
}
void phase_defused()
{
char v0; // [esp+14h] [ebp-54h] BYREF
char v1[80]; // [esp+18h] [ebp-50h] BYREF
if ( num_input_strings == 6 )
{
if ( sscanf(s, "%d %s", &v0, v1) == 2 && !strings_not_equal(v1, "austinpowers") )
{
printf("Curses, you've found the secret phase!\n");
printf("But finding it and solving it are quite different...\n");
secret_phase();
}
printf("Congratulations! You've defused the bomb!\n");
}
}
.data:0804B320 n1 db 24h ; $ ; DATA XREF: secret_phase+30↑o
int secret_phase()
{
const char *v0; // 读取的行
int v1; // 转换后的整数
v0 = (const char *)read_line(); // 读取一行
v1 = __strtol_internal(v0, 0, 10, 0); // 将读取的行转换为整数
if ( (unsigned int)(v1 - 1) > 0x3E8 ) // 如果整数大于1000
explode_bomb(); // 爆炸
if ( fun7(&n1, v1) != 7 ) // 如果fun7函数的返回值不等于7
explode_bomb(); // 爆炸
printf("Wow! You've defused the secret stage!\n"); // 打印消息
return phase_defused(); // 返回phase_defused函数的结果
}
int read_line()
{
unsigned int v0; // 字符串长度
unsigned int v1; // 字符串长度减一
int v2; // 输入字符串的索引
int result; // 返回结果
if ( !skip() ) // 如果没有跳过
{
if ( infile == (_IO_FILE *)stdin ) // 如果输入文件是标准输入
goto LABEL_6; // 跳转到LABEL_6
if ( getenv("GRADE_BOMB") ) // 如果环境变量中有"GRADE_BOMB"
exit(0); // 退出
infile = (_IO_FILE *)stdin; // 将输入文件设置为标准输入
if ( !skip() ) // 如果没有跳过
{
LABEL_6:
printf("Error: Premature EOF on stdin\n"); // 打印错误消息
explode_bomb(); // 爆炸
}
}
v0 = strlen(&input_strings[80 * num_input_strings]) + 1; // 计算字符串长度
v1 = v0 - 1; // 字符串长度减一
if ( v0 == 80 ) // 如果字符串长度等于80
{
printf("Error: Input line too long\n"); // 打印错误消息
explode_bomb(); // 爆炸
}
v2 = 80 * num_input_strings; // 计算输入字符串的索引
byte_804B67F[v1 + v2] = 0; // 在字符串末尾添加空字符
result = v2 + 134526592; // 计算返回结果
++num_input_strings; // 输入字符串数量加一
return result; // 返回结果
}
int __cdecl fun7(_DWORD *a1, int a2)
{
if ( !a1 ) // 如果a1为空
return -1; // 返回-1
if ( a2 < *a1 ) // 如果a2小于a1的值
return 2 * fun7(a1[1], a2); // 返回2倍的fun7函数的结果
if ( a2 == *a1 ) // 如果a2等于a1的值
return 0; // 返回0
return 2 * fun7(a1[2], a2) + 1; // 返回2倍的fun7函数的结果加1
}
// attributes: thunk
int __strtol_internal(const char *nptr, char **endptr, int base, int group)
{
return _strtol_internal(nptr, endptr, base, group); // 返回_strtol_internal函数的结果
}
void phase_defused()
{
char v0; // 变量v0
char v1[80]; // 字符数组v1
if ( num_input_strings == 6 ) // 如果输入字符串数量等于6
{
if ( sscanf(s, "%d %s", &v0, v1) == 2 && !strings_not_equal(v1, "austinpowers") ) // 如果sscanf函数的返回值等于2且v1等于"austinpowers"
{
printf("Curses, you've found the secret phase!\n"); // 打印消息
printf("But finding it and solving it are quite different...\n"); // 打印消息
secret_phase(); // 调用secret_phase函数
}
printf("Congratulations! You've defused the bomb!\n"); // 打印消息
}
}
答案总结
Public speaking is very easy.
1 2 6 24 120 720
0 q 777
9 austinpowers
O05KM1
4 2 6 3 1 5
1001
附录
程序的调试过程主要有:单步执行,跳入函数,跳出函数,设置断点,设置观察点,查看变量。
本文将主要介绍linux下的强大调试工具是怎么完成这些工作的。
之所以要调试程序,是因为程序的运行结果和预期结果不一致,或者程序出现运行时错误。
调试的基本思想是:
分析现象 -> 假设错误原因 -> 产生新的现象去验证假设
调试器(如GDB)的目的是允许你在程序运行时进入到某个程序内部去看看该程序在做什么,或者在该程序崩溃时它在做什么。
GDB主要可以做4大类事(加上一些其他的辅助工作),以帮助用户在程序运行过程中发现bug。
* 启动您的程序,并列出可能会影响它运行的一些信息
* 使您的程序在特定条件下停止下来
* 当程序停下来的时候,检查发生了什么
* 对程序做出相应的调整,这样您就能尝试纠正一个错误并继续发现其它错误
您能使用GDB调试用C、C++、Modula-2写的程序等GNU Fortran编译器准备好过后,GDB将提供对Fortran的支持
gdb参数选项详解
gcc调试相关编译选项
GDB通过在命令行方式下输入gdb来执行。启动过后,GDB会从终端读取命令,直到您输入GDB命令quit使GDB退出。您能通过GDB命
gcc -g main.c
1
gdb主要调试的是C/C++的程序。要调试C/C++的程序,首先在编译时,必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数即可。如:
如果没有-g,将看不见程序的函数名和变量名,代替它们的全是运行时的内存地址。当用-g把调试信息加入,并成功编译目标代码以后,看看如何用gdb来调试。
要用gdb调试程序,必须在编译时加上-g和-ggdb选项,-g选项的作用是在可执行文件中加入源文件信息,但并不是将源文件嵌入可执行文件,所以在调试时必须保证gdb必须能找到源文件.
-g 和 -ggdb 都是令 gcc 生成调试信息,但是它们也是有区别的
选项 | 描述 |
---|---|
g | 该选项可以利用操作系统的“原生格式(native format)”生成调试信息。GDB 可以直接利用这个信息,其它调试器也可以使用这个调试信息 |
ggdb | 使 GCC为GDB 生成专用的更为丰富的调试信息,但是,此时就不能用其他的调试器来进行调试了 (如 ddx) |
-g 和 -ggdb 也是分级别的
选项 | 描述 |
---|---|
g1 | 级别1(-g1)不包含局部变量和与行号有关的调试信息,因此只能够用于回溯跟踪和堆栈转储之用。回溯跟踪指的是监视程序在运行过程中的函数调用历史,堆栈转储则是一种以原始的十六进制格式保存程序执行环境的方法,两者都是经常用到的调试手段 |
g2 | 这是默认的级别,此时产生的调试信息包括扩展的符号表、行号、局部或外部变量信息 |
g3 | 包含级别2中的所有调试信息,以及源代码中定义的宏 |
gdb参数选项(启动)
启动gdb的标准命令如下
gdb [-help] [-nx] [-q] [-batch] [-cd=dir] [-f] [-b bps]
[-tty=dev] [-s symfile] [-e prog] [-se prog] [-c core]
[-x cmds] [-d dir] [prog[core|procID]]123
您能以无参数无选项的形式运行GDB,不过通常的情况是以一到两个参数运行GDB,以待调试的可执行程序名(-se指定)为参数和core dump文件(-c指定)
但是我们启动的时候,往往不需要指定-se和-c, 因为如果启动gdb时候提供了参数, 那么任何参数而非选项指明了一个可执行文件及core 文件(或者进程ID)
- 所遇到的第一个未关联选项标志的参数与 ‘-se’ 选项等价
- 第二个,如果存在,且是一个文件的名字,则等价与 ‘-c’ 选项。
许多选项都有一个长格式与短格式;都会在这里表示出来。如果你把一个长格式截短,只要不引起歧义,那么它还是可以被识别。(如果你愿意,你可以使用 ‘+’ 而非 ‘-’ 标记选项参数,不过我们在例子中仍然遵从通常的惯例)
选项 | 描述 |
---|---|
gdb 程序名, gdb 程序名 core | 您能用两个参数来运行GDB,可执行程序名与core文件 |
gdb 程序名 1234 | 您可以以进程ID作为第二个参数,以调式一个正在运行的进程, 将会把gdb附在进程1234之上(除非您正好有个文件叫1234,gdb总是先查找core文件) |
启动gdb的方法有以下几种:
\1. gdb
program也就是执行文件,一般在当前目录下。
所遇到的第一个未关联选项标志的参数与 ‘-se’ 选项等价
因此等价于gdb -se
- gdb core
用gdb同时调试一个运行程序和core文件,core是程序非法执行后,core dump后产生的文件。
相当于 gdb -se -c core
- gdb
如果程序是一个服务程序,那么可以指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试它。program应该在PATH环境变量中搜索得到。
选项 | 简写 | 描述 |
---|---|---|
-help | -h | 列出所有选项,并附简要说明 |
-symbols=file | -s file | 读出文件(file)中的符号表 |
-write | 无 | 开通(enable)往可执行文件和核心文件写的权限 |
-exec=file | -e file | 在适当时候把File作为可执行的文件执行,来检测与core dump结合的数据 |
-se File | 无 | 从指定文件中读取符号表信息,并把它用在可执行文件中 |
-core File | -c File | 把File作为core dump来执行,调试时core dump的core文件。 |
-command=File | -x File | 从File中执行GDB命令 |
-directory=Directory | -d Directory | 把Dicrctory加入源文件搜索的路径中,加入一个源文件的搜索路径。默认搜索路径是环境变量中PATH所定义的路径 |
-nx | -n | 不从任何.gdbinit初始化文件中执行命令。通常情况下,这些文件中的命令是在所有命令选项和参数处理完后才执行 |
-quiet | -q | “Quiet”.不输入介绍和版权信息。这些信息输出在batch模式下也被关闭 |
-batch | 运行batch模式。在处理完所有用’-x’选项指定的命令文件(还有’.gdbi-nit’,如果没禁用)后退出,并返回状态码0.如果在命令文件中的命令被执行时发生错误,则退出,并返回状态码非0.batch模式对于运行GDB作为过滤器也许很有用,比如要从另一台电脑上下载并运行一个程序;为了让这些更有用,当在batch模式下运行时,消息:Program exited normally.(不论什么时候,一个程序在GDB控制下终止运行,这条消息都会正常发出.),将不会发出 | |
-cd=Directory | 无 | 运行GDB,使用Directory作为它的工作目录,取代当前工作目录 |
-fullname | -f | 当Emacs让GDB作为一个子进程运行时,设置这个选项.它告诉GDB每当一个堆栈结构(栈帧)显示出来(包括每次程序停止)就用标准的,认同的方式输出文件全名和行号.这里,认同的格式看起来像两个’ 32’字符,紧跟文件名,行号和字符位置(由冒号,换行符分隔).Emacs同GDB的接口程序使用这两个’ 32’字符作为一个符号为框架来显示源代码 |
-b | 无 | BAUDRATE设置行速(波特率或bits/s).在远程调试中GDB在任何串行接口中使用的行速 |
-tty=Device | 无 | 使用Device作为你程序运行的标准输入输出 |
内部命令(调试)
命令 | 描述 |
---|---|
file [filename] | 装入想要调试的可执行文件 |
kill [filename] | 终止正在调试的程序 |
break [file:]function | 在(file文件的)function函数中设置一个断点 |
clear | 删除一个断点,这个命令需要指定代码行或者函数名作为参数 |
run [arglist] | 运行您的程序 (如果指定了arglist,则将arglist作为参数运行程序) |
bt | Backtrace: 显示程序堆栈信息 |
print expr | 打印表达式的值 |
continue | 继续运行您的程序 (在停止之后,比如在一个断点之后) |
list | 列出产生执行文件的源代码的一部分 |
next | 单步执行 (在停止之后); 跳过函数调用 |
nexti | 执行下一行的源代码中的一条汇编指令 |
set | 设置变量的值。例如:set nval=54 将把54保存到nval变量中 |
step | 单步执行 (在停止之后); 进入函数调用 |
stepi | 继续执行程序下一行源代码中的汇编指令。如果是函数调用,这个命令将进入函数的内部,单步执行函数中的汇编代码 |
watch | 使你能监视一个变量的值而不管它何时被改变 |
rwatch | 指定一个变量,如果这个变量被读,则暂停程序运行,在调试器中显示信息,并等待下一个调试命令。参考rwatch和watch命令 |
awatch | 指定一个变量,如果这个变量被读或者被写,则暂停程序运行,在调试器中显示信息,并等待下一个调试命令。参考rwatch和watch命令 |
Ctrl-C | 在当前位置停止执行正在执行的程序,断点在当前行 |
disable | 禁止断点功能,这个命令需要禁止的断点在断点列表索引值作为参数 |
display | 在断点的停止的地方,显示指定的表达式的值。(显示变量) |
undisplay | 删除一个display设置的变量显示。这个命令需要将display list中的索引做参数 |
enable | 允许断点功能,这个命令需要允许的断点在断点列表索引值作为参数 |
finish | 继续执行,直到当前函数返回 |
ignore | 忽略某个断点制定的次数。例:ignore 4 23 忽略断点4的23次运行,在第24次的时候中断 |
info [name] | 查看name信息 |
load | 动态载入一个可执行文件到调试器 |
xbreak | 在当前函数的退出的点上设置一个断点 |
whatis | 显示变量的值和类型 |
ptype | 显示变量的类型 |
return | 强制从当前函数返回 |
txbreak | 在当前函数的退出的点上设置一个临时的断点(只可使用一次) |
make | 使你能不退出 gdb 就可以重新产生可执行文件 |
shell | 使你能不离开 gdb 就执行 UNIX shell 命令 |
help [name] | 显示GDB命令的信息,或者显示如何使用GDB的总体信息 |
quit | 退出gdb |
要得到所有使用GDB的资料,请参考Using GDB: A Guide to the GNU Source-Level Debugger, by Richard M. Stallman and Roland H. Pesch. 当用info查看的时候,也能看到相同的文章
gdb的命令实在太多了,我们不可能全部列出来, 因此只列出了一部分,我们将在下一节”gdb帮助”中帮助在调试的过程中通过help来查看gdb的调试命令
gdb帮助
我们知道gdb调试的命令是非常多的, 我们不可能完全记住有些记住的用法也可能不太熟悉,那么我们在使用的过程中,如果希望查看某个命令的帮助信息,可以使用gdb调试帮助信息
启动gdb后,进入gdb的调试环境中,就可以使用gdb的命令开始调试程序了。
gdb
1
gdb的命令可以使用help调试命令来查看,如下所示:
help
1
注意我们此处所说的help调试帮助命令与之前在终端中
gdb的命令很多,gdb将之分成许多种类。help命令只是列出gdb的命令种类
种类 | 描述 |
---|---|
aliases | Aliases of other commands |
breakpoints | Making program stop at certain points |
data | Examining data |
files | Specifying and examining files |
internals | Maintenance commands |
obscure | Obscure features |
running | Running the program |
stack | Examining the stack |
status | Status inquiries |
support | Support facilities |
tracepoints | Tracing of program execution without stopping the program |
user-defined – User-defined commands |
如果要看其中的命令,可以使用help 命令。
如 help stack
或者help breakpoints
也可以直接用help [command]来查看命令的帮助。
比如我们知道break可以插入一个断点, 我们就查看它的详细信息
help break
1
gdb中,输入命令时,可以不用输入全部命令,只用输入命令的前几个字符就可以了。当然,命令的前几个字符应该标志着一个惟一的命令,在Linux下,可以按两次TAB键来补齐命令的全称,如果有重复的,那么gdb会把它全部列出来。
要退出gdb时,只用输入quit或其简称q就行了。
gdb使用
gdb中运行Linux的shell程序
在gdb环境中,可以执行Linux的shell命令
shell <command string>
1
调用Linux的shell来执行,环境变量SHELL中定义的Linux的shell将会用来执行。如果SHELL没有定义,那就使用Linux的标准shell:/bin/sh(在Windows中使用Command.com或cmd.exe)
还有一个gdb命令是make:
make <make-args>
1
可以在gdb中执行make命令来重新build自己的程序。这个命令等价于shell make
在gdb中运行程序
当以gdb 方式启动gdb后,gdb会在PATH路径和当前目录中搜索的源文件。如要确认gdb是否读到源文件,可使用l或list命令,看看gdb是否能列出源代码。
在gdb中,运行程序使用r或是run命令。程序的运行,有可能需要设置下面四方面的事。
程序运行参数
set args 可指定运行时参数。如:
set args 10 20 30 40 50
1
show args 命令可以查看设置好的运行参数。
运行环境
参数 | 描述 |
---|---|
path | 可设定程序的运行路径 |
show paths | 查看程序的运行路径 |
set environment varname [=value] | 设置环境变量。如:set env USER=hchen |
show environment [varname] | 查看环境变量 |
工作目录
参数 | 描述 |
---|---|
cd | 相当于shell的cd命令 |
pwd | 显示当前的所在目录 |
程序的输入输出
参数 | 描述 |
---|---|
info terminal | 显示程序用到的终端的模式 |
run > outfile | 使用重定向控制程序输出 |
tty /dev/ttyb | tty命令可以指写输入输出的终端设备 |
调试已运行的程序
调试已经运行的程序有两种方法:
- 在Linux下用ps(第一章已经对ps作了介绍)查看正在运行的程序的PID(进程ID),然后用gdb PID格式挂接正在运行的程序。
- 先用gdb 关联上源代码,并进行gdb,在gdb中用attach命令来挂接进程的PID,并用detach来取消挂接的进程。
暂停/恢复程序运行
调试程序中,暂停程序运行是必需的,gdb可以方便地暂停程序的运行。可以设置程序在哪行停住,在什么条件下停住,在收到什么信号时停往等,以便于用户查看运行时的变量,以及运行时的流程。
当进程被gdb停住时,可以使用info program 来查看程序是否在运行、进程号、被暂停的原因。
在gdb中,有以下几种暂停方式:断点(BreakPoint)、观察点(WatchPoint)、捕捉点(CatchPoint)、信号(Signals)及线程停止(Thread Stops)。
如果要恢复程序运行,可以使用c或是continue命令。
设置断点(BreakPoint)
用break命令来设置断点。有下面几种设置断点的方法:
参数 | 描述 |
---|---|
break | 在进入指定函数时停住。C++中可以使用class::function或function(type,type)格式来指定函数名 |
break | 在指定行号停住 |
break +offset和beak -offset | 在当前行号的前面或后面的offset行停住。offiset为自然数 |
break filename:linenum | 在源文件filename的linenum行处停住 |
break filename:function | 在源文件filename的function函数的入口处停住 |
break *address | 在程序运行的内存地址处停住 |
break | 该命令没有参数时,表示在下一条指令处停住 |
break … if | condition表示条件,在条件成立时停住。比如在循环体中,可以设置break if i=100,表示当i为100时停住程序 |
查看断点时,可使用info命令,如下所示(注:n表示断点号):
参数 | 描述 |
---|---|
info breakpoints [n] | 查看断点 |
info break [n] | 查看断点 |
设置观察点(WatchPoint)
观察点一般用来观察某个表达式(变量也是一种表达式)的值是否变化了。如果有变化,马上停住程序。有下面的几种方法来设置观察点:
参数 | 描述 |
---|---|
watch | 为表达式(变量)expr设置一个观察点。一旦表达式值有变化时,马上停住程序 |
rwatch | 当表达式(变量)expr被读时,停住程序 |
awatch | 当表达式(变量)的值被读或被写时,停住程序 |
info watchpoints | 列出当前设置的所有观察点 |
设置捕捉点(CatchPoint)
可设置捕捉点来补捉程序运行时的一些事件。如载入共享库(动态链接库)或是C++的异常。设置捕捉点的格式为:
参数 | 描述 |
---|---|
catch | 当event发生时,停住程序 |
tcatch | 只设置一次捕捉点,当程序停住以后,应点被自动删除 |
event可以是下面的内容
参数 | 描述 |
---|---|
throw | 一个C++抛出的异常 (throw为关键字) |
catch | 一个C++捕捉到的异常 (catch为关键字) |
exec | 调用系统调用exec时(exec为关键字,目前此功能只在HP-UX下有用) |
fork | 调用系统调用fork时(fork为关键字,目前此功能只在HP-UX下有用) |
vfork | 调用系统调用vfork时(vfork为关键字,目前此功能只在HP-UX下有) |
load 或 load | 载入共享库(动态链接库)时 (load为关键字,目前此功能只在HP-UX下有用) |
unload 或 unload | 卸载共享库(动态链接库)时 (unload为关键字,目前此功能只在HP-UX下有用) |
维护停止点
上面说了如何设置程序的停止点,gdb中的停止点也就是上述的三类。在gdb中,如果觉得已定义好的停止点没有用了,可以使用delete、clear、disable、enable这几个命令来进行维护
参数 | 描述 |
---|---|
Clear | 清除所有的已定义的停止点 |
clear 和clear | 清除所有设置在函数上的停止点 |
clear 和clear | 清除所有设置在指定行上的停止点 |
delete [breakpoints] [range…] | 删除指定的断点,breakpoints为断点号。如果不指定断点号,则表示删除所有的断点。range 表示断点号的范围(如:3-7)。其简写命令为d,比删除更好的一种方法是disable停止点。disable了的停止点,gdb不会删除,当还需要时,enable即可,就好像回收站一样 |
disable [breakpoints] [range…] | disable所指定的停止点,breakpoints为停止点号。如果什么都不指定,表示disable所有的停止点。简写命令是dis |
enable [breakpoints] [range…] | enable所指定的停止点,breakpoints为停止点号 |
enable [breakpoints] once range… | enable所指定的停止点一次,当程序停止后,该停止点马上被gdb自动disable |
enable [breakpoints] delete range… | enable所指定的停止点一次,当程序停止后,该停止点马上被gdb自动删除 |
停止条件维护
前面在介绍设置断点时,提到过可以设置一个条件,当条件成立时,程序自动停止。这是一个非常强大的功能,这里,专门介绍这个条件的相关维护命令。
一般来说,为断点设置一个条件,可使用if关键词,后面跟其断点条件。并且,条件设置好后,可以用condition命令来修改断点的条件(只有break和watch命令支持if,catch目前暂不支持if)。
参数 | 描述 |
---|---|
condition | 修改断点号为bnum的停止条件为expression |
condition | 清除断点号为bnum的停止条件 |
ignore | 还有一个比较特殊的维护命令ignore,可以指定程序运行时,忽略停止条件几次。表示忽略断点号为bnum的停止条件count次 |
为停止点设定运行命令
可以使用gdb提供的command命令来设置停止点的运行命令。也就是说,当运行的程序在被停住时,我们可以让其自动运行一些别的命令,这很有利行自动化调试。
commands [bnum]
... command-list ...
end123
为断点号bnum指定一个命令列表。当程序被该断点停住时,gdb会依次运行命令列表中的命令。
例如:
break foo if x>0
commands
printf "x is %d/n",x
continue
end12345
断点设置在函数foo中,断点条件是x>0,如果程序被断住后,也就是一旦x的值在foo函数中大于0,gdb会自动打印出x的值,并继续运行程序。
如果要清除断点上的命令序列,那么只要简单地执行一下commands命令,并直接在输入end就行了。
断点菜单
在C++中,可能会重复出现同一个名字的函数若干次(函数重载)。在这种情况下,break 不能告诉gdb要停在哪个函数的入口。当然,可以使用break
恢复程序运行和单步调试
continue [ignore-count]
,c [ignore-count]
,fg [ignore-count]
:这三个命令的功能相同,都是用来恢复程序运行,直到程序结束,或是下一个断点到来。ignore-count
表示忽略其后的断点次数。step
:单步跟踪,如果有函数调用,它会进入该函数。后面可以加count
也可以不加,不加表示一条条地执行,加表示执行后面的count
条指令,然后再停住。next
:同样单步跟踪,如果有函数调用,它不会进入该函数。后面可以加count
也可以不加,不加表示一条条地执行,加表示执行后面的count
条指令,然后再停住。set step-mode
,set step-mode on
:打开step-mode
模式。在进行单步跟踪时,程序不会因为没有debug信息而不停住。set step-mode off
:关闭step-mode
模式。finish
:运行程序,直到当前函数完成返回。并打印函数返回时的堆栈地址和返回值及参数值等信息。until
或u
:当厌倦了在一个循环体内单步跟踪时,这个命令可以运行程序直到退出循环体。stepi
或si
,nexti
或ni
:单步跟踪一条机器指令。一条程序代码有可能由数条机器指令完成,stepi
和nexti
可以单步执行机器指令。
关于信号(Signals)的部分,信号是一种软中断,是一种处理异步事件的方法。一般来说,操作系统都支持许多信号,尤其是Linux,比较重要的应用程序一般都会处理信号。Linux定义了许多信号,比如SIGINT
表示中断字符信号,也就是Ctrl+C的信号,SIGBUS
表示硬件故障的信号;SIGCHLD
表示子进程状态改变信号;SIGKILL
表示终止程序运行的信号等。信号量编程是UNIX下非常重要的一种技术。GDB有能力在调试程序的时候处理任何一种信号。可以告诉GDB需要处理哪一种信号;可以要求GDB收到所指定的信号时,马上停住正在运行的程序,以供用户进行调试。可用GDB的handle
命令来完成这一功能。
实例
进入gdb调试环境
list n | list | list 函数名
l n | l | l 函数名
在调试过程中查看源文件,n为源文件的行号,每次显示10行。
list可以简写为l,不带任何参数的l表示从当前执行行查看。
注意:在(gdb)中直接回车,表示执行上一条命令。
start | s
开始执行程序,并main函数的停在第一条语句处。
(gdb)run|r
连续执行程序,直到遇到断点
(gdb)continue|c
继续执行程序,直到下个断点
(gdb)next|n
执行下一行语句
(gdb)step|s
进入正在执行的函数内部
(gdb)finish
一直执行到当前函数返回,即跳出当前函数,执行其调用函数
变量信息管理
(gdb)info 变量名|i 变量名|i locals
i变量名查看一个变量的值,i locals查看所有局部变量的值
修改变量的值
(gdb)set var 变量名=变量值
(gdb) print 表达式
打印表达式,通过表达式可以修改变量的值,p 变量名=变量值
(gdb)display 变量名
使得程序每次停下来都会显示变量的值
x/nbx 变量名
查看从变量名开始的n个字节,例x/7bx input 表示查看从变量input开始的7个内存单元的内容
查看函数调用栈
(gdb)backtrace|bt
查看其调用函数的信息
(gdb)frame n|f n
n为栈的层次,然后可以用其他命令(info)查看此级别的变量信息
断点管理
设置断点
break n|break 函数名|b n| b 函数名|b n(函数名)if 条件
n为行号,添加if表示设置条件断点,只有条件为真时,才中断
查看断点
info breakpoints|i breakpoints
删除断点
delete breakpoints n
使断点失效
disable breakpoints n
使断点生效
enable breakpoints n
其中n为断点的序列号,可以用info breakpoints查看
观察点管理
断点是程序执行到某行代码是触发,观察点是程序访问某个内存单元时触发
(gdb)watch 变量名
当程序访问变量名指定的内存单元时,停止程序
info watchpoints|delete watchpoints
类似断点管理
退出gdb环境
(gdb)quit | q