开头,如果想知道编译最小的ELF文件,参考http://web.archive.org/web/20111205062215/http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html  这篇文章就可以了。

最小的PE可执行文件我参考的是:http://web.archive.org/web/20111214171623/http://www.phreedom.org/solar/code/tinype/ 这篇文章。

  • 最小 PE file:97 bytes
  • 最小 PE 文件在 Windows 2000:133 bytes
  • 可以通过WebDAV下载文件并且可以执行的最小的 PE 文件  :133 bytes

编写一个很简单的C语言文件

使用

cl /nologo /c /tiny.c
link /nologo tiny.obj

进行编译。可以看到文件大小为70多KB

其中有许多部分是C运行库,如果我们link的时候使用/NODEFAULTLIB选项,将会得到更小的一个exe文件。我们将会将控制台从程序中移除通过设定子系统为窗口程序。

cl /nologo /c /O1 tiny.c
link /nologo /ENTRY:main /NODEFAULTLIB /SUBSYSTEM:WINDOWS tiny.obj

编译选项O1为优化代码的数量,

现在代码的大小为1.5KB,我们通过IDA将tiny.exe进行反编译,可以得到代码

我们可以看到,我们.txt段和data段的对齐是0x1000,官方的PE文件说明最小的对齐是512,但是Microsoft的linker可以将对齐做到16,我们可以使用下面的语句重新编译我们的程序

cl /c /O1 tiny.c
link /nologo /ENTRY:main /NODEFAULTLIB /SUBSYSTEM:WINDOWS /ALIGN:1 tiny.obj

之后我们可以看到我们文件的大小变为608个字节。

如果我们想更进一步的压缩文件的大小,我们需要能够编辑PE文件的头部,我们将会反汇编到汇编代码通过NASM,NASM的下载地址为https://www.nasm.us/pub/nasm/releasebuilds/

这是一个汇编和反汇编器,我们平时写的C文件,会经过预处理,编译阶段编译成汇编指令,再经过汇编阶段变成二进制文件,之后再链接阶段,形成可运行文件,NASM只是将asm文件经过汇编阶段变成PE或ELF或COFF等二进制文件,并没有进行链接,同时使用NASM时,asm文件的汇编指令要符合NASM汇编语言的语法规则,不然NASM.exe在运行时会出现error:instruction expected等错误,所以需要我们自己编写符合NASM汇编语言规范的汇编程序,由于没有那么多的时间,尝试使用IDA逆向生成asm文件,或者使用gcc、cl等工具生成asm文件,放入NASM的输出,均以失败告终。所以下面的内容没有经过实验,都是纯粹的翻译。

剩下的工作就是对PE文件的头部进行删除工作,PE文件头部仍然需要MZ头,但是只有e_magic and e_lfanew这两个部分会被使用,我们可以将未使用的部分用零填充,同时有许多未使用的PE头的域没有被使用,我们可以在程序未被破坏的前提下修改这些部分,未使用区域用红色标注了。

上图只是列举了一部分例子,全部图片可以到原文去观看。

作者为了验证哪一个域可以修改,作者使用了基于Ruby写的一个asm fuzzer的工具,该工具就是将PE header文件中的每一个域都用一个随机值替换一下,如果替换后的文件还可以使用就代表这个域没有被使用,现在汇编后PE文件的大小为356 bytes。

MZ头部e_lfanew域为从文件开始到PE头的偏移地址,通常PE头是在MZ头部和DOS 桩(stub)的后面,但是如果我们将e_lfanew设置成一个小于0x40的值,PE header将会指定在MZ header中,这样允许我们可以合并一些PE header和MZ header的一些信息从而产生更加小的文件。

PE header文件的offset不能是0,也就是说e_lfanew不能为0,因为我们需要PE文件的前两个byte为‘MZ’,根据PE的说明文档,PE header部分不许是8byte对齐的,但是Windows 的加载器只需要4 byte对齐,这就意味着e_lfanew这个字段的最小值为4。

如果PE header 头部的offset为4,我们将会覆盖那些没有使用的MZ header的域。在PE header中,我们同样需要注意e_lfanew域,这个域是段对齐的offset,因为e_lfanew 必须是4,所以我们就设置段对齐为4。PE说明书说如果一个段的对齐小于页的大小(内存的一个页),我们就必须设定段对齐和文件对齐是一样的,也就是说文件对齐也要设置为4。在这个PE文件中,data段已经设置为4 byte对齐,所以改变这个文件的对齐从1到4并不会增加文件的大小(因为我们是要得到最小的PE文件,所以文件对齐也很重要)。

将MZ header 压缩了之后,文件大小变为296 bytes。

删除data directories

