91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

從病毒開始聊聊那些windows下大雜燴

發布時間:2020-09-02 20:45:14 來源:網絡 閱讀:858 作者:長路慢 欄目:開發技術

?豬年送安康,祝大家新一年健康、快樂。愿大家都做一個勤奮努力、真誠奉獻的人,幸運會永遠的眷顧你們。
?
引子:
?某一天饒有興趣在卡飯上瀏覽著帖子,故事的相遇就那么簡單。當時一條評論勾起我的好奇心,那么好逆向開始。
?根據我的習慣,拿到樣本我會線上惡意代碼分析,直接拉到virustotal之類的網站上,看看是否已經被大多數殺毒軟件所能識別,看一些有價值的數據,如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片一:基本信息
?當看到這個頁面時候,看到最后的分析日期是18年11月,又看了一下導出表的函數信息,是一款老病毒。根據各大廠商對這個病毒行為特性、分析定位為特洛伊、偽裝等,定位不一很正常......,其實興趣降低了一大半,并不是新鮮品種,但不能這樣侮辱一個病毒!接著習慣性拉入到IDA中,當我看到熟悉的匯編之后,如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片二:GetProcAddress實現
?當點進去其中的一個函數,看到了fs寄存器,且一大堆比較復雜的操作,看到熟悉的匯編指令以后,心中已有定數,這是一個自己實現的GetProcAddress函數。
?
?

理論篇 匯編篇
保護模式,定時器,PE雜談 手動實現GetProcAddress函數及Hash加密字符比對

