系统安全绕不开PE文件,PE文件又与恶意样本检测及分析紧密相关。前文作者带领大家逆向分析两个CrackMe程序,包括逆向分析和源码还原。这篇文章主要介绍了PE文件基础知识及恶意样本检测的三种处理知识,手动编写代码实现了提取IAT表、二进制转字符串及获取PE文件时间戳,这是恶意样本分析和溯源至关重要的基础,并且网络上还没见到同时涵盖这三个功能且详细的文章,希望对您有所帮助。术路上哪有享乐,为了提升安全能力,别抱怨,干就对了~

同时,PE文件基础知识推荐作者另一个安全系列:

  • [网络安全自学篇] PE文件逆向之PE文件解析、PE编辑工具使用和PE结构修改
  • [网络安全自学篇] PE文件逆向之数字签名解析及Signcode、PEView、010Editor等工具用法
  • [网络安全自学篇] Windows PE病毒原理、分类及感染方式详解

从2019年7月开始,我来到了一个陌生的专业——网络空间安全。初入安全领域,是非常痛苦和难受的,要学的东西太多、涉及面太广,但好在自己通过分享100篇“网络安全自学”系列文章,艰难前行着。感恩这一年相识、相知、相趣的安全大佬和朋友们,如果写得不好或不足之处,还请大家海涵!

接下来我将开启新的安全系列,叫“安全攻防进阶篇”,也是免费的100篇文章,作者将更加深入的去研究恶意样本分析、逆向分析、内网渗透、网络攻防实战等,也将通过在线笔记和实践操作的形式分享与博友们学习,希望能与您一起进步,加油~

  • 推荐前文:网络安全自学篇系列-100篇

话不多说,让我们开始新的征程吧!您的点赞、评论、收藏将是对我最大的支持,感恩安全路上一路前行,如果有写得不好或侵权的地方,可以联系我删除。基础性文章,希望对您有所帮助,作者目的是与安全人共同进步,加油~

文章目录

  • 一.PE文件
    • 1.PE文件基础
    • 2.PE文件格式解析
  • 二.编写代码提取IAT表
  • 三.二进制PE文件转字符串
  • 四.自动提取PE文件时间戳
  • 五.总结

作者的github资源:
软件安全:https://github.com/eastmountyxz/Software-Security-Course
其他工具:https://github.com/eastmountyxz/NetworkSecuritySelf-study
Windows-Hacker:https://github.com/eastmountyxz/PE-InfoGet

声明:本人坚决反对利用教学方法进行犯罪的行为,一切犯罪行为必将受到严惩,绿色网络需要我们共同维护,更推荐大家了解它们背后的原理,更好地进行防护。(参考文献见后)

前文回顾:
[安全攻防进阶篇] 一.什么是逆向分析、逆向分析应用及经典扫雷游戏逆向
[安全攻防进阶篇] 二.如何学好逆向分析、逆向路线推荐及吕布传游戏逆向案例
[安全攻防进阶篇] 三.OllyDbg和Cheat Engine工具逆向分析植物大战僵尸游戏
[安全攻防进阶篇] 四.逆向分析之条件语句和循环语句源码还原及流程控制逆向
[安全攻防进阶篇] 五.逆向分析之Win32 API获取及加解密目录文件、OllyDbg逆向其原理
[安全攻防进阶篇] 六.逆向分析之OllyDbg逆向CrackMe01-02及加壳判断


一.PE文件

1.PE文件基础

什么是PE文件?
PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微软Windows操作系统上的程序文件(可能是间接被执行,如DLL)。

EXE文件格式:

  • DOS:MZ格式
  • WIndows 3.0/3.1:NE(New Executable)、16位Windows可执行文件格式

为什么要重点学习这种文件格式呢?

  • PE文件是可移植、可执行、跨Win32平台的文件格式
  • 所有Win32执行体(exe、dll、kernel mode drivers)
  • 知道PE文件本质后,能更好进行恶意样本分析、APT攻击分析、勒索病毒分析
  • 了解软件加密和加壳的思想,能够PJ相关的PE文件
  • 它是您熟悉Windows操作系统的第一步,包括EXE程序怎么映射到内存,DLL怎么导入等
  • 软件逆向工程的基本思想与PE文件格式息息相关
  • 如果您想成为一名黑客、系统安全工程师,那么精通PE文件是非常必要的

可执行程序是具有不同的形态的,比如用户眼中的QQ如下图所示。

本质上,QQ如下图所示。

PE文件格式总体结构
接着让我们来欣赏下PE文件格式总体结构图,包括:MZ头部、DOS stub、PE文件头、可选文件头、节表、节等。

本文的第二部分我们将对PE文件格式进行详细解析。比如,MZ头文件是定位PE文件头开始位置,用于PE文件合法性检测。DOS下运行该程序时,会提示用户“This Program cannot be run in DOS mode”。

PE文件格式与恶意软件的关系

  • 何为文件感染或控制权获取?
    使目标PE文件具备或启动病毒功能(或目标程序)
    不破坏目标PE文件原有功能和外在形态(如图标)等
  • 病毒代码如何与目标PE文件融为一体呢?
    代码植入
    控制权获取
    图标更改
    Hook

