PE 學習之路 —— DOS 頭、NT 頭

来源:https://www.cnblogs.com/importthis/archive/2018/12/28/10192440.html
-Advertisement-
Play Games

1. 前述 可執行文件的格式是操作系統本身執行機制的反映,理解它有助於對操作系統的深刻理解,掌握可執行文件的數據結構及其一些機理,是研究軟體安全的必修課。`PE(Portable Executable File Format)`是目前 windows 平臺上的主流可執行文件格式。PE 文件衍生於早期 ...


1. 前述

可執行文件的格式是操作系統本身執行機制的反映,理解它有助於對操作系統的深刻理解,掌握可執行文件的數據結構及其一些機理,是研究軟體安全的必修課。`PE(Portable Executable File Format)`是目前 windows 平臺上的主流可執行文件格式。PE 文件衍生於早期的 COFF 文件格式,描述 PE 格式及 COFF 文件的主要地方在 winnt.h 這個頭文件,其中有一節叫 Image Format,如下:

 該節給出了 DOS MZ 格式和 windows 3.1 的 NE 格式文件頭,之後就是 PE 文件的內容,在這個頭文件中,幾乎能找到關於 PE 文件的每一個數據結構的定義、枚舉類型、常量定義。winnt.h 這個頭文件是 PE 文件定義的最終決定者。DLL 和 EXE 文件之間的區別完全是語義上的,它們使用完全相同的 PE 格式。唯一的區別就是用一個欄位標識出這個文件是 EXE 還是 DLL。同時也包括其它的 DLL 擴展,比如 OCX 控制項和控制面板程式(CPL 文件)。另外,64 位 windows 只是對 PE 格式做了一些簡單的修飾,新格式叫 PE32+,沒有新的結構加進去,其餘的改變只是簡單地將以前的 32 位欄位擴展成64位,比如 `IMAGE_NT_HEADERS`,如下:

 2. PE 文件大體結構

結構的選擇依賴於用戶正在編譯的模式(尤其是 `_WIN64` 是否被定義),在具體學習 PE 之前,先大概清楚下 PE 格式佈局是怎樣子的,如下:

PE 文件使用的是一個平面地址空間,所有代碼和數據都被合併在一起,組成一個很大的結構,文件的內容被分割為不同的區塊,區塊包含代碼和數據,各個區塊按頁邊界來對齊,區塊沒有大小限制,是一個連續結構,每個塊都有它自己在記憶體中的一套屬性。PE 文件是由 PE 載入器載入到記憶體中的,這個 PE 載入器也就是 windows 載入器,它並不是將 PE 文件作為單一記憶體映射文件裝入到記憶體中,而是去遍歷 PE 文件,決定將哪一部分進行映射,這種映射方式是將文件較高的偏移位置映射到較高的記憶體地址,當磁碟文件裝入到記憶體中,其數據結構佈局是一致的,但是數據之間的相對位置可能會改變,如下:

3. 模塊和基地址

下麵需要理清兩個概念,那就是 **模塊** 和 **基地址**,當 PE 文件通過 windows 載入器載入到記憶體後,記憶體中的版本被稱為模塊(Module),映射文件的起始地址被稱為模塊句柄(hModule),可以通過模塊句柄來訪問在記憶體中其它的數據結構,這個初始地址也被稱為基地址(ImageBase)。在 32 位 windows 系統中可以直接調用 `GetModuleHandle` 以取得指向 DLL 的指針,通過指針訪問該 DLL Module 的內容,函數原型為:`HMODULE WINAPI GetModuleHandle(LPCTSTR lpModuleName)`

功能:獲取一個應用程式或動態鏈接庫的模塊句柄。

參數:傳遞一個可執行文件或 DLL 文件名字元串

返回值:若執行成功,則返回模塊的句柄,也就是載入的基地址,若返回零,則表示失敗。如果傳遞參數為 NULL,則返回調用的可執行文件的基地址。