?
一、理論篇
?先來看病毒樣本中的一段代碼,如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片三:CreateTimerQueueTimer
?
?還記著以前分析熊貓燒香時候的定時器,如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片四:SetTimer
?
?惡意代碼大多都會利用到WinAPI提供的定時器操作,從而實現有規劃、周期性的惡意代碼,既然那么重要,所以我們先來聊聊那些定時器。
?經常用ARK工具的朋友,應該都使用過遍歷定時器相關的功能,有用戶層定時器,IO定時器,DCP定時器,包括我們的時鐘中斷機制,都是具有定時器相關操作的。
?我們先從用戶層入手,windbg下深入分析一下上面提到的兩個定時器操作,NtSetTimer匯編源碼如下所示:
注:(為什么SetTimer會調用NtSetTimer,請看https://blog.51cto.com/13352079/2343452)
函數原型如下:

UINT_PTR SetTimer(

    HWND hWnd,                        // 窗口句柄

    UINT_PTR nIDEvent,            // 定時器ID,多個定時器時,可以通過該ID判斷是哪個定時器

    UINT nElapse,                       // 時間間隔,單位為毫秒

    TIMERPROC lpTimerFunc  // 回調函數

);

?為了更好的理解定時器的匯編代碼,簡單分析一下函數調用的過程,就是如何獲取當前線程。

kd> u PsGetCurrentProcess
nt!PsGetCurrentProcess:
mov     eax,dword ptr fs:[00000124h]
mov     eax,dword ptr [eax+50h]
ret

?
保護模式:
?那么根據書籍或者相關資料,我們知道fs寄存器的值恒定(注意windows7 32位測試的),內核態是fs = 0x30,用戶態 fs = 0x3B,fs在內核態指向_KPCR,用戶態指向_TEB.什么依據呢?憑什么說fs指向KPCR? 這里屬于保護模式得內容,但是這里還是想與大家一起分享其中的原理,那么先說說段寄存器,為了方便理解做了一個簡陋的圖,如下所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片五:段寄存器
?
?其實段寄存器共96位,只有其中的16位是可見的,剩余部分隱藏,可見的部分就是我們能查詢到的立即數,也叫做選擇子。隱藏部分只可以被CPU操作,不可以使用指令進行操作。
?GDT全局描述符表,系統中按照不同的屬性、類型進行描述,所以這些描述符統一存儲到內存中,并且形成了一個數組,這就是GDT。全局描述符的索引保存在了可見部分16位的選擇子中,這就是GDT與段選擇子的關聯。如何從選擇子中知道索引呢?如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片六:選擇子
?
?高13位是索引號,也就是下標。TI = 0 代表GDT,TI = 1代表LDT。RPL是當前請求特權級別,權限檢查會用到,這里不對權限檢測做詳細介紹。
?清楚了上面的知識后,我們分析一下內核態fs = 30,16位選擇子內容,如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片七:解析fs寄存器
?
?通過上述分解,我們知道了fs在GDT中的第六項(0開始),接著獲取gdtr,并且獲取段描述符的屬性狀態,如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片八:gdtr寄存器
?段描述符如何來分解?段描述符都有那些屬性呢?如下圖所示:

從病毒開始聊聊那些windows下大雜燴

??????????????????圖片九:通用描述符
?
介紹一些主要屬性:
?

L D/B P S DPL TYPE G
64位代碼段 默認操作大小 段有效值 描述符類型 描述符特權級別 段類型 粒度

?
?我們按照上圖分解,取Base Address,按照想對應的規則10101100 01001000 10000100 01000000進行地址拼接,其實這個就獲取到了KPCR的結構。
?fs寄存器其實擁有那么的數據量,本質是是從結構數據中獲取,便于操作。推薦一下bochs這款x86硬件平臺的開源模擬器,學習保護模式,除了書中獲取相關知識以外,還可以多多閱讀源碼,才能更深層的學習理解。
?
?回到主題,我們既然知道fs在內核態指向的是什么了,我們觀察一下fs:[00000124h]是什么?結構體相關內容以前介紹過,這里不羅嗦,如下圖所示:
從病毒開始聊聊那些windows下大雜燴

??????????????????圖片十:_KPRC
?fs寄存器內核態指向的是_KPRC,fs:[0x124]指向CurrentThread(_EPROCESS),有了這些基礎以后,我們繼續分析NtSetTimer得調用過程。
NtSetTimer匯編代碼:(因為排版 所以就上圖了)
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片十一:NtSetTimer解析1
?如上圖所示,先是獲取_ETHREAD,然后獲取了ETHREAD+0x13a(Previous Mode),如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片十二:

?什么是Previous Mode?,簡單來說調用Nt或Zw版本時,系統調用機制將調用線程捕獲到內核模式,判定參數是否來源于用戶模式標志。

?The native system services routine checks the PreviousMode field of the calling thread to determine whether the parameters are from a user-mode source.
詳細得內容介紹參考:https://msdn.microsoft.com/zh-cn/windows/desktop/ff559860
PreviousMode其中得兩個狀態值
?1、UserMode 狀態碼是1
?2、KernelMode 狀態碼是0

定時函數分析:
?所以上圖中與0進行判斷,判斷當前是否內核態,是則跳轉0x8402fdd。我們先來看看如果是內核態,是怎樣一條執行路線,如下圖所示:
?從病毒開始聊聊那些windows下大雜燴
??????????????????圖片十三:定時器ID判定
?第二個參數必須大于等于0,否則會拋出異常,繼續看,如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片十四:內核態匯編解析
?OD中我們跟中一下看是否真的追加了第五個參數,如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片十五:NtUserSetTimer

?如果為0則跳轉,跳轉位置如下圖所示:
從病毒開始聊聊那些windows下大雜燴
從病毒開始聊聊那些windows下大雜燴
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片十六:ExpSetTimer
?我們會發現,SetTimer->NtUserSetTimer->Wow64得函數(如果32位運行在64位)-->KiFastSystemCall->ExSetTimer-->ObReferenceObjectByHandle-->..........
?所以SetTimer在內核態得過曾還是比較復雜得,大家可以通過函數棧來觀察到底如何運作得,這告訴我們一個道理,誰HOOK得函數越底層,誰就有可能做更多得事情。
?如果Previous Mode = UserMode呢?如何執行?如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片十七:用戶態匯編分析
?在做了一些判斷賦值及參數保存操作以后,又跳回了與內核態執行得流程,所以說不論怎樣最終還會調用那些函數。
?關于SetTimer函數簡單得分析到這里,我們下面接著看CreateTimerQueueTimer函數,先來看函數原型:

BOOL WINAPI CreateTimerQueueTimer(
  _Out_    PHANDLE             phNewTimer,
  _In_opt_ HANDLE              TimerQueue,
  _In_     WAITORTIMERCALLBACK Callback,
  _In_opt_ PVOID               Parameter,
  _In_     DWORD               DueTime,
  _In_     DWORD               Period,
  _In_     ULONG               Flags
);
圖三中已經對參數進行了詳細得介紹,這里不再做介紹

OD中我們動態觀察一下,如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片十八:CreateTimerQueueTimer
?函數內部調用了RtlCreateTimer,我們繼續動態跟蹤,如下所示:
從病毒開始聊聊那些windows下大雜燴
從病毒開始聊聊那些windows下大雜燴
?內部調用了大量的函數,其中包括TpSetTimer也在其中,基本確定內部是調用TpSetTimer來實現該函數功能,在windbg中簡答了分析一下,內部調用了TppTimerpSet,且使用了Slim讀寫鎖機制,因為觸碰到了盲區,感覺不太準確,也找不到相關的參考所以有興趣的朋友可以深入分析一下,這里就不講解了。
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片十九:TppTimerpSet
?這里以上是給大家提供一些函數分析的思路罷了,有時間的話寫一篇相關的話題一起討論一下。
?
PE雜談 :
?關于PE知識雖然看起來雜亂,但還是比較有序的。PE涉獵的范圍較廣,PE文件是指一種格式,如可執行文件、動態鏈接庫、驅動等等,都屬于PE格式的文件。
?想深入學習的朋友,推薦一本書籍《Windows PE權威指南》,里面內容是win32匯編撰寫而成。
?我們這里只對用到的基本知識和導出表做介紹,PE結構體大概分為幾個部分,如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片二十:PE大體結構
?上面順序是一定的,PE是一個有序結構,標準的PE格式每個結構體對應的偏移是固定的,當然也有很多惡意代碼會對PE結構體進行數據壓縮等技術,達到隱匿、免殺的目的。
?我們介紹一下DOS頭的數據介紹,其實我們用VS編程的時候就可以獲取到結構體,這里不再windbg下獲取了,如下所示:

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                                           // Magic number
    WORD   e_cblp;                                              // Bytes on last page of file
    WORD   e_cp;                                                 // Pages in file
    WORD   e_crlc;                                               // Relocations
    WORD   e_cparhdr;                                        // Size of header in paragraphs
    WORD   e_minalloc;                                       // Minimum extra paragraphs needed
    WORD   e_maxalloc;                                      // Maximum extra paragraphs needed
    WORD   e_ss;                                                // Initial (relative) SS value
    WORD   e_sp;                                                // Initial SP value
    WORD   e_csum;                                           // Checksum
    WORD   e_ip;                                                 // Initial IP value
    WORD   e_cs;                                               // Initial (relative) CS value
    WORD   e_lfarlc;                                           // File address of relocation table
    WORD   e_ovno;                                           // Overlay number
    WORD   e_res[4];                                          // Reserved words
    WORD   e_oemid;                                         // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                                      // OEM information; e_oemid specific
    WORD   e_res2[10];                                     // Reserved words
    LONG   e_lfanew;                                         // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

?上面結構體是DOS頭部的全部信息,其中DOS中兩個重要屬重點介紹一下:

e_magi
“魔術”標志,判斷是否PE格式第一道防線,恒定值為0x4D5A(MZ)
e_lfanew
Dos頭與NT頭之間有一部分Dos Stub的數據(Dos的數據)大小不確定,意味著NT頭偏移不確定,所以 e_lfanew記錄了該模塊NT的偏移

?如何找到NT頭?模塊基址 + e_lfanew = NT的位置。第二部分我們會用匯編獲取且深入學習,用C/C++如何實現呢?如下代碼所示:


// 1.獲取PE格式文件
m_strNamePath = PathName;

// 2.打開文件
HANDLE hFile = CreateFile(PathName, GENERIC_READ | GENERIC_WRITE, FALSE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

if ((int)hFile <= 0){ AfxMessageBox(L"當前進程有可能被占用或者意外錯誤"); return FALSE; }

HANDLE hFile = NULL;

// 3.獲取文件大小
DWORD dwSize = GetFileSize(hFile, NULL);

// 4.申請堆空間
PuPEInfo::m_pFileBase = (void *)malloc(dwSize);

memset(PuPEInfo::m_pFileBase, 0, dwSize);

DWORD dwRead = 0;

OVERLAPPED OverLapped = { 0 };

void* pFileBaseAddress = nullptr;

// 5.讀取文件到內存
int nRetCode =  ReadFile(hFile, pFileBaseAddress, dwSize, &dwRead, &OverLapped);

// 6.轉換成DOS頭結構體
PIMAGE_DOS_HEADER pDosHander = (PIMAGE_DOS_HEADER)pFileBaseAddress;

// 7.Dos起始地址 + e_lfanew = NT頭
PIMAGE_NT_HEADERS pHeadres = (PIMAGE_NT_HEADERS)(pDosHander->e_lfanew + (LONG)pFileBaseAddress);

?如上述代碼,獲取可執文件路徑,創建(獲取文件句柄)、打開文件、讀取文件大小、申請堆空間、讀取文件數據到內存(加載到了內存)、獲取NT頭,第7步正式上述所表達的 模塊基址 + e_lfanew
NT頭內部是如何?如下所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片二十一:NT結構
如上所示,NT分為三部分,介紹如下:

Signature FileHeader OptionalHeader
標記,判斷是否PE格式第二道防線,恒定值為0x4550(PE) 文件頭,存儲這PE文件的基本信息 存儲著關于PE文件的附加信息

既然已經介紹了PE格式兩條應規定,兩道標桿,如果判斷是否是一個PE格式的文件呢?如下代碼所示:

//判定是否是PE文件
BOOL IsPE(char* lpBase)
{
    PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBase;
    if (pDos->e_magic != IMAGE_DOS_SIGNATURE/*0x4D5A*/)
    {
        return FALSE;
    }
    PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + lpBase);
    if (pNt->Signature != IMAGE_NT_SIGNATURE/*0x4550*/)
    {
        return FALSE;
    }
    return TRUE;
}

FileHeader結構體如下:

// File header format.
typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
Machine NumberOfSections TimeDateStamp NumberOfSymbols
文件運行平臺 區段的數量 文件創建時間 符號個數
SizeOfOptionalHeader PointerToSymbolTable Characteristics
擴展頭大小 符號表偏移 PE文件屬性

補充:
?1、Machine:0x014c代表i386,平時intel32為平臺,0x0200表示Intel 64為平臺。
?2、NumberOfSymbols:這個很重要了,你遍歷節表先要獲取數量,這個就是。
?3、Characteristics:PE的文件屬性值,如下所示:

數值 介紹 宏定義
0x0001 從文件中刪除重定位信息 IMAGE_FILE_RELOCS_STRIPPED
0x0002 可執行文件 IMAGE_FILE_EXECUTABLE_IMAGE
0x0004 行號信息無 IMAGE_FILE_LINE_NUMS_STRIPPED
0x0008 符號信息無 IMAGE_FILE_LOCAL_SYMS_STRIPPED
0x0010 強制性縮減工作 IMAGE_FILE_AGGRESIVE_WS_TRIM
0x0020 應用程序可以處理> 2GB的地址 IMAGE_FILE_LARGE_ADDRESS_AWARE
0x0080 機器字的字節相反的 IMAGE_FILE_BYTES_REVERSED_LO
0x0100 運行在32位平臺 IMAGE_FILE_32BIT_MACHINE
0x0200 調試信息從.DBG文件中的文件中刪除 IMAGE_FILE_DEBUG_STRIPPED
0x0400 如果文件在可移動媒體上,則從交換文件復制并運行。 IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP
0x0800 如果在網絡存儲介質中,則從交換文件中復制并運行。 IMAGE_FILE_NET_RUN_FROM_SWAP
0x1000 系統文件 IMAGE_FILE_SYSTEM
0x2000 DLL文件 IMAGE_FILE_DLL
0x4000 單核CPU運行 IMAGE_FILE_UP_SYSTEM_ONLY
0x8000 機器字的字節相反的 IMAGE_FILE_BYTES_REVERSED_HI

?
OptionalHeader結構體介紹:


typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    //
    // NT additional fields.
    //

    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

挑重點介紹一下:

Magic AddressOfEntryPoint BaseOfData
標志一個文件什么類型 程序入口點RVA 起始數據的相對虛擬地址(RVA)
ImageBase SizeOfImage SizeOfHeaders
默認加載基址0x400000 文件加載到內存后大小(對齊后) 所有頭部大小
NumberOfRvaAndSizes DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] SizeofStackReserve
數據目錄個數(一般是0x10) 數據目錄表 棧可增長大小

