恶意软件的艺术
使死者复活
我想将这篇文章(或系列文章)献给Mark Ludwig 11 ,他是2011年去世的《计算机病毒巨型黑书30》 的作者。您在2013年引起了我对病毒的最初兴趣,当时我当时只有15岁,尽管那时候我几乎听不懂您的书,但我想在当今时代有所作为。您将病毒视为一种艺术和自我表达,那时那条信息在我无法形容的水平上引起了我的共鸣,直到今天,我仍然受它影响。

谢谢。

免责声明:

我不容忍出于恶意目的而开发恶意软件,从本文中获得的信息仅用于教育目的。请保持头脑清醒,健康意识和责任心行事。

以开发恶意软件为例
今天的恶意软件被自动描述为一件坏事。是的,它在某种程度上被称为恶意软件,但是它可以被视为一种自我表达或艺术形式。有人会问:“他妈的什么?” 但是在您大声疾呼为何制造恶意软件之前,我的动机是要窃取并造成破坏,并且没有任何好处,让我为您设置不同的观点。通常,恶意软件需要创造力,并且需要规避和绕过防御层或操纵其运行的系统。恶意软件的作者必须以一种聪明而富有创造力的方式来实现自己的想法。是的,确实确实很多次都做得很糟糕,有时作者会盲目复制粘贴其他恶意软件作者或研究人员的代码,但通常在进行一些创新后,结果通常会很有趣。

让我们TDL3 13 /4,导致在2010 - 2013年的伤害回显著量一个rootkit。在内核的最低级别实现挂钩以避免检测,创建其自己的虚拟文件系统并构建实际病毒作为攻击的第一阶段。它大规模地感染了计算机,以至于Microsoft逐字逐句地提出了针对性的缓解措施,开发了专门针对它的工具,因为大多数反病毒软件直到开发的后期才轻易识别它,甚至造成了大量计算机Windows更新发布时崩溃,因为它修改了系统。作为缓解措施,Microsoft根本没有在受感染的系统上安装更新。太疯狂了!

当然,作者的主要目标是赚钱,但他们也很富有创造力,并且对Windows Internals有深入的了解,以至于他们能够以前所未有的方式将系统用于自身。即使微软发布了新的政策,即所有Windows驱动程序都必须由微软进行数字签名才能在内核中执行,但作者还是推出了针对64位系统的TDL4,后者开始利用bootkit策略感染计算机。媒体称其为“坚不可摧” 7 。恶意软件希望包含TDL,而Rootkit作者希望成为TDL。当作者被捕25时,乐趣结束了 。

如果您阅读了TDL的分析报告,就无法停止喘息,为什么?因为它只是在设计时考虑了这种创造力,所以您不禁会欣赏它。

让我们以CIH 5 为例,该病毒于1998年发布,据报道造成10亿美元的损失,并感染了约6000万台计算机。开发证明AV公司不值得他们懈怠。但是,CIH感染计算机的独特方式令人着迷。它扫描文件中是否有空白,然后将其自身拆分为夹头,然后将其自身注入其中。4月26 日在其有效负载上触发MBR覆盖,这将导致计算机无法启动。破坏性的?是。惊人?还可以

在我看来,恶意软件与细菌一样有害,是的,在我们看来,它会造成损害并可能杀死人,但是,它是一种活着的有机体,其性质在起作用。我们对这种性质的理解是不好的,因为我们将动机归因于它,但是没有作者的动机,它仅仅是正义而有时令人难以置信。

是的,有时候恶意软件只是用来窃取数据并加密文件的,老实说,这就像蚊子和不良艺术品一样令人感到无聊和烦恼,但有时却像观看寄生虫将蜗牛变成迪斯科僵尸一样有趣而令人惊奇。11

进入太平间
“好的Danus,对哲学已经足够了,让我们开始吧!” 是的,好吧……让我们开始吧。首先,我们必须选择我们将要开发的确切产品,以及比病毒更合适的人选。什么是病毒?所有恶意软件都不都是病毒吗?没有。

维基百科将病毒定义为“ 计算机病毒是一种计算机程序,在执行时会通过修改其他计算机程序并插入自己的代码来自我复制。[1] 1 如果复制成功,则将受影响的区域称为“感染了计算机病毒”。

是的,它是恶意软件,但不是特洛伊木马或蠕虫。一个木马不会自我复制,有时也被称为细菌和蠕虫不像病毒或木马并不需要用户的交互,它这样做独立使用网络来传播自身。

但是为什么要病毒?好吧,它们很旧,不再制造太多了(VIRUT 5 感染了恶意软件开发人员4 )。它们中的大多数不是用来窃取数据的,大多数被用作互联网涂鸦。开发病毒早在病毒的辉煌年代就证明了其技能,创造力和创新能力,但由于很难从中获利,因此如今几乎没有被利用。就我所知,这就是一切的起点,并且打算从头开始。

现在,我们需要选择一个系统来运行这些病毒。我认为Windows 7 x32是一个很好的候选人,因为它足够现代,但防御能力有限,因此它是理想的候选人。

现在我们需要选择一种病毒,以及比https://malware.wikia.org/wiki 4 ,http://virus.wikidot.com/ 4 或https://vxug.fakedoma.in 更好的发现它们的地方。/ 4。但是,在我们真正开发出真正的病毒之前,必须使用《计算机病毒巨型黑皮书》第2 版第12版了解病毒开发的基础知识,该书可在Internet档案库中在线找到。