註意事項:只有在當前進程中,這個句柄才會有效,也就是說已映射到調用該函數的進程內,才會正確得到模塊句柄。

 1 #include <windows.h>
 2 #include <iostream>
 3 
 4 int main()
 5 
 6 {
 8     HMODULE hModule = GetModuleHandle(NULL);
 9 
10     std::cout << hModule << std::endl;
11 
12     return 0;
14 }

 PE文件載入的基地址(ImageBase):EXE 預設基地址為 `0x00400000H`,DLL 預設基地址為 `0x10000000H`,這個值可以在鏈接應用時使用鏈接程式的 `/BASE` 選項設定,或者通過 REBASE 應用程式進行設置。說完基地址,再來說下相對虛擬地址,由於 PE 文件中里的東西可以載入到空間的任何位置,所以不能依賴於 PE 的載入點,必須有一個方法來指定地址而不依賴於 PE 載入點的地址,所以出現相對虛擬地址(RVA)概念,RVA 只是記憶體中的一個簡單的相對於 PE 文件裝入地址的偏移位置,例如,假設一個 EXE 文件從地址 `0x400000H` 處裝入,並且它的代碼區塊開始於 `0x401000H`,代碼區塊的 RVA 就是:`0x401000H - 0x400000H = 0x1000H`,在這裡,`0x401000H` 是實際的記憶體地址,這個地址被稱為虛擬記憶體地址(VA),另外也可以把虛擬地址想象為加上首選裝入地址的RVA。

 4. 文件偏移地址

當PE文件儲存在磁碟上,某個數據的位置相對於文件頭的偏移量,稱為文件偏移地址或物理地址。文件偏移地址從PE文件的第一個位元組開始計數,起始值為0,用十六進位文本編輯器打開文件,裡頭顯示的就是文件偏移地址。

 5. IMAGE_DOS_HEADER 結構

在這個結構體中,有兩個欄位非常重要,分別是第一個和最後一個,其它的不重要,其中第一個 e_magic 欄位需要被設置為 0x5A4DH。它也被稱為魔術數字。

這個值有個巨集定義,名為 `IMAGE_DOS_SIGNATURE`,它的 ASCII 值為 MZ,是 MS-DOS 的最初創建者之一 `Mark Zbikowski` 字母的縮寫。

e_lfanew 欄位是真正PE文件頭的相對偏移(RVA),那麼,這個欄位在哪呢?

上圖已經說明瞭,為了驗證是否正確,如下:

在 3CH 偏移處,顯示 0x00000110H(由於 Intel CPU 屬於 Little-Endian 類,字元存儲時低位在前,高位在後,反序排列,將順序恢復後便是 0x00000110H),這個是 e_lfanew 欄位所存儲的值,它占4 個位元組。後面就是 PE 頭了。

 6. IMAGE_NT_HEADERS 結構

在一個有效的 PE 文件里,Signature 欄位被設置為 0x00004550H,ASCII 碼字元是 PE00

巨集定義為 `IMAGE_NT_SIGNATURE`

