我们的团队最近不得不查看SPARC装配规范,问题是我没有SPARC处理器可以尝试在上面进行操作(我应该设置模拟器,或者在桌上找到这些旧的Sparc工作站之一... )。

以某种方式,ba(始终分支)指令使我感到困惑,因为它是延迟的分支执行指令。这意味着位于ba之后的指令将在跳转发生之前执行。

我的一位同事提出了一个非常有趣的问题,在这种情况下会发生什么?
0x804b38 ba 0x805a10
0x804b3c ba 0x806844
...
0x805a10 add %r3, %r2, %r5
...
0x806844 sub %r3, %r5, %r2
...


根据规格,我们的猜测是奔跑的行为应如下:在另一个块中拾取一条指令并运行到下一条ba ...我想知道CFG是什么样的。跳转(jmp指令类似于ba,但基于给定寄存器中存储的地址): ?或者,是否可以通过一种简单的计算来猜测其作用?

评论

您是否有更多代码,例如完整的二进制文件?我有一个SPARC可以尝试;)

抱歉,我只是根据规格推测...:-/我需要快速获取sparc和完整的构建链。我必须尝试。

@ 0xC0000022L我删除了我的评论,直到最后我才读到问题...

#1 楼

我对整个示例进行了一些编辑,以便更好地匹配该问题。用C中的jumps / goto表示),然后使用gcc -S将其转换为汇编。我有一个运行SPARC的计算机,详细信息: 。一个立即取值(b),另一个取一个寄存器(jmp)。

事实证明,您给的链接对于GCC是正确的: >请注意,最后一条指令在跳转发生之前执行,
不在子例程返回之后执行。跳转后的第一条指令称为延迟槽。通常的做法是使用一种不执行任何任务的特殊操作来填充延迟槽,称为无操作。

我认为我们需要在有和没有调试器的情况下进行此操作,因为目前尚不清楚在调试器下它是否可能表现出不同。所以代码应该输出一些可读的内容,以便我们可以看到我们的修修补补有什么效果;)

C代码大量分支指令可用于解决问题中提出的想法。 />
$ isainfo -v
64-bit sparcv9 applications
        vis2 vis
32-bit sparc applications
        vis2 vis v8plus div32 mul32


我将集中精力修改b的结果,因此,我不再重复所有的汇编代码,而只重复一点点。 br /> btw:GCC为jmp指令创建了额外的缩进,但是当然可以很容易地发现它们。 br />这里是进入修改后的程序的步骤。


使用nop获取gcc -S文件
修改foo()

nop组装

使用gcc -S test.c与GCC链接


对汇编代码的修改

首先,我感到不得不“优化” test.stest.sgas -o test.o test.sgcc -o test test.oLL6LL9中的指令,例如:如果您的同事是对的,我们应该可以用LL5的指令代替LL8的指令,以查看期望值以外的内容。

我将修改后的汇编文件LL11称为,以免混淆自己; )

验证我的“优化”

第一个测试是对我的“仅优化”进行的。我写了一个小测试脚本:
#include <stdio.h>

int foo(int argc)
{
        switch(argc)
        {
        case 0:
        case 1:
                goto a1;
        case 2:
                return 3;
        case 4:
                goto a2;
        case 5:
                return -1;
        default:
                goto a4;
        }
a1:     return 1;
a2:     return 2;
a4:     return 4;
}

int main(int argc, char** argv)
{
        printf("Hello world: %i\n", foo(argc));
        return foo(argc);
}


二进制文件称为LL1(我从上面的“优化”)和nop(由GCC从C代码创建的普通程序集)。

结果:

        .file   "test.c"
        .section        ".text"
        .align 4
        .global foo
        .type   foo, #function
        .proc   04
foo:
        !#PROLOGUE# 0
        save    %sp, -120, %sp
        !#PROLOGUE# 1
        st      %i0, [%fp+68]
        ld      [%fp+68], %g1
        cmp     %g1, 5
        bgu     .LL11
        nop
        ld      [%fp+68], %g1
        sll     %g1, 2, %i5
        sethi   %hi(.LL12), %g1
        or      %g1, %lo(.LL12), %g1
        ld      [%i5+%g1], %g1
        jmp     %g1
         nop
.LL6:
        mov     3, %g1
        st      %g1, [%fp-20]
        b       .LL1
         nop