在PE文件中的IMAGE_OPTIONAL_HEADER中最后有一个长度为16的指针数组,就是data directories,每一个数组都指向一个数据结构,包括导入导出表,debug的信息表,relocation表,OS说明数据等信息。我们的PE文件没有用到任何的这些特征,所以data directories为0,如果我们将data directories删除掉,我们将会获得许多的空间。

PE文件的说明书中说到,data directories的数量在IMAGE_OPTIONAL_HEADER中的NumberOfRvaAndSizes中可以设定,并且PE文件的optional header是可变的,如果我们将NumberOfRvaAndSizes域设置为0并且减少SizeOfOptionalHeader域,我们可以将data directories从我们编译的文件中删除。

dd 0                                      ; NumberOfRvaAndSizes

大多数读取data directories的函数会检查NumberOfRvaAndSizes是否足够大以避免访问非法内存(不懂这个地方,大佬懂得可以留言,谢谢)。但是Debug directory在windows XP中是个例外。如果Debug directory 没有设置为0,不管NumberOfRvaAndSizes,loader都将在ntdll!LdrpCheckForSecuROMImage访问冲突中崩溃。我们需要确保从optional header开始偏移0x94的那个位置上的dword始终为0。在我们的PE文件中,这个地址位于内存映射文件之外,并且由操作系统将其归零(这句话也不懂,也希望大佬解答)。

这是PE文件的大小只有168bytes,一个很客观的进步。

减少PE section header

windows的加载器希望在optional header 之后为 PE section header,PE section header是一个数组,每一个section在这个数组中有一个结构体,加载器会通过optional header的开始地址加上SizeOfOptionalHeader的数从而得到第一个section header的地址。然而,读取optional header大小的代码从来不会验证这个大小的数值。所以我们可以将SizeOfOptionalHeader的这个数值设置成比真实值小的一个数值,然后将我们的PE section移动到optional header中没有使用的空间中,下面显示具体的code信息:

 这种PE文件头部会导致dumpbin(一个文件名称,作者创作了一个名为dumpbin的最小PE文件)崩溃,但是在weinDbg !dh命令中仍然能够正确的解析header,这时的PE文件大小为128bytes。

最小的PE文件

下一步我们可以我们移动4bytes的代码到header中未使用的地方去,例如TimeDateStamp域。这导致我们PE文件的结束就是optional header的结束,因为我们的section header 也已经放到其他地方去了。看起来我们已经没有什么空间可以减少了,因为PE header开始在了最小的偏移中,并且有一个固定的大小。然后是PE optinal header,开始地址也是最小的偏移了。所有其他的信息都包含在了这两个header中。

然而我们还有一个事情可以做,就是PE文件是映射在4KB页大小的内存页中,因为这个PE文件是小于4KB的,这个内存页的剩余都被置为0,如果我们将PE optional header中末尾的几个域删除,但是加载到内存之后会被映射到一个包含很多0的readonly内存页中。由于optional header  的末尾几个域设置为0也是有效的,事实上是7个域,所以我们可以将这些域删除掉,并且当加载到内存之后,操作系统会将我们设置为0,这样又节省了26bytes的空间。

optional header在删除了7个域的字段之后,现在的最后一个字段名称为Sybsystem域,这个域是WORD的大小,而且必须是2,但是intel是小端模式,也是就是说,高位在高地址,比方说这个WORD在内存中是02 00,这个就是代表的2,因为高位在高地址,0002=2,我们可以只存一个byte的信息,并且又节省了一个byte的空间的大小。

由此这个PE文件的全部源代码就在下面了:

这样我们PE文件真的到达最小了。文件偏移为0x94的地方为optional header中的Subsystem域,并且这个域必须为2。我们不能删除这个域或者绕过这个域。这就是最小的PE文件。

这个PE文件大小是97bytes。

拥有import的最小PE文件

不幸的是,97bytes的PE文件不能再windows2000上面运行,这是因为loader想要从KERNEL32去调用一个函数,但是KERNEL32.dll还没有loader进来。其他版本的windows会自动load KERNEL32.dll,但是windows2000 我们必须确保KERNEL32.dll在执行文件的导入表中,运行一个PE文件但是没有import是不可能的。

所以要添加import table

导入表的结构是很复杂的,但是我们从KERNEL32中导入单个序列(不理解可以看导入表ordinal--序列)是很简单的。我们需要将我们要导入的dll的名称放在Name 域中并且创建两个相同的IMAGE_THUNK_DATA结构体数组,一个是IMPORT Lookup Table(作者写的是Lookup Table,但是我认为是INT)另外一个是IAT。当loader解析这个import时,loader会从先查看INT,找到函数名称,函数名称的前一个WORD是序号,根据序号(ordinal)再到IAT中找到相应的函数地址,loader在加载IAT时会根据KERNEL32的导出表加上加载基址之后的值重新写在IAT表中。