那麼這兩個重要的欄位(e_lfanew 和 Signature)有什麼用呢?這個在以後解析PE文件,判斷一個文件是否是一個 PE 文件時提供重要依據,即判斷這兩個欄位的值是否為 0x5A4DH 和 0x00004550H,你也可以用它們的巨集定義,分別為 `IMAGE_DOS_SIGNATURE` 和 `IMAGE_NT_SIGNATURE`,如果相等,則為一個 PE 文件,如果不相等,則不是一個 PE 文件。

 1 #include <windows.h>
 2 #include <iostream>
 3 
 4 int main()
 5 {
 6     // 1.首先須打開一個文件
 7     HANDLE hFile = CreateFile(
 8         TEXT("test.png"),
 9         GENERIC_ALL,
10         NULL,
11         NULL,
12         OPEN_EXISTING,
13         NULL,
14         NULL
15     );
16     // 2.判斷文件句柄是否有效,若無效則提示打開文件失敗並退出
17     if (hFile == INVALID_HANDLE_VALUE)
18     {
19         std::cout << "打開文件失敗!" << std::endl;
20         CloseHandle(hFile);
21         exit(EXIT_SUCCESS);
22     }
23     // 3.若打開文件成功,則獲取文件的大小
24     DWORD dwFileSize = GetFileSize(hFile, NULL);
25     // 4.申請記憶體空間,用於存放文件數據
26     BYTE * FileBuffer = new BYTE[dwFileSize];
27     // 5.讀取文件內容
28     DWORD dwReadFile = 0;
29     ReadFile(hFile, FileBuffer, dwFileSize, &dwReadFile, NULL);
30     // 6.判斷這個文件是不是一個有效的PE文件
31     //    6.1 先檢查DOS頭中的MZ標記,判斷e_magic欄位是否為0x5A4D,或者是IMAGE_DOS_SIGNATURE
32     DWORD dwFileAddr = (DWORD)FileBuffer;
33     auto DosHeader = (PIMAGE_DOS_HEADER)dwFileAddr;
34     if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE)
35     {
36         // 如果不是則提示用戶,並立即結束
37         MessageBox(NULL, TEXT("這不是一個有效PE文件"), TEXT("提示"), MB_OK);
38         delete FileBuffer;
39         CloseHandle(hFile);
40         exit(EXIT_SUCCESS);
41     }
42     //    6.2 若都通過的話再獲取NT頭所在的位置,並判斷e_lfanew欄位是否為0x00004550,或者是IMAGE_NT_SIGNATURE
43     auto NtHeader = (PIMAGE_NT_HEADERS)(dwFileAddr + DosHeader->e_lfanew);
44     if (NtHeader->Signature != IMAGE_NT_SIGNATURE)
45     {
46         // 如果不是則提示用戶,並立即結束
47         MessageBox(NULL, TEXT("這不是一個有效PE文件"), TEXT("提示"), MB_OK);
48         delete FileBuffer;
49         CloseHandle(hFile);
50         exit(EXIT_SUCCESS);
51     }
52     // 7.若上述都通過,則為一個有效的PE文件
53     MessageBox(NULL, TEXT("這是一個有效PE文件"), TEXT("提示"), MB_OK);
54     delete FileBuffer;
55     CloseHandle(hFile);
56     // 8.結束程式
57     return 0;
58 }

以上代碼就是簡單實現判斷一個文件是不是有效的 PE 文件。在上述代碼中,運用了 CreateFile()、GetFileSize()、ReadFile() 來獲取文件內容,得到文件的基址 dwFileAddr,只需將該變數轉換成 `PIMAGE_DOS_HEADER` 類型,那麼就能獲取到NT頭的開始位置,NT頭的位置可同 (PIMAGE_NT_HEADERS)((PIMAGE_DOS_HEADER)dwFileAddr->e_lfanew + dwFileAddr) 獲取,有了這個,後面的工作就變得簡單多了。

 7. IMAGE_FILE_HEADER 結構

該結構體描述的是文件的一般性質,有 7 個欄位,共占 20 個位元組,20 相當於十六進位的 14H,下圖已標出實際位置,如下:

 

  • 這裡標記的是 Machine 欄位,占兩個位元組,它的值為 0x014CH,代表的是 Intel i386 平臺。
  • 這裡標記的是 NumberOfSections 欄位,占兩個位元組,它的值為 0x0006H,代表的是有 6 個區塊,也可以說有 6 個節。
  • 這裡標記的是 TimeDateStamp 欄位,占四個位元組,它的值為 0x5C0748D5H,代表的是文件創建日期和時間。

 

