Windows10 x64 shellcode concept

2021-05-14

這一篇算是 Windows10 x64 shellcode 的前哨站,在寫 shellcode 之前發現有太多知識需要先科普,先在這邊整理一些知識後進去 windows shellcode 的領域會比較懂
首先要先知道一隻 windows 程式進入記憶體會是長怎麼樣

  • TEB

  • PEB

  • Ldr

    • 串接所有會用到的 DLL 位址
    • Link list
    • Ldr.InInitializationOrderModuleList
    • Ldr.InLoadOrderModuleList
    • Ldr.InMemoryOrderModuleList
    • DLL name
    • DLL Base address
    • 串接順序有固定,前三個是 binary 本身 -> ntdll -> kernel32
    • InInitializationOrderModuleList InLoadOrderModuleList InMemoryOrderModuleList 這三個彼此是一個串一個
    • !peb
    • dt ntdll!_LDR_DATA_TABLE_ENTRY

整理下來的流程圖大概是這樣 (這是 x86 的,x64 要乘以2)

https://blog.the-playground.dk/2012/06/understanding-windows-shellcode.html

看一下 DLL base 可以看到是熟悉的 MZ Header,下面開始介紹 PE Structure

https://www.slideshare.net/Hexxx/pe-format

  • DOS Header (_IMAGE_DOS_HEADER)
    • Structure
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      typedef struct _IMAGE_DOS_HEADER {
      WORD e_magic; /* 00: MZ Header signature */
      WORD e_cblp; /* 02: Bytes on last page of file */
      WORD e_cp; /* 04: Pages in file */
      WORD e_crlc; /* 06: Relocations */
      WORD e_cparhdr; /* 08: Size of header in paragraphs */
      WORD e_minalloc; /* 0a: Minimum extra paragraphs needed */
      WORD e_maxalloc; /* 0c: Maximum extra paragraphs needed */
      WORD e_ss; /* 0e: Initial (relative) SS value */
      WORD e_sp; /* 10: Initial SP value */
      WORD e_csum; /* 12: Checksum */
      WORD e_ip; /* 14: Initial IP value */
      WORD e_cs; /* 16: Initial (relative) CS value */
      WORD e_lfarlc; /* 18: File address of relocation table */
      WORD e_ovno; /* 1a: Overlay number */
      WORD e_res[4]; /* 1c: Reserved words */
      WORD e_oemid; /* 24: OEM identifier (for e_oeminfo) */
      WORD e_oeminfo; /* 26: OEM information; e_oemid specific */
      WORD e_res2[10]; /* 28: Reserved words */
      DWORD e_lfanew; /* 3c: Offset to extended header */
      } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
    • RVA (Relative Virtual Address)
      • 在記憶體中,相對於 Binary Base 之間的 offset
      • 如果 RVA 為 0x3000, 代表實際位置在 Binary Base + 0x3000
    • e_magic 通常是 File Header 也就是 0x5a4d (MZ)
    • e_lfanew 是 NT header 的 RVA
    • dt !_IMAGE_DOS_HEADER
    • https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
  • NT Header (_IMAGE_NT_HEADERS64)
    • Structure
      1
      2
      3
      4
      5
      typedef struct _IMAGE_NT_HEADERS64 {
      DWORD Signature;
      IMAGE_FILE_HEADER FileHeader;
      IMAGE_OPTIONAL_HEADER64 OptionalHeader;
      } IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
      • Signature
  • OptionalHeader (_IMAGE_OPTIONAL_HEADER)

    • Structure
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      typedef struct _IMAGE_OPTIONAL_HEADER {
      WORD Magic;
      BYTE MajorLinkerVersion;
      BYTE MinorLinkerVersion;
      DWORD SizeOfCode;
      DWORD SizeOfInitializedData;
      DWORD SizeOfUninitializedData;
      DWORD AddressOfEntryPoint;
      DWORD BaseOfCode;
      DWORD BaseOfData;
      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
      • PE32 : 0x10b
      • PE32+ : 0x20b
    • AddressOfEntryPoint
      • 程式進入點的 RVA (以ImageBaseAddress 為起點)
    • NumberOfRvaAndSizes : DataDirectory 的數量
    • DataDirectory (_IMAGE_DATA_DIRECTORY)
    • dt _IMAGE_OPTIONAL_HEADER64

    • https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header64
    • https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_optional_header32
  • Export Directory

    • 透過上面 DataDirectory 的第0個陣列的 VirtualAddress 可以到達
    • windbg 沒有他的 display type 所以直接貼官方的圖
    • Structure
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      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
      };

    • NumberOfFunctions
      • 紀錄內部 function 的數量,與 NumberOfName 數量相等 (表示 dll 內有幾個 function)
    • NumberOfName
      • Name pointer(array) 的數量,與 ordinal table 數量相等 (表示 dll 內有幾個 function name)
    • AddressOfFunctions
      • Export function 的 RVA
    • AddressOfNames
      • 存放 Name pointer(array) 的 RVA,這邊存放有所有 Export function 的名稱
    • AddressOfNameOrdinals
      • 存放 ordinal table(array) 的相對位置
      • 與 Name Pointer array 是一對一 mapping 的,也就是說如果 WinExec 在 Name[x] ,那麼 WinExec 在 Ordinal table 的 index 就是 x
      • ordinal table
        1. 其元素內容每個為 2 bytes
        2. 表示在 Export Address Table 中的 index
        3. 這邊不是 RVA, 直接就是 index value
    • https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#export-directory-table
    • https://sites.google.com/site/peofcns/win32forth/pe-header-f/02-image_directory/01-entry_export
    • https://ghidra.re/ghidra_docs/api/ghidra/app/util/bin/format/pe/ExportDataDirectory.html

該介紹的都介紹差不多了
這邊會示範如何 trace 出 WinExec 來當 example

複習一下 Export Table 的 RVA

Export Table 拿出來看

照著上面針對 Export Table 介紹的 Structure 進行排列

1
2
3
4
5
6
7
8
9
10
11
Characteristics = 0x00000000 # Reserved, must be 0.
TimeDateStamp = 0xf5ff295e # The time and date that the export data was created.
MajorVersion = 0x0000 # The major version number. The major and minor version numbers can be set by the user.
MinorVersion = 0x0000 # The minor version number.
Name = 0x091efa # The address of the ASCII string that contains the name of the DLL. This address is relative to the image base.
Base = 0x1 # The starting ordinal number for exports in this image. This field specifies the starting ordinal number for the export address table. It is usually set to 1.
NumberOfFunctions = 0x0655 # The number of entries in the export address table.
NumberOfNames = 0x655 # The number of entries in the name pointer table. This is also the number of entries in the ordinal table.
AddressOfFunctions = 0x08dfa8 # The address of the export address table, relative to the image base.
AddressOfNames = 0x08f8fc # The address of the export name pointer table, relative to the image base. The table size is given by the Number of Name Pointers field.
AddressOfNameOrdinals = 0x091250 # The address of the ordinal table, relative to the image base.

整個找 function RVA 的 flow 大概是這樣

來源: https://www.programmersought.com/article/19694281094/

接著取得 AddressOfFunctions (0x1c) 的內容


簡單來說如果 Export function 如果在自己的 dll 的話就會直接給出 function 位置 (jmp),若 Export function 在其他 dll 的話就會給出 Name pointer 的位置,這邊解釋了為什麼上面的圖沒有將每個函數地址表做對應

再來看 AddressOfNames (0x20) 的內容

AddressOfNameOrdinals (0x24)

直接用 windbg 在 AddressOfNames 尋找 WinExec 的字串
相關 command 請看
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/s--search-memory-
長度部分使用該 Export Directory 的總大小,在這個 memory range 裡面尋找 WinExec 的 ASCII code
00007ff82e170000+0008f8fcAddressOfNames 的 base address
s -a (00007ff82e170000+0008f8fc) l 0xdb48 "WinExec"

我們知道 AddressOfNames 裡面放的都是以 4byte 為一個單位的陣列,需要找到是第幾個陣列存放 WinExec
首先我們先取得 WinExec 的 Name pointer RVA
s -d (00007ff82e170000+0008f8fc) l 0xdb48 0009b59b

得知 00007ff82e201114 存放 WinExec 的 Name pointer RVA,那是第幾個陣列呢 ?
把 Array 的位置扣除 AddressOfNames 的 base address 再除以 4 就是我們要的答案

第 0x606 個陣列,把這個數字拿去看 ordinal table,記得 0x606 要乘以 2, 因為 ordinal table 的陣列是以 2 bytes 為一個單位的陣列
00007ff82e170000+00091250AddressOfNameOrdinals 的 base address
dw (00007ff82e170000+00091250+(0x606*4))

可以看到一樣是 0x606, 現在把這個數字拿去 AddressOfFunctions 就能求出 WinExec 的 RVA
0008dfa8+00007ff82e170000AddressOfFunctions 的 base address
一樣記得要把 0x606 乘以 4, 因為 AddressOfFunctions 是以 4 bytes 為一個單位的陣列

放上整個 flow

至此文章就告一段落了,這篇文章主要就是在學習 windows shellcode 需要惡補的內容
這篇文章真的幫我很多
http://bufferoverflow123.blogspot.com/2018/06/bufferoverflow-3-4-kernel32dl.html
https://bsodtutorials.wordpress.com/2014/03/02/import-address-tables-and-export-address-tables/
https://docs.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10)?redirectedfrom=MSDN