binary bombs
准备阶段
反汇编:
objdump -d bomb > bomb.s
通过gdb调试程序:
gdb ./bomb
设置断点:
(gdb) b phase_1
以字符串形式显示内存内容:
(gdb) x/s 0x80497c0
在GDB(GNU调试器)中,x/s 0x80497c0
命令的含义是从内存地址0x80497c0
开始,以字符串(string)的形式显示内存内容。
这里的x
是“examine”的缩写,表示检查内存内容。/s
表示以字符串的形式显示结果。0x80497c0
是要检查的内存地址。
这个命令通常用于查看某个内存地址中存储的字符串内容。
阶段1 字符串比较
源代码文本
08048b20 <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
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>
8048b37: 83 c4 10 add $0x10,%esp
8048b3a: 85 c0 test %eax,%eax
8048b3c: 74 05 je 8048b43 <phase_1+0x23>
8048b3e: e8 b9 09 00 00 call 80494fc <explode_bomb>
8048b43: 89 ec mov %ebp,%esp
8048b45: 5d pop %ebp
8048b46: c3 ret
8048b47: 90 nop
分段解读
函数的初始化操作
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
8048b29: 83 c4 f8 add $0xfffffff8,%esp
push %ebp
:将基指针寄存器(ebp)的值压入栈中。这是为了保存调用者的基指针。mov %esp,%ebp
:将栈指针寄存器(esp)的值复制到基指针寄存器(ebp)。这是为了让ebp指向当前的栈顶,作为这个函数的基指针。sub $0x8,%esp
:将栈指针寄存器(esp)的值减8。这是为了在栈上分配8个字节的空间,用于存储局部变量或者临时数据。mov 0x8(%ebp),%eax
:将基指针寄存器(ebp)加8的地址处的值复制到累加器寄存器(eax)。这是为了获取函数的第一个参数。add $0xfffffff8,%esp
:将栈指针寄存器(esp)的值减8(0xfffffff8是-8的补码表示)。这是为了在栈上再分配8个字节的空间。
高地址
-----------------
| 参数1 | <- ebp + 8
-----------------
| 返回地址 | <- ebp + 4
-----------------
| 调用者ebp | <- ebp (<- 开始esp)
-----------------
| 未知 | <- esp(执行sub $0x8,%esp指令后)
-----------------
| 未知 | <- esp(执行add $0xfffffff8,%esp指令后)
-----------------
低地址
高地址
-----------------
| 参数n | <- ebp + 8 + 4 * (n - 1)
-----------------
| ... |
-----------------
| 参数2 | <- ebp + 12
-----------------
| 参数1 | <- ebp + 8
-----------------
| 返回地址 | <- ebp + 4
-----------------
| 调用者ebp | <- ebp
-----------------
低地址
函数调用的准备工作
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>
add $0xfffffff8,%esp
:将栈指针寄存器(esp)的值减8(0xfffffff8是-8的补码表示)。这是为了在栈上分配8个字节的空间。push $0x80497c0
:将立即数0x80497c0
压入栈中。这可能是一个字符串的地址,将作为strings_not_equal
函数的参数。push %eax
:将累加器寄存器(eax)的值压入栈中。这是strings_not_equal
函数的另一个参数。call 8049030 <strings_not_equal>
:调用strings_not_equal
函数。这会将返回地址压入栈中,并将指令指针(eip)设置为strings_not_equal
函数的地址。
高地址
-----------------
| 参数1 | <- ebp + 8
-----------------
| 返回地址 | <- ebp + 4
-----------------
| 调用者ebp | <- ebp
-----------------
| 未知 | <- esp(执行sub $0x8,%esp指令后)
-----------------
| 未知 |
-----------------
| 0x80497c0 | <- esp(执行push $0x80497c0指令后)
-----------------
| 参数1 | <- esp(执行push %eax指令后)
-----------------
低地址
寻找目标字符串
思路:执行<strings_not_equal>函数前栈中压入了两个参数,一个是调用者给出的,另一个是地址,猜测另一个地址上就是需要匹配的字符串,通过gdb调试,使用x/s命令将那个参数地址中的值转化为字符串,得到我们最终的答案
阶段2 循环
源代码文本
08048b48 <phase_2>:
8048b48: 55 push %ebp
8048b49: 89 e5 mov %esp,%ebp
8048b4b: 83 ec 20 sub $0x20,%esp
8048b4e: 56 push %esi
8048b4f: 53 push %ebx
8048b50: 8b 55 08 mov 0x8(%ebp),%edx
8048b53: 83 c4 f8 add $0xfffffff8,%esp
8048b56: 8d 45 e8 lea -0x18(%ebp),%eax
8048b59: 50 push %eax
8048b5a: 52 push %edx
8048b5b: e8 78 04 00 00 call 8048fd8 <read_six_numbers>
8048b60: 83 c4 10 add $0x10,%esp
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
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>
8048b8e: 8d 65 d8 lea -0x28(%ebp),%esp
8048b91: 5b pop %ebx
8048b92: 5e pop %esi
8048b93: 89 ec mov %ebp,%esp
8048b95: 5d pop %ebp
8048b96: c3 ret
8048b97: 90 nop
分段分析
初始化阶段
esi:
esi
是一个32位的寄存器,很多情况下,它被用作源地址索引,特别是在一些数据移动的操作中。另外,esi
寄存器也经常被用作循环中的计数器或者索引,这在处理数组或者缓冲区时非常有用。
esi
寄存器的值压入栈中。在函数调用过程中,通常会保存那些可能会被改变的寄存器的值,以便在函数返回时可以恢复它们的原始值。
ebx:
同esi
在这个程序中作用:
esi
寄存器被用来存储数组的地址,ebx
寄存器被用来作为数组的索引,并计算下一个数组元素的预期值。
-
mov 0x8(%ebp),%edx
:这条指令将存储在ebp + 8
的值(也就是phase_2
函数的第一个参数)复制到edx
寄存器。 -
lea -0x18(%ebp),%eax
:这条指令计算ebp - 0x18
的地址,并将结果存储在eax
寄存器中。这个地址将被用作read_six_numbers
函数的第一个参数。 -
call 8048fd8 <read_six_numbers>
:这条指令调用了read_six_numbers
函数。这个函数的作用是读取六个数字并将它们存储在由第一个参数指定的地址开始的内存中。 -
add $0x10,%esp
:这条指令将esp
寄存器的值增加16(0x10)。这实际上是在清理栈上的参数。因为之前在调用read_six_numbers
函数之前压入了两个参数,每个参数占用4字节,所以现在需要增加16字节来恢复esp
寄存器的值。 -
cmpl $0x1,-0x18(%ebp)
:这条指令比较存储在ebp - 0x18
的值(也就是数组的第一个元素)和1。如果它们相等,ZF
(零标志)会被设置。 -
je 8048b6e
:这条指令检查ZF
是否被设置。如果是,程序就跳转到地址8048b6e
。这实际上是在检查数组的第一个元素是否为1。如果不是,程序就会继续执行下一条指令。
循环阶段
mov $0x1,%ebx
:这条指令将ebx
寄存器的值设置为1。ebx
寄存器在这个循环中被用作索引。lea -0x18(%ebp),%esi
:这条指令将esi
寄存器的值设置为数组的起始地址(ebp - 0x18
)。lea 0x1(%ebx),%eax
:这条指令将eax
寄存器的值设置为ebx + 1
。imul -0x4(%esi,%ebx,4),%eax
:这条指令将数组的当前元素(-0x4(%esi,%ebx,4)
)乘以eax
寄存器的值,并将结果存储在eax
寄存器中。cmp %eax,(%esi,%ebx,4)
:这条指令比较eax
寄存器的值和数组的下一个元素。如果它们相等,ZF
(零标志)会被设置。je 8048b88
:这条指令检查ZF
是否被设置。如果是,程序就跳转到地址8048b88
。这实际上是在检查数组的下一个元素是否等于当前元素乘以ebx + 1
。如果不是,程序就会调用explode_bomb
函数。inc %ebx
:这条指令将ebx
寄存器的值增加1。cmp $0x5,%ebx
:这条指令比较ebx
寄存器的值和5。如果ebx
小于或等于5,ZF
会被设置。jle 8048b76
:这条指令检查ZF
是否被设置。如果是,程序就跳回到地址8048b76
,也就是循环的开始。否则,程序就会退出循环。
寻找输入数组
-
由
cmpl $0x1,-0x18(%ebp)
:这条指令比较存储在ebp - 0x18
的值(也就是数组的第一个元素)和1。知,首数组元素为1。 -
由
lea 0x1(%ebx),%eax imul -0x4(%esi,%ebx,4),%eax cmp %eax,(%esi,%ebx,4) inc %ebx
知相邻数组之间的关系。
得:1 2 6 24 120 720
阶段3 条件/分支
源代码文本
08048b98 <phase_3>:
8048b98: 55 push %ebp
8048b99: 89 e5 mov %esp,%ebp
8048b9b: 83 ec 14 sub $0x14,%esp
8048b9e: 53 push %ebx
8048b9f: 8b 55 08 mov 0x8(%ebp),%edx
8048ba2: 83 c4 f4 add $0xfffffff4,%esp
8048ba5: 8d 45 fc lea -0x4(%ebp),%eax
8048ba8: 50 push %eax
8048ba9: 8d 45 fb lea -0x5(%ebp),%eax
8048bac: 50 push %eax
8048bad: 8d 45 f4 lea -0xc(%ebp),%eax
8048bb0: 50 push %eax
8048bb1: 68 de 97 04 08 push $0x80497de
8048bb6: 52 push %edx
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>
8048bc9: 83 7d f4 07 cmpl $0x7,-0xc(%ebp)
8048bcd: 0f 87 b5 00 00 00 ja 8048c88 <phase_3+0xf0>
8048bd3: 8b 45 f4 mov -0xc(%ebp),%eax
8048bd6: ff 24 85 e8 97 04 08 jmp *0x80497e8(,%eax,4)
8048bdd: 8d 76 00 lea 0x0(%esi),%esi
8048be0: b3 71 mov $0x71,%bl
8048be2: 81 7d fc 09 03 00 00 cmpl $0x309,-0x4(%ebp)
8048be9: 0f 84 a0 00 00 00 je 8048c8f <phase_3+0xf7>
8048bef: e8 08 09 00 00 call 80494fc <explode_bomb>
8048bf4: e9 96 00 00 00 jmp 8048c8f <phase_3+0xf7>
8048bf9: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
8048c00: b3 62 mov $0x62,%bl
8048c02: 81 7d fc d6 00 00 00 cmpl $0xd6,-0x4(%ebp)
8048c09: 0f 84 80 00 00 00 je 8048c8f <phase_3+0xf7>
8048c0f: e8 e8 08 00 00 call 80494fc <explode_bomb>
8048c14: eb 79 jmp 8048c8f <phase_3+0xf7>
8048c16: b3 62 mov $0x62,%bl
8048c18: 81 7d fc f3 02 00 00 cmpl $0x2f3,-0x4(%ebp)
8048c1f: 74 6e je 8048c8f <phase_3+0xf7>
8048c21: e8 d6 08 00 00 call 80494fc <explode_bomb>
8048c26: eb 67 jmp 8048c8f <phase_3+0xf7>
8048c28: b3 6b mov $0x6b,%bl
8048c2a: 81 7d fc fb 00 00 00 cmpl $0xfb,-0x4(%ebp)
8048c31: 74 5c je 8048c8f <phase_3+0xf7>
8048c33: e8 c4 08 00 00 call 80494fc <explode_bomb>
8048c38: eb 55 jmp 8048c8f <phase_3+0xf7>
8048c3a: 8d b6 00 00 00 00 lea 0x0(%esi),%esi
8048c40: b3 6f mov $0x6f,%bl
8048c42: 81 7d fc a0 00 00 00 cmpl $0xa0,-0x4(%ebp)
8048c49: 74 44 je 8048c8f <phase_3+0xf7>
8048c4b: e8 ac 08 00 00 call 80494fc <explode_bomb>
8048c50: eb 3d jmp 8048c8f <phase_3+0xf7>
8048c52: b3 74 mov $0x74,%bl
8048c54: 81 7d fc ca 01 00 00 cmpl $0x1ca,-0x4(%ebp)
8048c5b: 74 32 je 8048c8f <phase_3+0xf7>
8048c5d: e8 9a 08 00 00 call 80494fc <explode_bomb>
8048c62: eb 2b jmp 8048c8f <phase_3+0xf7>
8048c64: b3 76 mov $0x76,%bl
8048c66: 81 7d fc 0c 03 00 00 cmpl $0x30c,-0x4(%ebp)
8048c6d: 74 20 je 8048c8f <phase_3+0xf7>
8048c6f: e8 88 08 00 00 call 80494fc <explode_bomb>
8048c74: eb 19 jmp 8048c8f <phase_3+0xf7>
8048c76: b3 62 mov $0x62,%bl
8048c78: 81 7d fc 0c 02 00 00 cmpl $0x20c,-0x4(%ebp)
8048c7f: 74 0e je 8048c8f <phase_3+0xf7>
8048c81: e8 76 08 00 00 call 80494fc <explode_bomb>
8048c86: eb 07 jmp 8048c8f <phase_3+0xf7>
8048c88: b3 78 mov $0x78,%bl
8048c8a: e8 6d 08 00 00 call 80494fc <explode_bomb>
8048c8f: 3a 5d fb cmp -0x5(%ebp),%bl
8048c92: 74 05 je 8048c99 <phase_3+0x101>
8048c94: e8 63 08 00 00 call 80494fc <explode_bomb>
8048c99: 8b 5d e8 mov -0x18(%ebp),%ebx
8048c9c: 89 ec mov %ebp,%esp
8048c9e: 5d pop %ebp
8048c9f: c3 ret
分段解读
初始化阶段
-
push %ebp; mov %esp,%ebp; sub $0x14,%esp; push %ebx
:这些指令是函数的开头,它们设置了新的堆栈帧并保存了ebx
寄存器的值。 -
mov 0x8(%ebp),%edx
:这条指令将函数的第一个参数(存储在ebp+8
的位置)移动到edx
寄存器。 -
add $0xfffffff4,%esp
:这条指令将栈指针esp
减少了12(0xfffffff4
是-12的补码表示),为接下来的操作预留了空间。 -
lea -0x4(%ebp),%eax; push %eax; lea -0x5(%ebp),%eax; push %eax; lea -0xc(%ebp),%eax; push %eax
:这些指令将ebp
寄存器减去4、5和12的地址分别加载到eax
寄存器,并将这些地址压入栈中。 -
push $0x80497de; push %edx
:这两条指令将一个常量地址和edx
寄存器的值压入栈中。这可能是sscanf
函数的输入参数。 -
push %edx
:这条指令将edx
寄存器的值(即函数的第一个参数)压入栈中。 -
add $0x20,%esp
:这条指令将esp
寄存器的值增加32(0x20)。这实际上是在清理栈上的参数。因为之前在调用sscanf
函数之前压入了5个参数,每个参数占用4字节,所以现在需要增加20字节来恢复esp
寄存器的值。 -
cmp $0x2,%eax
:这条指令比较eax
寄存器的值和2。eax
寄存器的值是sscanf
函数的返回值,表示成功读取的项数。(知读入了两个以上个数据) -
cmpl $0x7,-0xc(%ebp)
:这条指令比较存储在ebp - 0xc
的值和7。这可能是检查第一个变量是否大于7。
高地址
|-----------------|
| %ebp | <- 通过 push %ebp 压入,此时的 %ebp 指向这里
|-----------------|
| 未知 | <- 通过 sub $0x14,%esp 创建的空间
|-----------------|
| %ebx | <- 通过 push %ebx 压入
|-----------------|
| 未知 | <- 通过 add $0xfffffff4,%esp 创建的空间
|-----------------|
| 参数:&var3 | <- 通过 lea -0x4(%ebp),%eax 和 push %eax 压入,此处为 %ebp - 0x4
|-----------------|
| 参数:&var2 | <- 通过 lea -0x5(%ebp),%eax 和 push %eax 压入,此处为 %ebp - 0x5
|-----------------|
| 参数:&var1 | <- 通过 lea -0xc(%ebp),%eax 和 push %eax 压入,此处为 %ebp - 0xc
|-----------------|
| 参数:0x80497de| <- 通过 push $0x80497de 压入
|-----------------|
| 参数:%edx | <- 通过 push %edx 压入
|-----------------|
| ... |
低地址
条件分支阶段
-
jmp *0x80497e8(,%eax,4)
:这是一个间接跳转指令。它将跳转到地址0x80497e8 + eax * 4
。这通常用于实现基于表的跳转,例如 switch 语句。eax
的值决定了跳转到哪个地址。 -
lea 0x0(%esi),%esi
:这条指令将esi
寄存器的值加载到esi
寄存器。这实际上没有改变esi
寄存器的值,可能是用于调试或其他目的。 -
mov $0x71,%bl
:这条指令将常量0x71
(ASCII 码中的 ‘q’)移动到bl
寄存器。(第二个参数) -
cmpl $0x309,-0x4(%ebp)
:这条指令比较存储在ebp - 0x4
的值和0x309
。这是第三个参数是否等于0x309
。 -
cmp -0x5(%ebp),%bl
:比较存储在ebp - 0x5
的值和bl
寄存器的值。这可能是第二个参数是否等于bl
寄存器的值。 -
mov -0x18(%ebp),%ebx
:将存储在ebp - 0x18
的值移动到ebx
寄存器。这可能是恢复ebx
寄存器的原始值,因为ebx
寄存器在函数调用中需要被保留。 -
其他分支同理
寻找输入
通过x/s将0x80497de这个地址存的字符串解析出来
- 根据
cmpl $0x7,-0xc(%ebp)
判断出第一个参数大小为0到7之间的数。 - 根据
mov $0x71,%bl
得出当第一个参数为0时,第二个参数为q。 - 根据
cmpl $0x309,-0x4(%ebp)
得出第一个参数为0时,第三个参数为0x309(777)。 - 输入可为 0 q 777
阶段4 递归调用栈
源代码文本
phase_4:
08048ce0 <phase_4>:
8048ce0: 55 push %ebp
8048ce1: 89 e5 mov %esp,%ebp
8048ce3: 83 ec 18 sub $0x18,%esp
8048ce6: 8b 55 08 mov 0x8(%ebp),%edx
8048ce9: 83 c4 fc add $0xfffffffc,%esp
8048cec: 8d 45 fc lea -0x4(%ebp),%eax
8048cef: 50 push %eax
8048cf0: 68 08 98 04 08 push $0x8049808
8048cf5: 52 push %edx
8048cf6: e8 65 fb ff ff call 8048860 <sscanf@plt>
8048cfb: 83 c4 10 add $0x10,%esp
8048cfe: 83 f8 01 cmp $0x1,%eax
8048d01: 75 06 jne 8048d09 <phase_4+0x29>
8048d03: 83 7d fc 00 cmpl $0x0,-0x4(%ebp)
8048d07: 7f 05 jg 8048d0e <phase_4+0x2e>
8048d09: e8 ee 07 00 00 call 80494fc <explode_bomb>
8048d0e: 83 c4 f4 add $0xfffffff4,%esp
8048d11: 8b 45 fc mov -0x4(%ebp),%eax
8048d14: 50 push %eax
8048d15: e8 86 ff ff ff call 8048ca0 <func4>
8048d1a: 83 c4 10 add $0x10,%esp
8048d1d: 83 f8 37 cmp $0x37,%eax
8048d20: 74 05 je 8048d27 <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
func4:
08048ca0 <func4>:
8048ca0: 55 push %ebp
8048ca1: 89 e5 mov %esp,%ebp
8048ca3: 83 ec 10 sub $0x10,%esp
8048ca6: 56 push %esi
8048ca7: 53 push %ebx
8048ca8: 8b 5d 08 mov 0x8(%ebp),%ebx
8048cab: 83 fb 01 cmp $0x1,%ebx
8048cae: 7e 20 jle 8048cd0 <func4+0x30>
8048cb0: 83 c4 f4 add $0xfffffff4,%esp
8048cb3: 8d 43 ff lea -0x1(%ebx),%eax
8048cb6: 50 push %eax
8048cb7: e8 e4 ff ff ff call 8048ca0 <func4>
8048cbc: 89 c6 mov %eax,%esi
8048cbe: 83 c4 f4 add $0xfffffff4,%esp
8048cc1: 8d 43 fe lea -0x2(%ebx),%eax
8048cc4: 50 push %eax
8048cc5: e8 d6 ff ff ff call 8048ca0 <func4>
8048cca: 01 f0 add %esi,%eax
8048ccc: eb 07 jmp 8048cd5 <func4+0x35>
8048cce: 89 f6 mov %esi,%esi
8048cd0: b8 01 00 00 00 mov $0x1,%eax
8048cd5: 8d 65 e8 lea -0x18(%ebp),%esp
8048cd8: 5b pop %ebx
8048cd9: 5e pop %esi
8048cda: 89 ec mov %ebp,%esp
8048cdc: 5d pop %ebp
8048cdd: c3 ret
8048cde: 89 f6 mov %esi,%esi
分段分析
初始化阶段
mov 0x8(%ebp),%edx
:将存储在ebp + 0x8
的值(即函数的第一个参数)移动到edx
寄存器。lea -0x4(%ebp),%eax
和push %eax
:计算地址ebp - 0x4
并将其压入堆栈。这是sscanf
函数的一个输出参数。
高地址
|-----------------|
| 旧 %ebp | <- 通过 push %ebp 压入,此时的 %ebp 指向这里
|-----------------|
| 未知 | <- 通过 sub $0x18,%esp 创建的空间
|-----------------|
| 未知 | <- 通过 add $0xfffffffc,%esp 创建的空间
|-----------------|
| 参数:&var | <- 通过 lea -0x4(%ebp),%eax 和 push %eax 压入,此处为 %ebp - 0x4
|-----------------|
| 参数:0x8049808| <- 通过 push $0x8049808 压入
|-----------------|
| 参数:%edx | <- 通过 push %edx 压入
|-----------------|
| ... |
低地址
基础判断阶段
add $0x10,%esp
:将esp
寄存器的值增加 16(0x10)。这是在清理堆栈上之前为sscanf
函数调用分配的空间。cmp $0x1,%eax
:比较eax
寄存器的值和 1。eax
寄存器通常用于存储函数的返回值,所以这可能是检查sscanf
函数的返回值是否为 1。得出输入的项数一定为1。即只有一个输入。cmpl $0x0,-0x4(%ebp)
:比较存储在ebp - 0x4
的值和 0。这可能是检查输入值是否大于 0。(输入值一定大于0)add $0xfffffff4,%esp
:将esp
寄存器的值减 12(0xfffffff4)。这是在堆栈上分配更多的空间。mov -0x4(%ebp),%eax
和push %eax
:将存储在ebp - 0x4
的值移动到eax
寄存器,然后将eax
寄存器的值压入堆栈。这可能是func4
函数的一个参数。
调用func4
push %esi
和push %ebx
:保存esi
和ebx
寄存器的值。这是因为这些寄存器在函数调用中需要被保留。mov 0x8(%ebp),%ebx
:将存储在ebp + 0x8
的值(即函数的第一个参数)移动到ebx
寄存器。cmp $0x1,%ebx
:比较ebx
寄存器的值和 1。这可能是检查输入参数是否大于 1。(得出如果小于等于1直接跳到8048cd0,即返回值直接为1)(这里是递归的边界条件)lea -0x1(%ebx),%eax
和push %eax
:计算ebx - 1
的值并将其压入堆栈。call 8048ca0 <func4>
:递归调用func4
函数。mov %eax,%esi
:将eax
寄存器的值(即func4
函数的返回值)移动到esi
寄存器。add $0xfffffff4,%esp
,lea -0x2(%ebx),%eax
和push %eax
:再次在堆栈上分配空间,并计算ebx - 2
的值并将其压入堆栈。这可能是另一个func4
函数的参数。call 8048ca0 <func4>
:再次递归调用func4
函数。add %esi,%eax
:将esi
寄存器的值(即第一次func4
调用的返回值)加到eax
寄存器的值(即第二次func4
调用的返回值)。jmp 8048cd5
:无条件跳转到地址8048cd5
。这可能是函数的结束部分。mov $0x1,%eax
:将eax
寄存器的值设置为 1。这可能是函数的默认返回值。lea -0x18(%ebp),%esp
:将esp
寄存器的值设置为ebp - 0x18
。这是在清理堆栈上之前为函数调用分配的空间。
回到phase_4中
add $0x10,%esp
:将esp
寄存器的值增加 16(0x10)。这是在清理堆栈上之前为func4
函数调用分配的空间。cmp $0x37,%eax
:比较eax
寄存器的值和 55(0x37)。eax
寄存器通常用于存储函数的返回值,所以这可能是检查func4
函数的返回值是否为 55。(得出函数返回值为55)
寻找输入
通过分析func4知,其实是求斐波那契数列的值为55时是第几个元素。
求解得9
这个函数的c语言代码:
int f(int n)
{
if(n<=1) return 1;
else return f(n-1) + f(n-2);
}
阶段5 指针
源代码文本
08048d2c <phase_5>:
8048d2c: 55 push %ebp
8048d2d: 89 e5 mov %esp,%ebp
8048d2f: 83 ec 10 sub $0x10,%esp
8048d32: 56 push %esi
8048d33: 53 push %ebx
8048d34: 8b 5d 08 mov 0x8(%ebp),%ebx
8048d37: 83 c4 f4 add $0xfffffff4,%esp
8048d3a: 53 push %ebx
8048d3b: e8 d8 02 00 00 call 8049018 <string_length>
8048d40: 83 c4 10 add $0x10,%esp
8048d43: 83 f8 06 cmp $0x6,%eax
8048d46: 74 05 je 8048d4d <phase_5+0x21>
8048d48: e8 af 07 00 00 call 80494fc <explode_bomb>
8048d4d: 31 d2 xor %edx,%edx
8048d4f: 8d 4d f8 lea -0x8(%ebp),%ecx
8048d52: be 20 b2 04 08 mov $0x804b220,%esi
8048d57: 8a 04 1a mov (%edx,%ebx,1),%al
8048d5a: 24 0f and $0xf,%al
8048d5c: 0f be c0 movsbl %al,%eax
8048d5f: 8a 04 30 mov (%eax,%esi,1),%al
8048d62: 88 04 0a mov %al,(%edx,%ecx,1)
8048d65: 42 inc %edx
8048d66: 83 fa 05 cmp $0x5,%edx
8048d69: 7e ec jle 8048d57 <phase_5+0x2b>
8048d6b: c6 45 fe 00 movb $0x0,-0x2(%ebp)
8048d6f: 83 c4 f8 add $0xfffffff8,%esp
8048d72: 68 0b 98 04 08 push $0x804980b
8048d77: 8d 45 f8 lea -0x8(%ebp),%eax
8048d7a: 50 push %eax
8048d7b: e8 b0 02 00 00 call 8049030 <strings_not_equal>
8048d80: 83 c4 10 add $0x10,%esp
8048d83: 85 c0 test %eax,%eax
8048d85: 74 05 je 8048d8c <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
8048d90: 5e pop %esi
8048d91: 89 ec mov %ebp,%esp
8048d93: 5d pop %ebp
8048d94: c3 ret
8048d95: 8d 76 00 lea 0x0(%esi),%esi
分段分析
初始化阶段
-
mov 0x8(%ebp),%ebx
将函数的第一个参数(存储在基指针加8的位置)复制到%ebx
寄存器。 -
add $0x10,%esp
是在调用完函数后清理栈,移除之前为函数调用所压入栈的参数。 -
cmp $0x6,%eax
将%eax
(通常用于存储函数返回值)与6进行比较。(得出字符串长度为6)
字符串处理阶段
-
xor %edx,%edx
将%edx
寄存器清零。 -
lea -0x8(%ebp),%ecx
将%ebp
减 8 的地址加载到%ecx
寄存器,这可能是一个局部变量的地址。 -
mov $0x804b220,%esi
将地址0x804b220
加载到%esi
寄存器,这可能是另一个字符串数组的起始地址。 -
接下来的代码是一个循环,它会执行以下操作:
mov (%edx,%ebx,1),%al
从%ebx
加%edx
的地址加载一个字节到%al
寄存器,这可能是从输入字符串中读取一个字符。and $0xf,%al
将%al
寄存器的值与0xf
进行与操作。(取出字节后四位)movsbl %al,%eax
将%al
寄存器的值符号扩展到%eax
寄存器。mov (%eax,%esi,1),%al
从%esi
加%eax
的地址加载一个字节到%al
寄存器,这可能是从数组中读取一个元素。(以eax为索引从esi为首地址的字符串中取出字符)mov %al,(%edx,%ecx,1)
将%al
寄存器的值存储到%ecx
加%edx
的地址,这可能是将字符存储到输出字符串。inc %edx
将%edx
寄存器的值加 1。cmp $0x5,%edx
将%edx
寄存器的值与5
进行比较。jle 8048d57 <phase_5+0x2b>
如果%edx
寄存器的值小于或等于5
,则跳转到地址8048d57
继续执行循环。
这段代码的主要功能是将输入字符串的每个字符转换为另一个字符。转换的方式是取字符的 ASCII 值的低四位,然后用这个值作为索引从一个位于 0x804b220
的数组中取出一个新的字符。
字符串配对阶段
movb $0x0,-0x2(%ebp)
将一个字节的 0 存储到%ebp
减 2 的地址,这可能是在新字符串的末尾添加一个 null 字符。push $0x804980b
将地址0x804980b
压入栈,这可能是一个预定义的字符串的地址。lea -0x8(%ebp),%eax
将%ebp
减 8 的地址加载到%eax
寄存器,这可能是新字符串的地址。push %eax
将%eax
寄存器的值压入栈,这是strings_not_equal
函数的第二个参数。add $0x10,%esp
将%esp
加 16,这可能是为了清理栈上的空间。test %eax,%eax
测试%eax
寄存器的值,这可能是检查strings_not_equal
函数的返回值。je 8048d8c <phase_5+0x60>
如果%eax
寄存器的值为 0,即两个字符串相等,那么跳转到地址8048d8c
。
这段代码的目的是比较新字符串和某个预定义的字符串是否相等。如果相等,就跳转到另一个地址。
寻找输入
mov $0x804b220,%esi
:通过x/s 找到0x804b220中的字符数组
(gdb) x/s 0x804b220
0x804b220: "isrveawhobpnutfg\260\001"
push $0x804980b
:通过x/s找到0x804980b中的预定义的字符串数组。
(gdb) x/s 0x804980b
0x804980b: "giants"
根据提供的数组 “isrveawhobpnutfg”,我们可以推断出原始的输入字符串。“giants” 中的每个字符在数组中的位置如下:
- ‘g’ 在数组中的索引是 15
- ‘i’ 在数组中的索引是 0
- ‘a’ 在数组中的索引是 5
- ‘n’ 在数组中的索引是 11
- ‘t’ 在数组中的索引是 13
- ‘s’ 在数组中的索引是 1
因为这个程序取的是字符 ASCII 值的低四位作为索引,所以我们需要找到一组字符,它们的 ASCII 值的低四位分别是 15, 0, 6, 11, 13, 1。这组字符就是原始的输入字符串。
这样的字符有很多组,因为 ASCII 值的高四位可以是任何值。
得出opekma就是其中一个符合的答案。
阶段6 链表/指针/结构
注:对于这题还不太理解,主要是阅读汇编代码能力太弱,还是等我深入学习后回来解决本题!
源代码文本
08048d98 <phase_6>:
8048d98: 55 push %ebp
8048d99: 89 e5 mov %esp,%ebp
8048d9b: 83 ec 4c sub $0x4c,%esp
8048d9e: 57 push %edi
8048d9f: 56 push %esi
8048da0: 53 push %ebx
8048da1: 8b 55 08 mov 0x8(%ebp),%edx
8048da4: c7 45 cc 6c b2 04 08 movl $0x804b26c,-0x34(%ebp)
8048dab: 83 c4 f8 add $0xfffffff8,%esp
8048dae: 8d 45 e8 lea -0x18(%ebp),%eax
8048db1: 50 push %eax
8048db2: 52 push %edx
8048db3: e8 20 02 00 00 call 8048fd8 <read_six_numbers>
8048db8: 31 ff xor %edi,%edi
8048dba: 83 c4 10 add $0x10,%esp
8048dbd: 8d 76 00 lea 0x0(%esi),%esi
8048dc0: 8d 45 e8 lea -0x18(%ebp),%eax
8048dc3: 8b 04 b8 mov (%eax,%edi,4),%eax
8048dc6: 48 dec %eax
8048dc7: 83 f8 05 cmp $0x5,%eax
8048dca: 76 05 jbe 8048dd1 <phase_6+0x39>
8048dcc: e8 2b 07 00 00 call 80494fc <explode_bomb>
8048dd1: 8d 5f 01 lea 0x1(%edi),%ebx
8048dd4: 83 fb 05 cmp $0x5,%ebx
8048dd7: 7f 23 jg 8048dfc <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
8048de6: 8b 55 c8 mov -0x38(%ebp),%edx
8048de9: 8b 04 32 mov (%edx,%esi,1),%eax
8048dec: 3b 04 9e cmp (%esi,%ebx,4),%eax
8048def: 75 05 jne 8048df6 <phase_6+0x5e>
8048df1: e8 06 07 00 00 call 80494fc <explode_bomb>
8048df6: 43 inc %ebx
8048df7: 83 fb 05 cmp $0x5,%ebx
8048dfa: 7e ea jle 8048de6 <phase_6+0x4e>
8048dfc: 47 inc %edi
8048dfd: 83 ff 05 cmp $0x5,%edi
8048e00: 7e be jle 8048dc0 <phase_6+0x28>
8048e02: 31 ff xor %edi,%edi
8048e04: 8d 4d e8 lea -0x18(%ebp),%ecx
8048e07: 8d 45 d0 lea -0x30(%ebp),%eax
8048e0a: 89 45 c4 mov %eax,-0x3c(%ebp)
8048e0d: 8d 76 00 lea 0x0(%esi),%esi
8048e10: 8b 75 cc mov -0x34(%ebp),%esi
8048e13: bb 01 00 00 00 mov $0x1,%ebx
8048e18: 8d 04 bd 00 00 00 00 lea 0x0(,%edi,4),%eax
8048e1f: 89 c2 mov %eax,%edx
8048e21: 3b 1c 08 cmp (%eax,%ecx,1),%ebx
8048e24: 7d 12 jge 8048e38 <phase_6+0xa0>
8048e26: 8b 04 0a mov (%edx,%ecx,1),%eax
8048e29: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
8048e30: 8b 76 08 mov 0x8(%esi),%esi
8048e33: 43 inc %ebx
8048e34: 39 c3 cmp %eax,%ebx
8048e36: 7c f8 jl 8048e30 <phase_6+0x98>
8048e38: 8b 55 c4 mov -0x3c(%ebp),%edx
8048e3b: 89 34 ba mov %esi,(%edx,%edi,4)
8048e3e: 47 inc %edi
8048e3f: 83 ff 05 cmp $0x5,%edi
8048e42: 7e cc jle 8048e10 <phase_6+0x78>
8048e44: 8b 75 d0 mov -0x30(%ebp),%esi
8048e47: 89 75 cc mov %esi,-0x34(%ebp)
8048e4a: bf 01 00 00 00 mov $0x1,%edi
8048e4f: 8d 55 d0 lea -0x30(%ebp),%edx
8048e52: 8b 04 ba mov (%edx,%edi,4),%eax
8048e55: 89 46 08 mov %eax,0x8(%esi)
8048e58: 89 c6 mov %eax,%esi
8048e5a: 47 inc %edi
8048e5b: 83 ff 05 cmp $0x5,%edi
8048e5e: 7e f2 jle 8048e52 <phase_6+0xba>
8048e60: c7 46 08 00 00 00 00 movl $0x0,0x8(%esi)
8048e67: 8b 75 cc mov -0x34(%ebp),%esi
8048e6a: 31 ff xor %edi,%edi
8048e6c: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
8048e70: 8b 56 08 mov 0x8(%esi),%edx
8048e73: 8b 06 mov (%esi),%eax
8048e75: 3b 02 cmp (%edx),%eax
8048e77: 7d 05 jge 8048e7e <phase_6+0xe6>
8048e79: e8 7e 06 00 00 call 80494fc <explode_bomb>
8048e7e: 8b 76 08 mov 0x8(%esi),%esi
8048e81: 47 inc %edi
8048e82: 83 ff 04 cmp $0x4,%edi
8048e85: 7e e9 jle 8048e70 <phase_6+0xd8>
8048e87: 8d 65 a8 lea -0x58(%ebp),%esp
8048e8a: 5b pop %ebx
8048e8b: 5e pop %esi
8048e8c: 5f pop %edi
8048e8d: 89 ec mov %ebp,%esp
8048e8f: 5d pop %ebp
8048e90: c3 ret
8048e91: 8d 76 00 lea 0x0(%esi),%esi
分段分析
读入阶段
mov 0x8(%ebp),%edx
:将函数的第一个参数(通常是一个指向输入字符串的指针)复制到%edx
寄存器。movl $0x804b26c,-0x34(%ebp)
:将地址0x804b26c
存储到%ebp
减 0x34 的位置,这可能是为了在后续的代码中使用。add $0xfffffff8,%esp
:将%esp
减 8,这可能是为了在栈上预留空间。lea -0x18(%ebp),%eax
:将%ebp
减 0x18 的地址加载到%eax
寄存器,这可能是为了存储read_six_numbers
函数的结果。push %eax
:将%eax
寄存器的值压入栈,这是read_six_numbers
函数的第二个参数。push %edx
:将%edx
寄存器的值压入栈,这是read_six_numbers
函数的第一个参数。
双重循环阶段
xor %edi,%edi
:将%edi
寄存器清零,这个寄存器在后面的循环中用作计数器。add $0x10,%esp
:将%esp
加 16,这可能是为了清理栈。lea 0x0(%esi),%esi
:这是一个无操作指令,可能是编译器优化的结果。- 外层循环从
lea -0x18(%ebp),%eax
开始,到jle 8048dc0 <phase_6+0x28>
结束。这个循环遍历用户输入的六个数字。 - 在外层循环中,
mov (%eax,%edi,4),%eax
和dec %eax
获取用户输入的一个数字并减 1。 cmp $0x5,%eax
和jbe 8048dd1 <phase_6+0x39>
检查这个数字是否在 1 到 6 的范围内。如果不在这个范围内,就会调用explode_bomb
函数。- 内层循环从
lea 0x1(%edi),%ebx
开始,到jle 8048de6 <phase_6+0x4e>
结束。这个循环检查当前的数字是否与其他的数字相同。 - 在内层循环中,
cmp (%esi,%ebx,4),%eax
和jne 8048df6 <phase_6+0x5e>
检查当前的数字是否与其他的数字相同。如果有相同的数字,就会调用explode_bomb
函数。
内层循环
lea 0x1(%edi),%ebx
:将%edi
加 1 并将结果存储在%ebx
中,这是内部循环的计数器。cmp $0x5,%ebx
和jg 8048dfc <phase_6+0x64>
:检查%ebx
是否大于 5。如果大于 5,就跳出内部循环。lea 0x0(,%edi,4),%eax
和mov %eax,-0x38(%ebp)
:计算%edi
乘以 4 的结果并将其存储在%ebp
减 0x38 的位置。lea -0x18(%ebp),%esi
:将%ebp
减 0x18 的地址加载到%esi
寄存器,这可能是为了存储read_six_numbers
函数的结果。mov -0x38(%ebp),%edx
和mov (%edx,%esi,1),%eax
:获取当前的数字。cmp (%esi,%ebx,4),%eax
和jne 8048df6 <phase_6+0x5e>
:检查当前的数字是否与其他的数字相同。如果有相同的数字,就会调用explode_bomb
函数。inc %ebx
和cmp $0x5,%ebx
和jle 8048de6 <phase_6+0x4e>
:增加%ebx
的值并检查是否小于等于 5。如果小于等于 5,就继续内部循环。inc %edi
和cmp $0x5,%edi
和jle 8048dc0 <phase_6+0x28>
:增加%edi
的值并检查是否小于等于 5。如果小于等于 5,就继续外部循环。
这段代码的目的是检查用户输入的六个数字是否都是唯一的,如果不是,就会调用 explode_bomb
函数。
遍历链表
xor %edi,%edi
:将%edi
清零,这是外部循环的计数器。lea -0x18(%ebp),%ecx
:将%ebp
减 0x18 的地址加载到%ecx
寄存器,这可能是为了存储read_six_numbers
函数的结果。lea -0x30(%ebp),%eax
和mov %eax,-0x3c(%ebp)
:将%ebp
减 0x30 的地址加载到%eax
寄存器,并将其存储在%ebp
减 0x3c 的位置。mov -0x34(%ebp),%esi
:将%ebp
减 0x34 的值加载到%esi
寄存器,这可能是链表的头节点。mov $0x1,%ebx
:将 1 存储在%ebx
寄存器,这是内部循环的计数器。lea 0x0(,%edi,4),%eax
和mov %eax,%edx
:计算%edi
乘以 4 的结果并将其存储在%edx
寄存器。cmp (%eax,%ecx,1),%ebx
和jge 8048e38 <phase_6+0xa0>
:检查%ebx
是否大于等于(%eax,%ecx,1)
的值。如果大于等于,就跳出内部循环。mov (%edx,%ecx,1),%eax
:获取当前的数字。mov 0x8(%esi),%esi
:获取链表的下一个节点。inc %ebx
和cmp %eax,%ebx
和jl 8048e30 <phase_6+0x98>
:增加%ebx
的值并检查是否小于%eax
。如果小于,就继续内部循环。mov -0x3c(%ebp),%edx
:将%ebp
减 0x3c 的值加载到%edx
寄存器。mov %esi,(%edx,%edi,4)
:将找到的节点存储在数组中。inc %edi
和cmp $0x5,%edi
和jle 8048e10 <phase_6+0x78>
:增加%edi
的值并检查是否小于等于 5。如果小于等于,就继续外部循环。
这段代码的目的是在链表中查找特定的节点,如果找到,就将其存储在数组中,然后继续查找下一个节点。如果找不到,就会继续查找下一个节点。
…(还未完结)