首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >试图反向工程一个函数

试图反向工程一个函数
EN

Stack Overflow用户
提问于 2018-03-18 02:43:54
回答 2查看 1.6K关注 0票数 2

我正在尝试更多地理解x86中的程序集。这里有一个神秘函数,我知道它返回一个int并接受一个int参数。所以看起来像int mystery(int n){}。但是,我无法用C语言计算出这个函数。大会是:

代码语言:javascript
运行
复制
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在这里做了什么,它能起什么作用。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-03-18 06:01:08

装配代码似乎是由计算机生成的,而且可能是GCC编写的,因为在无条件分支(call)之后有一个call。还有一个迹象表明,由于在转到jmp时没有尾调用( call )而不是call,所以代码是用-O1编译的(更高的优化级别可能会内联这里没有发生的函数)。缺少帧指针和额外的加载/存储表明它不是用-O0编译的

x乘以7等于将x乘以8并减去x。以下代码就是这样做的:

代码语言:javascript
运行
复制
lea  0x0(,%rdi, 8), %edi
sub  %eax, %edi

利娅可以计算地址,但也可以用于简单的算术。内存操作数的语法是移位(基、索引、缩放)。标度可为1,2,4,8,计算为位移+基准面+指数*标度。在您的例子中,lea 0x0(,%rdi, 8), %edi实际上是EDI = 0x0 + RDI * 8或EDI = RDI *8。

mystery_util的计算似乎很简单

代码语言:javascript
运行
复制
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程序的确切汇编代码

代码语言:javascript
运行
复制
#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);
}

程序集输出是:

代码语言:javascript
运行
复制
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

您可以在哥德波特上播放这段代码。

重要更新-没有bool版本

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

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

代码语言:javascript
运行
复制
mov  %edi, %eax
shr  %eax

EDI是第一个参数。SHR是合乎逻辑的右移。只有当EDI是unsigned int (或等效的)时,编译器才会生成这种情况。int是一个符号类型a将生成合成孔径雷达(算术移位右)。这意味着mystery_util的参数必须是unsigned int (因此返回值可能是unsigned int )。这意味着代码将如下所示:

代码语言:javascript
运行
复制
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。这段代码可以在哥德波特上找到。程序集输出与使用bool的版本相同。

如果使用unsigned int mystery_util(int n),您会发现它没有完全输出我们想要的内容:

代码语言:javascript
运行
复制
mystery_util:
        movl    %edi, %eax
        sarl    %eax          ; <------- SAR (arithmetic shift right) is not SHR
        andl    $1, %edi
        andl    %edi, %eax
        ret
票数 9
EN

Stack Overflow用户

发布于 2018-03-18 04:01:43

LEA执行地址计算,但它不是取消引用地址,而是将计算的地址存储到目标寄存器中。在AT&T语法中,lea C(b,c,d), reg的意思是reg = C + b + c*d,其中C是常量,bc是寄存器,d是{1,2,4,8}的标量。因此,您可以看到为什么LEA在简单的数学操作中很受欢迎:它在一条指令中做得相当多。(*包括以下prl评论的更正)

这个程序集代码有一些奇怪的特性:只有在应用于某些指令时才严格定义repz前缀,而retq并不是其中之一(尽管处理器的一般行为是忽略它)。请参阅Michael下面的评论和链接,以获得更多信息。使用lea (,rdi,8), edisub eax, edi一起计算arg1 * 7似乎也很奇怪,但一旦prl指出标量d必须是2的恒定幂,就有意义了。

代码语言:javascript
运行
复制
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。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/49344011

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档