前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手动打造一个弹窗程序

手动打造一个弹窗程序

作者头像
信安本原
发布2020-07-23 11:37:02
3040
发布2020-07-23 11:37:02
举报
文章被收录于专栏:信安本原信安本原信安本原

在平时的分析当中,经常会碰到PE结构的文件,虽然 010 Editor 等工具会提供一个模板,把各个部分都详细的标记出来,但是在调试的时候,经常会需要在 VS 等程序框中进行调试,所以,就需要对PE结构有一定的了解,才能够快速定位到自己想要的地方。

为了更好的了解PE结构中的每一位的作用,最好的办法就是自己手写一个PE文件,这样对每一个部分的理解,都会清晰很多。

目录

0x00 准备工作

0x01 构造DOS头

0x02 构造File头

0x03 构造Optional头

0x04 构造节表

0x05 构造导入表

0x06 执行代码

0x00 准备工作

在开始之前,有一些细节是需要提前思考好的,这些细节对于整个PE结构来说是非常重要的。

因为只需要完成一个弹窗的效果,代码量是非常少的,所以在程序的设计上,一个节表就完全足够了,同时,我们希望保证文件尽可能小,所以将文件对齐设置为200,将内存对齐设置为1000。

再加上头部和对齐的考虑,文件就需要占用400个字节了,到内存展开以后就占用2000字节。

注:PE格式中,所有可以被覆盖掉,而不影响程序运行的位置,我都会用CC来填充,这些位置可以写入字节的shellcode等。

0x01 构造DOS头

DOS头部在编辑器中占用了4行,其中的多数数据都是在16位的DOS环境下运行时所必备的,在现在看来,已经是可以占用的内容,只有两个参数是必须的:e_magic和e_lfanew。

e_magic

这个位是识别性的头部(MZ),这个位置是会被作为一个合法PE文件的检测位。

e_lfanew

用来指向一个新的结构,这个也就是我们现在来说,最重要的结构,所有的参数信息都是在这个结构中定义的。

在DOS头部后面还有一个Stub数据区,是16位程序的残留数据,是可以去掉的,所以就直接将e_lfanew指向了0x40,在这个位置开始新的结构。

0x02 构造File头

PE标识、File头以及Optional头统称为NT头,这里就不提NT的概念了,PE标识有4字节。

File头有1.4行,有4个重要的参数。

Machine

这是一组宏,表示在什么硬件下运行,一定要根据实际的情况来进行更改,当前是Intel386,所以填0x014c。

NumberOfSections

描述节表的个数,在前面规划的时候提到了,使用一个节表就足够了。

SizeOfOptionalHeader

描述Optional头的长度,在自己写程序进行分析的时候,一定要注意这一点,原版的OD直接使用sizeof来获得了,忽视了这个值是变长的,我们可以自己来更改,达到反调试的目的,这里填写0xE0。

Characteristics

描述可执行文件的属性,具体参照下面这张图。

最终填写如下

0x03 构造Optional头

Optional头涉及到的参数就比较多了,也是最重要的一个部分

Magic

类型识别,可以来判断到底是32位还是64位,再或者是其他的,我们使用32位的,为0x10b

AddressOfEntryPoint

程序入口点,因为头部对齐后为200字节,所以程序就从文件的200位置开始写,对应到内存中就是1000,所以填写0x1000

ImageBase

建议装载地址,这个地址并不是一定能占住的,如果没有到这个位置的话,会根据重定位表来修正程序中的地址,因为要写一个exe文件,一般默认是0x400000

SectionAlignment

内存对齐,填写0x1000

FileAlignment

文件对齐,填写0x200

MajorSubsystemVersion

这个版本号是不能进行修改的,目前系统一般都是NT4的,填写0x4

SizeOfImage

内存中的文件大小,在前面规划的时候也提到过了,这里填0x2000

SizeOfHeaders

头部的大小,这里都是要考虑对齐后大小的,所以填0x200

CheckSum

校验和,在3环程序中,是不会检测这个位置的,在0环中才会进行校验,但是计算这个值的算法是公开的,所以可以自己计算并填写,检测的意义不大,我们把这个位填0。

Subsystem

这个位置是程序运行在什么情况下,填3,是命令行下,2是图形化界面下,1是内核文件中,根据实际情况填写。

DllCharacteristics

是否是基于WDM的驱动程序,填0就可以了,如果是的话,填0x2000

SizeOfStackReserve

准备保留多大的栈空间,自己填写,合理即可

SizeOfStackCommit

程序运行的时候,占用多大的栈空间,自己填写,合理即可

SizeOfHeapReserve

准备保留多大的堆空间,自己填写,合理即可

SizeOfHeapCommit

程序运行的时候,占用多大的堆空间,自己填写,合理即可

NumberOfRvaAndSizes

数据目录的长度,默认是16个,填写0x10

紧接着后面就是数据目录的描述了,一个描述占用8个字节,4个字节的RVA,4个字节的长度,只有导出表和重定位表的长度是会被使用的,其他的数据目录的长度都是可以覆盖掉的,他们通过一个全零结构来判断结尾。