只拥有一个序号(ordinal)的导入表,这是PE文件的大小增加到209bytes。

折叠导入表

209bytes显然太大了对于一个只引入一个函数的PE文件来说,所有我们要使得这个文件更小。第一件事情就是要删除Import Lookup Table。这个表只是IAT的一个copy,并且linker并没有用到这个结构。所以删除Import Lookup Table可以节省8bytes。

导入表有40个bytes的大小,但是只有3个域我们会使用到,这允许我们将import table移动到 PE optional header中去。

这时PE文件只有161bytes。

折叠IAT表和DLL的名称

还有两个结构体还在PE header的外面,只IAT和import 的DLL的名称。我们可以折叠IAT到一个8-byte大小的没有用过的域在PE section header中。DLL name 可以存放在 PE optional header的末尾没有使用的域和8 bytes的导出数据directory中。这对一个15个字符加1个terminator而言是足够的。

data directory 的最后一个域是导入表的大小,但是这个域loader并没有用到所以可以置为0。导入表的最后3个bytes也是0,因为指针是小端模式存放的,我们可以删除这些0bytes的数据,等着内存给我们设置。就像我们上面97bytes文件那样。

全源代码PE文件如下:

最终文件的大小为133bytes。

最小的能从网上下载文件的PE文件

最小PE文件目标是写一个能从网上下载文件并且能运行这个从网上下载下来的文件的PE文件。标准技术就是使用URLDownloadToFileA函数之后WinExec运行这个文件。有许多使用这个API的shellcode,但是都加载了URLMON.dll这个库并且调用了许多函数,那将会大大增大PE文件的大小。

Windows Xp 有个很少有人知道的特性就是WebDAV Mini-Redirector(我也不知道是什么),这个特性将许多Windows applications 使用的UNC path翻译成URL,并且尝试使用WebDAV协议去访问他们。这就意味着我们可以传一个UNC path给WinExec并且the redirector 回尝试去下载这个文件通过WebDAV在80端口。

我们可以将这个UNC path放在PE文件的import section中,如果我们设定 \\66.93.68.6\z  作为导入的DLL的名称。windows loader将会尝试去下载这个DLL文件通过web 服务器。

这就允许我们去创建一个PE文件,这个PE文件下载并且执行这个文件但是没有一行代码在里面。我们所要做的就是将我们的payload放在DLL中的DllMain函数中,将DLL放在可公开访问的WebDAV服务器上,并在PE文件的导入部分指定文件的UNC路径。loader处理PE文件的导入时,将从WebDAV服务器加载DLL并执行其DllMain函数。

这是PE文件仍为133bytes。

warning:这个PE文件尝试从http://66.93.68.6/z 下载一个payload DLL。这个DLL将会弹出一个message box 并且推出。但是你应该做好恰当的预警并且信任它。

tiny.asm 地址:http://web.archive.org/web/20111214171623/http://www.phreedom.org/solar/code/tinype/tiny.webdav.133/tiny.asm

tiny.exe地址:http://web.archive.org/web/20111214171623/http://www.phreedom.org/solar/code/tinype/tiny.webdav.133/tiny.exe

Makefile地址:http://web.archive.org/web/20111214171623/http://www.phreedom.org/solar/code/tinype/tiny.webdav.133/Makefile

由于上面这个asm文件是我们自己构造的,所以没有.c文件,直接就是asm文件。

将Apache或IIS设置为WebDAV服务器并不复杂,但是出于开发目的,您可以使用以下Ruby脚本。它将作为最小的WebDAV服务器与刚刚足够的功能,攻击工作:

webdav.rb地址:

http://web.archive.org/web/20111214171623/http://www.phreedom.org/solar/code/tinype/webdav.rb

payload DLL 和 源代码:

payload.c地址:http://web.archive.org/web/20111214171623/http://www.phreedom.org/solar/code/tinype/payload.dll/payload.c

payload.dll地址:http://web.archive.org/web/20111214171623/http://www.phreedom.org/solar/code/tinype/payload.dll/payload.dll

test.c地址:http://web.archive.org/web/20111214171623/http://www.phreedom.org/solar/code/tinype/payload.dll/test.c

test.exe地址:http://web.archive.org/web/20111214171623/http://www.phreedom.org/solar/code/tinype/payload.dll/test.exe

Makefile地址:http://web.archive.org/web/20111214171623/http://www.phreedom.org/solar/code/tinype/payload.dll/Makefile

关键信息:

扫描133字节的PE文件,下载一个DLL通过WebDAV与常见的杀毒软件显示,检测率很低。反病毒软件供应商的建议是使用UNC import 作为发现恶意软件的启发。(言外之意,可以用UNC import下载shellcode并运行。。。)。

下面这张图片是作者用了当时的virusDetector扫面tiny.exe得到的结果。