PE文件解析常用工具包括:

  • PEView:可按照PE文件格式对目标文件的各字段进行详细解析。
  • Stud_PE:可按照PE文件格式对目标文件的各字段进行详细解析。
  • Ollydbg:可跟踪目标程序的执行过程,属于用户态调试工具。
  • UltraEdit \ 010Editor:可对目标文件进行16进制查看和修改。

2.PE文件格式解析

我们通过010Editor观察PE文件例子程序hello-2.5.exe的16进制数据,详细讲解PE文件格式。PE文件结构如下图所示,推荐大家使用010Editor工具及其模板来进行PE文件分析。

MZ头部+DOS stub+PE文件头+可选文件头+节表+节

(1) 使用010Editor工具打开PE文件,并运行模板。
该PE文件可分为若干结构,如下图所示。

(2) MZ文件头(000h-03fh)。
下图为hello-2.5.exe的MZ文件头,该部分固定大小为40H个字节。偏移3cH处字段Offset to New EXE Header,指示“NT映象头的偏移地址”,其中000000B0是NT映象头的文件偏移地址,定位PE文件头开始位置,用于PE文件合法性检验。

000000B0指向PE文件头开始位置。

(3) DOS插桩程序(040h-0afh)
DOS Stub部分大小不固定,位于MZ文件头和NT映象头之间,可由MZ文件头中的Offset to New EXE Header字段确定。下图为hello-2.5.exe中的该部分内容。

(4) PE文件头(0b0h-1a7h)
该部分包括PE标识、映像文件头、可选文件头。

  • Signature:字串“PE\0\0”,4个字节(0b0H~0b4H)
  • 映象文件头File Header:14H个字节(0b5H~0c7H)
    偏移2H处,字段Number of Section 给出节的个数(2个字节):0003
    偏移10H处,字段Size of Optional Header 给出可选映象头的大小(2个字节):00E0
  • 可选映象头Optional Header:0c8H~1a7H

对应解析如下图所示,包括PE标识、X86架构、3个节、文件生成时间、COFF便宜、可选头大小、文件信息标记等。

010Editor使用模板定位PE文件各节点信息。

PE文件可选文件头224字节,其对应的字段信息如下所示:

typedef struct _IMAGE_OPTIONAL_HEADER {WORD    Magic;                  /*机器型号,判断是PE是32位还是64位*/BYTE    MajorLinkerVersion;          /*连接器版本号高版本*/BYTE    MinorLinkerVersion;          /*连接器版本号低版本,组合起来就是 5.12 其中5是高版本,C是低版本*/DWORD   SizeOfCode;               /*代码节的总大小(512为一个磁盘扇区)*/DWORD   SizeOfInitializedData;        /*初始化数据的节的总大小,也就是.data*/DWORD   SizeOfUninitializedData;       /*未初始化数据的节的大小,也就是 .data ? */DWORD   AddressOfEntryPoint;          /*程序执行入口(OEP) RVA(相对偏移)*/DWORD   BaseOfCode;               /*代码的节的起始RVA(相对偏移)也就是代码区的偏移,偏移+模块首地址定位代码区*/DWORD   BaseOfData;               /*数据结的起始偏移(RVA),同上*/DWORD   ImageBase;               /*程序的建议模块基址(意思就是说作参考用的,模块地址在哪里)*/DWORD   SectionAlignment;           /*内存中的节对齐*/DWORD   FileAlignment;             /*文件中的节对齐*/WORD    MajorOperatingSystemVersion;    /*操作系统版本号高位*/WORD    MinorOperatingSystemVersion;    /*操作系统版本号低位*/WORD    MajorImageVersion;          /*PE版本号高位*/WORD    MinorImageVersion;          /*PE版本号低位*/WORD    MajorSubsystemVersion;        /*子系统版本号高位*/WORD    MinorSubsystemVersion;        /*子系统版本号低位*/DWORD   Win32VersionValue;          /*32位系统版本号值,注意只能修改为4 5 6表示操作系统支持nt4.0 以上,5的话依次类推*/DWORD   SizeOfImage;               /*整个程序在内存中占用的空间(PE映尺寸)*/DWORD   SizeOfHeaders;            /*所有头(头的结构体大小)+节表的大小*/DWORD   CheckSum;               /*校验和,对于驱动程序,可能会使用*/WORD    Subsystem;              /*文件的子系统 :重要*/WORD    DllCharacteristics;         /*DLL文件属性,也可以成为特性,可能DLL文件可以当做驱动程序使用*/DWORD   SizeOfStackReserve;         /*预留的栈的大小*/DWORD   SizeOfStackCommit;          /*立即申请的栈的大小(分页为单位)*/DWORD   SizeOfHeapReserve;          /*预留的堆空间大小*/DWORD   SizeOfHeapCommit;           /*立即申请的堆的空间的大小*/DWORD   LoaderFlags;             /*与调试有关*/DWORD   NumberOfRvaAndSizes;         /*下面的成员,数据目录结构的项目数量*/IMAGE_DATA_DIRECTORY DataDirectory[16];  /*数据目录,默认16个,16是宏,这里方便直接写成16*/
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

(5) 节表(1a8h-21fh)

  • 表项大小固定,28H个字节;表项个数由映象文件头的字段Number of Section 给出。
  • 每个表项的起始位置起(8个字节),字段Name给出对应节的名称。
  • 每个表项的偏移14H处(4个字节),字段Offset to Raw Data给出对应节的起始文件偏移。

该结构包括3个节,对应上图的3个struct IMAGE_SECTION_HEADER,即“.test”、“.rdata”、“.data”节,其偏移地址对应下图紫色区域,分别是400、600、800的位置。

(6) 3个节

  • 400H-5ffH:代码节
  • 600H-7ffH:引入函数节
  • 800H-9ffH:数据节

注意,代码节“.text”前46H为数据,后面全是0位填充值,为了实现文件的200H对齐,所以代码节是400H到5ffH。

(7) 引入函数节
⽤来从其他DLL中引⼊函数,引入了kernel32.dll和user32.dll,这个节一般名为“.rdata”。引入函数是被某模块调用的但又不在调用者模块中的函数,用来从其他(系统或第三方写的)DLL中引入函数,例如kernel32.dll、gdi32.dll等。

010Editor打开如下图所示:

详细标注信息如下图所示:(图引自HYQ同学,再此感谢)

(8) 数据节
数据节实际大小58h,对齐后大小200h,地址为800h-9ffh,包括对话框弹出的具体内容。


二.编写代码提取IAT表

IAT的全称是Import Address Table,导入地址表。 IAT表是执行程序或者DLL为了实现动态加载和重定位函数地址,用到的一个导入函数地址表,这里面记录了每个导入函数的名字和所在的DLL名称。在PE加载的时候系统会加载这些DLL到用户的地址空间然后把函数地址覆盖这个表里的函数地址,然后重构所有用到这个表的代码,让其调用直接指向实际函数地址,PE的IAT表会留在内存,其中导入地址表就指示函数实际地址。

首先,我们通过Stud_PE软件打开我们的hello-2.5.exe,发现它调用了两个DLL和两个函数。

  • kernel32.dll:ExitProcess
  • user32.dll:MessageBoxA

同样的方法我们打开恶意样本就可以发现它加载的DLL文件及IAT表内容,如下图所示的网络函数及进程、文件操作,包括对应的地址和名称。

第二步,我们打开VS或VC++,新建工程添加main.cpp函数。编写代码如下,它将实现一个自动提取IAT表名称的功能。

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <Dbghelp.h>void ReadNTPEInfo(PIMAGE_NT_HEADERS pImageNtPE);
ULONG RvaToOffset(IMAGE_NT_HEADERS* pNtHeader, ULONG Rva);#define pNtHeaders pImageNtHeadersint main()
{//PE文件名称char file[] = "hello-2.5.exe";char name[] = "test";//DOS头PIMAGE_DOS_HEADER pImageDosHeader;//NT头(包括PE标识+Image_File_Header+OptionHeader)PIMAGE_NT_HEADERS pImageNtHeaders;//标准PE头、PIMAGE_FILE_HEADER pImageFileHeader;//扩展PE头IMAGE_OPTIONAL_HEADER32 pImageOptionHeaders;HANDLE hFile;HANDLE hMapObject;//DOS头PUCHAR uFileMap;hFile = CreateFile(file, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);if (hFile == NULL){printf("打开文件失败\n");system("pause");return 0;}hMapObject = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);if (hMapObject == NULL){printf("创建文件映射内核对对象失败\n");system("pause");return 0;}//PE基址uFileMap = (PUCHAR)MapViewOfFile(hMapObject, FILE_MAP_READ, 0, 0, 0);if (uFileMap == NULL){printf("映射到进程地址空间失败\n");system("pause");return 0;}pImageDosHeader = (PIMAGE_DOS_HEADER)uFileMap;if (pImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE){printf("不是PE结构\n");system("pause");return 0;}//定位到NT PE头pImageNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)uFileMap + pImageDosHeader->e_lfanew);//导入表的相对虚拟地址(RVA)ULONG rva_ofimporttable = pImageNtHeaders->OptionalHeader.DataDirectory[1].VirtualAddress;//根据相对虚拟(rva)地址计算偏移地址(offset)ULONG offset_importtable = RvaToOffset(pImageNtHeaders, rva_ofimporttable);if (!offset_importtable){printf("获取导入表偏移地址失败\n");system("pause");return 0;}PIMAGE_THUNK_DATA s;//取得导入表的地址IMAGE_IMPORT_DESCRIPTOR* pImportTable = (IMAGE_IMPORT_DESCRIPTOR*)((char*)uFileMap + offset_importtable);IMAGE_IMPORT_DESCRIPTOR null_iid;IMAGE_THUNK_DATA null_thunk;memset(&null_iid, 0, sizeof(null_iid));memset(&null_thunk, 0, sizeof(null_thunk));//每个元素代表了一个引入的DLL。for (int i = 0; memcmp(pImportTable + i, &null_iid, sizeof(null_iid)) != 0; i++){//获取DLL名称char* dllName = (char*)(uFileMap + RvaToOffset(pImageNtHeaders, pImportTable[i].Name));printf("模块[%d]: %s\n", i, (char*)dllName);printf("%s\n", (char*)dllName);PIMAGE_THUNK_DATA32 pThunk = (PIMAGE_THUNK_DATA32)(uFileMap + RvaToOffset(pImageNtHeaders, pImportTable[i].FirstThunk));while (pThunk->u1.Ordinal != NULL){PIMAGE_IMPORT_BY_NAME pname = (PIMAGE_IMPORT_BY_NAME)(uFileMap + RvaToOffset(pImageNtHeaders, pThunk->u1.AddressOfData));printf("函数编号: %d 名称: %s\n", pname->Hint, pname->Name);//文件名称 DLL名称 函数名称 组织名称//printf("%s,%s,%s,%s\n", file, (char*)dllName, pname->Name, name);pThunk++;}printf("\n");}system("pause");return 0;
}//读取PE文件信息
void ReadNTPEInfo(PIMAGE_NT_HEADERS pImageNtPE)
{printf("运行平台:   0x%04X\n", pImageNtPE->FileHeader.Machine);printf("节数量:   %d\n", pImageNtPE->FileHeader.NumberOfSections);printf("PE属性:   0x%04X\n", pImageNtPE->FileHeader.Characteristics);
}//计算Offset
ULONG RvaToOffset(IMAGE_NT_HEADERS* pNtHeader, ULONG Rva)
{//PE节IMAGE_SECTION_HEADER* p_section_header;ULONG sNum, i;//取得节表项数目sNum = pNtHeader->FileHeader.NumberOfSections;//取得第一个节表项p_section_header = (IMAGE_SECTION_HEADER*)((BYTE*)pNtHeader + sizeof(IMAGE_NT_HEADERS));for (i = 0; i < sNum; i++){//printf("PE 节名称: %s\n",p_section_header->Name);if ((p_section_header->VirtualAddress <= Rva) && Rva < (p_section_header->VirtualAddress + p_section_header->SizeOfRawData)){return Rva - p_section_header->VirtualAddress + p_section_header->PointerToRawData;}p_section_header++;}return 0;
}

输出结果如下图所示:

同样的方法我们可以获取恶意样本的IAT表,如下图所示:

模块[0]: msvcrt.dll
msvcrt.dll
函数编号: 221 名称: _controlfp
函数编号: 158 名称: __set_app_type
函数编号: 746 名称: memcpy
函数编号: 138 名称: __p__fmode
函数编号: 133 名称: __p__commode
函数编号: 189 名称: _adjust_fdiv
函数编号: 160 名称: __setusermatherr
函数编号: 322 名称: _initterm
函数编号: 170 名称: __wgetmainargs
函数编号: 560 名称: _wcmdln
函数编号: 668 名称: exit
函数编号: 207 名称: _cexit
函数编号: 79 名称: _XcptFilter
函数编号: 253 名称: _exit
函数编号: 204 名称: _c_exit
函数编号: 740 名称: malloc
函数编号: 244 名称: _except_handler3
函数编号: 748 名称: memset模块[1]: urlmon.dll
urlmon.dll
函数编号: 113 名称: UrlMkGetSessionOption模块[2]: WININET.dll
WININET.dll
函数编号: 154 名称: InternetOpenW
函数编号: 105 名称: InternetCheckConnectionW
函数编号: 159 名称: InternetReadFile
函数编号: 107 名称: InternetCloseHandle
函数编号: 153 名称: InternetOpenUrlW模块[3]: KERNEL32.dll
KERNEL32.dll
函数编号: 449 名称: GetCurrentProcessId
函数编号: 453 名称: GetCurrentThreadId
函数编号: 659 名称: GetTickCount
函数编号: 935 名称: QueryPerformanceCounter
函数编号: 1189 名称: SetUnhandledExceptionFilter
函数编号: 1235 名称: UnhandledExceptionFilter
函数编号: 1216 名称: TerminateProcess
函数编号: 611 名称: GetStartupInfoW
函数编号: 1273 名称: WaitForSingleObject
函数编号: 181 名称: CreateThread
函数编号: 448 名称: GetCurrentProcess
函数编号: 1258 名称: VirtualAllocEx
函数编号: 1202 名称: Sleep
函数编号: 633 名称: GetSystemTimeAsFileTime.

该部分代码参考看雪SuperProgram师傅文章,非常感谢。

  • https://www.52pojie.cn/thread-549840-1-1.html

第三步,为什么要实现这个功能呢?其它工具不是都有类似的功能了。
首先,在线沙箱在分析恶意代码时,它们也会从IAT表这个角度进行分析。其操作比较简单,就是将恶意样本上传至指定在想网址即可。

  • virustotal沙箱:https://www.virustotal.com/
  • 360沙箱:https://ti.qianxin.com/
  • Cuckoo沙箱:https://cuckoo.cert.ee/
  • 微步沙箱:https://s.threatbook.cn/

我们以 virustotal沙箱为例,打开主页如下图所示,点击“choose file”,上传我们的勒索exe文件。

结果从72个在线引擎中扫描出4个是恶意样本的引擎,如下图所示:

我们可以看到该样本的基本信息,包括MD5、SHA-1、文件历史信息、PE文件节点信息。其中关注的重点是该文件的导入函数信息,在Imports中显示,主要包括:

  • KERNEL32.dll
  • VCRUNTIME140D.dll
  • ucrtbased.dll

ucrtbased.dll主要包括的文件操作如下图所示,比如fopen、fputc、system、rename等函数。

其次,当我们要分析海量样本,从中提取其关联性时,是需要编写代码实现自动提取特征,再进行分析的,所以本部分实现了一个C++代码提取IAT表的技术,希望对您有所帮助。当获取各个APT组织的函数调用信息,才能进一步挖掘它们的特征及习惯。


三.二进制PE文件转字符串

下面分享如何将二进制文件转换成十六进制,再转换成字符串的过程。这里作者真心请教大家两个问题:

  • 如果自动获取PE文件中定义的字符串呢?
  • 如果自动转换成对应的源代码呢?
  • 如何自动提取每部分功能对应的核心代码呢?

代码如下:

import os
import binascii#-----------------------------------定义转换函数-----------------------------------
def str_to_hex(s):return r"\x"+r'\x'.join([hex(ord(c)).replace('0x', '') for c in s])def hex_to_str(s):return ''.join([chr(i) for i in [int(b, 16) for b in s.split(r'\x')[1:]]])def str_to_bin(s):return ' '.join([bin(ord(c)).replace('0b', '') for c in s])def bin_to_str(s):return ''.join([chr(i) for i in [int(b, 2) for b in s.split(' ')]])#--------------------------------二进制转字节码---------------------------------
fileIn = 'hello-2.5.exe'
fileOut = 'hex-hello'
inp = open(fileIn,'rb')
outp = open(fileOut,'w')i = 0
for c in inp.read():outp.write('\\%#04x' %(c))i += 1if i >= 16:outp.write('\n')i = 0
inp.close()
outp.close()
print('二进制换十六进制成功\n')"""
a="abcdefg"
x=str_to_hex(a)
print(x)
print(hex_to_str(x))
"""#--------------------------------字节码转换字符串--------------------------------
#decode():bytes编码转为str
#encode():str编码转为bytes
f = open('hex-hello', 'r')
outp = open("result-hello.txt",'w', encoding="utf-8")
for n in f.readlines():n = n.strip()txt = n.replace('\\0x','\\x')res = hex_to_str(txt)outp.write(res + '\n')
outp.close()
print('十六进制转字符串成功\n')

如果我们直接打开一个EXE文件,发现它显示如下图所示的内容:

当我们转换成16进制和字符串后,它变成了如下图所示的内容。字符串勉强还能进行下一步和自然语言处理结合的分析,但更详细的分析需要和PE文件结构结合。

如果我们使用IDA、010editor类似软件打开,它能够更清晰地对比各部分内容。


四.自动提取PE文件时间戳

接着我们尝试通过Python来获取时间戳,python的PE库是pefile,它是用来专门解析PE文件的,可静态分析PE文件。

第一步,我们通过010Editor分析PE文件。
其时间戳的输出结果如下:

  • 06/19/2020 10:46:21

我们希望通过Python写代码实现自动化提取,为后续自动化溯源提供帮助。

第二步,撰写Python代码实现简单分析。

import pefile
import os,string,shutil,rePEfile_Path = "MFCApplication.exe"pe = pefile.PE(PEfile_Path)
print(type(pe))
print(pe)

输出如下图所示结果,这是Python包自定义的PE结构。

squeezed text表示python的一种编程规范要求,简称pep8,你只需要将鼠标放到Squeezed上,右键Copy即可查看内容,显示的是该PE文件的基本结构。如下所示,与010Editor分析的结果前后是一致的。

----------DOS_HEADER----------[IMAGE_DOS_HEADER]
0x0        0x0   e_magic:                       0x5A4D
0x2        0x2   e_cblp:                        0x90
0x4        0x4   e_cp:                          0x3
0x6        0x6   e_crlc:                        0x0
0x8        0x8   e_cparhdr:                     0x4
0xA        0xA   e_minalloc:                    0x0
0xC        0xC   e_maxalloc:                    0xFFFF
0xE        0xE   e_ss:                          0x0
0x10       0x10  e_sp:                          0xB8
0x12       0x12  e_csum:                        0x0
0x14       0x14  e_ip:                          0x0
0x16       0x16  e_cs:                          0x0
0x18       0x18  e_lfarlc:                      0x40
0x1A       0x1A  e_ovno:                        0x0
0x1C       0x1C  e_res:
0x24       0x24  e_oemid:                       0x0
0x26       0x26  e_oeminfo:                     0x0
0x28       0x28  e_res2:
0x3C       0x3C  e_lfanew:                      0x108     ----------NT_HEADERS----------[IMAGE_NT_HEADERS]
0x108      0x0   Signature:                     0x4550    ----------FILE_HEADER----------[IMAGE_FILE_HEADER]
0x10C      0x0   Machine:                       0x14C
0x10E      0x2   NumberOfSections:              0xA
0x110      0x4   TimeDateStamp:                 0x5EEC977D [Fri Jun 19 10:46:21 2020 UTC]
0x114      0x8   PointerToSymbolTable:          0x0
0x118      0xC   NumberOfSymbols:               0x0
0x11C      0x10  SizeOfOptionalHeader:          0xE0
0x11E      0x12  Characteristics:               0x102
Flags: IMAGE_FILE_32BIT_MACHINE, IMAGE_FILE_EXECUTABLE_IMAGE----------OPTIONAL_HEADER----------[IMAGE_OPTIONAL_HEADER]
0x120      0x0   Magic:                         0x10B
0x122      0x2   MajorLinkerVersion:            0xE
0x123      0x3   MinorLinkerVersion:            0x1A
0x124      0x4   SizeOfCode:                    0x700C00
0x128      0x8   SizeOfInitializedData:         0x2F1E00
0x12C      0xC   SizeOfUninitializedData:       0x0
0x130      0x10  AddressOfEntryPoint:           0x36CE65
0x134      0x14  BaseOfCode:                    0x1000
0x138      0x18  BaseOfData:                    0x1000
0x13C      0x1C  ImageBase:                     0x400000
....----------PE Sections----------[IMAGE_SECTION_HEADER]
0x200      0x0   Name:                          .textbss
0x208      0x8   Misc:                          0x35B30B
0x208      0x8   Misc_PhysicalAddress:          0x35B30B
0x208      0x8   Misc_VirtualSize:              0x35B30B
0x20C      0xC   VirtualAddress:                0x1000
0x210      0x10  SizeOfRawData:                 0x0
....

第三步,注意这里同样可以通过Python获取IAT表相关信息。

import pefile
import os,string,shutil,rePEfile_Path = "MFCApplication.exe"#解析PE文件
pe = pefile.PE(PEfile_Path)
print(type(pe))
print(pe)#获取导入表信息
for item in pe.DIRECTORY_ENTRY_IMPORT:print(item.dll)for con in item.imports:print(con.name)print("") #换行

输出如下所示的结果,包括KERNEL32.dll、USER32.dll等。

b'KERNEL32.dll'
b'RtlUnwind'
b'GetModuleHandleExW'
b'GetCommandLineA'
b'GetSystemInfo'
b'CreateThread'
...b'USER32.dll'
b'DlgDirSelectExA'
b'FindWindowExA'
b'FindWindowA'
b'SetParent'
b'ChildWindowFromPointEx'
...

对应010editor的PE软件分析结果如下:

第四步,获取PE时间。通过pe.DOS_HEADER、pe.FILE_HEADER和正则表达式等方法获取对应的内容。

import pefile
import os,string,shutil,rePEfile_Path = "MFCApplication.exe"#解析PE文件
pe = pefile.PE(PEfile_Path, fast_load=True)
print(type(pe))
print(pe)
print(pe.get_imphash())#显示DOS_HEADER
dh = pe.DOS_HEADER#显示NT_HEADERS
nh = pe.NT_HEADERS#显示FILE_HEADER
fh = pe.FILE_HEADER#显示OPTIONAL_HEADER
oh = pe.OPTIONAL_HEADERprint(type(fh)) #<class 'pefile.Structure'>
print(str(fh))#通过正则表达式获取时间
p = re.compile(r'[[](.*?)[]]', re.I|re.S|re.M)   #最小匹配
res = re.findall(p, str(fh))
print(res[1])                                    #第一个值是IMAGE_FILE_HEADER
# Fri Jun 19 10:46:21 2020 UTC

最终输出结果如下所示,这样我们就完成了Python自动化提取PE软件的时间戳过程。任何一个PE软件都能进行提取,该时间戳也记录了软件的编译时间。

<class 'pefile.PE'>
Squeezed text(347 lines).<class 'pefile.Structure'>
[IMAGE_FILE_HEADER]
0x10C      0x0   Machine:                       0x14C
0x10E      0x2   NumberOfSections:              0xA
0x110      0x4   TimeDateStamp:                 0x5EEC977D [Fri Jun 19 10:46:21 2020 UTC]
0x114      0x8   PointerToSymbolTable:          0x0
0x118      0xC   NumberOfSymbols:               0x0
0x11C      0x10  SizeOfOptionalHeader:          0xE0
0x11E      0x12  Characteristics:               0x102     Fri Jun 19 10:46:21 2020 UTC

为什么要进行这样的时间戳分析呢?
在过去的四年中,安天的工程师们关注到了中国的机构和用户反复遭遇来自“西南方向”的网络入侵尝试。这些攻击虽进行了一些掩盖和伪装,我们依然可以将其推理回原点——来自南亚次大陆的某个国家。他们是怎么做的呢?

安天通过对样本集的时间戳、时区分析进行分析,发现其来自南亚。样本时间戳是一个十六进制的数据,存储在PE文件头里,该值一般由编译器在开发者创建可执行文件时自动生成,时间单位细化到秒,通常可以认为该值为样本生成时间(GMT时间)。

时间戳的分析需要收集所有可用的可执行文件时间戳,并剔除过早的和明显人为修改的时间,再将其根据特定标准分组统计,如每周的天或小时,并以图形的形式体现,下图是通过小时分组统计结果:

从上图的统计结果来看,如果假设攻击者的工作时间是早上八九点至下午五六点的话,那么将工作时间匹配到一个来自UTC+4或UTC+5时区的攻击者的工作时间。根据我们匹配的攻击者所在时区(UTC+4 或UTC+5),再对照世界时区分布图,就可以来推断攻击者所在的区域或国家。

所以当我们受到持续攻击,并且样本存在相似性的时候,就可以通过这种方法简单溯源其攻击地区来源。当然该方法比较粗,您需要进一步结合样本特征深入分析。

最终代码:

import pefile
import time
import warnings
import datetime
import os,string,shutil,re#忽略警告
warnings.filterwarnings("ignore")PEfile_Path = "MFCApplication.exe"#----------------------------------第一步 解析PE文件-------------------------------
pe = pefile.PE(PEfile_Path, fast_load=True)
print(type(pe))
print(pe)
print(pe.get_imphash())#显示DOS_HEADER
dh = pe.DOS_HEADER#显示NT_HEADERS
nh = pe.NT_HEADERS#显示FILE_HEADER
fh = pe.FILE_HEADER#显示OPTIONAL_HEADER
oh = pe.OPTIONAL_HEADERprint(type(fh)) #<class 'pefile.Structure'>
print(str(fh))#----------------------------------第二步 获取UTC时间-------------------------------
#通过正则表达式获取时间
p = re.compile(r'[[](.*?)[]]', re.I|re.S|re.M)   #最小匹配
res = re.findall(p, str(fh))
print(res[1])                                    #第一个值是IMAGE_FILE_HEADER
res_time = res[1].replace(" UTC","")
# Fri Jun 19 10:46:21 2020 UTC#获取当前时间
t = time.ctime()
print(t,"\n")                                    # Thu Jul 16 20:42:18 2020
utc_time = datetime.datetime.strptime(res_time, '%a %b %d %H:%M:%S %Y')
print("UTC Time:", utc_time)
# 2020-06-19 10:46:21#----------------------------------第三步 全球时区转换-------------------------------
#http://zh.thetimenow.com/india
#UTC时间比北京时间晚八个小时 故用timedelta方法加上八个小时
china_time = datetime.datetime.strptime(res_time, '%a %b %d %H:%M:%S %Y') + datetime.timedelta(hours=8)
print("China Time:",china_time)#美国 UTC-5
america_time = datetime.datetime.strptime(res_time, '%a %b %d %H:%M:%S %Y') - datetime.timedelta(hours=5)
print("America Time:",america_time)#印度 UTC+5
india_time = datetime.datetime.strptime(res_time, '%a %b %d %H:%M:%S %Y') + datetime.timedelta(hours=5)
print("India Time:",india_time)#澳大利亚 UTC+10
australia_time = datetime.datetime.strptime(res_time, '%a %b %d %H:%M:%S %Y') + datetime.timedelta(hours=10)
print("Australia Time",australia_time)#俄罗斯 UTC+3
russia_time = datetime.datetime.strptime(res_time, '%a %b %d %H:%M:%S %Y') + datetime.timedelta(hours=3)
print("Russia Time",russia_time)#英国 UTC+0
england_time = datetime.datetime.strptime(res_time, '%a %b %d %H:%M:%S %Y')
print("England Time",england_time)#日本 UTC+9
japan_time = datetime.datetime.strptime(res_time, '%a %b %d %H:%M:%S %Y') + datetime.timedelta(hours=9)
print("Japan Time",england_time)#德国 UTC+1
germany_time = datetime.datetime.strptime(res_time, '%a %b %d %H:%M:%S %Y') + datetime.timedelta(hours=1)
print("Germany Time",germany_time)#法国 UTC+1
france_time = datetime.datetime.strptime(res_time, '%a %b %d %H:%M:%S %Y') + datetime.timedelta(hours=1)
print("France Time",france_time)#加拿大 UTC-5
canada_time = datetime.datetime.strptime(res_time, '%a %b %d %H:%M:%S %Y') - datetime.timedelta(hours=5)
print("Canada Time:",canada_time)#越南 UTC+7
vietnam_time = datetime.datetime.strptime(res_time, '%a %b %d %H:%M:%S %Y') + datetime.timedelta(hours=7)
print("Vietnam Time:",vietnam_time)

输出结果如下图所示,不同地区有对应的时间分布,如果正常作息是早上9点到12点、下午2点到5点,从结果看更像是来自India、England、Japan等地区。当然,只有恶意样本很多、持续攻击的时候,单个样本意义并不大,我们才能进行更好的溯源,哈哈~


五.总结

写到这里,这篇文章就介绍完毕,这三个技术在恶意代码溯源和分析中都非常普遍,希望对您有所帮助,最后进行简单的总结下。

  • PE文件
    PE文件基础
    PE文件格式解析
  • 编写代码提取IAT表
  • 二进制PE文件转字符串
  • 自动提取PE文件时间戳

学安全一年,认识了很多安全大佬和朋友,希望大家一起进步。这篇文章中如果存在一些不足,还请海涵。作者作为网络安全初学者的慢慢成长路吧!希望未来能更透彻撰写相关文章。同时非常感谢参考文献中的安全大佬们的文章分享,深知自己很菜,得努力前行。

有点想家和女神了!月是故乡圆啊~接着加油。

编程没有捷径,逆向也没有捷径,它们都是搬砖活,少琢磨技巧,干就对了。什么时候你把攻击对手按在地上摩擦,你就赢了,也会慢慢形成了自己的安全经验和技巧。加油吧,少年希望这个路线对你有所帮助,共勉。

(By:Eastmount 2020-08-12 星期三 中午2点写于武汉 http://blog.csdn.net/eastmount/ )


2020年8月18新开的“娜璋AI安全之家”,主要围绕Python大数据分析、网络空间安全、人工智能、Web渗透及攻防技术进行讲解,同时分享CCF、SCI、南核北核论文的算法实现。娜璋之家会更加系统,并重构作者的所有文章,从零讲解Python和安全,写了近十年文章,真心想把自己所学所感所做分享出来,还请各位多多指教,真诚邀请您的关注!谢谢。

[安全攻防进阶篇] 七.恶意样本检测之编写代码自动提取IAT表、字符串及时间戳溯源相关推荐

  1. [系统安全] 三十四.恶意代码检测(4)编写代码自动提取IAT表、字符串及时间信息

    您可能之前看到过我写的类似文章,为什么还要重复撰写呢?只是想更好地帮助初学者了解病毒逆向分析和系统安全,更加成体系且不破坏之前的系列.因此,我重新开设了这个专栏,准备系统整理和深入学习系统安全.逆向分 ...

  2. [安全攻防进阶篇] 一.什么是逆向分析、逆向分析应用及经典扫雷游戏逆向

    从2019年7月开始,我来到了一个陌生的专业--网络空间安全.初入安全领域,是非常痛苦和难受的,要学的东西太多.涉及面太广,但好在自己通过分享100篇"网络安全自学"系列文章,艰难 ...

  3. [安全攻防进阶篇] 二.如何学好逆向分析、逆向路线推荐及吕布传游戏逆向案例

    从2019年7月开始,我来到了一个陌生的专业--网络空间安全.初入安全领域,是非常痛苦和难受的,要学的东西太多.涉及面太广,但好在自己通过分享100篇"网络安全自学"系列文章,艰难 ...

  4. [安全攻防进阶篇] 六.逆向分析之OllyDbg逆向CrackMe01-02及加壳判断

    前文作者讲解了OllyDbg和在线沙箱的逆向分析过程,分享了恶意软件如何通过宏脚本发送勒索信息或密码至用户邮箱.这篇文件将带领大家逆向分析两个CrackMe程序,包括逆向分析和源码还原,基础性文章,希 ...

  5. [安全攻防进阶篇] 四.逆向分析之条件语句和循环语句源码还原及流程控制逆向

    从2019年7月开始,我来到了一个陌生的专业--网络空间安全.初入安全领域,是非常痛苦和难受的,要学的东西太多.涉及面太广,但好在自己通过分享100篇"网络安全自学"系列文章,艰难 ...

  6. PowerShell攻防进阶篇:nishang工具用法详解

    PowerShell攻防进阶篇:nishang工具用法详解 导语:nishang,PowerShell下并肩Empire,Powersploit的神器. 开始之前,先放出个下载地址! 下载地址:htt ...

  7. 准确率99%!基于深度学习的二进制恶意样本检测——瀚思APT 沙箱恶意文件检测使用的是CNN,LSTM TODO...

    所以我们的流程如图所示.将正负样本按 1:1 的比例转换为图像.将 ImageNet 中训练好的图像分类模型作为迁移学习的输入.在 GPU 集群中进行训练.我们同时训练了标准模型和压缩模型,对应不同的 ...

  8. Activiti7工作流引擎:进阶篇(七) 调用子流程 CallActivityTask

    知识传送门 >>>>>>>>>>>>>>>>>>> 造句:"朋友" ...

  9. 阿里云天池大赛赛题(机器学习)——阿里云安全恶意程序检测(完整代码)

    目录 赛题背景 全代码(ML 和 DL) 特征工程进阶与方案优化 代码 特征工程进阶部分 基于LightGBM 的模型验证 模型测试 深度学习解决方案:TextCNN建模 代码 数据读取 数据预处理 ...

最新文章

  1. Django web框架
  2. 第一次在Linux系统上操作mysql数据库,看完这篇轻松应对
  3. DBShop前台RCE
  4. Oracle新建用户并授权
  5. 设计模式系列:小小总结
  6. PySide 简易教程二-------工欲善其事,必先利其器
  7. dedeCms在首页显示要搜索的关键词
  8. 八皇后(N皇后)问题算法程序(回溯法)
  9. ASP.NET留言板 文字加表情
  10. 小小一款代码编辑器竟然也可以有程序运行之功能——Sublime Text3运行各种语言程序的总结
  11. 什么是CPU虚拟化?打开好还是关闭好?
  12. 红月3.8登入器重新设计并且支持窗口化兼容win10
  13. html 字体立体效果,如何利用CSS3制作3D效果文字具体实现样式
  14. android照片同步到另一部手机,换手机后怎么把照片转移到新手机上?
  15. 小熊学Java第六天
  16. vscode打开项目从中文界面变成英文界面的问题
  17. python实现最小二乘法的线性回归_Python中的线性回归与闭式普通最小二乘法
  18. python微控制器编程pdf_Python高性能编程 中文高清pdf完整版[17MB]
  19. 【心理】执行功能障碍
  20. NOIP 陶陶摘苹果

热门文章

  1. 测试工具:Windows下超强日志工具BareTail
  2. Spring Tool Suite记录
  3. openwrt多wan限上下行速脚本,基于qosv4,imq模块替换成ifb模块[ZT]
  4. 联合索引使用规则(转载)
  5. Android app:transformNativeLibsWithStripDebugSymbolForDebug错误分析
  6. Android开源工具库
  7. [一天一个小知识]instanceof
  8. js中的局部变量和全局变量
  9. 解决 Ubuntu Software (Software Center) Crash 问题
  10. [转]第(前)k大数问题