试图对一个函数进行反向工程

时间:2018-03-18 02:43:54

标签: c assembly x86-64 reverse-engineering

我正在尝试更多地了解x86中的程序集。我在这里有一个神秘的功能,我知道返回一个int,并采用int参数。 所以它看起来像int mystery(int n){}。但我无法弄清楚C中的功能。大会是:

mov  %edi, %eax
lea  0x0(,%rdi, 8), %edi
sub  %eax, %edi
add  $0x4, %edi
callq < mystery _util >
repz retq

< mystery _util >
mov  %edi, %eax
shr  %eax
and  $0x1, %edi
and  %edi, %eax
retq

我不明白lea在这里做了什么以及它可能是什么样的功能。

3 个答案:

答案 0 :(得分:8)

汇编代码似乎是由计算机生成的,并且可能是由GCC编译的,因为在无条件分支(repz retq)之后有call。还有一个迹象表明,因为在转到jmp代码是使用call编译时,没有尾调用(mystery_util)而不是-O1(更高的优化水平可能会内联这里没有发生的功能)。缺少框架指针和额外的加载/存储表明它没有用-O0

编译

x乘以7与将x乘以8并减去x相同。这就是以下代码所做的事情:

lea  0x0(,%rdi, 8), %edi
sub  %eax, %edi

LEA可以计算地址,但也可以用于简单算术。内存操作数的语法是位移(base,index,scale)。比例可以是1,2,4,8。计算是位移+基数+指数*比例。在您的情况下,lea 0x0(,%rdi, 8), %edi实际上是EDI = 0x0 + RDI * 8或EDI = RDI * 8.完整计算是n * 7 - 4;

mystery_util的计算似乎只是

n &= (n>>1) & 1;

如果我将所有这些因素放在一起,我们有一个函数mystery,它将n * 7 - 4传递给一个名为mystery_util的函数,该函数返回n &= (n>>1) & 1

由于mystery_util返回单个位值(0或1),因此bool是返回类型是合理的。

我很好奇是否可以获得具有优化级别1(-O1)的特定版本的 GCC 来重现此汇编代码。我发现GCC 4.9.x将为这个给定的 C 程序产生精确汇编代码

#include<stdbool.h>

bool mystery_util(unsigned int n)
{
    n &= (n>>1) & 1;
    return n;
}

bool mystery(unsigned int n)
{
    return mystery_util (7*n+4);
}

程序集输出为:

mystery_util:
        movl    %edi, %eax
        shrl    %eax
        andl    $1, %edi
        andl    %edi, %eax
        ret
mystery:
        movl    %edi, %eax
        leal    0(,%rdi,8), %edi
        subl    %eax, %edi
        addl    $4, %edi
        call    mystery_util
        rep ret

您可以在godbolt上使用此代码。

重要更新 - 没有布尔的版本

我在解释这个问题时显然错了。我假设这个问题的人自己确定mystery的原型是int mystery(int n)。我以为我可以改变它。根据一天后在Stackoverflow上询问的related question,似乎int mystery(int n)作为分配的一部分给你作为原型。这很重要,因为这意味着必须进行修改。

需要进行的更改与mystery_util有关。在要反向工程的代码中有以下几行:

mov  %edi, %eax
shr  %eax

EDI 是第一个参数。 SHR 是合乎逻辑的右移。如果 EDI unsigned int(或等效的),编译器只会生成此项。 int是一个签名类型,会生成 SAR (算术右移)。这意味着mystery_util的参数必须为unsigned int(并且后面的返回值很可能是unsigned int。这意味着代码看起来像这样:

unsigned int mystery_util(unsigned int n)
{
    n &= (n>>1) & 1;
    return n;
}

int mystery(int n)
{
    return mystery_util (7*n+4);
}

mystery现在有您的教授给出的原型(bool已删除),我们使用unsigned int作为mystery_util的参数和返回类型。为了使用GCC 4.9.x生成此代码,我发现您需要使用-O1 -fno-inline。此代码可在godbolt上找到。程序集输出与使用bool的版本相同。

如果你使用unsigned int mystery_util(int n),你会发现它并没有输出我们想要的东西:

mystery_util:
        movl    %edi, %eax
        sarl    %eax          ; <------- SAR (arithmetic shift right) is not SHR
        andl    $1, %edi
        andl    %edi, %eax
        ret

答案 1 :(得分:2)

LEA只是左移3,并将结果截断为32位(即将零延伸EDI转换为RDI隐含)。 x86-64 System V传递RDI中的第一个整数arg,因此所有这些都与一个int arg一致。 LEA使用内存操作数语法和机器编码but it's really just a shift-and-add instruction。将它作为乘以常数is a common compiler optimization for x86的一部分。

生成此函数的编译器在此错过了优化;

可以避免第一个mov
lea  0x0(,%rdi, 8), %eax     # n << 3 = n*8
sub  %edi, %eax              # eax = n*7
lea  4(%rax), %edi           # rdi = 4 + n*7

但相反,编译器在n*7中生成%edi时遇到困难,可能是因为它为常量乘法应用了一个窥孔优化,以至于重做寄存器分配的时间太晚了。

mystery_util在低位返回其arg的低2位的按位AND,因此为0或1整数值,也可以是bool

shr没有计数意味着计数为1;请记住,x86对于隐含计数为1的班次有一个特殊的操作码.8086只有1或cl的计数;立即计数是稍后作为扩展添加,隐式表单操作码仍然更短。)

答案 2 :(得分:1)

LEA执行地址计算,但不是解除引用地址,而是将计算出的地址存储到目标寄存器中。 在AT&amp; T语法中,lea C(b,c,d), reg表示reg = C + b + c*d其中C是常量,bc是寄存器,d是标量来自{1,2,4,8}。因此,您可以看到为什么LEA在简单的数学运算中很受欢迎:它在单个指令中有相当多的作用。 (*包括下面prl评论的更正)

此汇编代码有一些奇怪的特性:repz前缀仅在应用于某些指令时被严格定义,而retq不是其中之一(尽管处理器的一般行为是忽略它)。请参阅下面的Michael Petch的评论以及更多信息的链接。使用lea (,rdi,8), edi后跟sub eax, edi来计算arg1 * 7也似乎很奇怪,但是一旦prl注意到标量d必须是2的恒定幂,这是有道理的。 case,这是我阅读片段的方式:

mov  %edi, %eax          ; eax = arg1
lea  0x0(,%rdi, 8), %edi ; edi = arg1 * 8
sub  %eax, %edi          ; edi = (arg1 * 8) - arg1 = arg1 * 7
add  $0x4, %edi          ; edi = (arg1 * 7) + 4
callq < mystery _util >  ; call mystery_util(arg1 * 7 + 4)
repz retq                ; repz prefix on return is de facto nop.


< mystery _util >
mov  %edi, %eax          ; eax = arg1
shr  %eax                ; eax = arg1 >> 1
and  $0x1, %edi          ; edi = 1 iff arg1 was odd, else 0
and  %edi, %eax          ; eax = 1 iff smallest 2 bits of arg1 were both 1.
retq

注意第4行的+4完全是虚假的。它不会影响mystery_util的结果。

因此,整体而言,此ASM片段计算布尔值(arg1 * 7)%4 == 3.