補充:
?1、文件中的數據是0x200對齊的(FileAlinment),內存中是以0x1000對齊的(SectionAlignment),對齊什么意思?打個比方,假如從0開始,數據只占用了0x88字節,那么下一段數據會在0x200開始,中間填充0。
?2、DataDirectory這是一個數組,IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16。所以共有16項,每一項對于整個執行程序來說都有特殊的意義,當然不是每個程序每一項數據表都有內容。下面我們介紹的導出表,便是這16項中的第1項,下標為0。
?那么DataDirectory是什么樣結構呢?如下所示:

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

?每一個數組都保存了這樣的一個結構體指針,VirtualAddress是什么?就是相對虛擬地址RVA,而Size意味著數據的大小。
?
術語介紹:

**虛擬地址**: 在一個程序運行起來的時候,會被加載到內存中,并且每個進程都有自己的4GB,這個4GB叫做**虛擬地址**,由物理地址映射過來的,4GB的空間,并沒有全部被用到。

**物理地址**:在物理內存中存在的地址。在windows中是沒有表現出來的,因為windows使用了保護模式。

**所有的數據都存儲在了相應的區段(節)**,rdata存儲只讀數據,data存儲的全局數據,text存儲的代碼,rsrc存儲的是資源。

**入口點(OEP)**:他保存的是一個 **RVA** ,然后使用 OEP + Imagebase == 入口點的VA,通常情況下,OEP指向的不是main函數,是一個用于初始化(實際加載地址)