首先,我们将在FreeDOS 4 (在此处获取副本)或旧版本的Windows(95/98)上模拟病毒,以查看病毒的实际执行方式(因为学习不同的旧操作系统的工作方式很有趣),然后根据其运行方式它们执行回去,那么我们将尝试一些如何在Windows 7中重新创建它们。为什么要使用DOS?我们不能只从Windows开始吗?是的,是的,我们可以,但是我认为应该理解为什么今天的情况保持原样。什么是保护模式和实模式?虚拟内存如何工作?如今,防御系统和硬件融合了哪些以前不存在的防御系统?了解事物为何以其原样的方式是基础知识。

为什么是FreeDOS而不是MS-DOS?我不是那么老派-FreeDOS为我们提供了一个模拟DOS病毒的平台,但是集成了许多现代有用的内置工具,并且VMware与之完美地通信。这是 FreeDOS用户指南的副本2。

此外,我们将使用纯汇编语言进行编码,而我将使用适用于FreeDOS的NASM 1 和Visual MASM 2 ,这是一个非常酷的IDE,可以使我们对Windows汇编进行编码。但您可以使用所需的任何汇编程序。为什么要汇编程序?我们不能用C语言制作病毒吗?还是C#?还是Java?不,因为一个;违背将它们组装成两个的伟大传统;高级语言将使我们无法发挥自己想要的创意。

好的,还请下载:

崇高9
VMware 3
X64dbg 4
刀具10
PE熊5
需要的背景知识:

体验英特尔x86组装
C开发经验
基本的Windows内部
x64dbg中的基础
UNIX或DOS Shell的基本经验
我将教你的是:

技术知识

教读者如何在DOS和Win32汇编中编程
教读者老式病毒架构的基础知识
向读者介绍计算机体系结构的历史记录和Windows Internals
您必须掌握的研究人员技能

数据采集​​:

了解如何使用Google,您的所有答案都可以在互联网上找到。

仅查找您需要了解的内容。例如,如果您正在阅读有关虚拟内存的信息,请不要开始研究MMU中电路的工作方式。在本文中这是浪费时间。

学会使用CTRL + F,它将节省大量时间。

MSDN是Windows API的圣经,它通常具有您需要的有关特定功能的所有信息。

结构体:

列出所有您不了解或不了解的事物,将其分为子类别,并努力理解它们。

我列出了很多资源,通过只阅读您需要知道的内容来节省时间。

不要迷失于大量的信息,呼吸……并使自己只阅读必要的内容。

看大图:

如果您发现我的代码中看不懂的内容,请不要淹没在细节中。如果有必要进入,我会进入。
注意
这不是汇编教程,也不是Windows编程教程。假定读者可以读写基本的Assembly,并且对Windows API有一定的经验。如果读者不具备此知识,则以下信息可能很难阅读。

好吧,让我们穿上外科医生的大衣,别忘了手套,因为我们正进入停尸房!

1032×684
使用VMWare设置FreeDOS
在VMware中运行FreeDOS映像,它应该相对容易地安装。

欢迎来到16位实模式天堂。没有内存保护,没有保护模式,没有多线程。纯粹。老实说,这也是我第一次来。

让我们从安装NASM开始,以便我们可以在此处编写程序集,然后键入FDIMPLES,您应该会看到以下窗口:

1539×329
使用箭头键滚动到“ 开发 ”,然后滚动到“ FASM ”或“ NASM ”。现在,您可以再次使用箭头键退出。

英特尔x86实模式基础
因此,在我开始解释之前,是Michael Chourdakis 9撰写的《英特尔组装手册》, 如果您对我的解释感到困惑或困惑,可以随时返回那里,他会完美地进行解释,而且比我在本文中要更深入地解释。如果我犯了一个错误,请私下纠正我:脸红:

因此,实模式是CPU的基本状态,BIOS在其上运行。没有内存保护,没有虚拟内存。基本上,它对所有人都是免费的。

在本文范围内,DOS在实模式下执行对我们而言很重要,这意味着一次只能运行一个程序,而没有多线程。

内存访问限制为一兆字节,与普通Windows环境不同,内核和用户应用程序之间没有分隔,所有内容都可以访问所有内容。因此,应用程序可以在内存中的任何位置运行并可以访问所有内存,基本上任何人都可以做任何事情。这使得DOS成为病毒的沃土。

以实模式注册状态
通用寄存器16位寄存器

您可以将AX,BX,CX,DX拆分为高低部分

特殊指针寄存器16位寄存器

BP(基本帧指针),SP(堆栈指针),IP(指令指针)

段寄存器16位寄存器

ES(附加段),DS(数据段),SS(堆栈段),CS(代码段)

索引寄存器16位寄存器

DI(目标索引),SI(源索引),BP,SP –这些是程序员可以用来间接访问内存的唯一寄存器,因此无法执行mov ax,[cx]。

内存分割

在实模式下,程序直接访问物理内存,没有虚拟内存抽象。内存长1兆字节,但是可以通过16位寄存器基址和16位偏移量进行访问。这可以使寄存器保持16位对齐,但可以将内存扩展到1 MB。因此,Segment:Pointer。一个网段可以达到64 KB。您可能会问为什么需要这样做?嗯,这是一个逻辑内存抽象,它有助于在每个段代表其功能的意义上使内存访问井井有条。数据存储在数据段中,代码存储在代码段中,堆栈存储在堆栈段中。

因此,基本上说我们的程序有一个数据段,该数据段从0x2000开始,我们要访问该段的第 10 个字节偏移,我们将通过引用内存DS:[ 10h ]来实现。但是您可能会问该如何计算呢?好吧,我们采用段寄存器值并将其乘以0x10,因此我们向其添加另一个零,然后添加偏移量。

