系統安全繞不開PE檔案,PE檔案又與惡意樣本檢測及分析緊密相關。前文作者帶領大家逆向分析兩個CrackMe程式,包括逆向分析和原始碼還原。這篇文章主要介紹了PE檔案基礎知識及惡意樣本檢測的三種處理知識,手動編寫程式碼實現了提取IAT表、二進制轉字串及獲取PE檔案時間戳,這是惡意樣本分析和溯源至關重要的基礎,並且網路上還沒見到同時涵蓋這三個功能且詳細的文章,希望對您有所幫助。術路上哪有享樂,爲了提升安全能力,別抱怨,幹就對了~
同時,PE檔案基礎知識推薦作者另一個安全系列:
從2019年7月開始,我來到了一個陌生的專業——網路空間安全。初入安全領域,是非常痛苦和難受的,要學的東西太多、涉及面太廣,但好在自己通過分享100篇「網路安全自學」系列文章,艱難前行着。感恩這一年相識、相知、相趣的安全大佬和朋友們,如果寫得不好或不足之處,還請大家海涵!
接下來我將開啓新的安全系列,叫「安全攻防進階篇」,也是免費的100篇文章,作者將更加深入的去研究惡意樣本分析、逆向分析、內網滲透、網路攻防實戰等,也將通過線上筆記和實踐操作的形式分享與博友們學習,希望能與您一起進步,加油~
話不多說,讓我們開始新的徵程吧!您的點贊、評論、收藏將是對我最大的支援,感恩安全路上一路前行,如果有寫得不好或侵權的地方,可以聯繫我刪除。基礎性文章,希望對您有所幫助,作者目的是與安全人共同進步,也強烈推薦大家去看看錢老師的視訊,加油~
作者的github資源:
軟體安全:https://github.com/eastmountyxz/Software-Security-Course
其他工具:https://github.com/eastmountyxz/NetworkSecuritySelf-study
Windows-Hacker:https://github.com/eastmountyxz/PE-InfoGet
宣告:本人堅決反對利用教學方法進行犯罪的行爲,一切犯罪行爲必將受到嚴懲,綠色網路需要我們共同維護,更推薦大家瞭解它們背後的原理,更好地進行防護。(參考文獻見後)
前文回顧:
[安全攻防進階篇] 一.什麼是逆向分析、逆向分析應用及經典掃雷遊戲逆向 (1)
[安全攻防進階篇] 二.如何學好逆向分析、逆向路線推薦及呂布傳遊戲逆向案例 (2)
[安全攻防進階篇] 三.OllyDbg和Cheat Engine工具逆向分析植物大戰殭屍遊戲 (3)
[安全攻防進階篇] 四.逆向分析之條件語句和回圈語句原始碼還原及流程控制逆向 (4)
[安全攻防進階篇] 五.逆向分析之Win32 API獲取及加解密目錄檔案、OllyDbg逆向其原理 (5)
[安全攻防進階篇] 六.逆向分析之OllyDbg逆向CrackMe01-02及加殼判斷 (6)
什麼是PE檔案?
PE檔案的全稱是Portable Executable,意爲可移植的可執行的檔案,常見的EXE、DLL、OCX、SYS、COM都是PE檔案,PE檔案是微軟Windows操作系統上的程式檔案(可能是間接被執行,如DLL)。
EXE檔案格式:
爲什麼要重點學習這種檔案格式呢?
可執行程式是具有不同的形態的,比如使用者眼中的QQ如下圖所示。
本質上,QQ如下圖所示。
PE檔案格式總體結構
接着讓我們來欣賞下PE檔案格式總體結構圖,包括:MZ頭部、DOS stub、PE檔案頭、可選檔案頭、節表、節等。
本文的第二部分我們將對PE檔案格式進行詳細解析。比如,MZ標頭檔案是定位PE檔案頭開始位置,用於PE檔案合法性檢測。DOS下執行該程式時,會提示使用者「This Program cannot be run in DOS mode」。
PE檔案格式與惡意軟體的關係
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標識、映像檔案頭、可選檔案頭。
對應解析如下圖所示,包括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)
該結構包括3個節,對應上圖的3個struct IMAGE_SECTION_HEADER,即「.test」、「.rdata」、「.data」節,其偏移地址對應下圖紫色區域,分別是400、600、800的位置。
(6) 3個節
注意,程式碼節「.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的全稱是Import Address Table,匯入地址表。 IAT表是執行程式或者DLL爲了實現動態載入和重定位函數地址,用到的一個匯入函數地址表,這裏面記錄了每個匯入函數的名字和所在的DLL名稱。在PE載入的時候系統會載入這些DLL到使用者的地址空間然後把函數地址覆蓋這個表裏的函數地址,然後重構所有用到這個表的程式碼,讓其呼叫直接指向實際函數地址,PE的IAT表會留在記憶體,其中匯入地址表就指示函數實際地址。
首先,我們通過Stud_PE軟體開啓我們的hello-2.5.exe,發現它呼叫了兩個DLL和兩個函數。
同樣的方法我們開啓惡意樣本就可以發現它載入的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 pImageNtHeaders
int 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師傅文章,非常感謝。
第三步,爲什麼要實現這個功能呢?其它工具不是都有類似的功能了。
首先,線上沙箱在分析惡意代碼時,它們也會從IAT表這個角度進行分析。其操作比較簡單,就是將惡意樣本上傳至指定在想網址即可。
我們以 virustotal沙箱爲例,開啓主頁如下圖所示,點選「choose file」,上傳我們的勒索exe檔案。
結果從72個線上引擎中掃描出4個是惡意樣本的引擎,如下圖所示:
我們可以看到該樣本的基本資訊,包括MD5、SHA-1、檔案歷史資訊、PE檔案節點資訊。其中關注的重點是該檔案的匯入函數資訊,在Imports中顯示,主要包括:
ucrtbased.dll主要包括的檔案操作如下圖所示,比如fopen、fputc、system、rename等函數。
其次,當我們要分析海量樣本,從中提取其關聯性時,是需要編寫程式碼實現自動提取特徵,再進行分析的,所以本部分實現了一個C++程式碼提取IAT表的技術,希望對您有所幫助。當獲取各個APT組織的函數呼叫資訊,才能 纔能進一步挖掘它們的特徵及習慣。
下面 下麪分享如何將二進制檔案轉換成十六進制,再轉換成字串的過程。這裏作者真心請教大家兩個問題:
程式碼如下:
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 += 1
if 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類似軟體開啓,它能夠更清晰地對比各部分內容。
接着我們嘗試通過Python來獲取時間戳,python的PE庫是pefile,它是用來專門解析PE檔案的,可靜態分析PE檔案。
第一步,我們通過010Editor分析PE檔案。
其時間戳的輸出結果如下:
我們希望通過Python寫程式碼實現自動化提取,爲後續自動化溯源提供幫助。
第二步,撰寫Python程式碼實現簡單分析。
import pefile
import os,string,shutil,re
PEfile_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,re
PEfile_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,re
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_HEADER
print(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_HEADER
print(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等地區。當然,只有惡意樣本很多、持續攻擊的時候,單個樣本意義並不大,我們才能 纔能進行更好的溯源,哈哈~
寫到這裏,這篇文章就介紹完畢,這三個技術在惡意代碼溯源和分析中都非常普遍,希望對您有所幫助,最後進行簡單的總結下。
學安全一年,認識了很多安全大佬和朋友,希望大家一起進步。這篇文章中如果存在一些不足,還請海涵。作者作爲網路安全初學者的慢慢成長路吧!希望未來能更透徹撰寫相關文章。同時非常感謝參考文獻中的安全大佬們的文章分享,深知自己很菜,得努力前行。
有點想家和女神了!月是故鄉圓啊~接着加油。
程式設計沒有捷徑,逆向也沒有捷徑,它們都是搬磚活,少琢磨技巧,幹就對了。什麼時候你把攻擊對手按在地上摩擦,你就贏了,也會慢慢形成了自己的安全經驗和技巧。加油吧,少年希望這個路線對你有所幫助,共勉。
(By:Eastmount 2020-08-12 星期三 中午2點寫於武漢 http://blog.csdn.net/eastmount/ )