**加載基址**:默認由PE文件指定,但是通常開啟隨機基址后,它的位置是由系統指定的

**鏡像大小**: 就是exe在文件中展開之后的大小, = 最后一個區段的RVA + 最后一個區段的size 再按照0x1000對齊。

**代碼/數據基址**:第一個代碼區段和第一個數據區段的RVA

**虛擬地址(VA)**:在進程4GB中所處的位置。

**相對虛擬地址(RVA)**:相對于內存(映像)中<u>加載基址</u>的一個偏移,

**文件偏移(FOA)**:相對于文件(鏡像)起始位置的偏移。

**文件塊對齊:** 0x200(512),一個區段在文件的大小必須是0x200的倍數

**內存塊對齊:**0x1000(4kb),一個區段在內存中的大小必須是0x1000的倍數

**關系:** 數據段(有效數據長度是0x100) => 文件對齊 => (0x200) => 映射到內存 => 0x1000

文件對齊力度和內存對齊力度可以自己改變,但是文件對齊力度必須不大于內存對齊力度

**標志字:**標識可運行的平臺,x86,x64

**子系統**:窗口WinMain,控制臺main

**特征值**: 對應的是文件頭中的Characteristics,標識當前模塊有哪些屬性(重定位已分離=>動態基址)

**可選頭的大小**:可選頭有多少個字節,和操作系統的位數有關,x86/x64

?節表就不再這里過多的介紹,說說導出表,也就是數據目錄表的第1項,下標為0。
?導出表是干什么的?PE文件導出的供其他使用的函數、變量等行為。當查找導出函的時候,能夠方便快捷找到函數的位置。
?
看一看導出表的結構體,如下所示:

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

