Nguyễn Dũng
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
2
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
# PE Parser # I. Introduction - Đây là bài mở đầu cho một loạt các kĩ thuật liên quan đến malware mà mình sẽ viết sau này. Tương lai hướng tới sẽ là một bài về định dạng ELF nữa để cover đầy đủ các executable file format # II. Content ## Quick view - Về cơ bản thì PE format có cấu trúc như sau: ![image](https://tech-zealots.com/wp-content/uploads/2018/05/PE-Structure.png) trong đó một vài header quan trọng mình sẽ phân tích là :::info - Stub - Optional Header - Data Directory - Section Headers - .idata và .edata ::: - Các `PE` file không chỉ là executable file có extension `.exe` mà cũng có thể là `.dll` (đối với `ELF` file sẽ là `.so`, `.bin` hoặc `.o`), quick brief ở dưới đây: ![image](https://hackmd.io/_uploads/ry2Lscyrle.png) - Mình sẽ nói một chút overview của các header thường được examine trong PE file: ### DOS Header - Mỗi `PE` file đều bắt đầu với một header là một struct kích thước 64 bytes. Header này rất quan trọng và là tiền đề để một `PE` file có thể thực thi được ### Nt Header - Header này cung cấp khá nhiều thông tin quan trọng cho người phân tích cũng như cho `PE` file. Nt Header bao gồm File Header và Optional Header. #### File Header - Một số thông tin của `PE` file nằm trong đây như :::info - Machine: Thông tin về architecture của `PE` file - Section Count: Đếm số section của file - Size of Optional Header: Thông tin về kích thước của Optional Header ::: #### Optional Header - Có thể coi Optional Header là Header chứa nhiều thông tin nhất của `PE` file, nó cho biết một số điều quan trọng cho việc thực thi như :::info - Magic: Thông tin `PE` file thuộc dạng x86 hay x64 - Entry Point: Địa chỉ RVA khi load `PE` file - Base of Code: Địa chỉ RVA khi bắt đầu code section - Image Base: Địa chỉ bắt đầu khi chương trình được load vào memory - Data Directory (sẽ nói rõ ở dưới) ::: ### Section Header - Một vài thông tin về các section được liệt kê trong đây, hỗ trợ việc thực thi ## 0. Preparation - Chương trình mà mình sẽ parse chỉ đơn giản in ra: ```c= #include <stdio.h> int main(){ printf("Hello World!"); return 0; } ``` - Bên cạnh đó chúng ta cũng cần một file `.dll` để phân tích phần `Export`: ```c= #include <windows.h> #define DLL_EXPORT extern "C" __declspec(dllexport) DLL_EXPORT int add(int a, int b) { return a + b; } ``` ## 1. DOS Header Explanation - Đây là một ví dụ về DOS Header ![image](https://hackmd.io/_uploads/Symgmxxrle.png) - Trong đó một vài trường mà mình quan tâm nằm tại hai Offset: :::success - Offset 0x00: Magic Number - Offset 0x3C: File Address ::: - `Magic Number` với một file `.exe` (thông thường) là `PE` hay giá trị hex là `5A4D`. Bên cạnh đó, `File Address` (gọi tắt của `File Address of new exe header`) chính là file offset của `NtHeader` - Để parse header này, mình sử dụng struct được định nghĩa sẵn của `winnt.h`: ```c= 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; ``` - Do kiểu dữ liệu của các trường trong struct đều là `WORD` (2 `BYTE`) nên offset cũng là bội của 2, trừ `e_lfanew` là `LONG` - Với `e_magic` thì chỉ là dấu hiệu nhận biết một file có thể thực thi hay không với giá trị `MZ`. Còn với `e_lfanew`, nó mang giá trị để dẫn tới offset của `Nt Header`, về chức năng thì có thể hiểu nó là con trỏ cũng được. ## 2. Nt Header Explanation - Đối với header này, nó được trỏ tới bởi trường `e_lfanew` của header. Nhưng cũng có thể có câu hỏi tại sao lại tồn tại một trường như thế. Câu trả lời là giữa `Nt Header` và `DOS Header` còn có một đoạn code `Stub` và `Rich` ở giữa. ::: info - Tuy nhiên, `Stub` chỉ để in ra cảnh báo ```This program cannot be run in DOS mode.``` ![image](https://hackmd.io/_uploads/HJDoZWlHel.png) - Trong khi đó, `Rich Header` được sử dụng để chứa một vài thông tin của file `.exe` khi được compile từ file `.c` bằng `Visual Studio` ![image](https://hackmd.io/_uploads/BkwBMZlSxx.png) (đây là ví dụ khác với ảnh trên) ::: - Chính vì thế, sự tồn tại của trường `e_lfanew` là không thể xóa bỏ được vì mỗi `DOS Header` chỉ là 64-bytes, `e_lfanew` sẽ giúp liên kết các `Header` quan trọng trong file. Đối với file mình đang examine, `e_lfanew` mang giá trị hex `80`, và tại offset `0x80` sẽ là: ![image](https://hackmd.io/_uploads/S16hJWlSlx.png) ![image](https://hackmd.io/_uploads/SJqNxZgrxx.png) - Do file của mình là một file `PE` và valid nên `PE Bear` sẽ tự bỏ qua mà examine offset `0x84`. Cơ bản thì `Nt Header` bao gồm hai phần `File Header` và `Optional Header` mình đã nói ở trên ### 2.1. File Header Explanation - Như cái tên của nó, header này cung cấp thông tin về file của chúng ta, và cũng có một struct tương ứng với header này được định nghĩa sẵn: ```c= 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; ``` - Thông tin mình quan tâm trong trường này là `Machine`, `NumberOfSections`, `TimeDateStamp`, và `SizeOfOptionalHeader`. Cụ thể hơn: - `Machine` cung cấp thông tin về kiến trúc thiết bị cần để thực thi file, chi tiết có thể xem thêm ở [đây](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types) - `NumberOfSections` cho biết số sections có ở trong file `PE`, có thể sử dụng để verify sau này - `TimeDateStamp` cho biết ngày mà file được tạo ra - `SizeOfOptionalHeader` cho biết kích thước của `Optional Header` mà mình sẽ phân tích ở dưới đây, và trường này đặc biệt cần thiết cho mọi file thực thi. Riêng với file object thì có thể được set là 0 - Hình ảnh chi tiết về thông tin của `File Header` như sau: ![image](https://hackmd.io/_uploads/SJqNxZgrxx.png) ### 2.2. Optional Header Explanation - `Optional Header` cũng được phân chia đủ cho hai kiến trúc máy là x86 và x64, sự khác biệt là: - Ở trường `BaseOfData` không xuất hiện trong x64. `BaseOfData` có chức năng giống `BaseOfCode` là giá trị RVA của data secion (thường là `.data`) - Một số kiểu dữ liệu của vài trường trong x64 được mở rộng hơn bản 32 bit, từ `DWORD` thành `ULONGLONG`: :::info - `ImageBase` - `SizeOfStackReserve` - `SizeOfStackCommit` - `SizeOfHeapReserve` - `SizeOfHeapCommit` ::: - `Optional Header` cũng được định nghĩa struct của riêng nó, thông tin tham khảo thêm ở [OptHdr 32 bit](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header32), [OptHdr 64 bit](https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64) - Có khả năng đây sẽ là trường mình viết tốn giấy nhất, vì thông tin của nó quá nhiều, đây là hình ảnh minh họa: ![image](https://hackmd.io/_uploads/B11CrWxBgx.png) ![image](https://hackmd.io/_uploads/rJdg8WlHeg.png) ![image](https://hackmd.io/_uploads/ByvWLZeSxg.png) #### 2.2.1. Before Data Directory - Dưới đây sẽ là một số trường mình đặc biệt quan tâm khi parse một file `PE`: - `Magic`: Thông tin về kiến trúc máy x86 hoặc x64 - `SizeOfCode`: Tổng kích thước của `.text` và các trường "có thể thực thi được" - `SizeOfInitializedData`: Tổng kích thước của `.data` và các trường gần tương tự - `SizeOfUninitializedData`: Tổng kích thước của `.bss` và các trường gần tương tự - `AddressOfEntryPoint`: Địa chỉ RVA nơi mà file được load vào memory. Đối với `Dll` thì giá trị của nó có thể được đặt là 0 - `BaseOfCode`: Địa chỉ RVA nơi bắt đầu code section khi file được load vào memory - `ImageBase`: Đây là trường cho biết địa chỉ đầu tiên mà file được load vào trong memory. Trên thực tế đối với các vùng nhớ được set `ASLR`, trường này gần như không bao giờ được sử dụng. Trường này còn liên quan mật thiết với section `.reloc` nhưng tạm thời mình chưa research về nó - `SectionAlignment`: Trường này cho biết kích thước của một section nên có, các RVA của section sẽ được đặt trong các bội của 0x1000, cụ thể như sau: ![image](https://hackmd.io/_uploads/H198kzxreg.png) Section đầu tiên ở 0x1000 và có size 0x73D0 nhưng khi cộng vào thì chỉ được 0x83D0 nên section tiếp theo được đặt hẳn lên 0x9000. - `FileAlignment`: Tương tự như `SectionAlignment` nhưng áp dụng trên file, section sẽ được pad thêm zerobyte nếu kích thước của nó chưa đạt đủ. - `NumberOfRvaAndSizes`: Kích thước của mảng `DataDirectory`, thường là 16 (0x10) #### 2.2.2. Data Directory - Thực chất `Data Directory` là một mảng gồm `NumberOfRvaAndSizes` phần tử, cấu trúc của mảng đơn giản gồm hai trường: ```c= typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; ``` - Thông thường, 16 phần tử của `Data Directory` theo offset là: ```c=0 Export table address and size Import table address and size Resource table address and size Exception table address and size Certificate table address and size Base relocation table address and size Debugging information starting address and size Architecture-specific data address and size Global pointer register relative virtual address Thread local storage (TLS) table address and size Load configuration table address and size Bound import table address and size Import address table address and size Delay import descriptor address and size The CLR header address and size Reserved ``` - Trong đó, đối với việc packing và unpacking, mình đặc biệt quan tâm đến RVA của hai thành phần là `Import table address and size` và `Import address table address and size`. Từ hai phần tử này, mình có thể chuyển đổi từ RVA sang file Offset để phân tích file tốt hơn, công thức như sau: ```c= for (int i = 0; i < numberOfSections; i++) { IMAGE_SECTION_HEADER section = sectionHeader[i]; if (rva >= section.VirtualAddress && rva < section.VirtualAddress + section.SizeOfRawData) { return (rva - section.VirtualAddress) + section.PointerToRawData; } } ``` - Các phần tử trong `Data Directory` cũng được định nghĩa trong header `winnt.h`: ```c // Directory Entries #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage) #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers #define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor ``` ## 3. Sections and Section Headers Explanation - Hai phần này sẽ cover hầu như toàn bộ phần còn lại của file PE. ### 3.1. Section Explanation - Phần này thực chất nằm sau `Section Headers` mà mình sẽ nói dưới đây. Sau đây là một ví dụ về các section có trong file mà mình đã chuẩn bị: ![image](https://hackmd.io/_uploads/BJhvG8eBxe.png) - Trong đây cũng đã có một số section's name khá phổ biến, mở rộng hơn thì đây là [danh sách những tên section được documented lại bởi Microsoft](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#special-sections): :::info - `.text`: Chứa phần code có thể thực thi được - `.data`: Chứa phần data được khởi tạo sẵn. - `.bss`: Phân vùng data chưa được khởi tạo. - `.rdata`: Vùng chỉ đọc data đã khởi tạo. - `.edata`: Chứa bảng export. - `.idata`: Chứa bảng import. - `.reloc`: Thông tin về image relocation. - `.rsrc`: Chứa thông tin về các tài nguyên được sử dụng bởi chương trình, có thể là ảnh, icon hoặc thậm chí là binary được nhúng vào file - `.tls`: (Thread Local Storage), storage cho các luồng chương trình được thực thi ::: ### 3.2. Section Headers Explanation - `Section Headers` ở đây là số nhiều, và thực chất nó là một mảng, mỗi phần tử là 40 bytes được định nghĩa bằng cấu trúc `IMAGE_SECTION_HEADER`: ```c= typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; ``` - Trong `PE Bear` thì hình ảnh của `Section Headers` như sau: ![image](https://hackmd.io/_uploads/HytkPLlHlg.png) - Mình sẽ giải thích một vài trường trong cấu trúc của một `Header`: - `RawAddress` hay `PointerToRawData` là file Offset xuất hiện section đó - `RawSize` hay `SizeOfRawData` là kích thước của section **TRÊN FILE**, và là bội của giá trị tại trường `FileAlignment` đã nói ở trên `Optional Header` - `VirtualAddress`, đúng ra ở đây phải là `Virtual Offset`, là địa chỉ byte đầu tiên của section so với `ImageBase` - `VirtualSize` là kích thước của section **TRÊN MEM**, và là bội của giá trị tại trường `SectionAlignment` - Một số kiến thức quan trọng cần lưu ý khi làm việc với các section là `RVA`, `VA` và `File Offset`, chúng ta đều có thể dễ dàng chuyển đổi giữa 3 giá trị này. Mình phân biệt `RVA` và `VA` bằng cách tự định nghĩa: `RVA` là khoảng cách giữa section và `ImageBase`; trong khi đó `VA` là khoảng cách giữa section so với 0, tức là: ```javascript RVA = VA - ImageBase ``` - Và giả sử mình có ví dụ sau: ```python .text: Raw Address = 0x600 Raw Size = 0x7400 Virtual Address = 0x1000 Tính RVA của offset 0x800 ``` - Để giải bài này cũng khá đơn giản, mình sẽ xác định offset đó nằm trong section nào. Trong trường hợp này thì offset đó nằm giữa `.text` và section tiếp theo. Vậy để tính được `RVA` thì chỉ cần làm như sau: ```python RVA = (Offset - Raw Address) + Virtual Address RVA = 0x800 - 0x600 + 0x1000 RVA = 0x1200 ``` Check: ![image](https://hackmd.io/_uploads/ry2sj8gSxe.png) - Để convert ngược lại cũng khá đơn giản, cần xác định xem `RVA` của target đang nằm trong section nào, và thực hiện ngược lại tư duy ở trên để có thể tính lại `offset` của target: ```python Offset = (RVA - Virtual Address) + Raw Address Offset = (0x1200 - 0x1000) + 0x600 Offset = 0x800 ``` - Dựa vào tư duy này thì chúng ta có thể recover được các file `PE` hỏng ở mức tương đối đơn giản. ## 4. Import/Export Explanation - `Import` không phải là một header mà là một nội dung mình sẽ cover tương đối đầy đủ vì nó khá khó và quan trọng. - Trước hết, để biết được phần `Import` nằm ở đâu, ta có hai cách: - Nhìn vào phần tử `Import Directory` trong mảng `Data Directory`: ![image](https://hackmd.io/_uploads/HyH8ZPxree.png) - Nhìn vào section `.idata`: ![image](https://hackmd.io/_uploads/rJbdWPgSge.png) ![image](https://hackmd.io/_uploads/SJOd-PeSxg.png) - Thông thường với file `PE` không định nghĩa section `.idata`, giá trị tại `Import Directory` sẽ là `RVA`, chúng ta cần chuyển nó sang `offset` theo đúng tư duy ở trên. Khi đã tìm được đến `offset` của `Import Directory` rồi, chúng ta sẽ cần phân tích dựa vào struct này: ```c= typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; } DUMMYUNIONNAME; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR; ``` - Thực chất `Import Directory` là một mảng gồm nhiều phần tử liên tiếp nhau có cấu trúc `IMAGE_IMPORT_DESCRIPTOR`, và máy sẽ load từng phần tử một cho tới khi gặp zerobytes, cụ thể là 20 bytes 0 thì dừng. Trong struct này, các trường mình quan tâm là: - `OriginalFirstThunk`: RVA của Import Lookup Table (ILT) - `Name`: RVA chứa tên của (thường là `.dll`) thư viện load vào - `FirstThunk`: RVA của Import Address Table (IAT) - Một phần tử `IMAGE_IMPORT DESCRIPTOR` trông như sau: ![image](https://hackmd.io/_uploads/B1co8vlBxl.png) - Mình đang trỏ vào phần tử đầu tiên (load `KERNEL32.dll`). Thông tin của nó đều được parse đầy đủ, và tại các `RVA` đó là thông tin mình đã nói ở trên: - `RVA ILT`: ![image](https://hackmd.io/_uploads/HkkPDweHge.png) - `Name`: ![image](https://hackmd.io/_uploads/BkLkuDlrxe.png) - `RVA IAT`: ![image](https://hackmd.io/_uploads/SyI3DDeSel.png) nó còn kéo dài tới tận `RVA` 0xF228 nên mình bỏ qua - Một phần kiến thức nữa cũng không thể bỏ qua là bảng `ILT` hay `Import Lookup Table`. Như đã thấy trong ảnh chụp IDA, các `RVA` đó đều chứa thông tin về các hàm (chủ yếu là tên và một số hint dùng để tra cứu hàm), đầy đủ như sau: ![image](https://hackmd.io/_uploads/HkYddPeHee.png) - Mỗi địa chỉ `RVA` tại `ILT` lại trỏ tới vùng thông tin về hàm để tra cứu, dựa trên struct này: ```c= typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; CHAR Name[1]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME; ``` - Cuối cùng là `IAT` hay `Import Address Table` là mảng thông tin rất quan trọng về hàm để thực thi, đó chính là địa chỉ hàm. Về bản chất thì `IAT` có thể coi là một `Jump Table` giúp thực thi code một cách "gián tiếp". ### 4.1. How the program calls a function - Function ở đây được hiểu là các function được declare trong phần `Import Directory`, chủ yếu đây là các hàm load từ các file `.dll`. Để gọi được hàm từ `.dll`, chương trình cần có tên hàm (đại diện cho nó là `Name` từ bảng `ILT`), và địa chỉ của hàm (nằm trong bảng `IAT`). - Về flow, có thể biểu diễn như sau: ``` Executable (.exe) | +--> Import Directory Table | +--> ILT --> "func_name" +--> IAT --> [func_address from dll] ``` - `Compiler` khi nhận được một lời gọi hàm `func` trong code sẽ chuyển đổi nó thành `call [IAT_ENTRY]`. Trong đó, `IAT_ENTRY` là địa chỉ thực của hàm `func` trong file `.dll` - Bên cạnh đó, `Loader` đóng vai trò khá quan trọng là ghi lại địa chỉ hàm vào bảng `IAT`. Với mỗi `dll` được load, Loader sẽ trích xuất địa chỉ từng hàm trong `dll` đó và điền vào `IAT`. - Kết luận lại, chương trình gọi hàm theo kiểu gián tiếp, gọi hàm từ `.dll` như thể hàm nằm trong chương trình nhưng thực chất là thông qua một bảng `IAT` chứa địa chỉ hàm trong file `.dll` ### 4.2. Export Explanation - Nhân tiện nói về `Import Directory` thì `Export Directory` cũng là một chủ đề khá quan trọng. `Export Directory` thường được thấy trong các file thư viện như `.dll`, được sử dụng khi một chương trình ngoài cần thực thi hàm trong file. - `Export` cũng có mối liên hệ mật thiết với `Export Directory` trong mảng `Data Directory`, và section `.edata`. Thông thường, `.edata` sẽ không bị ẩn đi như `.idata`. Đây là `Export Directory`: ![image](https://hackmd.io/_uploads/Sy84xOeBel.png) - Trong header `winnt.h`, struct của `Export Directory` được định nghĩa như sau: ```c= 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; ``` - Với file của mình, `Export Directory` nằm tại `RVA` 0x8000, đã được chương trình tự động chuyển sang `offset` 0x2A00: ![image](https://hackmd.io/_uploads/BJmReulrll.png) - Một vài thông tin mình khá quan tâm là: - `Name`: chứa tên của file `.dll` - `AddressOfFunctions`: RVA trỏ tới địa chỉ của hàm `add` ![image](https://hackmd.io/_uploads/HyVXbOlSee.png) - `AddressOfNames`: RVA trỏ tới địa chỉ tên của hàm `add` ![image](https://hackmd.io/_uploads/rJSL-dgHxe.png) - Đối với `AddressOfFucntions`, trong bài, `RVA` là 0x1370, tương ứng với `offset` 0x970: ![image](https://hackmd.io/_uploads/B19kfOlBxe.png) - Và tại `offset` 0x970, `RVA` 0x1370 chứa code thực thi của hàm `add`: ![image](https://hackmd.io/_uploads/SyvSfueSxx.png) # III. Wrapping up - Với mình thực sự `PE File` là khởi đầu tốt để đến với Malware Analysis. Trong tương lai mình sẽ cover nhiều hơn chủ đề này. Hi vọng bài viết này hữu ích với các bạn. Dear!!! # IV. Practice - Phần này mình sẽ nói qua một chút kĩ thuật để recover PE file, một số kĩ thuật cơ bản thôi. Hai công cụ mình sẽ sử dụng là `PE Bear` để parse file và `HxD` để chỉnh sửa. ## 1. fix1.exe - File này đang bị mất `Import Directory` (`PE Bear` không hiển thị): ![image](https://hackmd.io/_uploads/ryYxfjbBeg.png) - Cách giải quyết khá đơn giản là ghi địa chỉ đúng của `Import Directory` vào trong mảng `Data Directory`: ![image](https://hackmd.io/_uploads/Sk-BzsWBge.png) - Để tìm được địa chỉ đó, mình sẽ tìm string, cụ thể ở đây là `.dll`: ![image](https://hackmd.io/_uploads/rkEjfiZrgx.png) - Thông thường `PE` file sẽ luôn load `kernel32.dll` đầu tiên, và theo cấu trúc của `_IMAGE_IMPORT_DESCRIPTOR` sẽ tồn tại một trường là `NameRVA`: ```c= typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; } DUMMYUNIONNAME; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR; ``` - Vì thế, `NameRVA` đầu tiên mà mình có là địa chỉ của `kernel32.dll`. - Offset của string `kernel32.dll` là `0x3606` như trên ảnh, do `NameRVA` lưu theo địa chỉ trên Mem nên cần chuyển `0x3606` thành: ![image](https://hackmd.io/_uploads/HJWNUsbrel.png) - Do `0x3606` nằm trên section `.rdata` nên mình có cách chuyển trên. Tìm kiếm giá trị `0x4C06` trên file bằng `HxD` thì tìm được: ![image](https://hackmd.io/_uploads/HJzDUsbBee.png) - Vậy `NameRVA` sẽ nằm ở file offset `0x2E60`. Khi đó, `OriginalFirstThunk` sẽ nằm ở offset `0x2E58` và đây chính là địa chỉ của phần tử đầu tiên trong mảng `Import Directory`. Chuyển `0x2E58` từ offset sang RVA là ![image](https://hackmd.io/_uploads/SkMUFo-reg.png) - Điền vào `Data Directory` là `Import` đã được recovered: ![image](https://hackmd.io/_uploads/r16_tjWHxx.png) ![image](https://hackmd.io/_uploads/HJwtKsbBxx.png) ## 2. fix2.bin - Đây là một bài tương đối khó, về cơ bản, file được cung cấp không phải file trên disk mà được dump ra từ memory. `PE Bear` cũng đưa ra cảnh báo: ![image](https://hackmd.io/_uploads/rJQyco-Sll.png) - Trong trường hợp `PE Bear` không đưa ra cảnh báo, ta cũng có thể nhận biết đây là file `PE` trên memory do thấy có sự sai lệch khi `Raw Address` của các section toàn là byte 0 trong khi `Virtual Address` chứa các byte thực sự: ![image](https://hackmd.io/_uploads/ByaOqoZSee.png) ![image](https://hackmd.io/_uploads/B19ccsZHlg.png) - Để làm được bài này, mình sẽ có hai cách: cách 1 dễ hơn và chỉ có thể đọc được `Export`, `Import`; cách 2 khó hơn chút và có thể thực thi được file ### 2.1. Cách 1 - Do file được dump ra từ memory, nên vốn dĩ các `Virtual Address` đều chứa byte chuẩn, khi đó chỉ cần thay đổi các giá trị `Raw Address` thành của `Virtual Address` là có thể xem được `Export` và `Import`: - Trước: ![image](https://hackmd.io/_uploads/SkW3siZSxx.png) - Sau: ![image](https://hackmd.io/_uploads/SJzaosbBgg.png) - `Export`: ![image](https://hackmd.io/_uploads/rk0FijZrxl.png) - `Import`: ![image](https://hackmd.io/_uploads/HJT5ijbBxx.png) ### 2.2. Cách 2 - Do cách 1 chỉ máy móc thay đổi `Raw Address` thành `Virtual Address`, các section trên disk không thực sự hợp lí về logic, đặc biệt là trường `Raw Size`. Chính vì thế, cách 2 này sẽ nâng cấp hơn cách 1 và mapping chuẩn chỉ hơn. - Do tại các section đều có byte 0 tại `Raw Address`, mình sẽ lợi dụng các byte này để ghi đè byte thực sự của nó, code như sau: ```python= fileAlign = 0x200 def fitSection(size, align): if size % align != 0: size = align * (size // align + 1) return size oldFile = bytearray(open("fix2.bin", "rb").read()) raw_section = [0x400, 0x1600, 0x2200, 0x2400, 0x2600] raw_size = [0x1200, 0xC00, 0x200, 0x200, 0x200] virtual_section = [0x1000, 0x3000, 0x4000, 0x5000, 0x6000] virtual_size = [0x10A5, 0xB02, 0x3DC, 0xF8, 0x1CC] for i in range(5): raw = raw_section[i] raw_end = raw + raw_size[i] virtual = virtual_section[i] virtual_end = virtual + virtual_size[i] srcWrite = oldFile[virtual : virtual_end] if raw_size[i] >= len(srcWrite): dstWrite = bytearray(srcWrite) + bytearray(raw_size[i] - len(srcWrite)) else: dstWrite = bytearray(srcWrite[:raw_size[i]]) #Chấp nhận cắt byte oldFile[raw : raw_end] = dstWrite newFile = open("fix2FIXED.bin", "ab") newFile.write(oldFile) newFile.close() ``` - Code của mình gặp phải chút vấn đề tại section `.data` khi `Virtual Size` lớn hơn `Raw Size`. Điều này dẫn tới section tiếp theo bị lấn bởi section trước nếu làm theo ý tưởng bê nguyên từ `Virtual` sang `Raw`. Nhưng trong trường hợp file `PE` được dump từ trong mem, có thể chấp nhận được section `.data` bị cắt một vùng byte sao cho vừa đủ `Raw Size`. `Virtual Size` lớn hơn `Raw Size` (đặc biệt đối với section `.data`) khi dump `PE` file từ mem có thể do chương trình sử dụng heap hoặc đụng tới vùng `Unitialized Data` trên mem (không được khai báo trước trên disk) nên `PE Bear` parse nó thành `.data`. ## 3. fix3 - Đối với file này thì `PE Bear` không thể nhận dạng được file, khi ném vào `HxD` thì mình thấy vài vấn đề: ![image](https://hackmd.io/_uploads/Skuzs3Wrle.png) ![image](https://hackmd.io/_uploads/HyC7s2ZSxg.png) - File đã mất đi signature `MZ`, `PE` và `Stub`. Để file thực thi được thì chỉ cần recover hai signature kia là đủ. `MZ` ở hai offset đầu, ứng với giá trị `0x4D5A`. Thêm vào đó, ta biết được tại offset `0x3C` sẽ là trường `e_lfanew` và nó có giá trị: ![image](https://hackmd.io/_uploads/SJEion-rgg.png) - Vậy ta sẽ di chuyển đến offset `0xF0` để thêm signature `PE` vào đó, cuối cùng được: ![image](https://hackmd.io/_uploads/r1Gr23-rlg.png) - Khi này `PE Bear` đã có thể phân tích bình thường: ![image](https://hackmd.io/_uploads/BJFd2hbBgg.png) # V. References - [0xrick](https://0xrick.github.io/win-internals/pe1/) - [ChuongDong](https://chuongdong.com/reverse%20engineering/2020/08/15/PE-Parser/) - [Skr1x](https://skr1x.github.io/portable-executable-format/) - [Yuriygeorgiev](https://yuriygeorgiev.com/2023/12/18/windows-portable-executable-pe-file-format/) - [Tech-zealots](https://tech-zealots.com/malware-analysis/pe-portable-executable-structure-malware-analysis-part-2/) ![image](https://yuriygeorgiev.com/wp-content/uploads/2023/12/PE-Struct.png)

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully