问题描述
题目的意思是在这段汇编代码中破解出6个数
本渣第二个炸弹就卡住了……汇编如上,大概是比较数组第0个元素和第三个元素 第一个和第四个 第三个和第五个 不相等就爆炸 一共循环三次。可是怎么知道数组的初始值呢?
题主这个是CS:APP 1e的lab2么?
之前我没做过CS:APP的lab,刚去瞄了眼CS:APP 2e和3e的lab2 self study版,都不是长这个样的…
<- 啊,原来是中科大的CS:APP 2e的非self study 32位版lab2。
题主截取的phase_2函数的内容其实很简单,毕竟源程序就很简单而编译的时候又没有加优化。静态读代码然后人肉反编译其实就够了。动用gdb来动态调试并不是必要的(当然也可以作为有效的辅助手段…)
结合上下文人肉反编译一小段就是这样:
// constant printf() may be compiled into a puts() even at -O0
void explode_bomb() {
puts("BOOM!!!"); // or printf("BOOM!!!\n")
puts("The bomb has blown up."); // or printf("The bomb has blown up.\n")
exit(8);
}
void read_six_numbers(char* input, int* nums) {
if (sscanf(input, "%d %d %d %d %d %d", &nums[0], &nums[1], &nums[2], &nums[3], &nums[4], &nums[5]) <= 5) {
explode_bomb();
}
}
void phase_2(char* input) {
int sum = 0;
int nums[6];
read_six_numbers(input, nums);
for (int i = 0; i < 3; i++) {
if (nums[i] != nums[i + 3]) {
explode_bomb();
}
sum += nums[i];
}
if (sum == 0) {
explode_bomb();
}
}
char* read_line() {
/* ... */
}
int main(int argc, char* argv[]) {
char* input;
/* ... */
input = read_line();
phase_2(input);
/* ... */
}用GCC 4.4.7在Linux x86-64上用-m32 -O0编译这个phase_2()会得到跟题主截图的几乎一样的代码。下面是编译结果:
phase_2: # phase_2(char*)
pushl %ebp
movl %esp, %ebp
subl $56, %esp
movl $0, -12(%ebp) #, sum
leal -40(%ebp), %eax #, tmp63
movl %eax, 4(%esp) # tmp63,
movl 8(%ebp), %eax # input, tmp64
movl %eax, (%esp) # tmp64,
call read_six_numbers # (char*, int*)
movl $0, -16(%ebp) #, i
jmp .L5
.L7:
movl -16(%ebp), %eax # i, tmp65
movl -40(%ebp,%eax,4), %edx # nums, D.2039
movl -16(%ebp), %eax # i, tmp66
addl $3, %eax #, D.2040
movl -40(%ebp,%eax,4), %eax # nums, D.2041
cmpl %eax, %edx # D.2041, D.2039
je .L6
call explode_bomb # ()
.L6:
movl -16(%ebp), %eax # i, tmp67
movl -40(%ebp,%eax,4), %eax # nums, D.2044
addl %eax, -12(%ebp) # D.2044, sum
addl $1, -16(%ebp) #, i
.L5:
cmpl $2, -16(%ebp) #, i
jle .L7
cmpl $0, -12(%ebp) #, sum
jne .L4
call explode_bomb # ()
.L4:
leave
ret
大家可以看看这个-O0的编译结果是不是跟题主贴出来的截图里的指令一一对应?指令的条数以及每条指令的内容都是对应的。
唯一的区别是我这里编译出来的版本的栈帧多了4个无用的slot。其栈帧布局是这样的:
esp -> [ outgoing argument 1 ] ebp-56 or esp+0
[ outgoing argument 0 ] ebp-52 or esp+4
[ ] ebp-48 or esp+8
[ ] ebp-44 or esp+12
[ nums[0] ] ebp-40
[ nums[1] ] ebp-36
[ nums[2] ] ebp-32
[ nums[3] ] ebp-28
[ nums[4] ] ebp-24
[ nums[5] ] ebp-20
[ i ] ebp-16
[ sum ] ebp-12
[ ] ebp-8
[ ] ebp-4
ebp -> [ old frame pointer ] ebp+0
[ return address ] ebp+4
[ incoming argument 0 ] ebp+8ebp-4、-8、-44、-48这4个slot在这个函数里是没有被用到的。
而看题主的截图里phase_2()的栈帧布局则是:
esp -> [ outgoing argument 1 ] ebp-40 or esp+0
[ outgoing argument 0 ] ebp-36 or esp+4
[ nums[0] ] ebp-32
[ nums[1] ] ebp-28
[ nums[2] ] ebp-24
[ nums[3] ] ebp-20
[ nums[4] ] ebp-16
[ nums[5] ] ebp-12
[ i ] ebp-8
[ sum ] ebp-4
ebp -> [ old frame pointer ] ebp+0
[ return address ] ebp+4
[ incoming argument 0 ] ebp+8
正好就是比我用GCC 4.4.7 -O0编译少用了4个slot。
补充:话说可以给GCC再传递个 -mpreferred-stack-boundary=2 参数来让它生成跟题主截图里的代码一模一样的结果:
phase_2:
pushl %ebp
movl %esp, %ebp
subl $40, %esp
movl $0, -4(%ebp)
leal -32(%ebp), %eax
movl %eax, 4(%esp)
movl 8(%ebp), %eax
movl %eax, (%esp)
call read_six_numbers
movl $0, -8(%ebp)
jmp .L7
.L9:
movl -8(%ebp), %eax
movl -32(%ebp,%eax,4), %edx
movl -8(%ebp), %eax
addl $3, %eax
movl -32(%ebp,%eax,4), %eax
cmpl %eax, %edx
je .L8
call explode_bomb
.L8:
movl -8(%ebp), %eax
movl -32(%ebp,%eax,4), %eax
addl %eax, -4(%ebp)
addl $1, -8(%ebp)
.L7:
cmpl $2, -8(%ebp)
jle .L9
cmpl $0, -4(%ebp)
jne .L11
call explode_bomb
.L11:
leave
ret
完美。
=====================================
所以简单来说这样的输入就能通过了:
1 2 3 1 2 3或
1 1 1 1 1 1而下面这几个输入则通不过:
0 0 0 0 0 0或
-1 0 1 -1 0 1或
3 -2 -1 3 -2 -1>_<