几周前,我在阅读有关PICO-8的信息,PICO-8是一款受限限制的幻想游戏机。真正引起我兴趣的是新颖的游戏发行方式,您将其编码为PNG图像。这包括游戏代码,资产以及所有内容。图像可以是您想要的任何图像,游戏的屏幕截图,精美的艺术品或仅仅是文字。要加载它们,您将图像作为输入传递到PICO-8程序并开始播放。
这让我开始思考,如果您可以为Linux上的程序做到这一点,这会很酷吗?没有!我听到你在哭,这是一个愚蠢的主意,但无论如何,这里概述了我今年从事的最愚蠢的工作。
我不确定PICO-8到底在做什么,但据猜测它可能使用隐写术技术“隐藏”了图像原始字节中的数据。有很多资源可以解释隐写术的工作原理,但是其症结很简单,您要隐藏数据的图像由字节组成,图像由像素组成。像素由3个红色,绿色和蓝色(RGB)值组成,表示为3个字节。要隐藏数据(“有效负载”),您实际上是将“有效负载”中的字节与图像中的字节“混合”。
如果您只是用有效负载中的字节替换了封面图像中的每个字节,那么最终图像的某些部分会看起来失真,因为颜色可能与原始图像不匹配。诀窍是要尽可能地微妙,或者隐藏在清晰的视线中。这可以通过以下方式实现:使用最低有效位将有效载荷字节散布在封面图像的字节上,以将其隐藏。换句话说,请对字节值进行细微调整,以使颜色变化不大得足以被人察觉。人眼。
例如,如果您的有效载荷是字母H,以二进制(72)表示为01001000,并且您的图像包含一系列黑色像素
输出的是两位像素,其黑色比以前少了一点,但是您能分辨出区别吗?
好吧,一个受过专门训练的色彩鉴赏家也许可以,但是实际上,这些细微的变化实际上只能由一台机器注意到。检索超级机密H只需从生成的图像中读取8个字节,然后将它们重新组合为1个字节即可。显然,隐藏单个字母很la脚,但这可以扩展到任何您想要的东西,一个超级秘密的句子,《战争与和平》的副本,一个指向您的Soundcloud的链接,一个go编译器,唯一的限制是您的可用字节数封面图片,因为无论您输入什么内容,您都需要至少8倍的图像。
因此,回到整个linux-executables-in-image的东西,那个古老的栗子。好吧,看到可执行文件只是字节,它们可以隐藏在图像中。就像在PICO-8中一样。
在实现这一目标之前,我决定编写自己的隐写术库和工具以支持将数据编码和解码为PNG。是的,那里有许多隐写术库和工具,但是通过构建,我学得更好。
$ stegtool编码\ --cover-image htop-logo.png \ --input-data / usr / bin / htop \ --output-image htop.png $$ echo"超级秘密隐藏消息" | stegtool编码\ --cover-image image.png \-输出图像image-with-hidden-message.png $ stegtool解码--image image-with-hidden-message.png
由于所有内容都是用Rust编写的,因此可以轻松地将其编译为WASM,因此可以在这里随意使用:
无论如何,现在可以将包括可执行文件在内的数据嵌入到映像中,我们如何运行它们?
简单的选择是只运行上面的工具,将数据解码为一个新文件,然后chmod + x然后运行它。可以,但是还不够有趣。我想要的是类似于PICO-8的体验,您传递了一个PNG图像,其余的都由它来处理。
但是,事实证明,您不能只是将任意一组字节加载到内存中并告诉Linux跳转到它。好吧,无论如何都不是直接的,但是您可以使用一些廉价的技巧来捏造它。
阅读此博客文章后,对我来说很明显,您可以创建一个内存文件并将其标记为可执行文件
仅仅获取一块内存,将二进制文件放入其中,然后运行它而不用猴子修补内核,在用户区中重写execve(2)或将库加载到另一个进程中,这不是很酷吗?
此方法使用syscall memfd_create(2)在进程的/ proc / self / fd命名空间下创建一个文件,并使用write加载所需的任何数据。我花了相当长的时间弄乱Rust的libc绑定才能使其正常工作,并且在理解您传递的数据类型时遇到了很多麻烦,这些Rust绑定的文档并没有太大帮助。
不安全{让write_mode = 119; // w //创建可执行的内存文件let fd = syscall(libc :: SYS_memfd_create,& write_mode,1);如果fd ==-1 {return Err(String :: from(" memfd_create failed")); } let file = libc :: fdopen(fd,& write_mode); //将二进制libc :: fwrite(data.as_ptr()的内容写为* mut libc :: c_void,8作为usize,data.len()作为usize,file,); }
调用/ proc / self / fd /< fd>作为创建它的父级的子进程,足以运行您的二进制文件。
接受从隐写术工具中嵌入了二进制文件的图像以及任何参数
不过,每次都必须输入pngrun令人讨厌,因此,我对此毫无意义的last头的最后一个便宜招是使用binfmt_misc,该系统可让您根据文件类型“执行”文件。我认为它主要是为Java等解释器/虚拟机设计的。因此,您不必键入java -jar my-jar.jar,而只需键入./my-jar.jar,它将调用Java进程来运行您的JAR。需要注意的是,您的文件my-jar.jar首先需要标记为可执行文件。
因此,向binfmt_misc添加一个条目以供pngrun尝试运行设置了x标志的所有png文件非常简单
好吧,真的没有人。制作PNG图像运行程序的想法让我着迷,但让它有些失落,但这仍然很有趣。以图像的形式分发程序对我来说很有趣,请记住前面带有艺术品的荒谬纸箱PC软件,为什么不把它带回来! (不要吧)
不过,它确实很傻,并且有很多警告,使其完全没有意义和不切实际,主要是需要计算机上安装愚蠢的pngrun程序。但是我也注意到clang之类的程序有些奇怪的东西。我将其编码为这个有趣的LLVM徽标,当它运行正常时,当您尝试编译某些内容时会失败。
$ ./clang.png --versionclang版本11.0.0(Fedora 11.0.0-2.fc33)目标:x86_64-unknown-linux-gnuThread模型:posixInstalledDir:/ proc / self / fd $ ./clang.png main。错误:无法执行命令:可执行""不存在!
这可能是匿名文件的产物,如果我不愿意调查的话,可以克服它。
很多二进制文件都非常大,并且由于需要将它们放入图像中而受到限制,有时这些二进制文件必须很大,这意味着您最终得到了可笑的大文件。
同样,大多数软件不仅是一个可执行文件,因此对于像游戏这样的更复杂的软件,仅分发PNG的梦想就落空了。
这可能是我一年四季从事的最愚蠢的项目,但它很有趣,我了解了Steganography,memfd_create,binfmt_misc,并与Rust一起玩了更多游戏。