由上圖可以看出,該文件創建時間為 2018-12-05 / 11:41:09。

  • 這個值以0填充,用不到。
  • 這個值以0填充,用不到。
  • 這個欄位就比較重要,劃重點,SizeOfOptionalHeader,占兩個位元組,它的值為 0x00E0H,代表的是 `IMAGE_OPTIONAL_HEADER32` 結構的大小,在 32 位系統,它的值為 0x00E0H,在 64 位系統,它的值為 0x00F0H,
  • 最後一個欄位 Characteristics,占兩個位元組,它的值為 0x0102H,代表的是文件的屬性。這個值是由 0x0100H 和 0x0002H 兩者之和,0x0100H 這個值代表的是目標平臺為 32 位機器,0x0002H 這個值代表文件可執行,如果為0,一般是鏈接出現了問題。
1 // 獲取文件頭
2     auto FileHeader = NtHeader->FileHeader;
3     // 接下來就是解析各欄位
4     std::cout << "運行平臺:0x" << std::hex << FileHeader.Machine << std::endl;
5     std::cout << "區塊數目:0x" << std::hex << FileHeader.NumberOfSections << std::endl;
6     std::cout << "文件創建日期和時間:0x" << std::hex << FileHeader.TimeDateStamp << std::endl;
7     std::cout << "IMAGE_OPTIONAL_HEADER32結構大小:0x" << std::hex << FileHeader.SizeOfOptionalHeader << std::endl;
8     std::cout << "文件屬性:0x" << std::hex << FileHeader.Characteristics << std::endl;

將上述代碼插入到 return 0; 之前,運行如下:

再將上面文件創建日期和時間進行轉換,代碼如下:

 1 // 獲取文件頭
 2     auto FileHeader = NtHeader->FileHeader;
 3     // 進行時間轉換
 4     tm * FileCreateTime = gmtime((time_t*)&FileHeader.TimeDateStamp);
 5     // 接下來就是解析各欄位
 6     std::cout << "運行平臺:0x" << std::hex << FileHeader.Machine << std::endl;
 7     std::cout << "區塊數目:0x" << std::hex << FileHeader.NumberOfSections << std::endl;
 8     std::cout << "文件創建日期和時間:" << std::dec << FileCreateTime->tm_year + 1900 << "-"
 9     << FileCreateTime->tm_mon + 1<< "-"
10     << FileCreateTime->tm_mday << " "
11     << FileCreateTime->tm_hour + 8 << ":"
12     << FileCreateTime->tm_min << ":"
13     << FileCreateTime->tm_sec << std::endl;
14     std::cout << "IMAGE_OPTIONAL_HEADER32結構大小:0x" << std::hex << FileHeader.SizeOfOptionalHeader << std::endl;

上面是用到了tm的結構,以及 gmtime 這個函數進行轉換,在用之前需要包含頭文件 time.h,運行如下:

不過還是要註意下,首先 tm_year 這個值為十六進位,需轉成十進位,而且要加上 1900,因為時間是從 1900 開始算,它的值為偏移,其次月是從 0 開始算的,所以要加 1,最後是時區問題,因為我這裡位於東八區,所以小時需加上 8。另外關於調試的那兩個欄位,沒有必要去對它深究,因為微軟的VS已用了新的 Debug 格式,這個只是用來設置 COFF 符號,跟 COFF 符號有關,一般這個值都為 0,所以不探討它。關於運行平臺代碼和文件屬性代碼可以去網上查表就行,這裡就省略。

 8. IMAGE_OPTIONAL_HEADER 結構

上圖展示的是 `IMAGE_OPTIONAL_HEADER32` 結構體各欄位,這個結構體相對來說就比較大,我已經分析好了,這個是 32 位的,64 位的大體結構沒變,只是有幾個欄位改成的 ULONGLONG 類型,那麼它在實際內部是怎麼樣的呢?下麵這張圖是驗證上面圖片所敘述的。

上圖所標記的,為 `IMAGE_OPTIONAL_HEADER32` 結構所有成員,你也註意到了,在結尾處,有 .text,說明已經到了該結構體的末尾了,算了一下,恰好占了 224 個位元組,這個值其實在 `IMAGE_FILE_HEADER` 中倒數第二個欄位已經指出了,值為 0xE0,這個值相當於十進位中的 224。為了更好的說明,我用序號標記了各個欄位,其中有一些為透明,一是沒地方標,二是能看清實際數值大小,這樣便於分析。

