PE结构-空白区添加代码

空白区域添加代码

经过一个多星期的PE学习,现在把几个能够较好体现PE结构和各种属性的练习整理出来

目的:在notepad.exe中添加一段代码,使其在运行的时候,先弹出一个窗口,然后再执行主程序。

所利用的知识点:

文件对齐,内存对齐,硬编码,PE头结构

①首先要知道,PE文件为了更好的执行,添加了文件对齐和内存对齐的属性,在老版本编译器中,文件对齐一般为200h,内存对齐一般为1000h,在现今版本编译器中,大多数都为1000h。

②DOS头+DOS存根+PE标识+PE文件头+PE可选头+节表      该部分组成了宏观意义上的“PE头”。“PE头”不管是在内存对齐还是文件对齐中,都不会拉伸,中间没有空隙。

③而节表和节数据之间为了对齐而产生的空隙区域,为我们利用提供了方法,添加一段保护代码,就叫做壳,添加shellcode,就叫做病毒。

步骤一 (寻找合适地址)

首先,因为要调用MessageBox函数,所以需要先找到MessageBox函数的地址。利用od寻找

 

 

 用winhex打开notepad,找到节表后的空白区域,这里我选择在300h处添加自己的代码

 

*步骤二 (RVA FOA)

要理解怎样写入上述的数据,这里需要掌握硬编码的知识,这里给出两个公式,不花费过多时间解释,请自行领会

E8后跟随的硬编码  =  要跳转的地址- E8所在的地址 - E8所在指令的长度

E9后跟随的硬编码  =  要跳转的地址- E9所在的地址 - E9所在指令的长度

 而且更要注意的是,在该notepad中,文件对齐和内存对齐大小并不一样,这里又需要将FOA 转化为 RVA的知识

FOA = n.PointerToRawData + 偏移(即在RVA中所属节+偏移,因为在内存中节空隙之间会被拉伸,但节中的数据并不会被拉伸)

因为300h在文件中仍属于节表区域,而从DOS头到节表区域中间是没有空隙的,所以在内存中,所对应的就是ImageBase+300h,即1000300(这里已经查看了ImageBase)

将我们所需要的数据带入公式中可得

E8后跟的硬编码 =要跳转的地址 - E8所在的地址 - E8所在指令的长度
77D507EA - 1000308 - 5
76D504DD
E9后跟的硬编码 =要跳转的地址 - E9所在的地址 - E9所在指令的长度
1000000+739D -100030D - 5
708B

随后,将结果写入即可,切记(小端序存储)

步骤三 (修改EP)

上述步骤已经将想要添加的代码成功添加到程序中,但仍不可运行,因为,程序的入口点(EP)没有改变,当程序加载到内存中时,入口点的VA就是ImageBase+EP,于是,下一步就是要让OEP指向自己写的代码,将自己写的代码执行完毕后,再jmp到原来的OEP处,执行原来的功能。

而则就要求熟练掌握PE头中的各个部分了,PE文件中的ImageBase属性正是在IMAGE_OPTIONAL_HEADER中,而PE头的结构想必都非常熟悉,我前面的博客中已经介绍了,请自行阅读。

typedef struct _IMAGE_NT_HEADERS {  
    DWORD Signature;  
    IMAGE_FILE_HEADER FileHeader;  
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;  
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;  

PE指纹和文件头分别占4个字节和20个字节,紧接着就是可选头,可选头的大小不固定,如果不更改,在32位系统中则为E0。

//可选PE头
struct _IMAGE_OPTIONAL_HEADER{
    0x00 WORD Magic;                    //※幻数(魔数),0x0107:ROM image,0x010B:32位PE,0X020B:64位PE 
    //0x02 BYTE MajorLinkerVersion;     //连接器主版本号
    //0x03 BYTE MinorLinkerVersion;     //连接器副版本号
    0x04 DWORD SizeOfCode;              //所有代码段的总和大小,注意:必须是FileAlignment的整数倍,存在但没用
    0x08 DWORD SizeOfInitializedData;   //已经初始化数据的大小,注意:必须是FileAlignment的整数倍,存在但没用
    0x0c DWORD SizeOfUninitializedData; //未经初始化数据的大小,注意:必须是FileAlignment的整数倍,存在但没用
    0x10 DWORD AddressOfEntryPoint;     //※程序入口地址OEP,这是一个RVA(Relative Virtual Address),通常会落在.textsection,此字段对于DLLs/EXEs都适用。
    0x14 DWORD BaseOfCode;              //代码段起始地址(代码基址),(代码的开始和程序无必然联系)
    0x18 DWORD BaseOfData;              //数据段起始地址(数据基址)
    0x1c DWORD ImageBase;               //※内存镜像基址(默认装入起始地址),默认为4000H
    0x20 DWORD SectionAlignment;        //※内存对齐:一旦映像到内存中,每一个section保证从一个「此值之倍数」的虚拟地址开始
    0x24 DWORD FileAlignment;           //※文件对齐:最初是200H,现在是1000H
    //0x28 WORD MajorOperatingSystemVersion;    //所需操作系统主版本号
    //0x2a WORD MinorOperatingSystemVersion;    //所需操作系统副版本号
    //0x2c WORD MajorImageVersion;              //自定义主版本号,使用连接器的参数设置,eg:LINK /VERSION:2.0 myobj.obj
    //0x2e WORD MinorImageVersion;              //自定义副版本号,使用连接器的参数设置
    //0x30 WORD MajorSubsystemVersion;          //所需子系统主版本号,典型数值4.0(Windows 4.0/即Windows 95)
    //0x32 WORD MinorSubsystemVersion;          //所需子系统副版本号
    //0x34 DWORD Win32VersionValue;             //总是0
    0x38 DWORD SizeOfImage;         //※PE文件在内存中映像总大小,sizeof(ImageBuffer),SectionAlignment的倍数
    0x3c DWORD SizeOfHeaders;       //※DOS头(64B)+PE标记(4B)+标准PE头(20B)+可选PE头+节表的总大小,按照文件对齐(FileAlignment的倍数)
    0x40 DWORD CheckSum;            //PE文件CRC校验和,判断文件是否被修改
    //0x44 WORD Subsystem;          //用户界面使用的子系统类型
    //0x46 WORD DllCharacteristics;   //总是0
    0x48 DWORD SizeOfStackReserve;  //默认线程初始化栈的保留大小
    0x4c DWORD SizeOfStackCommit;   //初始化时实际提交的线程栈大小
    0x50 DWORD SizeOfHeapReserve;   //默认保留给初始化的process heap的虚拟内存大小
    0x54 DWORD SizeOfHeapCommit;    //初始化时实际提交的process heap大小
    //0x58 DWORD LoaderFlags;       //总是0
    0x5c DWORD NumberOfRvaAndSizes; //目录项数目:总为0X00000010H(16)
    0x60 _IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
};

 

在可选头中,通过偏移,即可找到AddressOfEntryPoint,它的值就是需要修改成自己所写的代码所在的偏移地址。

 

 这样,空白区添加代码所需的全部工作已经完成。运行发现结果正常。先弹出一个窗口,关闭窗口后,打开notepad本体。

总结

必须熟练掌握RVA FOA的转换,在反病毒和脱壳加壳领域中,是非常重要的基本功

posted @ 2020-03-21 21:11  ReVe1Se  阅读(667)  评论(0编辑  收藏  举报