以某种方式,
ba
(始终分支)指令使我感到困惑,因为它是延迟的分支执行指令。这意味着位于ba
之后的指令将在跳转发生之前执行。我的一位同事提出了一个非常有趣的问题,在这种情况下会发生什么?
0x804b38 ba 0x805a10
0x804b3c ba 0x806844
...
0x805a10 add %r3, %r2, %r5
...
0x806844 sub %r3, %r5, %r2
...
根据规格,我们的猜测是奔跑的行为应如下:在另一个块中拾取一条指令并运行到下一条
ba
...我想知道CFG是什么样的。跳转(jmp
指令类似于ba
,但基于给定寄存器中存储的地址): ?或者,是否可以通过一种简单的计算来猜测其作用?#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.s
,test.s
,gas -o test.o test.s
,gcc -o test test.o
,LL6
和LL9
中的指令,例如:如果您的同事是对的,我们应该可以用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
评论
您是否有更多代码,例如完整的二进制文件?我有一个SPARC可以尝试;)抱歉,我只是根据规格推测...:-/我需要快速获取sparc和完整的构建链。我必须尝试。
@ 0xC0000022L我删除了我的评论,直到最后我才读到问题...