圖片二十一:Export Format

Characteristics TimeDateStamp MajorVersion NumberOfFunctions
保留值, 為0 時間 主版本號 函數數量
MinorVersion Name Base NumberOfNames
次版本號 PE名稱 序號基數 函數名稱數量
AddressOfFunctions AddressOfNames AddressOfNameOrdinals
函數地址表RVA 函數名稱表RVA 函數序號表RVA

補充:
?導出表一般會被安排到.edata中,一般也都合并到.rdata中。上述中有三個字段分別是AddressOfFunctions,AddressOfNames和AddressOfNameOrdinals,對應著三張表,上面三個字段保存了相對虛擬地址,且有關聯性,下面來看一下三個表的關聯性,如下所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片二十二:Table關聯
?如上圖所示,序號表與名稱表一一對應下標與下標中存儲的值是相關聯的,這三張表設計巧妙,利用了關系型數據庫的概念。
?需要注意的是,序號不是有序的,而且會有空白。地址表中有些沒有函數名,也就是地址表有地址卻無法關聯到名稱表中,這時候用序號調用,序號內容加上Base序號基址才是真正的調用號,且注意序號表是兩個字節WORD類型。
?了解這三張表之后,C/C++代碼實際應用獲取一下,代碼如下:

// lpBase就是讀取文件申請的緩沖區(把文件讀到內存后的首地址)
    // 1. 找到導出表
    PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)lpBase;
    PIMAGE_NT_HEADERS pNt =
        (PIMAGE_NT_HEADERS)(pDos->e_lfanew + lpBase);

    PIMAGE_DATA_DIRECTORY pDir = 
        &pNt->OptionalHeader.DataDirectory[0];

    DWORD dwExportFOA = RVAtoFOA(pDir->VirtualAddress);
    // 2. 導出表在文件中的位置
    PIMAGE_EXPORT_DIRECTORY pExportTable = 
                                (PIMAGE_EXPORT_DIRECTORY)
                                (dwExportFOA + lpBase);

    printf("模塊名稱%s\n", (RVAtoFOA(pExportTable->Name) + lpBase));
    // 3. 獲取函數數量
    DWORD dwFunCount = pExportTable->NumberOfFunctions;
    // 3.1 獲取函數名稱數量
    DWORD dwOrdinalCount = pExportTable->NumberOfNames;
    // 4. 獲取地址表
    DWORD* pFunAddr = 
        (DWORD*)(RVAtoFOA(pExportTable->AddressOfFunctions) + lpBase);
    // 5. 獲取名稱表
    DWORD* pNameAddr =
        (DWORD*)(RVAtoFOA(pExportTable->AddressOfNames) + lpBase);
    // 6. 獲取序號表
    WORD* pOrdinalAddr =
        (WORD*)(RVAtoFOA(pExportTable->AddressOfNameOrdinals) + lpBase);
    // 7. 循環遍歷
    for (DWORD i = 0; i < dwFunCount; i++)
    {
        // 7.1 如果為0說明是無效地址,直接跳過
        if (pFunAddr[i] == 0)
        {
            continue;
        }
        // 7.2 遍歷序號表中是否有此序號,如果有說明此函數有名字
        BOOL bFlag = FALSE;
        for (DWORD j = 0; j < dwOrdinalCount; j++)
        {
            if (i == pOrdinalAddr[j])
            {
                bFlag = TRUE;
                DWORD dwNameRVA = pNameAddr[j];
                printf("函數名:%s,函數序號:%04X,函數序號:%04X\n",
                    RVAtoFOA(dwNameRVA) + lpBase,
                    i + pExportTable->Base);
            }
        }
        // 7.3 如果序號表中沒有,說明此函數只有序號沒有名字
        if (!bFlag)
        {
            printf("函數名【NULL】,函數序號:%04X\n", i + pExportTable->Base);
        }
    }

?上述代碼是對導出表進行的遍歷,上述中也許有一些細節性的知識表達的不夠到位,如果你能對以上的知識都很熟悉且匯編還不錯,那么用匯編獲取函數導出表也許對你來說是一件比較輕松的事情。
?第二部分我們一起學習一下如何用匯編手動獲取函數名稱表及對應的函數地址(上面三張表關系一定搞清楚),用匯編實現自己的GetProcAddress,且Hash加密字符串進行與名稱表進行對比,理論知識先告一段落。
?
?
二、匯編篇:
?通過理論篇的閱讀,熟悉了如何使用C/C++(其他語言思路不變)來獲取且遍歷導出表,那么如圖二,當分析一段惡意代碼或者正向代碼,我們發現這些匯編指令如何去做?IDA中轉換成C語言?其實我很少使用IDA中的轉換,應為看匯編與看c差距并不是特別大,特別對于算法,想要還原規則及代碼,匯編最為真實可靠。當然如果說有大量工作需求,沒有太多時間去研究,只是對部分規則,邏輯進行分析形成報告,那么就另說了......
?上面介紹了保護模式相關內容及fs寄存器,分析了內核態的fs:[0x124],那么用戶態fs:[0x30]呢?,如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片二十三:TEB

