不依赖OS编译器,不依赖库,用汇编/机器码直接编程

万般含义在皮鞋/p>


本周末,写个小文,就当开胃小食吧,和TCP/IP无关,也没有皮鞋。

Linix系统中可执行文件是ELF格式,Windows系统可执行文件是PE格式,ELF和PE是完全不同的文件格式互不兼容,所以一个可执行文件不能在两个系统之间互相交换执行,然而两个程序均跑在Intel/AMD的处理器上,执行的都是一模一样的指令…

如果我在文件里就写一个0x90机器码,那么很遗憾,两个系统均无法执行!

这就是TMD门槛,将事情变成不再纯粹的门槛!


现在编程已经没有以前好玩了,现在的编程语言太丰富,现在的编程语言太繁琐,现在的编程环境太复杂,而这一切,都是旨在 最大程度的隐藏底层细节,让程序员将精力集中在关注业务逻辑上!

上面这段话讲得非常矛盾! 看你如何理解。

如果你把编程当成谋生之道,那么当然需要 集中精力于经理分配的任务,而不是纠结什么语言细节,底层原理。 反之,如果你只是觉得好玩,希望探究底层的秘密,那么 肯定不希望去处理什么业务逻辑,业务是需求驱动的,我只是玩玩而已! 然而现有的编程环境已经不适合去探究系统的秘密了。

你想让一段哪怕再简单的指令跑起来,额外的工作量已经超过了写这段指令本身,你要有编译器,要遵守该编程语言的语法规范,换句话说,你要花精力去搭建环境,以及调试程序本身。

现在的编程环境已经全部为业务程序员而优化了!和业务逻辑越来越关联的语言,库,中间件,网络协议…即便是一个写Apache,Nginx模块的 底层C/C++程序员 ,也至少要依赖操作系统,glibc这些,至少,你要 首先编译出一个Linux下可运行的ELF文件 吧。

举个例子,你能在Linux平台写一个非ELF的直接可执行的二进制文件吗最简单的,我们知道,0x90这个机器码代表 nop ,即 什么都不做 ,我能将0x90这个指令写入一个如下文件nop.exe:

然后,我执行nop.exe,然后 让CPU什么也不做 吗/p>

不能!!看看吧,一个程序员想 让CPU什么也不做 都不行!而且这个程序员写的还是超级牛逼的机器语言0x90指令!如此牛逼的底层技术,竟然跑不起来!

好吧!很惭愧!但是想让nop跑起来,系统程序员还是有办法的,写汇编呗。

然后 按照标准流程 编译之:

然后,看看编译好的nop.exe之细节:

一个简单的nop指令构成的可执行文件,竟然656字节!!

实际上起作用的只有1个字节,额外的655字节在我看来全是垃圾!有点极端,但是按照程序执行的视角来看,只要不涉及程序分发和交换,ELF这种标准化的约束就没有什么用!

我们看下纯粹的nop.exe应该是个什么样子:

看清楚了吗只有1个字节!!

遗憾的是,它跑不起来:

本文的目标,就是让这类纯粹的指令构成的可执行文件,跑起来!

换句话说, 达到一个目标,我们可以用十六进制编辑器徒手往一个文件里写指令,该文件里全部都是指令,然后载入该文件,从文件头第一条指令开始执行该文件。


说实话,本文由一篇知乎而起。早上坐班车上班,路上要经过暗黑的紫之隧道,便不能看书了,只能一半刷手机一半睡觉。

万事因知乎而起,知乎却往往解决不了问题:
C 如何编译出一个不需要操作系统的程序/strong> https://www.zhihu.com/question/49580321

在看到这个问题之前,正好纠结过另一个问题: 打造最小的ELF文件

关于这个话题,有篇文章非常好:
打造史上最小可执行 ELF 文件(45 字节,可打印字符串): https://www.kancloud.cn/kancloud/cbook/69003

我曾经是想试着破45字节记录的,然而考虑到 既然想要最小,为什么需要ELF头呢什么不直接只包含指令呢/strong>

马其顿的亚历山大曾经在面对 格尔迪奥斯绳结 时,选择了砍断绳子,绕过了问题,成为了亚洲的统治者。

这是正确的选择。本文中,我将展示这种砍断绳结的方法。

本文从 “打造最小ELF文件” 系列文章的相反的另一个方向,即从简单到复杂的方向,介绍如何直接用汇编指令或者机器码编程。让我们从最小的仅仅1个字节的程序开始,我敢打赌, 没有谁比我的这个nop程序更小了!


以纯粹直接的nop指令为例,还是那个代码:

我们把它编译成:

当然你也可以不写汇编,毕竟汇编也是一种 稍微高级点的语言 ,你仍然需要nasm,ld这类工具将其 编译 成机器码,你可以找一个16进制编辑器直接往文件里面写机器码,不经过编译的过程,或者说你根本就没有编译器!是的,完全可以!

如何能让这么直接的程序跑起来/p>

我不会选择使用裸机去跑,也不想使用单片机这种简易的玩意儿,因为这些设备上无法部署可用的分析调试工具,我选择在标准的Linux系统上去跑这个指令文件。

虽然我不怎么会编程,但是能用软件解决的问题就不用专用硬件设备,我不是说过吗,软件可以无限试错!

为了让这个指令文件像一个普通的二进制ELF可执行文件那样直接在命令行执行,我们其实要做的非常简单。

现代操作系统(拥有独立的进程虚拟地址空间)Linux 中,我们需要做的就是三件事:

  • 开辟一个新的进程P;
  • 将这个文件的内容载入到该新进程P的地址空间某个地址A;
  • 将该进程P的IP寄存器指向地址A。

为了让上面的三件事变成事实,我们需要了解Linux的程序加载机制。我们需要先了解ELF文件是如何载入内存并跑起来的,然后删掉一些复杂的约束,就成了。

换句话说,我们只需要照着ELF的加载程序框架比葫芦画瓢整一个更简单的加载程序就好了。幸运的是,这种加载程序在Linux内核是模块化的,我们只需要注册一个新的加载程序结构体,然后实现一些回调函数即可。

我就直接给出代码吧:

就这么简单一个加载程序就完成了,将其编译成模块,载入内核:

OK!接下来可以写只包含指令的文件了。我们以刚刚制作好的 只有1个字节的nop.exe 为例,试试它直接执行的效果:

出错了!但是却是正确的!程序确实是执行了的,我们看看为什么出错:

来源:dog250

声明:本站部分文章及图片转载于互联网,内容版权归原作者所有,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2019年3月22日
下一篇 2019年3月23日

相关推荐