我目前正致力于二元炸弹任务的第二阶段。我无法完全解密某个函数在调用时所执行的操作。我已经坚持了几天。
功能是:
class One {
public:
virtual ~One() {}
// ...
};
在较大的函数“phase_2”中调用它:
0000000000400f2a <func2a>:
400f2a: 85 ff test %edi,%edi
400f2c: 74 1d je 400f4b <func2a+0x21>
400f2e: b9 cd cc cc cc mov $0xcccccccd,%ecx
400f33: 89 f8 mov %edi,%eax
400f35: f7 e1 mul %ecx
400f37: c1 ea 03 shr $0x3,%edx
400f3a: 8d 04 92 lea (%rdx,%rdx,4),%eax
400f3d: 01 c0 add %eax,%eax
400f3f: 29 c7 sub %eax,%edi
400f41: 83 04 be 01 addl $0x1,(%rsi,%rdi,4)
400f45: 89 d7 mov %edx,%edi
400f47: 85 d2 test %edx,%edx
400f49: 75 e8 jne 400f33 <func2a+0x9>
400f4b: f3 c3 repz retq
我完全理解phase_2正在做什么,我只是不明白func2a正在做什么以及它如何影响0x30(%rsp)的值等等。因此,我总是到达0x401003的比较语句,炸弹最终在那里爆炸。
我的问题是我不明白输入(相位解决方案)如何通过func2a影响0x30(%rsp)的值。
答案 0 :(得分:11)
400f2a: 85 ff test %edi,%edi
400f2c: 74 1d je 400f4b <func2a+0x21>
这只是edi
为零时的早期退出(je
与jz
相同)。
400f2e: b9 cd cc cc cc mov $0xcccccccd,%ecx
400f33: 89 f8 mov %edi,%eax
400f35: f7 e1 mul %ecx
400f37: c1 ea 03 shr $0x3,%edx
这是一个经典的优化技巧;它是除以乘以倒数的整数算术等价物(见here for details);在实践中,这与说edx = edi / 10
相同;
400f3a: 8d 04 92 lea (%rdx,%rdx,4),%eax
400f3d: 01 c0 add %eax,%eax
这里利用lea
来执行算术(并且在英特尔语法中更明确,lea eax,[rdx+rdx*4]
=&gt; eax = edx*5
),然后将结果与自身相加。这一切归结为eax = edx*10
。
400f3f: 29 c7 sub %eax,%edi
然后,将其减去edi
。
所以,总而言之,这是计算edi
的最后一个十进制数字的一种复杂(但很快)的方法;到目前为止我们所拥有的是:
void func2a(unsigned edi) {
if(edi==0) return;
label1:
edx=edi/10;
edi%=10;
// ...
}
(label1:
因为400f33
以后是跳转目标)
继续:
400f41: 83 04 be 01 addl $0x1,(%rsi,%rdi,4)
同样,在英特尔语法中,这对我来说更清晰 - add dword [rsi+rdi*4],byte +0x1
。它是一个32位int
数组的常规增量(rdi
乘以4);所以,我们可以想象rsi
指向一个整数数组,用刚刚计算的edi
的最后一位数作为索引。
void func2a(unsigned edi, int rsi[]) {
if(edi==0) return;
label1:
edx=edi/10;
edi%=10;
rsi[edi]++;
}
然后:
400f45: 89 d7 mov %edx,%edi
400f47: 85 d2 test %edx,%edx
400f49: 75 e8 jne 400f33 <func2a+0x9>
将我们上面计算的除法结果移到edi
,如果它与零不同则循环。
400f4b: f3 c3 repz retq
返回(使用an unusual encoding of the instruction that is optimal for certain AMD processors)。
所以,通过用while
循环重写跳转并给出一些有意义的名字......
// number is edi, digits_count is rsi, as per regular
// x64 SystemV calling convention
void count_digits(unsigned number, int digits_count[]) {
while(number) {
digits_count[number%10]++;
number/=10;
}
}
即,这是一个函数,给定一个整数,通过递增digits_count
数组中相应的桶来计算单个十进制数字的出现次数。
有趣的事实:如果我们将上面的C代码提供给gcc
(几乎任何最新版本-O1
)we obtain back exactly the assembly you provided。