從病毒開始聊聊那些windows下大雜燴
??????????????????圖片二十四:PEB
?什么是TEB什么是PEB呢?在以前的博客中介紹過一些相關的內容,這里在簡單的說一說。
?TEB(Thread Environment Block),線程環境塊,也就是說每一個線程都會有TEB,用于保存系統與線程之間的數據,便于操作控制,通過理論篇述保護模式知識可以自己分析一下,用戶態取fs寄存器的段描述符的BaseAddress拼接后地址為TEB地址,以前的NT類系統上地址是固定的,每4KB是一個TEB,通過分解的段描述符,內存中是向下擴展
?PEB(Process Environment Block),進程環境塊,保存進程相關的信息,同樣每個進程都是由自己的進程信息的。
獲取PEB有那些途徑?
1、fs寄存器偏移+0x30 PEB的地址
2、EPROCESS+0x1a8 PEB地址

?以上偏移不是一定的根據環境而定,通過以上我們兩種方式我們在編程中就可以輕易的找到PEB了。
?圖片二十四中,PEB結構體中標紅了+0x00c偏移處,指向的是一個_PEB_LDR_DATA的結構體,如下所示:

kd> dt _PEB_LDR_DATA
nt!_PEB_LDR_DATA
   +0x000 Length           : Uint4B
   +0x004 Initialized      : UChar
   +0x008 SsHandle         : Ptr32 Void
   +0x00c InLoadOrderModuleList : _LIST_ENTRY
   +0x014 InMemoryOrderModuleList : _LIST_ENTRY
   +0x01c InInitializationOrderModuleList : _LIST_ENTRY
   +0x024 EntryInProgress  : Ptr32 Void
   +0x028 ShutdownInProgress : UChar
   +0x02c ShutdownThreadId : Ptr32 Void

?這個結構意味著什么?其實就是包含有關進程的已加載模塊的信息。而且微軟給他標記了This structure may be altered in future versions of Windows,此結構可能會在Windows的未來版本中更改。我們在windbg下(windwos7 32bit)與官網查詢到的結構體成員數量不一樣,如下所示:

typedef struct _PEB_LDR_DATA {
  BYTE       Reserved1[8];
  PVOID      Reserved2[3];
  LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;

?前兩個參數只給了同樣的介紹,Reserved for internal use by the operating system,供系統內部使用,而第三個參數則是一個雙向鏈表頭部,包含進程的已加載模塊。 列表中的每個項目都是指向LDR_DATA_TABLE_ENTRY結構的指針
?在windbg下+0x00c,+0x014,+0x01c三個都是雙線鏈表有什么不同呢?

InLoadOrderModuleList InMemoryOrderModuleList InInitializationOrderModuleList
模塊加載順序 模塊在內存中的順序 模塊初始化裝載順序

LDR_DATA_TABLE_ENTRY是怎樣一個雙向鏈表呢?如下所示:

從病毒開始聊聊那些windows下大雜燴
??????????????????圖片二十五:關聯

LDR_DATA_TABLE_ENTRY結構體,如下所示:

從病毒開始聊聊那些windows下大雜燴

??????????????????圖片二十六:LDR_DATA_TABLE_ENTRY

代碼中會用到以下屬性,簡單理解如下,其實一個驅動的加載過程這個結構體很重要:

DLLBase FullDllName BaseDllName
模塊基址 文件路徑 模塊名稱

匯編如何獲取呢?如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片二十七:獲取DLLBase
?補充:上面一段匯編代碼,我們通過fs獲取了PEB,通過PEB偏移+0x0C獲取_PEB_LDR_DATA,加上偏移+0x1c是InInitializationOrderModuleList為雙向鏈表進行的遍歷。
?接著獲取了字符串,然后通過Hash比對,注意模塊名稱存儲是寬字符,比對成功獲取DLLBase基地址,我們可以遍歷獲取想要的模塊基址如krnel32.dll等。
PE獲取:
?PE如何用c++獲取導出表且遍歷,理論篇已給出完整代碼。匯編如何實現呢?對于標準的PE來
說,相對于基址偏移是一定的如下:

0x3c 0x78
PE標頭 導出目錄表的相對虛擬地址(RVA)

如下圖所示:
從病毒開始聊聊那些windows下大雜燴
??????????????????圖片二十八:獲取Export Table

?因為是匯編來實現操作,關鍵的步驟都寫到了注釋當中,下面貼上完整的匯編代碼,實現函數如下:

puGetModule puGetProcAddress
獲取模塊基址,參數1:Hash值 獲取函數地址 參數1:模塊基址,參數2:Hash值

?關于Hash值的算法,大家可以逆向一下下面代碼中的匯編代碼,用c語言實現一下,貼出本代碼中測試使用的Hash值,如下:

           0xec1c6278;          kernel32.dll
            0xc0d832c7;        LoadlibraryExa
            0x4FD18963;         ExitPorcess
            0x5644673D          User32.dll      
            0x1E380A6A          MessageBoxA     
            0x9EBC86B           RtlExitUserProcess  
            0xF4E2F2C8          GetModuleHandleW
            0xBB7420F9          CreateSolidBrush
            0xBC05E48           RegisterClassW

puGetModule匯編代碼如下:

DWORD puGetModule(const DWORD Hash)
{
    DWORD   nDllBase = 0;
    __asm{
        jmp         start
    /*函數1:遍歷PEB_LDR_DATA鏈表HASH加密*/
    GetModulVA:
        push        ebp;
        mov         ebp, esp;
        sub         esp, 0x20;
        push        edx;
        push        ebx;
        push        edi;
        push        esi;
        mov         ecx, 8;
        mov         eax, 0CCCCCCCCh;
        lea         edi, dword ptr[ebp - 0x20];
        rep stos    dword ptr es : [edi];
        mov         esi, dword ptr fs : [0x30];
        mov         esi, dword ptr[esi + 0x0C];
        mov         esi, dword ptr[esi + 0x1C];
    tag_Modul:
        mov         dword ptr[ebp - 0x8], esi;  // 保存LDR_DATA_LIST_ENTRY
        mov         ebx, dword ptr[esi + 0x20]; // DLL的名稱指針(應該指向一個字符串)
        mov         eax, dword ptr[ebp + 0x8];
        push        eax;
        push        ebx;                        // +0xC
        call        HashModulVA;
        test        eax, eax;
        jnz         _ModulSucess;
        mov         esi, dword ptr[ebp - 0x8];
        mov         esi, [esi];                 // 遍歷下一個
        LOOP        tag_Modul
        _ModulSucess :
        mov         esi, dword ptr[ebp - 0x8];
        mov         eax, dword ptr[esi + 0x8];
        pop         esi;
        pop         edi;
        pop         ebx;
        pop         edx;
        mov         esp, ebp;
        pop         ebp;
        ret

        /*函數2:HASH解密算法(寬字符解密)*/
    HashModulVA :
        push        ebp;
        mov         ebp, esp;
        sub         esp, 0x04;
        mov         dword ptr[ebp - 0x04], 0x00
        push        ebx;
        push        ecx;
        push        edx;
        push        esi;
        // 獲取字符串開始計算
        mov         esi, [ebp + 0x8];
        test        esi, esi;
        jz          tag_failuers;
        xor         ecx, ecx;
        xor         eax, eax;
    tag_loops:
        mov         al, [esi + ecx];        // 獲取字節加密
        test        al, al;                 // 0則退出
        jz          tag_ends;
        mov         ebx, [ebp - 0x04];
        shl         ebx, 0x19;
        mov         edx, [ebp - 0x04];
        shr         edx, 0x07;
        or          ebx, edx;
        add         ebx, eax;
        mov[ebp - 0x4], ebx;
        inc         ecx;
        inc         ecx;
        jmp         tag_loops;
    tag_ends:
        mov         ebx, [ebp + 0x0C];      // 獲取HASH
        mov         edx, [ebp - 0x04];
        xor         eax, eax;
        cmp         ebx, edx;
        jne         tag_failuers;
        mov         eax, 1;
        jmp         tag_funends;
    tag_failuers:
        mov         eax, 0;
    tag_funends:
        pop         esi;
        pop         edx;
        pop         ecx;
        pop         ebx;
        mov         esp, ebp;
        pop         ebp;
        ret         0x08

    start:
    /*主模塊*/
        pushad;
        push        Hash;
        call        GetModulVA;
        add         esp, 0x4
        mov         nDllBase, eax;
        popad;
    }
    return nDllBase;
}

puGetProcAddress函數如下:

DWORD puGetProcAddress(const DWORD dllvalues, const DWORD Hash)
{
    DWORD FunctionAddress = 0;
    __asm{
        jmp         start
        // 自定義函數計算Hash且對比返回正確的函數
    GetHashFunVA:
        push        ebp;
        mov         ebp, esp;
        sub         esp, 0x30;
        push        edx;
        push        ebx;
        push        esi;
        push        edi;
        lea         edi, dword ptr[ebp - 0x30];
        mov         ecx, 12;
        mov         eax, 0CCCCCCCCh;
        rep stos    dword ptr es : [edi];
        // 以上開辟棧幀操作(Debug版本模式)
        mov         eax, [ebp + 0x8];               // ☆ kernel32.dll(MZ)
        mov         dword ptr[ebp - 0x8], eax;
        mov         ebx, [ebp + 0x0c];              // ☆ GetProcAddress Hash值
        mov         dword ptr[ebp - 0x0c], ebx;
        // 獲取PE頭與RVA及ENT
        mov         edi, [eax + 0x3C];              // e_lfanew
        lea         edi, [edi + eax];               // e_lfanew + MZ = PE
        mov         dword ptr[ebp - 0x10], edi;     // ☆ 保存PE(VA)
        // 獲取ENT
        mov         edi, dword ptr[edi + 0x78];     // 獲取導出表RVA
        lea         edi, dword ptr[edi + eax];      // 導出表VA
        mov[ebp - 0x14], edi;                       // ☆ 保存導出表VA
        // 獲取函數名稱數量
        mov         ebx, [edi + 0x18];
        mov         dword ptr[ebp - 0x18], ebx;     // ☆ 保存函數名稱數量
        // 獲取ENT
        mov         ebx, [edi + 0x20];              // 獲取ENT(RVA)
        lea         ebx, [eax + ebx];               // 獲取ENT(VA)
        mov         dword ptr[ebp - 0x20], ebx;     // ☆ 保存ENT(VA)
        // 遍歷ENT 解密哈希值對比字符串
        mov         edi, dword ptr[ebp - 0x18];
        mov         ecx, edi;
        xor         esi, esi;
        mov         edi, dword ptr[ebp - 0x8];
        jmp         _WHILE
        // 外層大循環
    _WHILE :
        mov         edx, dword ptr[ebp + 0x0c];     // HASH
        push        edx;
        mov         edx, dword ptr[ebx + esi * 4];  // 獲取第一個函數名稱的RVA
        lea         edx, [edi + edx];               // 獲取一個函數名稱的VA地址
        push        edx;                            // ENT表中第一個字符串地址
        call        _STRCMP;
        cmp         eax, 0;
        jnz         _SUCESS;
        inc         esi;
        LOOP        _WHILE;
        jmp         _ProgramEnd
        // 對比成功之后獲取循環次數(下標)cx保存下標數
    _SUCESS :
        // 獲取EOT導出序號表內容
        mov         ecx, esi;
        mov         ebx, dword ptr[ebp - 0x14];
        mov         esi, dword ptr[ebx + 0x24];
        mov         ebx, dword ptr[ebp - 0x8];
        lea         esi, [esi + ebx];               // 獲取EOT的VA
        xor         edx, edx;
        mov         dx, [esi + ecx * 2];            // 注意雙字 獲取序號
        // 獲取EAT地址表RVA
        mov         esi, dword ptr[ebp - 0x14];     // Export VA
        mov         esi, [esi + 0x1C];
        mov         ebx, dword ptr[ebp - 0x8];
        lea         esi, [esi + ebx];               // 獲取EAT的VA         
        mov         eax, [esi + edx * 4];           // 返回值eax(GetProcess地址)
        lea         eax, [eax + ebx];
        jmp         _ProgramEnd;

    _ProgramEnd:
        pop         edi;
        pop         esi;
        pop         ebx;
        pop         edx;
        mov         esp, ebp;
        pop         ebp;
        ret         0x8;

        // 循環對比HASH值
    _STRCMP:
        push        ebp;
        mov         ebp, esp;
        sub         esp, 0x04;
        mov         dword ptr[ebp - 0x04], 0x00;
        push        ebx;
        push        ecx;
        push        edx;
        push        esi;
        // 獲取字符串開始計算
        mov         esi, [ebp + 0x8];
        xor         ecx, ecx;
        xor         eax, eax;

    tag_loop:
        mov         al, [esi + ecx];        // 獲取字節加密
        test        al, al;                 // 0則退出
        jz          tag_end;
        mov         ebx, [ebp - 0x04];
        shl         ebx, 0x19;
        mov         edx, [ebp - 0x04];
        shr         edx, 0x07;
        or          ebx, edx;
        add         ebx, eax;
        mov[ebp - 0x4], ebx;
        inc         ecx;
        jmp         tag_loop

        tag_end :
        mov         ebx, [ebp + 0x0C];      // 獲取HASH
        mov         edx, [ebp - 0x04];
        xor         eax, eax;
        cmp         ebx, edx;
        jne         tag_failuer;
        mov         eax, 1;
        jmp         tag_funend;

    tag_failuer:
        mov         eax, 0;

    tag_funend:
        pop         esi;
        pop         edx;
        pop         ecx;
        pop         ebx;
        mov         esp, ebp;
        pop         ebp;
        ret         0x08

    start:
        pushad;
        push        Hash;                       // Hash加密的函數名稱
        push        dllvalues;                  // 模塊基址.dll
        call        GetHashFunVA;               // GetProcess
        mov         FunctionAddress, eax;       // ☆ 保存地址
        popad;
    }
    return FunctionAddress;
}
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

宁阳县| 云南省| 云阳县| 兴安县| 屯门区| 岳阳县| 蛟河市| 贵南县| 惠东县| 永康市| 太白县| 辽中县| 丰顺县| 盈江县| 高雄市| 文化| 清水河县| 金寨县| 黄梅县| 新津县| 邵阳县| 岑溪市| 汝南县| 武隆县| 湖口县| 化州市| 七台河市| 藁城市| 隆昌县| 淮滨县| 涡阳县| 屏东县| 温宿县| 寿宁县| 渭源县| 临安市| 乌鲁木齐市| 沂源县| 宣威市| 巴马| 台南县|