解密x86汇编功能

时间:2016-12-14 07:04:31

标签: c assembly x86 att

我目前正致力于二元炸弹任务的第二阶段。我无法完全解密某个函数在调用时所执行的操作。我已经坚持了几天。

功能是:

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)的值。

1 个答案:

答案 0 :(得分:11)

  400f2a:   85 ff                   test   %edi,%edi
  400f2c:   74 1d                   je     400f4b <func2a+0x21>

这只是edi为零时的早期退出(jejz相同)。

  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(几乎任何最新版本-O1we obtain back exactly the assembly you provided