[安全攻防进阶篇] 七.恶意样本检测之编写代码自动提取IAT表、字符串及时间戳溯源
系統安全繞不開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表、字符串及时间戳溯源的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [论文阅读] (03) 清华张超老师 -
- 下一篇: [Python人工智能] 二十三.基于机