0x2000 * 0x10 + 0x10 = 0x20010。该技术允许程序访问多达1 MB的RAM!因为计算的最终值是20位长。

磁盘操作系统(DOS)架构
即使DOS最多可以访问1 MB的内存,但0xA000以上的内存仍为系统保留,因此DOS用户应用程序只能访问640 KB的较低边界。

923×877

信用
内存的前1024个字节(或第一个千字节)包含BIOS和DOS中断。该存储区也称为中断向量表(IVT)。有三种类型的中断-由软件(例如python中的throw关键字)或在我们的情况下DOS触发的软件中断。由外部设备触发的硬件中断。此外,还有一些由CPU本身触发的异常,有三种类型的异常(故障,陷阱和中止)。

并非总是会发生硬件中断,CPU可以将其设置为可屏蔽(忽略)或不可屏蔽(不可忽略)。

硬件中断的一个示例是中断号9,该中断号是在按下键盘上的某个键时发出的。一个很好的例外示例是中断号0,该中断号是在CPU尝试将数字除以零时触发的。由DOS本身处理的软件中断的一个示例是21h中断,它是DOS API服务。

在MS - DOS下,BIOS处理中断0-31,而DOS处理中断32-63,其余的64-255可由用户定义。DOS和BIOS中断的类型很多,都可以在此处查看3 。

所述IVT可以被视为一个数组,该数组中的每个单元包含在存储器中的每个中断的地址。低两个字节是存储中断代码的偏移量的位置,高两个字节是存储中断的段的位置。

所以,假设我们具有线性地址线在0000:0000,其包含[ 10,20,30,40 ]的偏移所述第一中断的将是0x2010和所述第一中断的段将是0x4030。等待达努斯,你把它扭转了!不,我没有,不要忘记intel CPU是采用小字节序结构构建的,这意味着最低有效字节将首先存储。因此,INT 0的地址错误4030:2010(或获得物理内存地址4030h * 10h + 2010h = 42310h)‬

要从IVT执行中断,我们使用INT <十六进制索引>。

我们可以以某种方式查看IVT吗?是! 我们可以使用DEBUG实用程序,它可以用作系统调试器实用程序。它允许我们转储和查看内存,将指令直接组装到内存中并执行它们。由于始终只有一个内核且只有一个程序在运行,因此我们可以像调试内核调试器一样使用DEBUG调试整个系统。但是要小心,因为您记得我们可以访问系统的整个内存,并且没有限制。

让我们来看看IVT。让我们执行DOS “ DEBUG ”命令,然后使用D进行转储,并查看包含IVT的0000:0000区域的内容。

1177×327
因此,0364:1DB6是INT 0的地址,而0364:1DBF是INT 1的地址,依此类推 …

在DOS中编写基本的COM覆盖病毒
让我们检查一下mini-44,因为它只有29字节长,来自《计算机巨人电脑黑名单》中的病毒只有44个字节长。在本文中,我将其修改为MINI-51,并将其修改为源我将要使用的可以在我的GitHub上找到:

https://github.com/DanusMinimus/MalwareArt 6

原始文件位于@TMZ github(您的真棒TMZ!)6

618×879
我已经对该病毒进行了一些编辑,以使其能够在FreeDOS和NASM上运行。我并没有做太多更改,但是我建议您自己应用添加到文件中的更改。(由于我的编辑,迷你44变成了迷你51。)

在开始分析这段代码之前,我们必须了解有关COM文件的一些知识。它们围绕微小模型构建,该模型定义为:

“小模型。所有内容都必须包含在一个段中(COM文件)。指针就在附近。”

COM文件的最大大小只能为64 KB,并且所有内容都必须包含在一个段中,这意味着所有跳转都相对于一个段,并且不能跳转到64 KB边界之外。堆栈,数据和代码都必须位于这一段中。此外,COM文件从100h开始执行(h代表十六进制)。内存的前256个字节保留给程序段前缀(PSP),其中包含COM文件可以使用的各种信息和函数调用。该PSP是遗物过去两天,在DOS是为了保持与旧的COM文件的兼容性。在PSP中可以找到以下数据:

693×480
对于我们的病毒而言,唯一起作用的领域是DTA,我们将在稍后讨论。有关此问题的更多信息,请参阅《巨人黑皮书》第25-26页。

因此,让我们回到我们的代码。

618×879
它有据可查,但为您方便理解,我将与您逐一介绍。

首先在第6行将要搜索的文件名的地址传递到EDX中。然后设置带有E4h的AH来调用DOS API服务INT 21h。

那么,这执行什么呢?如果找到文件,则AX寄存器返回0,并且在PSP中DTA字段的43个字节中填充有与找到的第一个文件有关的数据(其属性,大小,创建时间和名称)。如果服务例程失败,则将设置CF标志,并在第47行执行JC,这将终止程序。

然后,在第15行DX中填充DTA中返回的文件名字符串的地址。AH充满3Dh,它将指示INT 21h服务例程打开文件句柄(文件句柄可以看作是指向资源的抽象指针,OS用来访问系统资源),而AL则充满02h,将打开文件进行读/写操作。INT 21h再次被调用。

如果调用失败,程序将跳转到NEXT_MATCH,稍后将进行讨论。

如果调用正确执行,则返回的文件句柄将传递到BX中。DX将填充写入位置的偏移量,它指向100h,这是我们自己的代码的开始。(请记住,COM文件从100h开始执行),CX被填充了我们代码的大小(51字节或33h) )。AH充满了40h,这将INT 21h设置为执行写文件服务。