将.C文件编译成最小的PE可执行文件相关推荐

  1. java虚拟机编译文件,理解Java虚拟机(1)之一个.java文件编译成.class文件发生了什么...

    理解Java虚拟机(1)之一个.java文件编译成.class文件发生了什么 最近在看<深入理解Java虚拟机>弄明白了很多java的底层知识,决定分几部分总结下,从.java文件编译,到 ...

  2. C#.NET如何将cs文件编译成dll文件 exe文件 如何调用dll文件

    比如我要把TestDLL.cs文件编译成dll文件,则在命令提示符下,输入下面的命令,生成的文件为TestDLL.dll csc /target:library TestDLL.cs 注意前提是你安装 ...

  3. 把CS文件编译成dll文件

    编译方法如下: 一.打开VS.NET的C命令提示 二.进入目录 三.输入如下命令: csc /t:library /out:F:\MD5.dll  F:\MD5.cs(常用) 如图: 如何做才能够把c ...

  4. printm matlab,求助:将matlab M文件编译成DLL时出现的问题!

    本人用的matlab2007a和vc6,想利用编译器将M文件编译成COM组件供C#调用,可是编译时却出现一下问题,麻烦高手帮忙解决,不胜感激! 产生的结果如下: Build output( 2009- ...

  5. 多个.c文件编译成.ko文件

    以两个C文件为例: 将本该被分别编译成adc_device.ko和adc_driver.ko的adc_device.c.adc_driver.c编译成一个ko文件! 采用方法: 第一步.修改C文件 1 ...

  6. 什么是pyc文件,把python的py文件编译成pyc文件,把pyc文件反编译成py文件。以及python编译的如何设置不生成pyc文件

    文章目录 1 什么是pyc文件 1.1 什么是pyc文件 1.2 pyc文件是怎么生成的,有什么好处 2 把python的py文件编译成pyc文件 2.1 使用python内置库py_compile把 ...

  7. aspx文件编译成DLL文件的原理

    前言 Asp.net不是asp的简单升级,而是微软.Net计划中的一个重要组成部分,它依托.Net的多语言与强大的类库支持,引进了服务端HTML控件与WEB控件,自动处理控件的客户端与服务端的 交互, ...

  8. 【转载】把aspx文件编译成DLL文件-.NET教程,Asp.Net开发

    前言 asp.net不是asp的简单升级,而是微软.net计划中的一个重要组成部分,它依托.net的多语言与强大的类库支持,引进了服务端html控件与web控件,自动处理控件的客户端与服务端的 交互, ...

  9. 将pyx文件编译成pyd文件(很多坑,已解决)

    项目场景: Faster R-CNN项目,将pyx文件编译成pyd文件(很多坑,请注意) 项目环境 python 3.6 – conda的虚拟环境 TensorFlow 1.15.0 win 10 问 ...

最新文章

  1. Qt最新版5.12在Windows环境静态编译安装和部署的完整过程(VS2017)
  2. 如何查看别人(自己)电脑最近的浏览记录
  3. java抽象类和接口总结
  4. kvmweb管理工具_KVM web管理工具——WebVirtMgr
  5. 使用sphinx快速为你python注释生成API文档
  6. 边缘检测中非极大值抑制简单解释
  7. java怎么让遮罩层下面滚动_vue项目弹出层后禁止body底层的滚动事件
  8. 组件加name属性_从零开始学习React-属性绑定(三)
  9. 条件查询时,如果有的输入框不输入,后台应该怎么处理?
  10. 《大数据基础教程、实验和案例教程---林子雨版》分布式模式的HBase配置
  11. TeamTalk源码分析(一)—— TeamTalk介绍
  12. 广义线性模型(GLMs)及算法介绍
  13. 数学机器证明与机器验证
  14. OSError: [WinError 193] %1 不是有效的 Win32 应用程序(完整的解决方案)
  15. 重装系统感悟之设置系统还原点
  16. unity shader - 毛发渲染,飘逸的毛发
  17. word文档doc格式转换成docx
  18. GetLastError 函数错误信息 代码大全
  19. 这样去做信用贷款违约预测项目,效果提升明显
  20. [bzoj5473] 仙人掌

热门文章

  1. vue-cli3以后,关于webpack打包等的相关配置
  2. 输入一个url到浏览器页面展示都经历了哪些过程
  3. husky的仿真建图和导航
  4. STC89C52开发学习(一)
  5. CSS常考面试题资料
  6. 轻轨列车 light rail train
  7. NOR FLASH (mx25u12835f) lock/unlock功能及linux对其支持
  8. 配置FCKeditor出现严重问题,调试时报错:
  9. [BZOJ1296][SCOI2009]粉刷匠
  10. js监听按钮按下弹起