这提供了极大的灵活性和可控性,但也使编译器更难执行最佳选择。它还要求隐写者维护他们的(派生的)编译器。这是个不错的主意,但需要付出相当大的努力。
另一种方法是忽略编译的指令本身,而专注于二进制容器:ELF、MACH-O或PE等。
这些格式提供了许多嵌入信息的机会:段和段的顺序(文件和虚拟地址)、重定位表的顺序和布局以及它们的内部规则的组成,等等。
插装二进制格式而不是可执行代码意味着不必搞乱指令选择,而且在信息密度方面也不那么丰富。它也是不可移植的,例如在ELF重定位条目中隐藏信息的技巧必须针对PE进行调整。
Tiny86没有控制指令选择或调整包含的二进制格式的细节,而是采取了第三种方法:它使用x86和AMD64指令格式中存在的语义对有选择地在编译后重写程序。
与每个ISA一样,x86(和AMD64)有多种方式来编码特定(更高级别的概念性)操作的语义。例如,清除一个寄存器:
;对寄存器本身进行异或运算将清除所有位xor eax,eax;而将寄存器设置为0也会清除所有位和eax,0;我们还可以设置0mov eax,0;...或减去SUB eax,eax;...或加载一个";计算出的地址,该地址始终为0表示eax,[0]。
…。还有很多其他的。不幸的是,这并不完全像我们希望的那样工作:
Add r/m8,R8<;=>;Add R8,r/m8ADD r/m16,R16<;=>;Add R16,r/m16ADD r/m32,R32<;=>;Add R32,r/m32ADD r/m64,R64<;=>;Add R64,r/m64ADC r/m8,R8<;=>;ADC R32,r/m32ADC r/m64,R64和=>;ADC R64,r/m64和r/m8,R8和r8,r/m8和r/m16,r16和r16,r/m16和r/m32,r32和r/m64;和r32,r/m32和r/m64。或r16,r/m16or r/m32,r32<;=>;or r32,r/m32or r/m64,r64<;=>;or r64,r/m64XOR r/m8,r8<;=>;XOR r8,r/m8XOR r/m16,r16<;=>;XOR R16,r/m16XOR r/m32。SUB R8,r/m8SUB r/m16,R16<;=>;sub R16,r/m16SUB r/m32,R32<;=>;sub R32,r/m32SUB r/m64,R64<;=>;subR64,r/m64SBB r/m8,R8<;=>;SBB R8,r/m8SBB r/m16,SBB R64,r/m64MOV r/m8,r8<;=>;MOV r8,r/m8MOV r/m16,r16<;=>;MOV r16,r/m16MOV r/m32,r32<;=>;MOV R32,r/m32MOV r/m64,R64<;=>;MOV R64,r/m64CMP r/。CMP R32,r/m32CMP r/m64,r64<;=>;CMP R64,r/m64。
每个对偶代表一位信息:我们可以任意指定一种形式为真,而另一种形式为假。如果目标程序中有足够多的表单,我们可以通过在表单之间转换来隐藏消息。结果是:一个与原始文件大小完全相同、具有相同性能特征的二进制文件,但带有隐藏的信息。
这就是steg86所做的一切:它在其目标程序中定位每个语义对,将输入消息映射到一个位串,并根据其对应的消息位来翻译每个对。因为它只使用DUAL,所以它与其包含的格式完全无关:steg86CLI目前支持ELF、Mach-O和PE/PE32+二进制文件。
下面是它的实际外观,其中(一些)不同的字节以红色突出显示:
$steg86profile/bin/bash.bin/bash的摘要:总共175828条指令,27957个潜在语义对,27925比特的信息容量(3,490字节,约。3KB)$steg86 Embed/bin/bash test.steg<;<;";hello world!";$file test.stegtest.steg:ELF 64位LSB共享对象,x86-64,版本1(SYSV),动态链接,解释器/lib64/ld-linux-x86-64.so.2,BuildID[sha1]=a6cb40078351e05121d46da.。
就像x86的每个部分一样,它实际上要比这复杂得多。但就我们的目的而言,它本质上是一个8位操作数选择器。“↩