如果该指令成功返回,则发现的COM文件将被感染。在第37行,文件句柄关闭。在第42行,病毒将执行服务4Fh,该服务将寻找另一个文件,如果此检查失败,则病毒将终止,如果成功,则程序将感染另一个文件。

要创建病毒,我们首先使用“ mkdir ” 创建一个新目录,并将其命名为COMDIR,然后使用cd COMDIR进行输入。

现在执行“ 编辑 ”并手动记下病毒内部,使用鼠标导航到“文件”选项卡,然后单击“新建”。我仍然不明白如何从主机复制并粘贴到FreeDOS中,因此,如果有人知道,可以随时向我更新。

写下病毒后,导航至文件选项卡,并将程序集文件另存为virus.asm。

1000×542
要使用NASM汇编此文件,请写下“ nasm virus.asm -fbin -o virus.com ”,这将为我们创建virus com文件。

编写主机COM应用程序
我们需要病毒的宿主,我认为本书的第23页中的代码段是一个不错的选择。

该程序非常简单,首先通过向AH加载9 来准备消息显示例程,然后将要显示的消息的起始地址传递到DX(显示在终端中的消息必须以'$'结尾),最后,程序通过将4c00h传递给AX然后调用INT 21h来终止。

我执行“ DIR ”命令以显示当前目录中的内容,汇编主机程序并检查其工作情况:

很酷,让我们使用“ COPY ” 制作一些副本:

现在,我们将拥有:

HOST.COM 1
HOST_1.COM
HOST_2.COM
病毒网

现在,让我们使用debug实用工具执行病毒,看看会发生什么(您可以随时使用'?'来查看debug的帮助菜单):

输入“ p <行数>”以执行代码行

1277×382
首先,我们执行第11-15行以执行dos“ Search First ”功能来查找第一个com文件,因为我们记得它应该将文件属性加载到PSP中,该文件可以在偏移量80h处找到。调用INT 21h服务例程后,我们应该看到DTA在内存中距代码段开始的偏移量80h内。让我们将代码段转储为偏移量80h“ d(ump)cs:80 ”

病毒发现了!没关系,因为我们将用病毒覆盖病毒!

接下来,我们执行第19 – 26行以打开找到的文件的文件句柄,如果对INT 21h服务例程的调用返回成功,则将文件句柄传递给AX。

1279×459
AX握住文件句柄,让我们执行第30-37行:

AX返回33h,即写入文件的字节数(十六进制)。基本上,这时我们已经用病毒本身覆盖了原始文件或病毒。

现在,让我们关闭句柄,执行“ Search Next ”服务例程,并再次查看PSP中的DTA字段,看是否可以找到下一个文件:

我们可以看到它找到了HOST.COM 1 ,此精确操作将一直运行,直到目录中的所有文件都被覆盖为止。让我们输入“ q(uit)”命令,只需在命令提示符下键入其名称即可运行该病毒。

910×599
我们的主机文件都已被病毒感染,我们怎么知道?它们都与它的大小匹配,如果我们执行它们,将不会发生任何事情。太酷了吧!

849×557
现在我们了解了如何在DOS中制作基本的COM感染器,我们可以尝试在Windows中制作自己的感染器!

Windows内部
为了应对实模式引入的大量安全性问题,当今大多数操作系统都在保护模式下执行。

Windows内部基础知识

在这里,我将解释在保护模式下运行的Windows的BARE基础,其中的一些解释已简化,以方便读者理解。如果您想扩展此处写的信息,请访问上面列出的文章。

保护模式是一种全新的体系结构,超出了本文的范围。我将只介绍Windows下与我们相关的内容。如果读者有兴趣了解更多信息,请参考上面列出的文章。

应用程序的执行环境与操作系统分开,应用程序在所谓的用户空间中分开,Windows内核在内核空间中执行。只有内核可以访问物理内存和系统资源。

Windows如何管理这种分离?它使用由CPU中的内存管理单元(MMU)芯片提供的所谓虚拟内存。该MMU分离整个物理内存页面,这些页面是由一个特殊的表处理,每一页通常引用4千字节的物理内存。

当进程在32位Windows环境中执行时,页面表中将为其保留页面。该进程不知道它是在这些页面的上下文中执行的,实际上,该进程可以访问线性地址中的所有4 GB RAM。它假定它是Windows中运行的唯一进程,并且还假定它有权访问系统中的整个内存。

在Windows中执行的每个进程都会发生这种情况,因此可以说我们有两个notepad.exe实例同时运行:notepad_proc_1和notepad_proc_2。如果notepad_proc_1试图访问其自身的地址0x1234中的值,并且notepad_proc_2试图访问其自身的地址0x1234中的值,则它们可能都将完全访问两个不同的值。但是怎么可能呢?好吧,这是因为它们在页面表的不同页面中执行。

933×535
从上图中可以看出,两个值都映射到两个不同的页面,并且这些页面指向物理内存中的不同区域。因此,在以“保护”模式运行的Windows中执行的进程不能相互触摸或覆盖内存,因此它们可以在同一环境中正常执行。