最终填写如下

0x04 构造节表

在数据目录的描述结束以后就是节表描述了,一个节表的描述是两行半

Name

节表名字的长度是固定的,而且是可以随便写的,并不是说.data就一定是数据段,一定不能通过名字来判断其中的内容。

VirtualSize

这是一个共用体,一般我们使用的都是VirtualSize位,节表在内存中的长度,有效字节的长度,这个是对齐前的长度,这里可以填0。

VirtualAddress

节表的开始位置在内存中的RVA,按照前面的设想,这里应该填0x1000

SizeOfRawData

节表在文件中的长度,这个是对齐后的大小,按照前面的设想,这里应该填0x200

PointerToRawData

节表的起始位置,按照前面的设想,这里应该填0x200

Characteristics

节属性,描述这个节是可读的,可写的还是可执行的。

对于上面的那四个参数,可以描述为,从文件中起始地址为PointerToRawData的地方,复制SizeOfRawData的数据,粘贴到内存中RVA为VirtualAddress的位置,实际字节为VirtualSize的地方。

最终填写如下,还需要对齐

0x05 构造导入表

在完成了这些内容以后,就需要开始构造导入表了,因为我们需要调用MessageBoxA函数来实现弹窗的功能。

导入表也是整个PE结构中最复杂的地方,占用1.4行,在程序执行前和执行后,导入表的结构是不一样的。

OriginalFirstThunk

导入名称表,这里是一个RVA,它指向了一个结构,说它是一个数组更为合适,里面存储的也是一个RVA,指向了_IMAGE_THUNK_DATA结构,这个结构也是一个共用体,可以填一个序号,也可以填一个函数名称,因为导出表有按名字导出和按序号导出两种形式。

这里我们使用按名字导出的方式,这样就又涉及到了一个结构_IMAGE_IMPORT_BY_NAME

在这个结构中,Hint属于废弃的状态,所以只需要写上函数的名字就可以了,这个名字是一个字节的,因为不知道函数名字的长短,也就没法使用定长的方式,为了避免空间的浪费,所以它只记录了名字的起始位置,通过00来判断结尾

Name

动态链接库的名称,也是一个RAV,它指向了名字

FirstThunk

导入地址表,这里也是一个RVA,一样指向了一个数组,与导入名称表是对应的,在运行前,与导入名称表一样,都指向了_IMAGE_THUNK_DATA结构,结构图下

当程序执行以后,导入地址表就会按照名称进行搜索,得到函数的地址,然后把地址填入到对应的位置中,结构图就变成了下面这个样子

在执行的时候,也就是间接调用的函数地址表

我们先把导入表的结构写出来,地址的位置先用0来补充,这里将导入表也到250的位置

文件偏移是250,对应的内存偏移是1050,所以在数据目录的第二项,也就是导入表的位置,写上导入表的RVA,长度是可以随便写的

因为导入表是依靠一个全零结构来判断结尾的,我们需要给它留下足够的空间,我们将 dll 名称写到 2C0 的位置,最后的 .dll 是可以不用写的,操作系统不依靠后缀名来判断

文件偏移2C0对应的内存偏移是10C0,写到导入表中对应的位置

然后布置函数名称MessageBoxA,开头的两个字节是可以随意填写的,我们将它放到 2E0 的位置,最后以00来结尾

文件偏移2E0对应的内存偏移是10E0,这个先记住,等一下再进行填写

然后是导入名称表和导入地址表,在执行前,这两个的内容是一样的,都指向了函数名称,也就是上面的10E0,因为这里我们只用一个函数,所以导入名称表和导入地址表都只有一项,前面也说过了,它们相当于是一个数组,是需要一个00来结尾的,所以每一个都需要占用8个字节,刚好是一行,所以,我们把导入名称表放到2D0的位置,将导入地址表放到2D8的位置。

文件偏移2D0对应的内存偏移是10D0,文件偏移2D8对应的内存偏移是10D8,然后将它们填到导入表中对应的位置。

到这里为止,导入表的编写也就完成了。

0x06 执行代码

最后就是代码的编写了,我们先设置一下弹窗的标题和内容,我们将标题放到2F0的位置,对应的内存偏移是10F0,因为ImageBase是400000,所以我们需要push的地址是4010F0,然后把弹窗的内容放到2F4的位置,对应的内存偏移是10F4,需要push的地址是4010F4

前面已经提到过了,在代码执行的时候,我们需要调用的函数地址在导入地址表中,所以需要调用的地址是4010D8

这样,所需要调用的内容也就都有了,接下来就是硬编码的事情了,如果对硬编码不熟悉的话,我们可以通过在OD中写汇编,然后把硬编码扣下来

然后把这段代码写到200的位置

这样就大功告成了,然后保存运行,看看效果

成功弹窗,也就完成了手写PE结构的任务,虽然还有导出表,重定位表等都没有涉及到,但是通过这样的一次小的练习,也就对整个PE结构都有了更深刻的了解了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 信安本原 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档