.LL9:
        mov     -1, %g1
        st      %g1, [%fp-20]
        b       .LL1
         nop
.LL5:
        mov     1, %g1
        st      %g1, [%fp-20]
        b       .LL1
         nop
.LL8:
        mov     2, %g1
        st      %g1, [%fp-20]
        b       .LL1
         nop
.LL11:
        mov     4, %g1
        st      %g1, [%fp-20]
.LL1:
        ld      [%fp-20], %i0
        ret
        restore
        .align 4
        .align 4
.LL12:
        .word   .LL5
        .word   .LL5
        .word   .LL6
        .word   .LL11
        .word   .LL8
        .word   .LL9
        .size   foo, .-foo
        .section        ".rodata"
        .align 8
.LLC0:
        .asciz  "Hello world: %i\n"
        .section        ".text"
        .align 4
        .global main
        .type   main, #function
        .proc   04
main:
        !#PROLOGUE# 0
        save    %sp, -112, %sp
        !#PROLOGUE# 1
        st      %i0, [%fp+68]
        st      %i1, [%fp+72]
        ld      [%fp+68], %o0
        call    foo, 0
         nop
        mov     %o0, %o5
        sethi   %hi(.LLC0), %g1
        or      %g1, %lo(.LLC0), %o0
        mov     %o5, %o1
        call    printf, 0
         nop
        ld      [%fp+68], %o0
        call    foo, 0
         nop
        mov     %o0, %g1
        mov     %g1, %i0
        ret
        restore
        .size   main, .-main
        .ident  "GCC: (GNU) 3.4.3 (csl-sol210-3_4-branch+sol_rpath)"


所以我的“优化”似乎还不错。现在让我们来修改一下。

对修改程序计数器的指令进行修改

声称,超出mov ..., %i0(即modified.s)的任何内容都将在跳转本身之前执行。我们有几个带有跳转的标签,所以让我们用改变optimized内的值和test的返回值的值替换每个jmp

因此,除了返回代码b(变为nop)和%i0(保持不变)之外,所有内容现在都应返回原始值乘以十。

让我们看一下结果(我在foo()循环的项目列表中添加了-1): br />
.LL6:
        mov     3, %i0
        b       .LL1
         nop
.LL9:
        mov     -1, %i0
        b       .LL1
         nop
.LL5:
        mov     1, %i0
        b       .LL1
         nop
.LL8:
        mov     2, %i0
        b       .LL1
         nop
.LL11:
        mov     4, %i0
.LL1:
        ret
        restore


修改测试脚本,输出如下:

#!/usr/bin/env bash
for i in optimized test; do
        echo -n "$i: "; ./$i
        echo -n "$i: "; ./$i a1
        echo -n "$i: "; ./$i a1 a2
        echo -n "$i: "; ./$i a1 a2 a3
done


困惑!

结果

毫无疑问,您可以在逆向工程师的脑海中耍把戏。我学到了一些新东西,仅此一项是值得的。

这里是情况

所有SPARC机器,但肯定是用于测试的机器(顶部规格)。工程师,也许还有反汇编程序(静态分析工具)。这基本上是一个不透明的谓词。即结果在编译时很明显,但是看起来像是动态的。

鉴于我这里只有IDA Pro和42,因此很难看到不同的反汇编程序有多好。我的有根据的猜测是,它们与其他不透明谓词的处理方式相同,即有时它们会被愚弄,有时它们会非常聪明。因此,这是否是一种合适的混淆方法仍未解决。观看此图形视图:

单击此处查看完整大小的图像(先前版本)通过IDA。

$ ./runtest
optimized: Hello world: 1
optimized: Hello world: 3
optimized: Hello world: 4
optimized: Hello world: 2
test: Hello world: 1
test: Hello world: 3
test: Hello world: 4
test: Hello world: 2


因此,根据GDB,我们在分支之前执行4。这似乎向我暗示,即使您链接多个分支指令,执行的第一件事就是链中最后一条之后的内容。

评论


@perror:关于您的编辑(谢谢!),看看这个用户脚本,我已经使用了一段时间了。

– 0xC0000022L♦
2013年4月30日12:00

您可以将其与不透明谓词进行比较,但这只会模糊CFG的结构。我不认为它会在语义层次上破坏分析器(除非您不知道在SPARC中应该执行jmp操作,除非如此)。也许我确实错过了什么?

–恐怖
13年5月2日在13:30