但是,如果将它们与系统本身分开,他们如何访问系统资源?如果这两个记事本进程与系统完全分开,它们如何打开文件?好问题啊!它们由Windows内核管理。Windows内核是在Windows上运行的单独进程,它映射到系统上运行的每个进程,内核是操作系统管理器。如前所述,只有内核可以访问系统资源和系统物理内存,因此,如果进程要访问系统资源,则它必须向内核执行请求。这是通过API调用完成的,就像INT 21h中断服务程序一样。在DOS中,程序如何使用DOS如果中断服务要访问内存中的系统资源,则可以中断服务,但这是可选的,DOS应用程序对系统资源执行任何操作都无法阻止它,这非常令人不舒服。在Windows中,正在运行的应用程序必须调用Windows API以获得访问权限或修改系统资源。

因此,我们的图形如下所示:

1042×686
如果内核认为这些请求不适合,它将拒绝进程对系统资源的访问。例如,假设notepad_proc_2当前正在读取README.TXT,但另一个应用程序希望将其删除。应用程序将调用DeleteFile API,此API将传递到内核,内核将看到当前正在读取文件,并将拒绝应用程序将其删除。

该机制代替了IVT,并在应用程序和系统之间建立了隔离。这些应用程序无法做他们想要的任何事情,它们与系统彼此分离,并且基本上可以并存。

好吧,现在出现了另一个问题。我声称每个进程都可以访问4 GB的内存,但是在32位系统中,只有4 GB的RAM,这怎么可能?应用程序溢出整个RAM后,系统是否会出错并崩溃?

答案很简单,首先,是的,可能会发生一个程序(例如病毒)需要大量内存而其他应用程序根本无法运行的情况。为了解决此问题,Windows执行了一种称为“分页”的操作,分页是获取页面并将其映射到磁盘上的一种操作。代替管理RAM中的所有页面,有些页面被放置在磁盘上并在那里进行管理。访问磁盘上的这些页面的速度要慢得多,并且可能导致某些应用程序或系统开始运行缓慢。分页还用于从内存中释放一些内存。假设应用程序将处于空闲状态,并且将停止使用RAM。当应用程序再次运行时,Windows将检测到此情况并释放临时内存,以保存应用程序页面并将它们映射到磁盘– Windows会将页面映射回RAM。

好了,请多多包涵一些理论知识,我们将重新开始发展。

进程中的用户空间在哪里,内核空间是否映射到进程中?

内核空间映射到每个进程,而用户空间是应用程序的代码,数据和资源所在的位置。将内核映射到每个进程是有意义的,因此可以更轻松地执行对它的请求。

用户空间位于地址0x00000000-0x7FFFFFFF处,内核空间从0x80000000-0xFFFFFFFF映射。内核获得2gb,用户获得2gb RAM。因此,这是公平的游戏。

便携式可执行(PE)文件格式
是时候编写我们的病毒了,这将是一种类似于COM感染程序的简单病毒。仅感染当前目录中的文件,而不使用隐形或直接系统调用。但是在Windows操作系统中可执行哪种文件?这些是可移植可执行文件,它们的文件格式比COM文件复杂得多。

由于它将成为仅影响PE文件的基本覆盖病毒,因此我们要做的就是覆盖PE中的代码。但是PE文件格式与COM文件格式完全不同。在Internet上已经进行了百万次讨论,但我真正相信,只有手工知道PE文件格式的人才能利用它,尤其是在进行恶意软件分析时。

就有关基础知识而言,PE文件包含:

PE标头,其中包含有关文件的各种信息

其他4个部分(有时更多)

.text部分 –用户代码在此部分执行

.data节 –用户代码的初始化全局变量和数据存储在此处

.bss部分 –未初始化的全局变量存储在此处

.reloc节 –重定位表存储在此处,它是一种机制,允许Windows PE加载程序将执行中的PE加载到内存中的不同区域中

在我们开始销毁PE文件之前,让我们看看它们是如何工作的。让我们首先尝试查找存储在PE中的用户定义代码的位置。但是发现它并不像人们想象的那么容易–值得庆幸的是,PE标头的构建方式相对容易找到我们正在寻找的所有字段,并且我们正在PE标头中寻找一个非常特殊的字段,称为“入口点地址”,它是用户代码位置相对于文件基址的偏移量。

为了对此进行试验,让我们在MASM中的程序集中编写一个宿主文件,该宿主文件在运行时会显示一条消息,打开Visual MASM->单击“文件”->“新建项目”->“ Windows 32 MessageBox应用程序” ->“将项目另存为指定文件夹”。现在让我们检查一下代码:

528×525
在第7-9行中,我们定义了程序集二进制文件的模型(我们将在后面的章节中进行介绍)。然后在第4-6行,我们使用“ Include ”指令来指示MASM从windows.inc,user32.inc和kernel32.inc导入所有常量类型。存储在这些文件中的这些常量定义了函数的各种常量参数(例如,MessageBox API的MB_OK常量,将生成消息框对话框类型为“ OK”)

然后,在第21-22行中,我们包含了user32.lib和kernel32.lib的所有汇编函数定义,它们定义了各种Windows API调用。然后,在第27-29行中,我们定义了PEs .data节,您应该记得它应该包含初始化的全局数据。

然后在34-41行,我们定义的程序.CODE部分是包含该程序的代码段。但是,请等达努斯!.text部分不应该包含PE中程序的代码吗?是的,但是在MASM中,由于兼容性原因,我猜(?)他们保留了.code而不是.text的名称。

然后,我们定义一个新的“开始”标签并调用由Windows API定义的MessageBox 1 :

在我们的代码中,我们可以看到参数传递如下:

0 –处理创建的消息框窗口的所有者。NULL(0)参数表示没有所有者,意味着消息框将简单地独立显示
ADDR strMessage –将在消息框中显示的字符串的地址
ADDR strTitle –将显示消息框标题的字符串的地址
MB_OK -包括在恒定user32.inc其指示消息框的类型
然后调用 ExitProcess,这是一个Windows API,它将终止该进程。它只有一个参数传递给它,在这种情况下为0,该过程将无错误地终止。

您会注意到,MASM使用“ Invoke ” 代替了“ Call ”。您仍然可以使用call,但是Invoke是一个MASM程序集宏,它使用户可以更方便地调用Windows API函数。

让我们改变它的调用一个电话,因为组件的优点像我们只使用本土组装。我假设读者知道汇编程序,但是以防万一,如果您不知道,请记住,传入汇编程序时的函数参数将传递到堆栈中,该堆栈是先进先出数据结构。因此,最后一个参数先被推送,最后一个参数被最后推送。如果您想扩展此信息,请阅读此内容。作为练习,请在阅读解决方案之前尝试自己做。

因此,如果您确实尝试自己执行该操作并且失败了-您将无法在推送指令中使用ADDR关键字。您必须将字符串的地址手动加载到寄存器中,并将其压入堆栈。

有用!但是与我们为DOS生成的COM文件相比,最终的二进制文件很大(3584字节对51字节)。

871×34
确实有道理,就像当我们使用“ Include ”指令时一样,我们包括了要导入的库中包含的所有代码。可以使用动态加载到PE中的DLL修复此问题,但是我们将在以后的文章中讨论该主题。

现在,让我们使用PEBear检查PE文件格式!PEBear允许我们遍历PE文件并查看其整个结构,因此打开PEBear并将程序加载到其中。

1258×534
我将使用OpenRCE 7的 PE文件格式海报 。这将有助于我们遵循PE结构,因此我强烈建议您与我一起使用它。PE File格式可以看作是一个大型线性结构,其中包含许多子结构,这在我初次学习遍历该格式时对我有很大帮助。每个结构中的几乎所有值都只是相对 虚拟地址(RVA)的偏移量,该偏移量是从文件开始到结构中另一个位置的偏移。什么是RVA?很快我会解释。

您可以使用左侧菜单列表查看PEBear内部的字段和结构,该列表会将您重定向到蓝色十六进制转储内的字段的位置

您可以使用中间的标签,以更舒适的方式简单地列出PE值。

965×61
让我们检查一下PE File头的第一个结构DOS HEADER:

788×273
和OpenRCE海报:

这里有很多值,但是我们只关心两个:

e_magic-这是一个两个字节(字)的值,其中包含字母“ MZ ”,它是PE文件的签名,您可以在所有PE文件中找到它。如果您要查看PEBear,也可以在十六进制转储中查看它:

1263×301
e_lfanew –包含到位置NT_HEADERS结构的RVA偏移值。 那么什么是RVA? 好吧,当我们像现在一样查看磁盘上的文件时,当它没有加载到内存中时,所有偏移都相对于文件的起始地址–当在磁盘上查看时,文件的基址为零。当文件被加载到内存中时,它将被加载到一个特定的地址,直到文件被加载到内存中才知道。要访问PE值内的特定字段,我们使用RVA计算这些字段的实际位置或这些字段的虚拟地址(VA)。计算这些字段实际位置的公式如下: 虚拟地址(VA)= RVA +

图像基础

图像基数可以是任何地址,没关系。当遍历文件时,如果文件未加载到内存中并且在磁盘上,则基址为零。

DOS Stub,其中包含一个小程序,如果我们尝试在其中运行此PE,它将在DOS上执行。它只会输出“ 此程序无法在DOS模式下运行 ”。我们可以从偏移量0x40 – 0x70复制蓝色标记的十六进制转储,并反汇编代码(您实际上可以在DOS中运行它:脸红:)

781×534
掌握了这些信息后,我们将尝试查找NT_HEADERS结构的起始位置,该位置位于 DOS HEADER的e_lfanew 字段内。

因此,可以在RVA 0xC0处找到NT_HEADERS(也是新头)的位置。很容易吧?0 + 0xC0 = 0xC0。

如果计算机在地址0x412000处加载了我们的文件怎么办?

简单!为了找到NT_HEADERS首先,我们必须找到e_lfanew值。我们知道它位于文件起始地址RVA 0x3C处。让我们计算一下它的虚拟地址:

0x412000 + 0x3C = 0x41203C。现在在此位置,我们找到值0xC0,它是NT_HEADERS的起始地址。让我们对这个标头的起始地址进行优化:

0x412000 + 0xC0 = 0x4120C0。

接下来,点击“ 转到RVA ”

输入0xC0,然后单击确定。

我们降落在0xC0位置:

现在确认我们实际上已到达 NT_Headers 单击左侧列表上的NT_HEADERS。单击它–它会带我们到NT_HEADERS的起始位置,并且它会带我们到同一位置,因为十六进制转储没有改变。

该NT_HEADERS包含3个重要参数:

签名 –始终包含0x5045,ASCII 中将其转换为“ PE”
FileHeader –包含FILE_HEADER结构的线性地址
OptionalHeader –包含OPTIONAL_HEADER结构的线性地址

文件头是一个交错结构,其中包含有关文件本身的非常重要的信息,例如它的CPU体系结构(“ 机器”字段),或者指示执行的文件是64位文件还是32位文件(“ 特性”字段)。但是我们寻求位于OPTIONAL_HEADER内部的两个值。

我们寻找名为ImageBase 和 AddressOfEntryPoint的字段!

