Assembly C组装回路
我对大会上发生的事情有点困惑。我可以看到基本情况,如果不输入六个数字,炸弹就会爆炸并结束程序。逐行检查输入,如果六个数字不是负数,则进入循环。我在这里迷路了Assembly C组装回路,assembly,x86-64,att,Assembly,X86 64,Att,我对大会上发生的事情有点困惑。我可以看到基本情况,如果不输入六个数字,炸弹就会爆炸并结束程序。逐行检查输入,如果六个数字不是负数,则进入循环。我在这里迷路了0x0000000000400f29:add-0x4(%rbp),%eax 这看起来很简单,但我真的不明白这里添加了什么。是否将-4相加,然后将其与0进行比较?如果相等,那么跳跃 我基本上是在寻找关于循环的澄清,以及什么样的输入模式将在循环中继续 转储 0x0000000000400f0c <+0>: push
0x0000000000400f29:add-0x4(%rbp),%eax
这看起来很简单,但我真的不明白这里添加了什么。是否将-4相加,然后将其与0进行比较?如果相等,那么跳跃
我基本上是在寻找关于循环的澄清,以及什么样的输入模式将在循环中继续
转储
0x0000000000400f0c <+0>: push %rbp
0x0000000000400f0d <+1>: push %rbx
0x0000000000400f0e <+2>: sub $0x28,%rsp
0x0000000000400f12 <+6>: mov %rsp,%rsi
0x0000000000400f15 <+9>: callq 0x40165a <read_six_numbers>
0x0000000000400f1a <+14>: cmpl $0x0,(%rsp)
0x0000000000400f1e <+18>: jns 0x400f44 <phase_2+56>
0x0000000000400f20 <+20>: callq 0x401624 <explode_bomb>
0x0000000000400f25 <+25>: jmp 0x400f44 <phase_2+56>
0x0000000000400f27 <+27>: mov %ebx,%eax
=> 0x0000000000400f29 <+29>: add -0x4(%rbp),%eax
0x0000000000400f2c <+32>: cmp %eax,0x0(%rbp)
0x0000000000400f2f <+35>: je 0x400f36 <phase_2+42>
0x0000000000400f31 <+37>: callq 0x401624 <explode_bomb>
0x0000000000400f36 <+42>: add $0x1,%ebx
0x0000000000400f39 <+45>: add $0x4,%rbp
0x0000000000400f3d <+49>: cmp $0x6,%ebx
0x0000000000400f40 <+52>: jne 0x400f27 <phase_2+27>
0x0000000000400f42 <+54>: jmp 0x400f50 <phase_2+68>
0x0000000000400f44 <+56>: lea 0x4(%rsp),%rbp
0x0000000000400f49 <+61>: mov $0x1,%ebx
0x0000000000400f4e <+66>: jmp 0x400f27 <phase_2+27>
0x0000000000400f50 <+68>: add $0x28,%rsp
0x0000000000400f54 <+72>: pop %rbx
0x0000000000400f55 <+73>: pop %rbp
0x0000000000400f56 <+74>: retq
End of assembler dump.
0x0000000000400f0c:推送%rbp
0x0000000000400f0d:推送%rbx
0x0000000000400f0e:子$0x28,%rsp
0x0000000000400f12:mov%rsp,%rsi
0x0000000000400f15:callq 0x40165a
0x0000000000400f1a:cmpl$0x0,(%rsp)
0x0000000000400f1e:jns 0x400f44
0x0000000000400f20:callq 0x401624
0x0000000000400f25:jmp 0x400f44
0x0000000000400f27:mov%ebx,%eax
=>0x0000000000400f29:添加-0x4(%rbp),%eax
0x0000000000400f2c:cmp%eax,0x0(%rbp)
0x0000000000400f2f:je 0x400f36
0x0000000000400f31:callq 0x401624
0x0000000000400f36:添加$0x1,%ebx
0x0000000000400f39:添加$0x4,%rbp
0x0000000000400f3d:cmp$0x6,%ebx
0x0000000000400f40:jne 0x400f27
0x0000000000400f42:jmp 0x400f50
0x0000000000400f44:lea 0x4(%rsp),%rbp
0x0000000000400f49:mov$0x1,%ebx
0x0000000000400f4e:jmp 0x400f27
0x0000000000400f50:添加$0x28,%rsp
0x0000000000400f54:弹出%rbx
0x0000000000400f55:弹出%rbp
0x0000000000400f56:retq
汇编程序转储结束。
读取六个数字
Dump of assembler code for function read_six_numbers:
=> 0x000000000040165a <+0>: sub $0x18,%rsp
0x000000000040165e <+4>: mov %rsi,%rdx
0x0000000000401661 <+7>: lea 0x4(%rsi),%rcx
0x0000000000401665 <+11>: lea 0x14(%rsi),%rax
0x0000000000401669 <+15>: mov %rax,0x8(%rsp)
0x000000000040166e <+20>: lea 0x10(%rsi),%rax
0x0000000000401672 <+24>: mov %rax,(%rsp)
0x0000000000401676 <+28>: lea 0xc(%rsi),%r9
0x000000000040167a <+32>: lea 0x8(%rsi),%r8
0x000000000040167e <+36>: mov $0x402871,%esi
0x0000000000401683 <+41>: mov $0x0,%eax
0x0000000000401688 <+46>: callq 0x400c30 <__isoc99_sscanf@plt>
0x000000000040168d <+51>: cmp $0x5,%eax
0x0000000000401690 <+54>: jg 0x401697 <read_six_numbers+61>
0x0000000000401692 <+56>: callq 0x401624 <explode_bomb>
0x0000000000401697 <+61>: add $0x18,%rsp
0x000000000040169b <+65>: retq
End of assembler dump.
函数读取六个数字的汇编程序代码转储:
=>0x000000000040165a:sub$0x18,%rsp
0x000000000040165e:mov%rsi,%rdx
0x0000000000401661:lea 0x4(%rsi),%rcx
0x0000000000401665:lea 0x14(%rsi),%rax
0x0000000000401669:mov%rax,0x8(%rsp)
0x000000000040166e:lea 0x10(%rsi),%rax
0x0000000000401672:mov%rax,(%rsp)
0x0000000000401676:lea 0xc(%rsi),%r9
0x000000000040167a:lea 0x8(%rsi),%r8
0x000000000040167e:mov$0x402871,%esi
0x0000000000401683:mov$0x0,%eax
0x0000000000401688:callq 0x400c30
0x000000000040168d:cmp$0x5,%eax
0x0000000000401690:jg 0x401697
0x0000000000401692:callq 0x401624
0x0000000000401697:添加$0x18,%rsp
0x000000000040169b:retq
汇编程序转储结束。
粗略地看,整个过程归结为:
void read_six_numbers(const char *sz, int numbers[6]) {
// the format string is inferred from the context,
// to see its actual value you should look at 0x402871
if(sscanf(sz, "%d %d %d %d %d %d", &numbers[0], &numbers[1], &numbers[2], &numbers[3], &numbers[4], &numbers[5])<6) explode_bomb();
}
void phase_2(const char *sz) {
int numbers[6];
read_six_numbers(sz, numbers);
if(numbers[0] < 0) explode_bomb();
for(int i=1; i!=6; ++i) {
int a = i + numbers[i-1];
if(numbers[i]!=a) explode_bomb();
}
}
它在rsi中接收一个参数,它是指向调用方堆栈上某个内容的指针。常规SystemV调用约定使用rsi
作为第二个参数,并且read_six_numbers
readsrdi
(隐式地,我们将在后面看到)。因此,我们可以假设phase_2
确实在rdi
中接收到一个参数,并将其留在那里,直接传递给读取六个数字
在“经典”的开场白之后,它为当地人保留了一堆
<+0>: sub rsp,0x18
rsi
现在被设置为某个固定地址1,考虑到它的地址和它在rsi
中的事实,它看起来真的像一个字符串文字或一个全局值(因此它将成为下面的sscanf
的第二个参数)
因此,这是到sscanf
2-它需要将参数(按顺序)传递到rdi
、rsi
、rdx
、rcx
、r8
、r9
,以及堆栈上的其余参数,并将ax
设置为存储在XMM寄存器中的浮点参数的数量(此处为零,因此为mov
3)
如果我们把迄今为止收集到的数据放在一起,我们可以推断:
rdi
是一个const char*
,是要读取的六个数字的源字符串
rsi
包含一个指向至少六个int
数组的指针(请参阅它加载偏移量为4的寄存器-AKAsizeof(int)
?),这些寄存器是从rdi
中的字符串读取的
- 最可能的情况是,如果我们查看
0x402871
,我们会看到类似于%d%d%d%d%d“
因此,我们可以开始编写此函数的暂定定义:
void read_six_numbers(const char *sz, int numbers[6]) {
int eax = sscanf(sz, "%d %d %d %d %d %d",
&numbers[0], &numbers[1], &numbers[2],
&numbers[3], &numbers[4], &numbers[5]);
...
}
注意,这里我写了数字[6]
仅作为提醒,对于C语言,数组参数中的大小被忽略-它只是一个常规指针;此外,我将void
写为返回类型,因为我看到调用此函数后,在调用代码中似乎没有人对rax
或eax
感兴趣
<+68>: add rsp,0x28
<+72>: pop rbx
<+73>: pop rbp
<+74>: ret
然后:
然后,在+61和+65处有标准函数尾声(修复堆栈并返回)
所以,总而言之,我们可以把整个事情写成
void read_six_numbers(const char *sz, int numbers[6]) {
if(sscanf(sz, "%d %d %d %d %d %d",
&numbers[0], &numbers[1], &numbers[2],
&numbers[3], &numbers[4], &numbers[5] < 6) {
explode_bomb();
}
}
通常的开场白;40字节的局部变量,保存rbp
和rbx
,因为它们将被使用(两者都是被调用方保存的寄存器);注意这里rbp
不是用作堆栈帧指针,而是用作“常规”寄存器
<+6>: mov rsi,rsp
<+9>: call 0x40165a <read_six_numbers>
检查第一个号码([rsp]
*numbers
numbers[0]
)是否为负数;如果是,请跳过对爆炸炸弹的呼叫
(要了解它的工作原理,请记住cmp
执行减法而不保存结果,但只执行与之对应的标志,因此[rsp]-0
是普通的[rsp]
,而jns
表示如果不是符号位则跳转,因此如果cmp
的结果为非负,则跳转)
让我们试着推测一下到目前为止我们所拥有的:
ret_type? phase_2(const char *sz) {
int numbers[6];
read_six_numbers(sz, numbers);
if(numbers[0]<0) explode_bomb();
...
}
如果您快速查看一下跳跃,您将看到:
- 小街区
<+51>: cmp eax,0x5
<+54>: jg 0x401697 <read_six_numbers+61>
<+56>: call 0x401624 <explode_bomb>
<+61>: add rsp,0x18
<+65>: ret
if(eax<6) explode_bomb();
void read_six_numbers(const char *sz, int numbers[6]) {
if(sscanf(sz, "%d %d %d %d %d %d",
&numbers[0], &numbers[1], &numbers[2],
&numbers[3], &numbers[4], &numbers[5] < 6) {
explode_bomb();
}
}
<+0>: push rbp
<+1>: push rbx
<+2>: sub rsp,0x28
<+6>: mov rsi,rsp
<+9>: call 0x40165a <read_six_numbers>
<+14>: cmp [rsp],0
<+18>: jns 0x400f44 <phase_2+56>
<+20>: call 0x401624 <explode_bomb>
<+25>: jmp 0x400f44 <phase_2+56>
ret_type? phase_2(const char *sz) {
int numbers[6];
read_six_numbers(sz, numbers);
if(numbers[0]<0) explode_bomb();
...
}
<+56>: lea rbp,[rsp+4]
<+61>: mov ebx,1
<+66>: jmp 0x400f27 <phase_2+27>
<+27>: mov eax,ebx
<+29>: add eax,[rbp-4]
<+32>: cmp [rbp],eax
<+35>: je 0x400f36 <phase_2+42>
<+37>: call 0x401624 <explode_bomb>
<+42>: add ebx,1
<+45>: add rbp,4
<+49>: cmp ebx,6
<+52>: jne 0x400f27 <phase_2+27>
<+54>: jmp 0x400f50 <phase_2+68>
<+27>: mov eax,ebx
<+29>: add eax,[rbp-4]
<+32>: cmp [rbp],eax
<+35>: je 0x400f36 <phase_2+42>
<+37>: call 0x401624 <explode_bomb>
<+42>: add ebx,1
<+45>: add rbp,4
<+49>: cmp ebx,6
<+52>: jne 0x400f27 <phase_2+27>
<+54>: jmp 0x400f50 <phase_2+68>
<+68>: add rsp,0x28
<+72>: pop rbx
<+73>: pop rbp
<+74>: ret
void phase_2(const char *sz) {
int numbers[6];
read_six_numbers(sz, numbers);
if(numbers[0]<0) explode_bomb();
int ebx = 1;
int *rbp = &numbers[1];
do {
int eax = ebx + rbp[-1];
if(eax != rbp[0]) explode_bomb();
++ebx;
++rbp;
} while(ebx!=6);
}
void phase_2(const char *sz) {
int numbers[6];
read_six_numbers(sz, numbers);
if(numbers[0]<0) explode_bomb();
for(int i=1; i<6; ++i) {
int n = i + numbers[i-1];
if(n != numbers[i]) explode_bomb();
}
}