Windows PE程序底层结构分析
PE(Portable Executable)是一种Windows操作系统下可执行文件的标准格式
Windows PE程序结构和Linux的elf程序结构类似,首先一个名为simple64.exe程序里有一个头文件和一个段文件,头文件里主要存放的是可执行文件的技术详细信息,段文件里主要存放的是可执行文件的内容
在头文件里的内容有DOS头信息,PE的头信息,optional头信息,数据目录,节表
头文件里信息的详细内容
而段文件里主要存放的是程序代码,导入,数据信息
为了更好的理解,这里写一个简单的exe程序来进行分析
#include <stdio.h>
int main()
{
printf("Hello, World!");
return 0;
}
安装编译工具:
apt-get install g++-mingw-w64-x86-64
编译程序:
x86_64-w64-mingw32-g++ --static test.cpp -o helloword.exe
将编译好的helloword.exe文件拖入到010 Editor里分析,如果没有010 Editor,也可以用linux里的xxd工具分析
010 Editor下载:
https://www.sweetscape.com/010editor/
图中的4D 5A是一个DOS头数据
图中紫色的地方提示系统这个程序不是以DOS模式运行的
50 45就是程序的PE头数据
在下面有.text .data .rdata .bss段,这些都是程序的节表名称
.text段:存储可执行代码(机器指令),即程序的指令部分
.data段:存储已初始化的全局变量和静态变量
.rdata段:存储只读数据,例如常量字符串等
.bss段:存储未初始化的全局变量和静态变量
使用pe-bear工具可以查看更详细的PE程序结构
https://github.com/hasherezade/pe-bear/releases/tag/v0.6.7.3
比如,这里我就可以查看.rdata段存储的字符串hello world,还可以看到程序当前的汇编指令
点击.text节,选择导入表,可以看到程序使用的一些dll文件名称,dll相当于是一个库,程序使用的函数都是从这个库里导入的
无论是PE或者ELF文件,里面都有一个entry函数,这个函数很重要,它是程序的入口函数,在分析恶意程序的时候,通常都找不到main函数,这里就需要看entry函数来定位程序主函数
入口函数的作用是指示程序从何处开始执行。它通常包含程序的主要逻辑,初始化代码以及程序的控制流程。在程序运行时,操作系统或运行时环境会找到入口函数,并从该函数开始执行程序的代码
恶意代码插入与执行
用xdbg导入可执行程序,在最下面,程序有一段都是0的部分,有些文件格式或文件类型在设计时可能会留有预留空间,以便将来添加或扩展文件内容。这些空白区域可能在文件创建时被填充为0,这部分是程序的一块空字节空间,攻击者可以在这个空间里写入恶意代码,通过汇编指令,达到正常运行程序和恶意代码的一个效果,这里用系统的calc.exe程序举例
这个区域就是程序的空字节空间,我们先用msf生成一段shellcode代码
启动msf,使用payload/windows/exec模块,如果要生成64位的shellcode就使用payload/windows/x64/exec模块,这里calc.exe程序是32位的,所以要用32位的模块
可生成cmd执行的命令,设置完成后用generate -f raw -o 命令输出内容到指定文件里,这里我的shellcode是打开一个文本文档
使用xxd可以看到shellcode的十六进制
回到xdbg,启动程序到entry point处,无论是PE或者ELF文件,里面都有一个entry函数,这个函数很重要,它是程序的入口函数,入口函数的作用是指示程序从何处开始执行。它通常包含程序的主要逻辑,初始化代码以及程序的控制流程。在程序运行时,操作系统或运行时环境会找到入口函数,并从该函数开始执行程序的代码
复制这一行,记录下来
003C1CE0 <c | E8 3EFCFFFF | call calc.3C1923 |
然后去到程序空字节区域,复制记录第一行
003C20C4 | 0000 | add byte ptr ds:[eax],al |
回到entry函数处,选中entry函数,按下空格编辑汇编指令,jmp到0x003C20C4空字节空间处
jmp 0x003C20C4
双击函数,会自动跳转到指定的内存地址,现在我们要先push两个寄存器,之后才能写入shellcode
pushad
push ad指令在32位程序下有用,它的作用是将所有通用寄存器push到栈中,比如EAX, ECX, EDX, EBX这些32位寄存器
pushfd
push fd指令作用是将标志寄存器压入堆栈,这个寄存器包含了各种处理器状态和控制标志位,比如条件码、中断允许位等
然后用010 Editor或者win hex工具打开刚刚msf生成的shellcode文件,复制文件里的十六进制值,回到xdbg的空字节空间,在刚刚添加汇编指令的下一行长按鼠标,框选一段区域,然后按下ctrl+e,将十六进制值粘贴进去
点击确定后,就能看到shellcode的汇编指令
为了使程序正常运行,我们还需要jmp回之前entry函数调用的地址,首先需要pop出之前push进去的寄存器指令,使堆栈平衡,在程序最后一行的后一行写入,因为程序最后一行的空指令处代表的是shellcode的结束地址
popfd
popad
然后写入之前entry原来的指令,让程序正常运行
call 0x003C1923
跳转到entry函数下一行指令处,正常执行程序
jmp 0x003C1CE5
最后用jmp指令替换掉shellcode后的退出指令,因为我们要正常打开calc.exe程序
跳转到popfd处
jmp 0x00F9218B
选择xdbg任务栏里的创可贴图标,保存修改后的文件
运行保存后的文件,程序会运行计算器和文本文档
这里还是推荐自己写汇编指令,用msf生成的shellcode特征太明显,容易被查杀