好了,让我们找到Optional_Header。我们知道它根据NT_HEADERS规范(请参见上图)从0x18开始,并且其位置相对于NT_HEADERS。NT_HEADERS从RVA 0xC0开始。因此,要找到Optional_Header,我们只需将0x18添加到0xC0:

可选标头的VA = ImageBase + NT_HEADERS(0xC0)的偏移 + OPTIONAL_HEADER的偏移 = 0 + 0xC0 + 0x18 = 0xD8。

785×276
我们是正确的!现在,使用“ Optional hdr ”选项卡查看标题内的值:

950×660
该入口点字段值包含其中代码开头的文件来执行的RVA。

的图像基础值包含在其中该文件将在被装载位置的虚拟内存。默认值始终为0x400000,但由于Windows中的一种机制称为地址空间布局随机化(ASLR),因此该值始终在运行时更改。因此,除非为文件禁用了ASLR,否则几乎永远不会知道PE的起始地址。

有了这些知识,我们可以在任何反汇编程序中打开文件。我将使用Cutter(向@megabeets_大喊)。Cutter是基于radare2的免费反汇编程序和调试器,因此每个人都可以使用它:脸红:

我将使用“ Virtual Addressing ”加载文件,这将使用默认的Image Base加载文件。
可以看出:

Entry0位于偏移量0x401000处。我们记得入口点的RVA是0x1000。加载到内存时的Image base为0x400000。

入口点的VA = Image Base(0x400000)+入口点(0x1000)= 0x401000

如果双击entry0,我们将直接进入该入口点,甚至可以看到我们的代码被标记为.section文本!

937×271
我注意到一个有趣的事情–我们可以看到MASM如何汇编代码。似乎不是直接调用MessageBox,而是MASM选择在0x401012执行一个调用以跳转到0x40101e,然后从那里跳转到MessageBox!

好吧,让我们总结一下我们所知道的:

我们了解Windows处理内存和内核的基础知识
我们了解到PE文件比简单的COM文件还要复杂
我们知道如何使用PEBear遍历PE文件头
我们知道如何找到可选标头,以及如何使用它查找内存中文件的默认虚拟地址,并使用它来找到位于入口点地址内的文件代码的开头
生成PE文件感染器
为了实践我们发现的新知识,让我们创建一个简单的覆盖病毒,它将像COM文件感染程序一样工作:

在当前目录中查找文件名中包含“ HOST ”的文件

如果文件被找到
一个。用感染器覆盖找到的文件,因为文件位于磁盘
a上。查找下一个文件
。如果找到文件,请再次执行2
。如果找不到文件,请结束程序

查找病毒PE领域的追随者,并打印出来:
一。e_magic -DOS标题
b。e_flanew -DOS头
c。签名 -NT标头
d。机器 -文件头
e。魔术 -可选标题
f。图片大小 -可选标题

结束程序

尽管在这种情况下,我们新发现的PE格式知识不会直接用于感染,但将在下一章中使用。该病毒不是高级病毒,也不是很隐秘,也不关心节省空间或使用局部变量。它只是破坏了它前进的一切。

让我们假设所有要感染的主机文件都位于同一目录中,包括病毒。

825×191
现在我们要讨论2.a,我们将如何覆盖主机文件?当病毒文件从ImageBase加载到内存中时,我们可以读取该文件,并转储虚拟内存以覆盖找到的主机文件。好的,这可能有效,但是问题在于,PE文件在内存中的映射方式与映射到磁盘的方式有很大不同,因此,如果我们用内存中的病毒覆盖磁盘上的主机文件,则被感染的主机文件将不会执行。还有什么?好了,我们可以打开病毒的句柄–读取其代码,然后将其覆盖主机文件。好的…但是有一个问题,如果我们从病毒过程中执行CreateFile API ,它将打开磁盘上病毒文件的句柄,内核就会很好地拒绝该请求。原因是因为该病毒当前已从磁盘上的病毒文件打开进程,这足以使内核拒绝对磁盘上病毒文件的任何进程访问,甚至是拒绝访问它。

即使内核不喜欢使用CreateFile,我们也可以将病毒文件复制到主机位置。复制的文件是相同的病毒文件,但是它是一个具有不同名称的新文件,并且未使用,因此没有什么阻止我们打开该文件的句柄。然后,我们将病毒内容读入缓冲区,删除复制的病毒,对于目录中的每个找到的文件,我们都将其简单覆盖。

有多种方法可以读取复制的病毒文件的内容,但为简单起见,我们只需使用GetFileSize来获取病毒的大小,并使用ReadFile来读取文件的内容。

下一个问题是该病毒无法感染自身,因为与DOS不同,Windows不允许我们打开该病毒本身的句柄,也不允许我们创建与自身名称相同的另一个文件。为了解决这个问题,我们将使用GetTickCount返回:

” 检索自系统启动以来经过的毫秒数,最长为49.7天。”

该值返回给EAX。将此值用作我们新复制的病毒文件的名称。然后,为避免打开病毒本身的文件句柄,我们将检查目录中找到的文件名是否与我们的病毒名匹配。为了遍历目录中的所有文件,我们将使用FindFirstFileA和FindNextFileA。因此,我们的结果应类似于com文件感染器。

可以在https://github.com/DanusMinimus/MalwareArt/tree/master 5中 找到源代码。由于篇幅太长,我不会在这里列出。我鼓励读者阅读我将在源代码中列出的API调用和PE标头值,学习Google并阅读特定信息是逆向工程和恶意软件开发中非常有用的技能:脸红:

请以组合形式将病毒加载到x64dbg中,然后开始执行。首先,我们执行第63-89行

