C 尝试对函数进行反向工程
我正在尝试更多地理解x86中的汇编。我知道这里有一个神秘函数,它返回一个C 尝试对函数进行反向工程,c,assembly,x86,reverse-engineering,att,C,Assembly,X86,Reverse Engineering,Att,我正在尝试更多地理解x86中的汇编。我知道这里有一个神秘函数,它返回一个int并接受一个int参数。 所以它看起来像是intsummary(intn){}。然而,我无法理解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
int
并接受一个int
参数。
所以它看起来像是intsummary(intn){}
。然而,我无法理解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
mov%edi,%eax
lea 0x0(,%rdi,8),%edi
子%eax,%edi
添加$0x4,%edi
callq
雷普兹雷特
<神秘_util>
mov%edi,%eax
shr%eax
和$0x1,%edi
和%edi,%eax
retq
我不明白lea在这里做什么,它可能是什么样的功能 LEA只是左移3,并将结果截断为32位(即,零将EDI隐式扩展为RDI)。x86-64系统V传递RDI中的第一个整数arg,因此所有这些都与一个
int
arg一致。LEA使用内存操作数语法和机器编码。将其用作乘以常数的乘法运算的一部分
生成此函数的编译器错过了此处的优化;第一个mov
可以通过
lea 0x0(,%rdi, 8), %eax # n << 3 = n*8
sub %edi, %eax # eax = n*7
lea 4(%rax), %edi # rdi = 4 + n*7
lea0x0(,%rdi,8),%eax#nlea
执行地址计算,但不是取消对地址的引用,而是将计算出的地址存储到目标寄存器中。
在AT&T语法中,leac(b,C,d),reg
表示reg=C+b+C*d
,其中C
是常数,b
,C
是寄存器,d
是{1,2,4,8}中的标量。因此,您可以看到为什么LEA在简单的数学运算中很受欢迎:它在一条指令中执行了相当多的操作。(*包括prl以下评论的更正)
此汇编代码有一些奇怪的特性:repz
前缀仅在应用于某些指令时才严格定义,retq
不是其中之一(尽管处理器的一般行为是忽略它)。有关更多信息,请参见下面迈克尔·佩奇的评论和链接。使用lea(,rdi,8),edi
,然后使用sub eax,edi
来计算arg1*7
似乎也很奇怪,但一旦prl注意到标量d
必须是2的恒定幂,就有意义了。在任何情况下,我都是这样读这段代码的:
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
mov%edi,%eax;eax=arg1
LEA0x0(,%rdi,8),%edi;edi=arg1*8
低于%eax,%edi;edi=(arg1*8)-arg1=arg1*7
添加$0x4,%edi;edi=(arg1*7)+4
callq<神秘_util>;调用神秘_util(arg1*7+4)
雷普兹雷特克;返回时的repz前缀实际上是nop。
<神秘_util>
mov%edi,%eax;eax=arg1
shr%eax;eax=arg1>>1
和$0x1,%edi;如果arg1为奇数,则edi=1,否则为0
和%edi,%eax;eax=1如果arg1的最小2位均为1。
retq
注意第4行的+4
完全是假的。它不能影响神秘的结果
因此,总的来说,这个ASM代码段计算布尔值(arg1*7)%4==3。汇编代码似乎是由计算机生成的,而且可能是由GCC编译的,因为在无条件分支(
调用
)之后有一个repz retq
)。还有一个迹象表明,由于在转到summary\u util
时没有尾部调用(jmp
)而不是调用,因此代码是使用-O1
编译的(更高的优化级别可能会内联此处未发生的函数)。缺少帧指针和额外的加载/存储表明它不是用-O0
将x
乘以7与将x
乘以8并减去x
相同。下面的代码就是这么做的:
lea 0x0(,%rdi, 8), %edi
sub %eax, %edi
可以计算地址,但也可以用于简单的算术。内存操作数的语法是位移(基、索引、刻度)。刻度可以是1、2、4、8。计算方法为位移+基底+指数*标度。在您的情况下,lea0x0(,%rdi,8),%edi
实际上是edi=0x0+rdi*8或edi=rdi*8。完整计算为n*7-4
summary\u util
的计算似乎简单
n &= (n>>1) & 1;
如果我把所有这些因素加在一起,我们就有一个函数summary
,它将n*7-4传递给一个名为summary\u util
的函数,该函数返回n&=(n>>1)&1
由于summary\u util
返回单个位值(0或1),因此合理的做法是将bool
作为返回类型
我很好奇是否可以得到一个优化级别为1(-O1
)的特定版本的GCC来重现这个汇编代码。我发现GCC4.9.x将为这个给定的C程序生成一个精确的汇编代码:
您可以在上玩此代码
重要更新-没有bool的版本
我显然在解释这个问题时犯了错误。我假设问这个问题的人自己决定了神秘
的原型是int神秘(int n)
。我想我可以改变这一点。根据一天后在Stackoverflow上被问到的问题,似乎int-summary(int-n)
是作为任务的一部分提供给你的原型。这很重要,因为这意味着必须进行修改
需要进行的更改与summary\u util
有关。要进行反向工程的代码中有以下几行:
mov %edi, %eax
shr %eax
EDI是第一个参数。SHR是逻辑右移。只有当EDI是一个无符号int
(或等效项)时,编译器才会生成此代码int
是一种有符号类型,可以生成SAR(算术右移)。这意味着unsigned_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);
}
神秘
现在有了你教授给出的原型(mov %edi, %eax
shr %eax
unsigned int mystery_util(unsigned int n)
{
n &= (n>>1) & 1;
return n;
}
int mystery(int n)
{
return mystery_util (7*n+4);
}
mystery_util:
movl %edi, %eax
sarl %eax ; <------- SAR (arithmetic shift right) is not SHR
andl $1, %edi
andl %edi, %eax
ret