以下是各欄位解析:

  • Magic:這個是一個標記,它的值為 0x010BH,代表的是普通的可執行映象,一般是 0x010BH,如果是 64 位,則為 0x020BH,如果為 ROM 映象,該值為 0x0107H。
  • MajorLinkerVersion:鏈接程式主版本號,值為 0x0EH。
  • MinorLinkerVersion:鏈接程式次版本號,值為 0x00H。
  • SizeOfCode:所有含有代碼區塊的總大小,該值為 0x0031D000H,這個代碼區塊是帶有 `IMAGE_SCN_CNT_CODE` 屬性,這個值是向上對齊某一個值的整數倍。通常情況下,多數文件只有一個 Code 塊,所以這個欄位和 .text 塊的大小匹配。
  • SizeOfInitializedData:所有初始化數據區塊總大小,該值為 0x000B4000H,這個是在編譯時所構成的塊的大小(不包括代碼段),一般這個值是不准確的。
  • SizeOfUninitializedData:所有未初始化數據區塊總大小,該值為 0,這些塊在程式開始運行時沒有指定值,未初始化的數據通常在 .bss 塊中。
  • AddressOfEntryPoint:程式執行入口 RVA,該值為 0x002B56D0H。在大多數可執行文件中,這個地址並不直接指向 Main、WinMain 或者是 DllMain,而是指向運行庫代碼並由它來調用上述函數。對於 DLL 來說,這個入口點是在程式初始化和關閉時以及線程創建和毀滅時被調用。
  • BaseOfCode:代碼段的起始 RVA,該值為 0x00001000H,如果是用微軟的鏈接器生成的,則該值通常是 0x00001000H。
  • BaseOfData:數據段的起始 RVA,該值為 0x0031E000H,數據段通常在記憶體的末尾,對於不同版本的微軟鏈接器,這個值是不一致的,在64位可執行文件中是不出現的。
  • ImageBase:程式預設裝入地址,該值為 0x00400000H,載入器試圖在這個地址表裝入 PE 文件,如果可執行文件是在這個地址裝入的,那麼載入器將跳過應用基址重定位的步驟。
  • SectionAlignment:記憶體中區塊對齊大小,值為 0x00001000H,預設對齊尺寸是目標 CPU 的頁尺寸,最小的對齊尺寸是一頁 1000H(4KB),在 IA-64 上,這個值是 8KB。每個區塊裝入地址必定是本欄位指定數值的整數倍。
  • FileAlignment:磁碟上 PE 文件內的區塊對齊大小,值為 0x00000200H,對於 x86 的可執行文件,這個值通常是 200H 或 1000H,這是為了保證塊總是從磁碟的扇區開始的,這個值必須是 2 的冪,最小為 200H。
  • MajorOpreatingSystemVersion:要求操作系統的最低版本號的主版本號,該值為 0x0006H,這個值似乎沒什麼用。
  • 同上,沒什麼用。
  • 同上,沒什麼用。
  • 同上,沒什麼用。
  • 同上,沒什麼用。
  • 同上,沒什麼用。
  • 同上,沒什麼用。
  • SizeOfImage:映象裝入記憶體後的總尺寸,該值為 0x003D5000H,它指裝入文件從 ImageBase 到最後一個塊的大小,最後一個塊根據其大小往上取整。
  • SizeOfHeaders:是 MS-DOS 頭部、PE 頭部、區塊表的組合尺寸。該值為 0x00000400H。
  • CheckSum:校驗和,IMAGEHLP.DLL 中的 CheckSumMappedFile 函數可以計算這個值,一般的EXE文件可以是 0,但一些內核模式的驅動程式和系統 DLL 必須有一個校驗和。
  • Subsystem:一個標明可執行文件所期望的子系統的枚舉值,這個值只對 EXE 是重要的。該值為 0x0003H。
  • DllCharacteristics:DllMain() 函數何時被調用,預設為 0。
  • SizeOfStackReserve:在 EXE 文件里,為線程保留的堆棧大小,它一開始只提交其中一部分,只有在必要時,才提交剩下的部分。
  • SizeOfStackCommit:在 EXE 文件里,一開始即被委派堆棧的記憶體數量,預設值為 4KB。
  • SizeHeapReserve:在 EXE 文件里,為進程的預設堆保留的記憶體,預設值為 1MB,但是在當前 Windows 里,堆值在用戶不幹涉的情況下就能增長超過這個值。
  • SizeOfHeapCommit:在 EXE 文件里,委派給堆的記憶體大小,預設值是 4KB。
  • LoaderFlag:與調試有關,預設為 0。
  • NumberOfRvaAndSizes:數據目錄表的項數,這個欄位一直以來都為 16。
  • DataDirectory[16]:數據目錄表,由數個 `IMAGE_DATA_DIRECTORY` 結構組成,指向輸入表、輸出表、資源等數據。