1056×615
我使用NULL参数调用GetModuleHandleA,该参数返回病毒加载到内存中时的基址。该值保存在全局变量dwImageBase中。

然后,我使用一个NULL参数调用GetModuleFileNameA,它将返回当前执行进程(即我们的病毒)的完整路径。该路径保存在lpcstrFileFullPathBuffer全局变量中。

我调用GetTickCount并使用sprintf生成一个新文件名并将其保存在lpcstrFilePathBufferVirus中。然后,我使用CopyFileA使用新名称将病毒的主要二进制文件复制到主机位置。然后,我使用CreateFileA打开具有GENERIC_READ权限的新复制病毒的句柄。可能会注意到我使用的是CreateFileA而不是CreateFileW,A后缀和W后缀之间的区别是,第一个期望使用ac字符串,而第二个期望使用宽字符串。在C字符串中,每个字符长一个字节,在宽字符串中,每个字符长两个字节。

然后执行90-112行

956×527
首先,我将复制的病毒文件句柄保存到名为hSelfFileHandle的全局变量中。然后,我调用GetFileSize来获取磁盘上病毒的文件大小并将其保存在dwImageSize中。我使用VirtualAlloc在病毒进程内部分配虚拟内存,该虚拟内存将在磁盘上保存病毒内容。我调用ReadFile并将其完全读取到VirtualAlloc返回的内存中。然后,我关闭复制病毒的句柄,并使用DeleteFileA将其删除。

该病毒已加载到内存中!我们识别出“ MZ”签名值和DOS STUB字符串!

接下来我执行115-143行

606×622
我将保存的ImageBase地址加载到EAX中并打印其内容,该内容应打印“ MZ”

1110×641
然后在第120-125行,将ImageBase保存在EAX和EBX中。我在EBX上添加了0x3c,我们记得它是DOS HEADER中的RVA to e_flanew 字段。此值包含NT HEADERS的RVA

992×114
我提取该值并将其添加到EAX中,该值应将EAX移至NT HEADERS的起始地址。然后我交换EAX和EBX的值,因为将在printf内修改EAX并打印NT HEADERS的签名

然后我执行第127-132行

我将NT HEADERS地址再次移到EAX并添加4,这将导致FILE HEADER的RVA,然后加载机器字段值,该值是NT HEADERS字段内的第一个字段。Machine值只有一个WORD长(2个字节),因此我们需要修剪EAX,以便将AX传递到CX并进行打印

Microsoft将14c定义为与Intel 386兼容的可执行文件,太酷了!

895×74
然后我执行第134-138行

我在EBX上添加了 0x18 ,这将使我们前进到OPTIONAL HEADER的RVA中,并将其第一个字段加载到EAX中。它只有一个字,又长了一个,所以我执行了与以前相同的技巧。可能会问为什么我不仅仅传递CX,这是因为printf期望%d是双字值。

什么是10b?

899×209
您可以从Microsoft官方文档中学到很多东西。

然后我执行140-143行

我将EBX推进到SizeOfImage字段并保存。请自己阅读该值。请注意,我首先将病毒文件的大小保存在EDI中,稍后我们将了解如何利用它。

现在文件感染从第146-156行开始

881×241
我再次调用GetModuleFileName并将当前正在执行的文件名保存在全局变量中。然后在第151行,我调用FindFirstFileA,它应该将搜索句柄返回到由指定搜索参数找到的文件

790×25
仅在特定目录内以字符串HOST开头的文件名,如果返回了搜索句柄,则将其保存在hSearchHandle内,打印该句柄,然后跳至第164-186行

1060×500
首先,我使用sprintf构造了一个新文件。FindFirstFileA填充了我们传递给它的名为WIN32_FIND_DATA的数据结构,其中包含一个名为cFileName的文件,该文件包含文件名而不是其完整路径。因此,我调用sprintf来使用其名称构造该文件的完整路径。在第170行,我将病毒的全名与新找到的文件进行比较,如果它们匹配,病毒将执行仅运行FindNextFileA的NEXT标签,此函数将在目录中查找下一个文件。

如果文件名不匹配,我将调用CreateFileA打开找到的主机文件的句柄。

我执行第188-194行

730×176
我使用WriteFile用VirtualAlloc返回的内存位置中存储的病毒缓冲区覆盖主机文件。注意我如何使用EDI传递病毒文件大小。然后,我关闭文件并跳转到NEXT,它将查找下一个文件。如果找不到另一个文件,病毒将直接终止。

文件HOST1.exe和HOST2.exe被覆盖。如果我们现在将它们加载到调试器中,它们将执行病毒。

我们在现代Windows环境中对Mini-44病毒进行了反应!多么酷啊!
关于此病毒的一个很酷的事情是,您可以相对轻松地在C中重新创建它,因此作为家庭作业(您不必这样做),我建议读者使用C重新创建该病毒。

结论
在本文中,我们学习了Windows内部和DOS内部的基础知识,它们为Windows环境下的恶意软件开发奠定了基础。在下一篇文章中,我们将学习:

寄生病毒的工作方式以及它们如何锁定DOS EXE文件
我们将在Windows 95上执行希拉里病毒
我们将如何绕过ASLR并在PE文件中使用.reloc节来发挥我们的优势。
如何覆盖PE文件的特定部分,而不是完全销毁它
使用创造性的方法来最小化我们的感染代码大小
下次见
资料:巨型计算机病毒黑皮书,第二版

Last modification:April 6th, 2020 at 03:12 pm
如果觉得我的文章对你有用,请随意赞赏