您好,登錄后才能下訂單哦!
?豬年送安康,祝大家新一年健康、快樂。愿大家都做一個(gè)勤奮努力、真誠奉獻(xiàn)的人,幸運(yùn)會(huì)永遠(yuǎn)的眷顧你們。
?
引子:
?某一天饒有興趣在卡飯上瀏覽著帖子,故事的相遇就那么簡(jiǎn)單。當(dāng)時(shí)一條評(píng)論勾起我的好奇心,那么好逆向開始。
?根據(jù)我的習(xí)慣,拿到樣本我會(huì)線上惡意代碼分析,直接拉到virustotal之類的網(wǎng)站上,看看是否已經(jīng)被大多數(shù)殺毒軟件所能識(shí)別,看一些有價(jià)值的數(shù)據(jù),如下圖所示:
??????????????????圖片一:基本信息
?當(dāng)看到這個(gè)頁面時(shí)候,看到最后的分析日期是18年11月,又看了一下導(dǎo)出表的函數(shù)信息,是一款老病毒。根據(jù)各大廠商對(duì)這個(gè)病毒行為特性、分析定位為特洛伊、偽裝等,定位不一很正常......,其實(shí)興趣降低了一大半,并不是新鮮品種,但不能這樣侮辱一個(gè)病毒!接著習(xí)慣性拉入到IDA中,當(dāng)我看到熟悉的匯編之后,如下圖所示:
??????????????????圖片二:GetProcAddress實(shí)現(xiàn)
?當(dāng)點(diǎn)進(jìn)去其中的一個(gè)函數(shù),看到了fs寄存器,且一大堆比較復(fù)雜的操作,看到熟悉的匯編指令以后,心中已有定數(shù),這是一個(gè)自己實(shí)現(xiàn)的GetProcAddress函數(shù)。
?
?
理論篇 | 匯編篇 |
---|---|
保護(hù)模式,定時(shí)器,PE雜談 | 手動(dòng)實(shí)現(xiàn)GetProcAddress函數(shù)及Hash加密字符比對(duì) |
?
一、理論篇
?先來看病毒樣本中的一段代碼,如下圖所示:
??????????????????圖片三:CreateTimerQueueTimer
?
?還記著以前分析熊貓燒香時(shí)候的定時(shí)器,如下圖所示:
??????????????????圖片四:SetTimer
?
?惡意代碼大多都會(huì)利用到WinAPI提供的定時(shí)器操作,從而實(shí)現(xiàn)有規(guī)劃、周期性的惡意代碼,既然那么重要,所以我們先來聊聊那些定時(shí)器。
?經(jīng)常用ARK工具的朋友,應(yīng)該都使用過遍歷定時(shí)器相關(guān)的功能,有用戶層定時(shí)器,IO定時(shí)器,DCP定時(shí)器,包括我們的時(shí)鐘中斷機(jī)制,都是具有定時(shí)器相關(guān)操作的。
?我們先從用戶層入手,windbg下深入分析一下上面提到的兩個(gè)定時(shí)器操作,NtSetTimer匯編源碼如下所示:
注:(為什么SetTimer會(huì)調(diào)用NtSetTimer,請(qǐng)看https://blog.51cto.com/13352079/2343452)
函數(shù)原型如下:
UINT_PTR SetTimer(
HWND hWnd, // 窗口句柄
UINT_PTR nIDEvent, // 定時(shí)器ID,多個(gè)定時(shí)器時(shí),可以通過該ID判斷是哪個(gè)定時(shí)器
UINT nElapse, // 時(shí)間間隔,單位為毫秒
TIMERPROC lpTimerFunc // 回調(diào)函數(shù)
);
?為了更好的理解定時(shí)器的匯編代碼,簡(jiǎn)單分析一下函數(shù)調(diào)用的過程,就是如何獲取當(dāng)前線程。
kd> u PsGetCurrentProcess
nt!PsGetCurrentProcess:
mov eax,dword ptr fs:[00000124h]
mov eax,dword ptr [eax+50h]
ret
?
保護(hù)模式:
?那么根據(jù)書籍或者相關(guān)資料,我們知道fs寄存器的值恒定(注意windows7 32位測(cè)試的),內(nèi)核態(tài)是fs = 0x30,用戶態(tài) fs = 0x3B,fs在內(nèi)核態(tài)指向_KPCR,用戶態(tài)指向_TEB.。什么依據(jù)呢?憑什么說fs指向KPCR? 這里屬于保護(hù)模式得內(nèi)容,但是這里還是想與大家一起分享其中的原理,那么先說說段寄存器,為了方便理解做了一個(gè)簡(jiǎn)陋的圖,如下所示:
??????????????????圖片五:段寄存器
?
?其實(shí)段寄存器共96位,只有其中的16位是可見的,剩余部分隱藏,可見的部分就是我們能查詢到的立即數(shù),也叫做選擇子。隱藏部分只可以被CPU操作,不可以使用指令進(jìn)行操作。
?GDT全局描述符表,系統(tǒng)中按照不同的屬性、類型進(jìn)行描述,所以這些描述符統(tǒng)一存儲(chǔ)到內(nèi)存中,并且形成了一個(gè)數(shù)組,這就是GDT。全局描述符的索引保存在了可見部分16位的選擇子中,這就是GDT與段選擇子的關(guān)聯(lián)。如何從選擇子中知道索引呢?如下圖所示:
??????????????????圖片六:選擇子
?
?高13位是索引號(hào),也就是下標(biāo)。TI = 0 代表GDT,TI = 1代表LDT。RPL是當(dāng)前請(qǐng)求特權(quán)級(jí)別,權(quán)限檢查會(huì)用到,這里不對(duì)權(quán)限檢測(cè)做詳細(xì)介紹。
?清楚了上面的知識(shí)后,我們分析一下內(nèi)核態(tài)fs = 30,16位選擇子內(nèi)容,如下圖所示:
??????????????????圖片七:解析fs寄存器
?
?通過上述分解,我們知道了fs在GDT中的第六項(xiàng)(0開始),接著獲取gdtr,并且獲取段描述符的屬性狀態(tài),如下圖所示:
??????????????????圖片八:gdtr寄存器
?段描述符如何來分解?段描述符都有那些屬性呢?如下圖所示:
??????????????????圖片九:通用描述符
?
介紹一些主要屬性:
?
L | D/B | P | S | DPL | TYPE | G |
---|---|---|---|---|---|---|
64位代碼段 | 默認(rèn)操作大小 | 段有效值 | 描述符類型 | 描述符特權(quán)級(jí)別 | 段類型 | 粒度 |
?
?我們按照上圖分解,取Base Address,按照想對(duì)應(yīng)的規(guī)則10101100 01001000 10000100 01000000進(jìn)行地址拼接,其實(shí)這個(gè)就獲取到了KPCR的結(jié)構(gòu)。
?fs寄存器其實(shí)擁有那么的數(shù)據(jù)量,本質(zhì)是是從結(jié)構(gòu)數(shù)據(jù)中獲取,便于操作。推薦一下bochs這款x86硬件平臺(tái)的開源模擬器,學(xué)習(xí)保護(hù)模式,除了書中獲取相關(guān)知識(shí)以外,還可以多多閱讀源碼,才能更深層的學(xué)習(xí)理解。
?
?回到主題,我們既然知道fs在內(nèi)核態(tài)指向的是什么了,我們觀察一下fs:[00000124h]是什么?結(jié)構(gòu)體相關(guān)內(nèi)容以前介紹過,這里不羅嗦,如下圖所示:
??????????????????圖片十:_KPRC
?fs寄存器內(nèi)核態(tài)指向的是_KPRC,fs:[0x124]指向CurrentThread(_EPROCESS),有了這些基礎(chǔ)以后,我們繼續(xù)分析NtSetTimer得調(diào)用過程。
NtSetTimer匯編代碼:(因?yàn)榕虐?所以就上圖了)
??????????????????圖片十一:NtSetTimer解析1
?如上圖所示,先是獲取_ETHREAD,然后獲取了ETHREAD+0x13a(Previous Mode),如下圖所示:
??????????????????圖片十二:
?什么是Previous Mode?,簡(jiǎn)單來說調(diào)用Nt或Zw版本時(shí),系統(tǒng)調(diào)用機(jī)制將調(diào)用線程捕獲到內(nèi)核模式,判定參數(shù)是否來源于用戶模式標(biāo)志。
?The native system services routine checks the PreviousMode field of the calling thread to determine whether the parameters are from a user-mode source.
詳細(xì)得內(nèi)容介紹參考:https://msdn.microsoft.com/zh-cn/windows/desktop/ff559860
PreviousMode其中得兩個(gè)狀態(tài)值:
?1、UserMode 狀態(tài)碼是1
?2、KernelMode 狀態(tài)碼是0
定時(shí)函數(shù)分析:
?所以上圖中與0進(jìn)行判斷,判斷當(dāng)前是否內(nèi)核態(tài),是則跳轉(zhuǎn)0x8402fdd。我們先來看看如果是內(nèi)核態(tài),是怎樣一條執(zhí)行路線,如下圖所示:
?
??????????????????圖片十三:定時(shí)器ID判定
?第二個(gè)參數(shù)必須大于等于0,否則會(huì)拋出異常,繼續(xù)看,如下圖所示:
??????????????????圖片十四:內(nèi)核態(tài)匯編解析
?OD中我們跟中一下看是否真的追加了第五個(gè)參數(shù),如下圖所示:
??????????????????圖片十五:NtUserSetTimer
?如果為0則跳轉(zhuǎn),跳轉(zhuǎn)位置如下圖所示:
??????????????????圖片十六:ExpSetTimer
?我們會(huì)發(fā)現(xiàn),SetTimer->NtUserSetTimer->Wow64得函數(shù)(如果32位運(yùn)行在64位)-->KiFastSystemCall->ExSetTimer-->ObReferenceObjectByHandle-->..........
?所以SetTimer在內(nèi)核態(tài)得過曾還是比較復(fù)雜得,大家可以通過函數(shù)棧來觀察到底如何運(yùn)作得,這告訴我們一個(gè)道理,誰HOOK得函數(shù)越底層,誰就有可能做更多得事情。
?如果Previous Mode = UserMode呢?如何執(zhí)行?如下圖所示:
??????????????????圖片十七:用戶態(tài)匯編分析
?在做了一些判斷賦值及參數(shù)保存操作以后,又跳回了與內(nèi)核態(tài)執(zhí)行得流程,所以說不論怎樣最終還會(huì)調(diào)用那些函數(shù)。
?關(guān)于SetTimer函數(shù)簡(jiǎn)單得分析到這里,我們下面接著看CreateTimerQueueTimer函數(shù),先來看函數(shù)原型:
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
);
圖三中已經(jīng)對(duì)參數(shù)進(jìn)行了詳細(xì)得介紹,這里不再做介紹
OD中我們動(dòng)態(tài)觀察一下,如下圖所示:
??????????????????圖片十八:CreateTimerQueueTimer
?函數(shù)內(nèi)部調(diào)用了RtlCreateTimer,我們繼續(xù)動(dòng)態(tài)跟蹤,如下所示:
?內(nèi)部調(diào)用了大量的函數(shù),其中包括TpSetTimer也在其中,基本確定內(nèi)部是調(diào)用TpSetTimer來實(shí)現(xiàn)該函數(shù)功能,在windbg中簡(jiǎn)答了分析一下,內(nèi)部調(diào)用了TppTimerpSet,且使用了Slim讀寫鎖機(jī)制,因?yàn)橛|碰到了盲區(qū),感覺不太準(zhǔn)確,也找不到相關(guān)的參考所以有興趣的朋友可以深入分析一下,這里就不講解了。
??????????????????圖片十九:TppTimerpSet
?這里以上是給大家提供一些函數(shù)分析的思路罷了,有時(shí)間的話寫一篇相關(guān)的話題一起討論一下。
?
PE雜談 :
?關(guān)于PE知識(shí)雖然看起來雜亂,但還是比較有序的。PE涉獵的范圍較廣,PE文件是指一種格式,如可執(zhí)行文件、動(dòng)態(tài)鏈接庫、驅(qū)動(dòng)等等,都屬于PE格式的文件。
?想深入學(xué)習(xí)的朋友,推薦一本書籍《Windows PE權(quán)威指南》,里面內(nèi)容是win32匯編撰寫而成。
?我們這里只對(duì)用到的基本知識(shí)和導(dǎo)出表做介紹,PE結(jié)構(gòu)體大概分為幾個(gè)部分,如下圖所示:
??????????????????圖片二十:PE大體結(jié)構(gòu)
?上面順序是一定的,PE是一個(gè)有序結(jié)構(gòu),標(biāo)準(zhǔn)的PE格式每個(gè)結(jié)構(gòu)體對(duì)應(yīng)的偏移是固定的,當(dāng)然也有很多惡意代碼會(huì)對(duì)PE結(jié)構(gòu)體進(jìn)行數(shù)據(jù)壓縮等技術(shù),達(dá)到隱匿、免殺的目的。
?我們介紹一下DOS頭的數(shù)據(jù)介紹,其實(shí)我們用VS編程的時(shí)候就可以獲取到結(jié)構(gòu)體,這里不再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;
?上面結(jié)構(gòu)體是DOS頭部的全部信息,其中DOS中兩個(gè)重要屬重點(diǎn)介紹一下:
e_magi |
---|
“魔術(shù)”標(biāo)志,判斷是否PE格式第一道防線,恒定值為0x4D5A(MZ) |
e_lfanew |
---|
Dos頭與NT頭之間有一部分Dos Stub的數(shù)據(jù)(Dos的數(shù)據(jù))大小不確定,意味著NT頭偏移不確定,所以 e_lfanew記錄了該模塊NT的偏移 |
?如何找到NT頭?模塊基址 + e_lfanew = NT的位置。第二部分我們會(huì)用匯編獲取且深入學(xué)習(xí),用C/C++如何實(shí)現(xiàn)呢?如下代碼所示:
// 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"當(dāng)前進(jìn)程有可能被占用或者意外錯(cuò)誤"); return FALSE; }
HANDLE hFile = NULL;
// 3.獲取文件大小
DWORD dwSize = GetFileSize(hFile, NULL);
// 4.申請(qǐng)堆空間
PuPEInfo::m_pFileBase = (void *)malloc(dwSize);
memset(PuPEInfo::m_pFileBase, 0, dwSize);
DWORD dwRead = 0;
OVERLAPPED OverLapped = { 0 };
void* pFileBaseAddress = nullptr;
// 5.讀取文件到內(nèi)存
int nRetCode = ReadFile(hFile, pFileBaseAddress, dwSize, &dwRead, &OverLapped);
// 6.轉(zhuǎn)換成DOS頭結(jié)構(gòu)體
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);
?如上述代碼,獲取可執(zhí)文件路徑,創(chuàng)建(獲取文件句柄)、打開文件、讀取文件大小、申請(qǐng)堆空間、讀取文件數(shù)據(jù)到內(nèi)存(加載到了內(nèi)存)、獲取NT頭,第7步正式上述所表達(dá)的 模塊基址 + e_lfanew。
NT頭內(nèi)部是如何?如下所示:
??????????????????圖片二十一:NT結(jié)構(gòu)
如上所示,NT分為三部分,介紹如下:
Signature | FileHeader | OptionalHeader |
---|---|---|
標(biāo)記,判斷是否PE格式第二道防線,恒定值為0x4550(PE) | 文件頭,存儲(chǔ)這PE文件的基本信息 | 存儲(chǔ)著關(guān)于PE文件的附加信息 |
既然已經(jīng)介紹了PE格式兩條應(yīng)規(guī)定,兩道標(biāo)桿,如果判斷是否是一個(gè)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結(jié)構(gòu)體如下:
// 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 |
---|---|---|---|
文件運(yùn)行平臺(tái) | 區(qū)段的數(shù)量 | 文件創(chuàng)建時(shí)間 | 符號(hào)個(gè)數(shù) |
SizeOfOptionalHeader | PointerToSymbolTable | Characteristics |
---|---|---|
擴(kuò)展頭大小 | 符號(hào)表偏移 | PE文件屬性 |
補(bǔ)充:
?1、Machine:0x014c代表i386,平時(shí)intel32為平臺(tái),0x0200表示Intel 64為平臺(tái)。
?2、NumberOfSymbols:這個(gè)很重要了,你遍歷節(jié)表先要獲取數(shù)量,這個(gè)就是。
?3、Characteristics:PE的文件屬性值,如下所示:
數(shù)值 | 介紹 | 宏定義 |
---|---|---|
0x0001 | 從文件中刪除重定位信息 | IMAGE_FILE_RELOCS_STRIPPED |
0x0002 | 可執(zhí)行文件 | IMAGE_FILE_EXECUTABLE_IMAGE |
0x0004 | 行號(hào)信息無 | IMAGE_FILE_LINE_NUMS_STRIPPED |
0x0008 | 符號(hào)信息無 | IMAGE_FILE_LOCAL_SYMS_STRIPPED |
0x0010 | 強(qiáng)制性縮減工作 | IMAGE_FILE_AGGRESIVE_WS_TRIM |
0x0020 | 應(yīng)用程序可以處理> 2GB的地址 | IMAGE_FILE_LARGE_ADDRESS_AWARE |
0x0080 | 機(jī)器字的字節(jié)相反的 | IMAGE_FILE_BYTES_REVERSED_LO |
0x0100 | 運(yùn)行在32位平臺(tái) | IMAGE_FILE_32BIT_MACHINE |
0x0200 | 調(diào)試信息從.DBG文件中的文件中刪除 | IMAGE_FILE_DEBUG_STRIPPED |
0x0400 | 如果文件在可移動(dòng)媒體上,則從交換文件復(fù)制并運(yùn)行。 | IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP |
0x0800 | 如果在網(wǎng)絡(luò)存儲(chǔ)介質(zhì)中,則從交換文件中復(fù)制并運(yùn)行。 | IMAGE_FILE_NET_RUN_FROM_SWAP |
0x1000 | 系統(tǒng)文件 | IMAGE_FILE_SYSTEM |
0x2000 | DLL文件 | IMAGE_FILE_DLL |
0x4000 | 單核CPU運(yùn)行 | IMAGE_FILE_UP_SYSTEM_ONLY |
0x8000 | 機(jī)器字的字節(jié)相反的 | IMAGE_FILE_BYTES_REVERSED_HI |
?
OptionalHeader結(jié)構(gòu)體介紹:
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;
挑重點(diǎn)介紹一下:
Magic | AddressOfEntryPoint | BaseOfData |
---|---|---|
標(biāo)志一個(gè)文件什么類型 | 程序入口點(diǎn)RVA | 起始數(shù)據(jù)的相對(duì)虛擬地址(RVA) |
ImageBase | SizeOfImage | SizeOfHeaders |
---|---|---|
默認(rèn)加載基址0x400000 | 文件加載到內(nèi)存后大小(對(duì)齊后) | 所有頭部大小 |
NumberOfRvaAndSizes | DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] | SizeofStackReserve |
---|---|---|
數(shù)據(jù)目錄個(gè)數(shù)(一般是0x10) | 數(shù)據(jù)目錄表 | ??稍鲩L(zhǎng)大小 |
補(bǔ)充:
?1、文件中的數(shù)據(jù)是0x200對(duì)齊的(FileAlinment),內(nèi)存中是以0x1000對(duì)齊的(SectionAlignment),對(duì)齊什么意思?打個(gè)比方,假如從0開始,數(shù)據(jù)只占用了0x88字節(jié),那么下一段數(shù)據(jù)會(huì)在0x200開始,中間填充0。
?2、DataDirectory這是一個(gè)數(shù)組,IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16。所以共有16項(xiàng),每一項(xiàng)對(duì)于整個(gè)執(zhí)行程序來說都有特殊的意義,當(dāng)然不是每個(gè)程序每一項(xiàng)數(shù)據(jù)表都有內(nèi)容。下面我們介紹的導(dǎo)出表,便是這16項(xiàng)中的第1項(xiàng),下標(biāo)為0。
?那么DataDirectory是什么樣結(jié)構(gòu)呢?如下所示:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
?每一個(gè)數(shù)組都保存了這樣的一個(gè)結(jié)構(gòu)體指針,VirtualAddress是什么?就是相對(duì)虛擬地址RVA,而Size意味著數(shù)據(jù)的大小。
?
術(shù)語介紹:
**虛擬地址**: 在一個(gè)程序運(yùn)行起來的時(shí)候,會(huì)被加載到內(nèi)存中,并且每個(gè)進(jìn)程都有自己的4GB,這個(gè)4GB叫做**虛擬地址**,由物理地址映射過來的,4GB的空間,并沒有全部被用到。
**物理地址**:在物理內(nèi)存中存在的地址。在windows中是沒有表現(xiàn)出來的,因?yàn)閣indows使用了保護(hù)模式。
**所有的數(shù)據(jù)都存儲(chǔ)在了相應(yīng)的區(qū)段(節(jié))**,rdata存儲(chǔ)只讀數(shù)據(jù),data存儲(chǔ)的全局?jǐn)?shù)據(jù),text存儲(chǔ)的代碼,rsrc存儲(chǔ)的是資源。
**入口點(diǎn)(OEP)**:他保存的是一個(gè) **RVA** ,然后使用 OEP + Imagebase == 入口點(diǎn)的VA,通常情況下,OEP指向的不是main函數(shù),是一個(gè)用于初始化(實(shí)際加載地址)
**加載基址**:默認(rèn)由PE文件指定,但是通常開啟隨機(jī)基址后,它的位置是由系統(tǒng)指定的
**鏡像大小**: 就是exe在文件中展開之后的大小, = 最后一個(gè)區(qū)段的RVA + 最后一個(gè)區(qū)段的size 再按照0x1000對(duì)齊。
**代碼/數(shù)據(jù)基址**:第一個(gè)代碼區(qū)段和第一個(gè)數(shù)據(jù)區(qū)段的RVA
**虛擬地址(VA)**:在進(jìn)程4GB中所處的位置。
**相對(duì)虛擬地址(RVA)**:相對(duì)于內(nèi)存(映像)中<u>加載基址</u>的一個(gè)偏移,
**文件偏移(FOA)**:相對(duì)于文件(鏡像)起始位置的偏移。
**文件塊對(duì)齊:** 0x200(512),一個(gè)區(qū)段在文件的大小必須是0x200的倍數(shù)
**內(nèi)存塊對(duì)齊:**0x1000(4kb),一個(gè)區(qū)段在內(nèi)存中的大小必須是0x1000的倍數(shù)
**關(guān)系:** 數(shù)據(jù)段(有效數(shù)據(jù)長(zhǎng)度是0x100) => 文件對(duì)齊 => (0x200) => 映射到內(nèi)存 => 0x1000
文件對(duì)齊力度和內(nèi)存對(duì)齊力度可以自己改變,但是文件對(duì)齊力度必須不大于內(nèi)存對(duì)齊力度
**標(biāo)志字:**標(biāo)識(shí)可運(yùn)行的平臺(tái),x86,x64
**子系統(tǒng)**:窗口WinMain,控制臺(tái)main
**特征值**: 對(duì)應(yīng)的是文件頭中的Characteristics,標(biāo)識(shí)當(dāng)前模塊有哪些屬性(重定位已分離=>動(dòng)態(tài)基址)
**可選頭的大小**:可選頭有多少個(gè)字節(jié),和操作系統(tǒng)的位數(shù)有關(guān),x86/x64
?節(jié)表就不再這里過多的介紹,說說導(dǎo)出表,也就是數(shù)據(jù)目錄表的第1項(xiàng),下標(biāo)為0。
?導(dǎo)出表是干什么的?PE文件導(dǎo)出的供其他使用的函數(shù)、變量等行為。當(dāng)查找導(dǎo)出函的時(shí)候,能夠方便快捷找到函數(shù)的位置。
?
看一看導(dǎo)出表的結(jié)構(gòu)體,如下所示:
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 | 時(shí)間 | 主版本號(hào) | 函數(shù)數(shù)量 |
MinorVersion | Name | Base | NumberOfNames |
---|---|---|---|
次版本號(hào) | PE名稱 | 序號(hào)基數(shù) | 函數(shù)名稱數(shù)量 |
AddressOfFunctions | AddressOfNames | AddressOfNameOrdinals |
---|---|---|
函數(shù)地址表RVA | 函數(shù)名稱表RVA | 函數(shù)序號(hào)表RVA |
補(bǔ)充:
?導(dǎo)出表一般會(huì)被安排到.edata中,一般也都合并到.rdata中。上述中有三個(gè)字段分別是AddressOfFunctions,AddressOfNames和AddressOfNameOrdinals,對(duì)應(yīng)著三張表,上面三個(gè)字段保存了相對(duì)虛擬地址,且有關(guān)聯(lián)性,下面來看一下三個(gè)表的關(guān)聯(lián)性,如下所示:
??????????????????圖片二十二:Table關(guān)聯(lián)
?如上圖所示,序號(hào)表與名稱表一一對(duì)應(yīng),下標(biāo)與下標(biāo)中存儲(chǔ)的值是相關(guān)聯(lián)的,這三張表設(shè)計(jì)巧妙,利用了關(guān)系型數(shù)據(jù)庫的概念。
?需要注意的是,序號(hào)不是有序的,而且會(huì)有空白。地址表中有些沒有函數(shù)名,也就是地址表有地址卻無法關(guān)聯(lián)到名稱表中,這時(shí)候用序號(hào)調(diào)用,序號(hào)內(nèi)容加上Base序號(hào)基址才是真正的調(diào)用號(hào),且注意序號(hào)表是兩個(gè)字節(jié)WORD類型。
?了解這三張表之后,C/C++代碼實(shí)際應(yīng)用獲取一下,代碼如下:
// lpBase就是讀取文件申請(qǐng)的緩沖區(qū)(把文件讀到內(nèi)存后的首地址)
// 1. 找到導(dǎo)出表
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. 導(dǎo)出表在文件中的位置
PIMAGE_EXPORT_DIRECTORY pExportTable =
(PIMAGE_EXPORT_DIRECTORY)
(dwExportFOA + lpBase);
printf("模塊名稱%s\n", (RVAtoFOA(pExportTable->Name) + lpBase));
// 3. 獲取函數(shù)數(shù)量
DWORD dwFunCount = pExportTable->NumberOfFunctions;
// 3.1 獲取函數(shù)名稱數(shù)量
DWORD dwOrdinalCount = pExportTable->NumberOfNames;
// 4. 獲取地址表
DWORD* pFunAddr =
(DWORD*)(RVAtoFOA(pExportTable->AddressOfFunctions) + lpBase);
// 5. 獲取名稱表
DWORD* pNameAddr =
(DWORD*)(RVAtoFOA(pExportTable->AddressOfNames) + lpBase);
// 6. 獲取序號(hào)表
WORD* pOrdinalAddr =
(WORD*)(RVAtoFOA(pExportTable->AddressOfNameOrdinals) + lpBase);
// 7. 循環(huán)遍歷
for (DWORD i = 0; i < dwFunCount; i++)
{
// 7.1 如果為0說明是無效地址,直接跳過
if (pFunAddr[i] == 0)
{
continue;
}
// 7.2 遍歷序號(hào)表中是否有此序號(hào),如果有說明此函數(shù)有名字
BOOL bFlag = FALSE;
for (DWORD j = 0; j < dwOrdinalCount; j++)
{
if (i == pOrdinalAddr[j])
{
bFlag = TRUE;
DWORD dwNameRVA = pNameAddr[j];
printf("函數(shù)名:%s,函數(shù)序號(hào):%04X,函數(shù)序號(hào):%04X\n",
RVAtoFOA(dwNameRVA) + lpBase,
i + pExportTable->Base);
}
}
// 7.3 如果序號(hào)表中沒有,說明此函數(shù)只有序號(hào)沒有名字
if (!bFlag)
{
printf("函數(shù)名【NULL】,函數(shù)序號(hào):%04X\n", i + pExportTable->Base);
}
}
?上述代碼是對(duì)導(dǎo)出表進(jìn)行的遍歷,上述中也許有一些細(xì)節(jié)性的知識(shí)表達(dá)的不夠到位,如果你能對(duì)以上的知識(shí)都很熟悉且匯編還不錯(cuò),那么用匯編獲取函數(shù)導(dǎo)出表也許對(duì)你來說是一件比較輕松的事情。
?第二部分我們一起學(xué)習(xí)一下如何用匯編手動(dòng)獲取函數(shù)名稱表及對(duì)應(yīng)的函數(shù)地址(上面三張表關(guān)系一定搞清楚),用匯編實(shí)現(xiàn)自己的GetProcAddress,且Hash加密字符串進(jìn)行與名稱表進(jìn)行對(duì)比,理論知識(shí)先告一段落。
?
?
二、匯編篇:
?通過理論篇的閱讀,熟悉了如何使用C/C++(其他語言思路不變)來獲取且遍歷導(dǎo)出表,那么如圖二,當(dāng)分析一段惡意代碼或者正向代碼,我們發(fā)現(xiàn)這些匯編指令如何去做?IDA中轉(zhuǎn)換成C語言?其實(shí)我很少使用IDA中的轉(zhuǎn)換,應(yīng)為看匯編與看c差距并不是特別大,特別對(duì)于算法,想要還原規(guī)則及代碼,匯編最為真實(shí)可靠。當(dāng)然如果說有大量工作需求,沒有太多時(shí)間去研究,只是對(duì)部分規(guī)則,邏輯進(jìn)行分析形成報(bào)告,那么就另說了......
?上面介紹了保護(hù)模式相關(guān)內(nèi)容及fs寄存器,分析了內(nèi)核態(tài)的fs:[0x124],那么用戶態(tài)fs:[0x30]呢?,如下圖所示:
??????????????????圖片二十三:TEB
??????????????????圖片二十四:PEB
?什么是TEB什么是PEB呢?在以前的博客中介紹過一些相關(guān)的內(nèi)容,這里在簡(jiǎn)單的說一說。
?TEB(Thread Environment Block),線程環(huán)境塊,也就是說每一個(gè)線程都會(huì)有TEB,用于保存系統(tǒng)與線程之間的數(shù)據(jù),便于操作控制,通過理論篇述保護(hù)模式知識(shí)可以自己分析一下,用戶態(tài)取fs寄存器的段描述符的BaseAddress拼接后地址為TEB地址,以前的NT類系統(tǒng)上地址是固定的,每4KB是一個(gè)TEB,通過分解的段描述符,內(nèi)存中是向下擴(kuò)展。
?PEB(Process Environment Block),進(jìn)程環(huán)境塊,保存進(jìn)程相關(guān)的信息,同樣每個(gè)進(jìn)程都是由自己的進(jìn)程信息的。
獲取PEB有那些途徑?
1、fs寄存器偏移+0x30 PEB的地址
2、EPROCESS+0x1a8 PEB地址
?以上偏移不是一定的根據(jù)環(huán)境而定,通過以上我們兩種方式我們?cè)诰幊讨芯涂梢暂p易的找到PEB了。
?圖片二十四中,PEB結(jié)構(gòu)體中標(biāo)紅了+0x00c偏移處,指向的是一個(gè)_PEB_LDR_DATA的結(jié)構(gòu)體,如下所示:
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
?這個(gè)結(jié)構(gòu)意味著什么?其實(shí)就是包含有關(guān)進(jìn)程的已加載模塊的信息。而且微軟給他標(biāo)記了This structure may be altered in future versions of Windows,此結(jié)構(gòu)可能會(huì)在Windows的未來版本中更改。我們?cè)趙indbg下(windwos7 32bit)與官網(wǎng)查詢到的結(jié)構(gòu)體成員數(shù)量不一樣,如下所示:
typedef struct _PEB_LDR_DATA {
BYTE Reserved1[8];
PVOID Reserved2[3];
LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
?前兩個(gè)參數(shù)只給了同樣的介紹,Reserved for internal use by the operating system,供系統(tǒng)內(nèi)部使用,而第三個(gè)參數(shù)則是一個(gè)雙向鏈表頭部,包含進(jìn)程的已加載模塊。 列表中的每個(gè)項(xiàng)目都是指向LDR_DATA_TABLE_ENTRY結(jié)構(gòu)的指針。
?在windbg下+0x00c,+0x014,+0x01c三個(gè)都是雙線鏈表有什么不同呢?
InLoadOrderModuleList | InMemoryOrderModuleList | InInitializationOrderModuleList |
---|---|---|
模塊加載順序 | 模塊在內(nèi)存中的順序 | 模塊初始化裝載順序 |
LDR_DATA_TABLE_ENTRY是怎樣一個(gè)雙向鏈表呢?如下所示:
??????????????????圖片二十五:關(guān)聯(lián)
LDR_DATA_TABLE_ENTRY結(jié)構(gòu)體,如下所示:
??????????????????圖片二十六:LDR_DATA_TABLE_ENTRY
代碼中會(huì)用到以下屬性,簡(jiǎn)單理解如下,其實(shí)一個(gè)驅(qū)動(dòng)的加載過程這個(gè)結(jié)構(gòu)體很重要:
DLLBase | FullDllName | BaseDllName |
---|---|---|
模塊基址 | 文件路徑 | 模塊名稱 |
匯編如何獲取呢?如下圖所示:
??????????????????圖片二十七:獲取DLLBase
?補(bǔ)充:上面一段匯編代碼,我們通過fs獲取了PEB,通過PEB偏移+0x0C獲取_PEB_LDR_DATA,加上偏移+0x1c是InInitializationOrderModuleList為雙向鏈表進(jìn)行的遍歷。
?接著獲取了字符串,然后通過Hash比對(duì),注意模塊名稱存儲(chǔ)是寬字符,比對(duì)成功獲取DLLBase基地址,我們可以遍歷獲取想要的模塊基址如krnel32.dll等。
PE獲?。?/strong>
?PE如何用c++獲取導(dǎo)出表且遍歷,理論篇已給出完整代碼。匯編如何實(shí)現(xiàn)呢?對(duì)于標(biāo)準(zhǔn)的PE來
說,相對(duì)于基址偏移是一定的如下:
0x3c | 0x78 |
---|---|
PE標(biāo)頭 | 導(dǎo)出目錄表的相對(duì)虛擬地址(RVA) |
如下圖所示:
??????????????????圖片二十八:獲取Export Table
?因?yàn)槭菂R編來實(shí)現(xiàn)操作,關(guān)鍵的步驟都寫到了注釋當(dāng)中,下面貼上完整的匯編代碼,實(shí)現(xiàn)函數(shù)如下:
puGetModule | puGetProcAddress |
---|---|
獲取模塊基址,參數(shù)1:Hash值 | 獲取函數(shù)地址 參數(shù)1:模塊基址,參數(shù)2:Hash值 |
?關(guān)于Hash值的算法,大家可以逆向一下下面代碼中的匯編代碼,用c語言實(shí)現(xiàn)一下,貼出本代碼中測(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
/*函數(shù)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的名稱指針(應(yīng)該指向一個(gè)字符串)
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]; // 遍歷下一個(gè)
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
/*函數(shù)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;
// 獲取字符串開始計(jì)算
mov esi, [ebp + 0x8];
test esi, esi;
jz tag_failuers;
xor ecx, ecx;
xor eax, eax;
tag_loops:
mov al, [esi + ecx]; // 獲取字節(jié)加密
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函數(shù)如下:
DWORD puGetProcAddress(const DWORD dllvalues, const DWORD Hash)
{
DWORD FunctionAddress = 0;
__asm{
jmp start
// 自定義函數(shù)計(jì)算Hash且對(duì)比返回正確的函數(shù)
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]; // 獲取導(dǎo)出表RVA
lea edi, dword ptr[edi + eax]; // 導(dǎo)出表VA
mov[ebp - 0x14], edi; // ☆ 保存導(dǎo)出表VA
// 獲取函數(shù)名稱數(shù)量
mov ebx, [edi + 0x18];
mov dword ptr[ebp - 0x18], ebx; // ☆ 保存函數(shù)名稱數(shù)量
// 獲取ENT
mov ebx, [edi + 0x20]; // 獲取ENT(RVA)
lea ebx, [eax + ebx]; // 獲取ENT(VA)
mov dword ptr[ebp - 0x20], ebx; // ☆ 保存ENT(VA)
// 遍歷ENT 解密哈希值對(duì)比字符串
mov edi, dword ptr[ebp - 0x18];
mov ecx, edi;
xor esi, esi;
mov edi, dword ptr[ebp - 0x8];
jmp _WHILE
// 外層大循環(huán)
_WHILE :
mov edx, dword ptr[ebp + 0x0c]; // HASH
push edx;
mov edx, dword ptr[ebx + esi * 4]; // 獲取第一個(gè)函數(shù)名稱的RVA
lea edx, [edi + edx]; // 獲取一個(gè)函數(shù)名稱的VA地址
push edx; // ENT表中第一個(gè)字符串地址
call _STRCMP;
cmp eax, 0;
jnz _SUCESS;
inc esi;
LOOP _WHILE;
jmp _ProgramEnd
// 對(duì)比成功之后獲取循環(huán)次數(shù)(下標(biāo))cx保存下標(biāo)數(shù)
_SUCESS :
// 獲取EOT導(dǎo)出序號(hào)表內(nèi)容
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]; // 注意雙字 獲取序號(hào)
// 獲取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;
// 循環(huán)對(duì)比HASH值
_STRCMP:
push ebp;
mov ebp, esp;
sub esp, 0x04;
mov dword ptr[ebp - 0x04], 0x00;
push ebx;
push ecx;
push edx;
push esi;
// 獲取字符串開始計(jì)算
mov esi, [ebp + 0x8];
xor ecx, ecx;
xor eax, eax;
tag_loop:
mov al, [esi + ecx]; // 獲取字節(jié)加密
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加密的函數(shù)名稱
push dllvalues; // 模塊基址.dll
call GetHashFunVA; // GetProcess
mov FunctionAddress, eax; // ☆ 保存地址
popad;
}
return FunctionAddress;
}
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。