polyglot装配 - 在多个体系结构上运行的汇编代码

2021-05-05 22:30:15

几年后,我在Oracle担任Solaris工程师时,我将4月愚人的笑话发表给我们的一个内部邮寄列表。你还记得太阳能吗? Pepperidge Farm Remembers.unfortunatelly Solaris 12从未释放过(无论如何),并且许多开发者在短时间内被解雇,但是一个不同的故事......四月傻瓜电子邮件如下阅读:

大家好,我可以获得我的新的Hello World计划吗?该程序的目的是输出一个问候语。代码非常短,我在此电子邮件结束时inlining它。代码已在SPARC和X86上进行测试。支持64位拱门,请使用CC -M64.CStyle编译没有问题。谢谢!vojtech $ cat hello.const float main [] = {-5.0076923e-29,-6.02841476e -21,1.75860328E-33,-4.3652462E-34,-2.03652633A-33,3.00046579E-32,-6.99961556E-33,-4.36343334,-253599.734,1.87611745E-33,-4.36724253E-34 ,-2.03652633E-33,2.62426763C-32,-4.3634373E-34,-253599.859,-1.0586372E-37,-2.84236475E-29,-4.28054830-29,-7.27646377E-27,-3.28364893E-28-28 ,-7.3422524E-38,-8.52233404E-38,-7.19531561E-38,-2.8423645E-29,-6.028422O-21,2.3509887E-38,-7.3422524E-38,-8.52233404E-38,-6.0284222 E-21,2.3509887E-38,-7.3422524E-38,-8.52233404E-38,1.69276854E-42,1.58918456E-40,-7.11823707E-31,3.30286048E-42,1.26058568E-39,6.72382769E- 36,5.90304592e + 22,2.02799612E-19,1.17234334E + 27,9.30333261E-40,1.7976867E-38,0.0};

我期待主要()不是一个函数,而是是一个数字' t成为许多人的新闻,这已经完成了。(如果你从未见过这个,请看看那个博客,它' s很好!)

Solaris Dev的任何代码都观察到Solaris针对的两个体系结构都要严格测试核心存储库,而且这段代码没有真正看起来它可以在多个架构上运行吗? ......但它确实如此。它与一把伎俩叫一个诡计,这是一个傻瓜,那个'有点过于表现出愚蠢的笑话,但我没有知道还有什么打电话给它......

我最近决定恢复这个想法,并且由于Solaris和Sparcare都非常模糊地掩盖,以在X86-64和ARM上搬到Linux的代码。

#include< stdint.h>常量uint64_t中_start [] __attribute__((部分("的.text")))= {0xe3a00001909032eb,0xe3a0200ce28f1014,0xef000000e3a07004,0xe3a07001e3a00000,0x6c6c6548ef000000,0x0a214d5241202c6f,0x18ec834800000000,0x4800000045058b48,0xa20fc03148240489,0x0c24548908245c89,0x142444c710244c89,0x01c0c74800000a21,0x0001c7c748000000, 0x480124748D480000,0X050F00000015C2C7,0X480000003CC0C748,0X480000003CC0C748,0X6C654820050FFF31,0X00000000202C6F6C,};

为了构建它,我推荐GCC -Static -Nostdlib.i发现了特殊的部分属性对于GCC将代码放在右侧部分所必需的属性。

请记住,由此产生的二进制文件仍然是特定于平台的。PolyGlot诀窍'■S的目的是可以从相同的源文件中构建x86-64和臂。当ELF文件中的可执行代码是平台的比特相同,ELF报头和部分布局有点不同,不幸的是,似乎没有任何方法(至少,内核检查ELF架构字节并赢得'除非它加载程序火柴)。

基本思想非常简单:代码以魔法赛替换方法从两个架构解释,基本上是基于哪个架构正在运行代码的架构。

架构#1将该位作为一个有效的No-Op指令(在我的示例中,它实际上是忽略其输出的Alu指令),并且简单地继续到图中标记为蓝色的区域。 arch-#1特定的代码存储在那里。

架构#2将初始部分解码为跳转,然后汇款跳转到拱形#2代码的黄色区域。

事实证明,X86实际上是拱门#2的一个相当不错的选择,因为它有短跳跃指令,只有2个字节。与2个nops一起(每一个字节),这构成了一个常规(非拇指)臂指令的长度。

三个x86指令,一个JMP和两个NOP,可以任意重新排序,也可以调整跳跃距离,并且当我发现这是那里的自由度找到有效的NOP样ARM指令。

原始的Solaris代码是基本上只是kokus-pokus.in我试图提出一种稍微更自动化的方式找到右组合的方式。所有NOP / NOP / 2字节JMPPERMUTATION的明文列表以及其ARM表示。

从列表中,它表​​明JMP,NOP,NOP排序大部分时间都会产生安全性ADDSLS指令,至少只要跳跃距离足够小,因为ADDSL不触摸SP或PC寄存器。 。

在我的情况下,我只需要提前的JMP 52字节。这导致Addsls R3,R0,R11,ROR#5在ARM上,这是一个完全无害的指令。也许有点太复杂(条件添加有点旋转),但无害。

总之,使X86 CPU跳过的神奇字节和ANAN CPU继续是:

上面引用的C文件有效地包含以下两个代码路径,分别用于X86-64和ARM:

x86:#在堆栈亚$ 24上创建Hello消息,%rsp movq msg(%rip),%rax mov%rax,(%rsp)xor%rax,%rax cpuid mov%ebx,8(%rsp)mov %EDX,12(%RSP)MOV%ECX,16(%RSP)MOVL $ 0x0a21,20(%rsp)#写字器mov $ 1,%rax mov $ 1,%rdi lea 1(%rsp),%RSI mov $ 21,%rdx syscall#exit syscall mov $ 60,%rax xor%rdi,%rdi syscall msg:.ascii"你好," 。 int 0x0.

ARM:#写SYSCALL MOV%R0,$ 1 ADR%R1,MSG MOV%R2,$ LEN MOV%R7,$ 4 SWI $ 0#EXIT SYSCALL MOV%R0,$ 0 MOV%R7,$ 1 SWI $ 0 MSG :.ascii"你好,手臂!\ n" 。 equ len,。 - msg。 int 0x0.

我决定自动化生成C文件和X86-64和ARM的二进制文件的整个过程。' SA大Makefile,通过源ARM ASM文件首次生成ARM代码的艰巨过程,转储已编译ARM,将其与X86 +编译的ARM代码的模板一起送到Python脚本,编译,生成C源,最后为X86和ARM编译C源。

运行makefile需要使用arw编译gcc以及匹配的gdb安装。 至少据我所知。较早说明,由此产生的二进制文件仍然是特定于平台的,这意味着技巧可以' t用于制作多平台elfs: - | 理论上它可用于创建一个多平台shellcode,但我发现它不太可能在实践中实际上很有用。 多平台内核可能? 但是,再次启动程序差异相当多的艺术架,所以再次' S可能不是很实际...... 所以是的,它真的只是一个笑话的笑话,也许会学到新的东西......