同樣,將上述代碼放置最後,對擴展頭進行解析,因欄位太多,沒一一列舉,運行後如下:

對於該結構的最後一個欄位,它是一個數組,這個數組有 16 個成員,代表的是目錄表中的項,遍歷它也不是很難,代碼如下:

 

運行後如下:

將上述與 LoadPE 對照,看下是否正確,如下:

 

從上面可以看出,已經成功遍歷出目錄表中每項的 RVA 和大小。

(本小節完)


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本節內容主要介紹了C語言程式的常量和變數。介紹了常量和變數的概念以及命名規範,然後講解了它們的一些基本使用方法。 ...
  • 管理屬性的幾種方式 在python中訪問、設置、刪除對象屬性的時候,有以下幾種方式: 1. 使用內置函數getattr()、setattr()和delattr() 2. 自己編寫 、`setter() deleter()`方法 3. 重載 、`__setattr__() __delattr__() ...
  • 等了好久終於等到今天,盼了好久終於把夢實現,啦啦啦.....學習python兩個多月了,終於等到python的高潮,老師說要是把Django學會了,python的web開發就差不多了,也相當於迎來了python學習之路最重要一環節,也是框架中比較難的,所以也是迎來了最大難點,好高興哦,可以上課開飛機 ...
  • 園友們好,元旦很快就到來了,提前祝各位園友們元旦快樂,今天給大家分享一個工作中必用一個知識點,就是使用枚舉構建自定義異常並應用於springboot的異常處理器。開始之前我先把這個案例的結構大致說明一下: 1、使用idea創建一個springboot的Gradle/Maven項目,引入web模塊即可 ...
  • 1、os 所有和操作系統相關的內容都在os模塊,一般用來操作文件系統 import os os.makedirs('dirname1/dirname2') # 可生成多層遞歸目錄 os.removedirs('dirname1') # 若目錄為空,則刪除,並遞歸到上一級目錄,如若也為空,則刪 除,依 ...
  • 一、python中文件操作的六種模式分為:r,w,a,r+,w+,a+ r叫做只讀模式,只可以讀取,不可以寫入 w叫做寫入模式,只可以寫入,不可以讀取 a叫做追加寫入模式,只可以在末尾追加內容,不可以讀取 r+叫做讀寫模式,可讀可寫,預設讀游標從0也就是開頭開始讀取,但是寫內容是從末尾開始寫的 w+ ...
  • 練習一:統計一個文件calcCharNum.txt中字母‘A’和'a'出現的總次數。 練習二:在電腦E盤下創建一個文件為HelloWord.txt文件, 判斷它是文件還是目錄, 再創建一個目錄IOTest, 之後將HelloWorld.txt移動到IOTest目錄下去, 之後遍歷IOTest這個目錄 ...
  • PHP Warning: Module 'openssl' already loaded in Unknown on line 0 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...