[安全攻防進階篇] 七.惡意樣本檢測之編寫程式碼自動提取IAT表、字串及時間戳溯源

2020-08-12 12:18:00

系統安全繞不開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檔案

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 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中顯示,主要包括:

  • 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 += 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類似軟體開啓,它能夠更清晰地對比各部分內容。

在这里插入图片描述


四.自動提取PE檔案時間戳

接着我們嘗試通過Python來獲取時間戳,python的PE庫是pefile,它是用來專門解析PE檔案的,可靜態分析PE檔案。

第一步,我們通過010Editor分析PE檔案。
其時間戳的輸出結果如下:

  • 06/19/2020 10:46:21

我們希望通過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等地區。當然,只有惡意樣本很多、持續攻擊的時候,單個樣本意義並不大,我們才能 纔能進行更好的溯源,哈哈~

在这里插入图片描述



五.總結

寫到這裏,這篇文章就介紹完畢,這三個技術在惡意代碼溯源和分析中都非常普遍,希望對您有所幫助,最後進行簡單的總結下。

  • PE檔案
    PE檔案基礎
    PE檔案格式解析
  • 編寫程式碼提取IAT表
  • 二進制PE檔案轉字串
  • 自動提取PE檔案時間戳

學安全一年,認識了很多安全大佬和朋友,希望大家一起進步。這篇文章中如果存在一些不足,還請海涵。作者作爲網路安全初學者的慢慢成長路吧!希望未來能更透徹撰寫相關文章。同時非常感謝參考文獻中的安全大佬們的文章分享,深知自己很菜,得努力前行。

有點想家和女神了!月是故鄉圓啊~接着加油。

程式設計沒有捷徑,逆向也沒有捷徑,它們都是搬磚活,少琢磨技巧,幹就對了。什麼時候你把攻擊對手按在地上摩擦,你就贏了,也會慢慢形成了自己的安全經驗和技巧。加油吧,少年希望這個路線對你有所幫助,共勉。

(By:Eastmount 2020-08-12 星期三 中午2點寫於武漢 http://blog.csdn.net/eastmount/ )