這是一個老話題,遠線程函數給我們提供了機會在其他進程中啟動一個新線程,所以我們可以做很多事情。但事情遠遠沒有結束,如果我們要做的事情非常復雜,那么將面臨編寫大量的ASM代碼,雖然我們可以用VC之類的工具編譯一個簡短的函數然后抄襲它。所以,我們經常做的是把我們的要做的放入一個DLL,而遠線程所做的只是調用LoadLibrary函數來加載我們的DLL,這只需要少量的ASM甚至可以不用ASM。
如果我們要注入一個標准的WINDOWS DLL文件,無論目標進程是不是托管的事情往往很簡單,但是如果注入托管DLL而恰好目標進程是一個非托管進程,就不是那么令人愉快了。幸好,我們可以用一個CLR DLL來給目標進程加載運行時。這里還涉及到操作系統版本和位數等等一些問題,依次來看一下:
一、識別進程位數和模塊位數、模塊導入導出函數。
可以使用函數IsWow64Process、GetFileVersionInfoSize等來識別進程位數,可以在本進程加載相應的DLL來獲得模塊中函數地址。但是這里也存在一些問題,例如不同系統對API的支持、模塊中函數調HOOK時的通用性等等。所以這里我沒有采用這些方法,而是解析了PE頭的部分內容來得到:
1、模塊位數(主程序、DLL)
2、DLL導入導出表
3、程序集所需CLR版本
對於PE文件結構不熟悉的話,可以自行查找相關文獻。這里只貼上代碼(代碼中結構體可能與C原型不同,注釋來源於網絡),首先確定模塊位數,它位於PE結構的DOS頭后面,magic數:
Imports System.Runtime.InteropServices ''' <summary> ''' 讀取模塊的Nt頭中Magic字段,以確定模塊是32位還是64位。 ''' </summary> ''' <remarks></remarks> Public Class GetNtHandleMagic <StructLayout(LayoutKind.Sequential)> Private Structure IMAGE_NT_HEADERS32 Public Signature As UInteger '4 ubytes PE文件頭標志:(e_lfanew)->‘PE\0\0’ Public Machine As UShort '標識CPU的數字。運行平台。 Public NumberOfSections As UShort '節的數目。Windows加載器限制節的最大數目為96。文件區塊數目。 Public TimeDateStamp As UInteger '文件創建日期和時間,UTC時間1970年1月1日00:00起的總秒數的低32位。 Public PointerToSymbolTable As UInteger '指向符號表(主要用於調試),已廢除。 Public NumberOfSymbols As UInteger '符號表中符號個數,已廢除。 Public SizeOfOptionalHeader As UShort 'IMAGE_OPTIONAL_HEADER32 結構大小,可選頭大小。 Public Characteristics As UShort '文件屬性,文件特征值。 Public Magic As UShort '標志字, 0x0107表明這是一個ROM 映像,0x10B表明這是一個32位鏡像文件。,0x20B表明這是一個64位鏡像文件。 End Structure Public Enum ModuleBit x86 = 4 x64 = 8 rom = 1 unk = 0 End Enum ''' <summary> ''' 獲取模塊的NT頭中Magic字段,以獲取模塊的位數。 ''' </summary> ''' <param name="pHandle">進程句柄</param> ''' <param name="ModuleBaseAddress">模塊地址</param> ''' <returns>x86說明模塊為32位,x64說明模塊為64位,rom說明模塊為ROM映像,unk為未知。</returns> Public Shared Function Doit(ByVal pHandle As IntPtr, ByVal ModuleBaseAddress As Long) As ModuleBit '確認PE格式 If ReadUShort(pHandle, ModuleBaseAddress) <> &H5A4D Then Return ModuleBit.unk '首先讀NT頭地址,即PE簽名地址 Dim E_lfanew As IntPtr = ModuleBaseAddress + BitConverter.ToInt32(ReadBytes(pHandle, ModuleBaseAddress + &H3C, 4), 0) '然后讀取NT頭中的Magic Dim NTHandle As New IMAGE_NT_HEADERS32 Dim Size As Integer = Marshal.SizeOf(NTHandle) Dim lpNTHandle As IntPtr = Marshal.AllocHGlobal(Size) ReadToGlobal(pHandle, E_lfanew, Size, lpNTHandle) NTHandle = Marshal.PtrToStructure(lpNTHandle, GetType(IMAGE_NT_HEADERS32)) Marshal.FreeHGlobal(lpNTHandle) If NTHandle.Magic = &H10B Then Return ModuleBit.x86 ElseIf NTHandle.Magic = &H20B Then Return ModuleBit.x64 ElseIf NTHandle.Magic = &H107 Then Return ModuleBit.rom Else Return ModuleBit.unk End If End Function ''' <summary> ''' 獲取磁盤文件的NT頭中Magic字段,以獲取模塊的位數。 ''' </summary> ''' <param name="FileFullPath">文件名</param> ''' <returns>x86說明模塊為32位,x64說明模塊為64位,rom說明模塊為ROM映像,unk為未知。</returns> Public Shared Function Doit(FileFullPath As String) As ModuleBit Dim pHandle As IntPtr = Process.GetCurrentProcess.Handle Dim buff As Byte() = IO.File.ReadAllBytes(FileFullPath) Dim ModuleBaseAddress As UInteger = Marshal.AllocHGlobal(buff.Length) Marshal.Copy(buff, 0, ModuleBaseAddress, buff.Length) Dim result As ModuleBit = Doit(pHandle, ModuleBaseAddress) Marshal.FreeHGlobal(ModuleBaseAddress) Return result End Function Private Shared Function ReadBytes(ByVal pHandle As IntPtr, ByVal lAddress As IntPtr, length As Integer) As Byte() Dim tmpArr(length - 1) As Byte Dim lOldProtect As Integer VirtualProtectEx(pHandle, lAddress, 1, &H40, lOldProtect) ReadProcessMemory(pHandle, lAddress, tmpArr, length, 0) VirtualProtectEx(pHandle, lAddress, 1, lOldProtect, lOldProtect) Return tmpArr End Function Private Shared Function ReadUShort(ByVal pHandle As IntPtr, ByVal lAddress As IntPtr) As UShort Dim tmpArr(1) As Byte Dim lOldProtect As Integer VirtualProtectEx(pHandle, lAddress, 1, &H40, lOldProtect) ReadProcessMemory(pHandle, lAddress, tmpArr, 2, 0) VirtualProtectEx(pHandle, lAddress, 1, lOldProtect, lOldProtect) Return BitConverter.ToUInt16(tmpArr, 0) End Function Private Shared Sub ReadToGlobal(ByVal pHandle As IntPtr, ByVal lAddress As IntPtr, length As Integer, lGlobal As IntPtr) Dim lOldProtect As Integer VirtualProtectEx(pHandle, lAddress, 1, &H40, lOldProtect) ReadProcessMemory(pHandle, lAddress, lGlobal, length, 0) VirtualProtectEx(pHandle, lAddress, 1, lOldProtect, lOldProtect) End Sub End Class
這個類可以讀取一個模塊是32位還是64位。因為32位和64位的PE格式有一定差異,所以先調用這個函數,得到模塊信息,然后再進一步解析PE頭中的導入導出函數,下面是32位的:
Imports System.Runtime.InteropServices Imports System.Text.Encoding ''' <summary> ''' 枚舉32位模塊的Nt頭信息,枚舉32位模塊的導入、導出函數。支持32位、64位系統。 ''' </summary> ''' <remarks></remarks> Public Class GetNtHandle32 <StructLayout(LayoutKind.Sequential)> Public Structure IMAGE_NT_HEADERS32 Public Signature As UInteger '4 ubytes PE文件頭標志:(e_lfanew)->‘PE\0\0’ Public FileHeader As IMAGE_FILE_HEADER '20 ubytes PE文件物理分布的信息 Public OptionalHeader As IMAGE_OPTIONAL_HEADER32 '224 ubytes PE文件邏輯分布的信息 End Structure <StructLayout(LayoutKind.Sequential)> Public Structure IMAGE_FILE_HEADER Public Machine As UShort '標識CPU的數字。運行平台。 Public NumberOfSections As UShort '節的數目。Windows加載器限制節的最大數目為96。文件區塊數目。 Public TimeDateStamp As UInteger '文件創建日期和時間,UTC時間1970年1月1日00:00起的總秒數的低32位。 Public PointerToSymbolTable As UInteger '指向符號表(主要用於調試),已廢除。 Public NumberOfSymbols As UInteger '符號表中符號個數,已廢除。 Public SizeOfOptionalHeader As UShort 'IMAGE_OPTIONAL_HEADER32 結構大小,可選頭大小。 Public Characteristics As UShort '文件屬性,文件特征值。 End Structure <StructLayout(LayoutKind.Sequential)> Public Structure IMAGE_OPTIONAL_HEADER32 Public Magic As UShort ' 標志字, 0x0107表明這是一個ROM 映像,0x10B表明這是一個32位鏡像文件。,0x20B表明這是一個64位鏡像文件。 Public MajorLinkerVersion As Byte ' 鏈接程序的主版本號 Public MinorLinkerVersion As Byte ' 鏈接程序的次版本號 Public SizeOfCode As UInteger ' 所有含代碼的節的總大小 Public SizeOfInitializedData As UInteger ' 所有含已初始化數據的節的總大小 Public SizeOfUninitializedData As UInteger ' 所有含未初始化數據的節的大小 Public AddressOfEntryPoint As UInteger ' 程序執行入口RVA Public BaseOfCode As UInteger ' 代碼的區塊的起始RVA Public BaseOfData As UInteger ' 數據的區塊的起始RVA Public ImageBase As UInteger ' 程序的首選裝載地址 Public SectionAlignment As UInteger ' 內存中的區塊的對齊大小 Public FileAlignment As UInteger ' 文件中的區塊的對齊大小 Public MajorOperatingSystemVersion As UShort ' 要求操作系統最低版本號的主版本號 Public MinorOperatingSystemVersion As UShort ' 要求操作系統最低版本號的副版本號 Public MajorImageVersion As UShort ' 可運行於操作系統的主版本號 Public MinorImageVersion As UShort ' 可運行於操作系統的次版本號 Public MajorSubsystemVersion As UShort ' 要求最低子系統版本的主版本號 Public MinorSubsystemVersion As UShort ' 要求最低子系統版本的次版本號 Public Win32VersionValue As UInteger ' 莫須有字段,不被病毒利用的話一般為0 Public SizeOfImage As UInteger ' 映像裝入內存后的總尺寸 Public SizeOfHeaders As UInteger ' 所有頭 + 區塊表的尺寸大小 Public CheckSum As UInteger ' 映像的校檢和 Public Subsystem As UShort ' 可執行文件期望的子系統 Public DllCharacteristics As UShort ' DllMain()函數何時被調用,默認為 0 Public SizeOfStackReserve As UInteger ' 初始化時的棧大小 Public SizeOfStackCommit As UInteger ' 初始化時實際提交的棧大小 Public SizeOfHeapReserve As UInteger ' 初始化時保留的堆大小 Public SizeOfHeapCommit As UInteger ' 初始化時實際提交的堆大小 Public LoaderFlags As UInteger ' 與調試有關,默認為 0 Public NumberOfRvaAndSizes As UInteger ' 下邊數據目錄的項數,這個字段自Windows NT 發布以來一直是16 Public IMAGE_DIRECTORY_ENTRY_EXPORT As IMAGE_DATA_DIRECTORY '導出表 Public IMAGE_DIRECTORY_ENTRY_IMPORT As IMAGE_DATA_DIRECTORY '導入表 Public IMAGE_DIRECTORY_ENTRY_RESOURCE As IMAGE_DATA_DIRECTORY '資源目錄 Public IMAGE_DIRECTORY_ENTRY_EXCEPTION As IMAGE_DATA_DIRECTORY '異常目錄 Public IMAGE_DIRECTORY_ENTRY_SECURITY As IMAGE_DATA_DIRECTORY '安全目錄 Public IMAGE_DIRECTORY_ENTRY_BASERELOC As IMAGE_DATA_DIRECTORY '重定位基本表 Public IMAGE_DIRECTORY_ENTRY_DEBUG As IMAGE_DATA_DIRECTORY '調試目錄 Public IMAGE_DIRECTORY_ENTRY_COPYRIGHT As IMAGE_DATA_DIRECTORY '描述字符串 Public IMAGE_DIRECTORY_ENTRY_ARCHITECTURE As IMAGE_DATA_DIRECTORY '機器值 Public IMAGE_DIRECTORY_ENTRY_GLOBALPTR As IMAGE_DATA_DIRECTORY '線程本地存儲 Public IMAGE_DIRECTORY_ENTRY_TLS As IMAGE_DATA_DIRECTORY 'TLS目錄 Public IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG As IMAGE_DATA_DIRECTORY '載入配置目錄 Public IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT As IMAGE_DATA_DIRECTORY '綁定倒入表 Public IMAGE_DIRECTORY_ENTRY_IAT As IMAGE_DATA_DIRECTORY '導入地址表 Public IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT As IMAGE_DATA_DIRECTORY '延遲倒入表 Public IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR As IMAGE_DATA_DIRECTORY 'COM描述符 End Structure <StructLayout(LayoutKind.Sequential)> Public Structure IMAGE_DATA_DIRECTORY Public VirtualAddress As UInteger '地址 Public Size As UInteger '大小 End Structure <StructLayout(LayoutKind.Sequential)> Private Structure IMAGE_EXPORT_DIRECTORY Public Characteristics As UInteger '未使用,為0 Public TimeDateStamp As UInteger '文件生成時間 Public MajorVersion As UShort '未使用,為0 Public MinorVersion As UShort '未使用,為0 Public Name As UInteger '這是這個PE文件的模塊名 Public Base As UInteger '基數,加上序數就是函數地址數組的索引值 Public NumberOfFunctions As UInteger '導出函數的個數 Public NumberOfNames As UInteger '以名稱方式導出的函數的總數(有的函數沒有名稱只有序數) Public AddressOfFunctions As UInteger 'RVA from base of image Nt頭基址加上這個偏移得到的數組中存放所有的導出地址表 Public AddressOfNames As UInteger 'RVA from base of image Nt頭基址加上這個偏移得到的數組中存放所有的名稱字符串 Public AddressOfNameOrdinals As UInteger 'RVA from base of image Nt頭基址加上這個偏移得到的數組中存放所有的函數序號,並不一定是連續的,但一般和導出地址表是一一對應的 End Structure Private Structure IMAGE_IMPORT_DESCRIPTOR Public OriginalFirstThunk As UInteger Public TimeDateStamp As UInteger ' 0 If Not bound, -1 if bound, And real date\time stamp in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (New BIND) O.W. date/time stamp of DLL bound to (Old BIND) Public ForwarderChain As UInteger ' -1 If no forwarders Public Name As UInteger ' Dll Name Public FirstThunk As UInteger ' RVA To IAT (If bound this IAT has actual addresses) End Structure 'Public Structure IMAGE_THUNK_DATA ' Public ForwarderString As UInteger ' Public FunctionAddr As UInteger ' Public Ordinal As UInteger '序號 ' Public AddressOfData As UInteger '指向IMAGE_IMPORT_BY_NAME 'End Structure 'Public Structure IMAGE_IMPORT_BY_NAME ' Public Hint As UShort 'ordinal ' Public Name As Byte 'Function name String 'End Structure Private Structure IMAGE_COR20_HEADER Public cb As UInteger Public MajorRuntimeVersion As UShort Public MinorRuntimeVersion As UShort Public MetaData As IMAGE_DATA_DIRECTORY 'IMAGE_DATA_DIRECTORY MetaData; '符號表和開始信息 Public Flags As UInteger Public EntryPointTokenAndRVA As IntPtr 'union DWORD EntryPointToken;DWORD EntryPointRVA; Public Resource As IMAGE_DATA_DIRECTORY '綁定信息 IMAGE_DATA_DIRECTORY Resource; Public StrongNameSignature As IMAGE_DATA_DIRECTORY '綁定信息 IMAGE_DATA_DIRECTORY StrongNameSignature; Public CodeMagagerTable As IMAGE_DATA_DIRECTORY '//常規的定位和綁定信息 'IMAGE_DATA_DIRECTORY CodeMagagerTable; Public VTableFixups As IMAGE_DATA_DIRECTORY 'IMAGE_DATA_DIRECTORY VTableFixups; Public ExprotAddressTableJumps As IMAGE_DATA_DIRECTORY 'IMAGE_DATA_DIRECTORY ExprotAddressTableJumps; Public MagageNativeHeader As IMAGE_DATA_DIRECTORY 'IMAGE_DATA_DIRECTORY MagageNativeHeader; End Structure Private Structure MetaDataInfo Public BSJB As UInteger '424A5342h,就是4個固定的ASCII碼,代表.NET四個創始人的首位字母縮寫 Public MajorVersion As UShort '元數據的主版本,一般為1 Public MinorVersion As UShort '元數據的副版本,一般為1 Public ExtraData As UInteger '保留數據0 Public Length As UInteger '接下來版本字符串的長度,包含尾部0,且按4字節對其 <MarshalAs(UnmanagedType.ByValArray, SizeConst:=16)> Public VersionString() As Byte 'UTF8格式的編譯環境版本號 'Public Flags As String '保留0 'Public Streams As UShort 'NStream的個數(流的個數) End Structure ''' <summary> ''' 讀取32位模塊NTHandle結構 ''' </summary> ''' <param name="pHandle">進程句柄</param> ''' <param name="ModuleBaseAddress">模塊地址</param> ''' <returns></returns> Public Shared Function Doit(ByVal pHandle As IntPtr, ByVal ModuleBaseAddress As Integer) As IMAGE_NT_HEADERS32 '確認PE格式 If ReadUShort(pHandle, ModuleBaseAddress) <> &H5A4D Then Return New IMAGE_NT_HEADERS32 '首先讀NT頭地址,即PE簽名地址 Dim E_lfanew As IntPtr = ModuleBaseAddress + ReadUInteger(pHandle, ModuleBaseAddress + &H3C) '然后讀取NT頭內容 Dim NTHandle As New IMAGE_NT_HEADERS32 Dim Size As Integer = Marshal.SizeOf(NTHandle) Dim lpNTHandle As IntPtr = Marshal.AllocHGlobal(Size) ReadToGlobal(pHandle, E_lfanew, Size, lpNTHandle) NTHandle = Marshal.PtrToStructure(lpNTHandle, GetType(IMAGE_NT_HEADERS32)) Marshal.FreeHGlobal(lpNTHandle) Return NTHandle End Function Private Shared Function ReadUShort(ByVal pHandle As IntPtr, ByVal lAddress As IntPtr) As UShort Dim tmpArr(1) As Byte Dim lOldProtect As Integer VirtualProtectEx(pHandle, lAddress, 1, &H40, lOldProtect) ReadProcessMemory(pHandle, lAddress, tmpArr, 2, 0) VirtualProtectEx(pHandle, lAddress, 1, lOldProtect, lOldProtect) Return BitConverter.ToUInt16(tmpArr, 0) End Function Private Shared Function ReadUInteger(ByVal pHandle As IntPtr, ByVal lAddress As IntPtr) As UInteger Dim tmpArr(3) As Byte Dim lOldProtect As Integer VirtualProtectEx(pHandle, lAddress, 1, &H40, lOldProtect) ReadProcessMemory(pHandle, lAddress, tmpArr, 4, 0) VirtualProtectEx(pHandle, lAddress, 1, lOldProtect, lOldProtect) Return BitConverter.ToUInt32(tmpArr, 0) End Function Private Shared Function ReadBytes(ByVal pHandle As IntPtr, ByVal lAddress As IntPtr, length As Integer) As Byte() Dim tmpArr(length - 1) As Byte Dim lOldProtect As Integer VirtualProtectEx(pHandle, lAddress, 1, &H40, lOldProtect) ReadProcessMemory(pHandle, lAddress, tmpArr, length, 0) VirtualProtectEx(pHandle, lAddress, 1, lOldProtect, lOldProtect) Return tmpArr End Function Private Shared Sub ReadToGlobal(ByVal pHandle As IntPtr, ByVal lAddress As IntPtr, length As Integer, lGlobal As IntPtr) Dim lOldProtect As Integer VirtualProtectEx(pHandle, lAddress, 1, &H40, lOldProtect) ReadProcessMemory(pHandle, lAddress, lGlobal, length, 0) VirtualProtectEx(pHandle, lAddress, 1, lOldProtect, lOldProtect) End Sub Private Shared Function ReadString(ByVal pHandle As IntPtr, ByVal lAddress As IntPtr) As String Dim lpString As IntPtr = Marshal.AllocHGlobal(1024) ReadToGlobal(pHandle, lAddress, 1024, lpString) Dim result As String = Marshal.PtrToStringAnsi(lpString) Marshal.FreeHGlobal(lpString) Return result End Function ''' <summary> ''' 解析32位模塊導出函數 ''' </summary> ''' <param name="pHandle">進程句柄</param> ''' <param name="ModuleBaseAddress">模塊地址</param> ''' <param name="NtHandle">Nt頭信息</param> ''' <returns></returns> Public Shared Function GetExportFunctionInfo(ByVal pHandle As IntPtr, ByVal ModuleBaseAddress As UInteger, NtHandle As IMAGE_NT_HEADERS32) As ExportOrImportsFunctionInfo() If NtHandle.OptionalHeader.IMAGE_DIRECTORY_ENTRY_EXPORT.VirtualAddress = UInteger.MinValue Then Return Nothing '從NtHandle中讀IMAGE_EXPORT_DIRECTORY結構地址 Dim IEDAddress As IntPtr = NtHandle.OptionalHeader.IMAGE_DIRECTORY_ENTRY_EXPORT.VirtualAddress + ModuleBaseAddress '讀IMAGE_EXPORT_DIRECTORY結構 Dim IED As New IMAGE_EXPORT_DIRECTORY Dim size As Integer = Marshal.SizeOf(IED) Dim lpIED As IntPtr = Marshal.AllocHGlobal(size) ReadToGlobal(pHandle, IEDAddress, size, lpIED) IED = Marshal.PtrToStructure(lpIED, GetType(IMAGE_EXPORT_DIRECTORY)) Dim ModleName As String = ReadString(pHandle, IED.Name + ModuleBaseAddress) Marshal.FreeHGlobal(lpIED) '從IMAGE_EXPORT_DIRECTORY結構遍歷函數入口、識別函數名、識別函數編號 Dim result(IED.NumberOfFunctions - 1) As ExportOrImportsFunctionInfo For i As Integer = 0 To IED.NumberOfFunctions - 1 result(i) = New ExportOrImportsFunctionInfo result(i).LibName = ModleName result(i).FunctionIndex = i + IED.Base result(i).FunctionRVA = IED.AddressOfFunctions + 4 * i + ModuleBaseAddress result(i).FunctionAddress = ReadUInteger(pHandle, result(i).FunctionRVA) + ModuleBaseAddress Next For i As Integer = 0 To IED.NumberOfNames - 1 Dim lpName = ModuleBaseAddress + ReadUInteger(pHandle, IED.AddressOfNames + i * 4 + ModuleBaseAddress) Dim lNameOrdinal = ReadUShort(pHandle, IED.AddressOfNameOrdinals + i * 2 + ModuleBaseAddress) If lNameOrdinal <= IED.NumberOfNames Then result(lNameOrdinal).FunctionName = ReadString(pHandle, lpName) End If Next Return result End Function ''' <summary> ''' 解析32位模塊導入函數 ''' </summary> ''' <param name="pHandle">進程句柄</param> ''' <param name="ModuleBaseAddress">模塊地址</param> ''' <param name="NtHandle">Nt頭信息</param> ''' <returns></returns> Public Shared Function GetImportsFunctionInfo(ByVal pHandle As IntPtr, ByVal ModuleBaseAddress As UInteger, NtHandle As IMAGE_NT_HEADERS32) As ExportOrImportsFunctionInfo() If NtHandle.OptionalHeader.IMAGE_DIRECTORY_ENTRY_IMPORT.VirtualAddress = 0 Then Return Nothing '從NtHandle中讀出IMAGE_IMPORT_DESCRIPTOR結構地址 Dim IIDAddress As UInteger = ModuleBaseAddress + NtHandle.OptionalHeader.IMAGE_DIRECTORY_ENTRY_IMPORT.VirtualAddress Dim result As New List(Of ExportOrImportsFunctionInfo) Dim DllIdx As UInteger Do '讀IMAGE_IMPORT_DESCRIPTOR結構 Dim IID As New IMAGE_IMPORT_DESCRIPTOR Dim size As Integer = Marshal.SizeOf(IID) Dim lpIID As IntPtr = Marshal.AllocHGlobal(size) ReadToGlobal(pHandle, IIDAddress + DllIdx * size, size, lpIID) IID = Marshal.PtrToStructure(lpIID, GetType(IMAGE_IMPORT_DESCRIPTOR)) Marshal.FreeHGlobal(lpIID) If IID.FirstThunk = 0 Then Exit Do 'IMAGE_IMPORT_DESCRIPTOR結構的Name屬性是函數所在DLL Dim dllname As String = ReadString(pHandle, IID.Name + ModuleBaseAddress) Dim functionIdx As UInteger = 0 Do Dim newImports As New ExportOrImportsFunctionInfo newImports.LibName = dllname 'FirstThunk屬性指向函數實際地址數組,數組最后一個元素為0 newImports.FunctionRVA = IID.FirstThunk + ModuleBaseAddress + functionIdx * 4 newImports.FunctionAddress = ReadUInteger(pHandle, newImports.FunctionRVA) If newImports.FunctionAddress = 0 Then Exit Do Else 'OriginalFirstThunk指向的IMAGE_THUNK_DATA結構地址(INT),當最高位為1時,按編號導出,去除最高位即可;當最高位為0時,指向一個IMAGE_IMPORT_BY_NAME結構。 Dim IMAGE_THUNK_DATA As UInteger = ReadUInteger(pHandle, IID.OriginalFirstThunk + ModuleBaseAddress + functionIdx * 4) If (IMAGE_THUNK_DATA >> 31) = 1 Then newImports.FunctionIndex = (IMAGE_THUNK_DATA And &H7FFFFFFF) Else newImports.FunctionIndex = ReadUShort(pHandle, IMAGE_THUNK_DATA + ModuleBaseAddress) newImports.FunctionName = ReadString(pHandle, IMAGE_THUNK_DATA + ModuleBaseAddress + 2) End If result.Add(newImports) End If functionIdx += 1 Loop DllIdx += 1 Loop Return result.ToArray End Function ''' <summary> ''' 讀取磁盤上一個程序集所需的CLR版本號,這個函數可以讀取使用任何版本CLR的程序集。 ''' 若使用Assembly.Load而后獲取則受到當前程序集所用CLR版本限制,若目標程序集所需CLR更高則引發錯誤。 ''' </summary> ''' <param name="AssemblyFullPath">程序集的完整名(路徑+文件名+擴展名)</param> ''' <returns>若為空說明不是托管程序集</returns> Public Shared Function GetAssemblyNeedCLRVersion(AssemblyFullPath As String) As String '這個函數直接讀取磁盤文件然后分析,所以過程和前幾個函數有區別 '把磁盤文件加載到內存 Dim pHandle As IntPtr = Process.GetCurrentProcess.Handle Dim buff As Byte() = IO.File.ReadAllBytes(AssemblyFullPath) Dim ModuleBaseAddress As UInteger = Marshal.AllocHGlobal(buff.Length) Marshal.Copy(buff, 0, ModuleBaseAddress, buff.Length) '讀nt頭 Dim NtHandle As IMAGE_NT_HEADERS32 = GetNtHandle32.Doit(Process.GetCurrentProcess.Handle, ModuleBaseAddress) '分析IMAGE_COR20_HEADER If NtHandle.OptionalHeader.IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT.VirtualAddress = 0 Then Return String.Empty '因為這里沒有實際加載只是讀了磁盤文件,所以需要再次重定位 Dim ICHAddress As UInteger = ModuleBaseAddress + NtHandle.OptionalHeader.IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT.VirtualAddress - NtHandle.OptionalHeader.BaseOfCode + NtHandle.OptionalHeader.SizeOfHeaders Dim size As Integer = Marshal.SizeOf(GetType(IMAGE_COR20_HEADER)) Dim lpGlobal As IntPtr = Marshal.AllocHGlobal(size) ReadToGlobal(pHandle, ICHAddress, size, lpGlobal) Dim ICH As IMAGE_COR20_HEADER = Marshal.PtrToStructure(lpGlobal, GetType(IMAGE_COR20_HEADER)) Marshal.FreeHGlobal(lpGlobal) 'IMAGE_COR20_HEADER中的版本號並不是所需CRL版本號,繼續讀取MetaDataInfo成員才能得到 '這里也需要重定位 Dim MDIAddress As UInteger = ModuleBaseAddress + ICH.MetaData.VirtualAddress - NtHandle.OptionalHeader.BaseOfCode + NtHandle.OptionalHeader.SizeOfHeaders Try If Marshal.ReadInt32(MDIAddress) <> &H424A5342 Then Return String.Empty End If Catch ex As Exception Return String.Empty End Try size = Marshal.SizeOf(GetType(MetaDataInfo)) lpGlobal = Marshal.AllocHGlobal(size) ReadToGlobal(pHandle, MDIAddress, size, lpGlobal) Dim MDI As MetaDataInfo = Marshal.PtrToStructure(lpGlobal, GetType(MetaDataInfo)) Marshal.FreeHGlobal(lpGlobal) Dim result As String = UTF8.GetString(MDI.VersionString, 0, MDI.Length) '釋放內存中的程序集 Marshal.FreeHGlobal(ModuleBaseAddress) Return result End Function End Class
這個類可以讀取一個32位模塊的PE頭信息,進一步解析就可以得到導入和導出表(附帶一個讀取托管程序集所需CLR版本的函數)。有了導入導出表,進行HOOK或者調用都非常簡單。稍加修改就是64位的:
Imports System.Runtime.InteropServices Imports System.Text.Encoding ''' <summary> ''' 枚舉64位模塊的Nt頭,枚舉64位模塊的導入、導出函數。 ''' </summary> ''' <remarks></remarks> Public Class GetNtHandle64 <StructLayout(LayoutKind.Sequential)> Public Structure IMAGE_NT_HEADERS64 Public Signature As UInteger '4 ubytes PE文件頭標志:(e_lfanew)->‘PE\0\0’ Public FileHeader As IMAGE_FILE_HEADER '20 ubytes PE文件物理分布的信息 Public OptionalHeader As IMAGE_OPTIONAL_HEADER64 '224 ubytes PE文件邏輯分布的信息 End Structure <StructLayout(LayoutKind.Sequential)> Public Structure IMAGE_FILE_HEADER Public Machine As UShort '標識CPU的數字。運行平台。 Public NumberOfSections As UShort '節的數目。Windows加載器限制節的最大數目為96。文件區塊數目。 Public TimeDateStamp As UInteger '文件創建日期和時間,UTC時間1970年1月1日00:00起的總秒數的低32位。 Public PointerToSymbolTable As UInteger '指向符號表(主要用於調試),已廢除。 Public NumberOfSymbols As UInteger '符號表中符號個數,已廢除。 Public SizeOfOptionalHeader As UShort 'IMAGE_OPTIONAL_HEADER32 結構大小,可選頭大小。 Public Characteristics As UShort '文件屬性,文件特征值。 End Structure <StructLayout(LayoutKind.Sequential)> Public Structure IMAGE_OPTIONAL_HEADER64 Public Magic As UShort ' 標志字, 0x0107表明這是一個ROM 映像,0x10B表明這是一個32位鏡像文件。,0x20B表明這是一個64位鏡像文件。 Public MajorLinkerVersion As Byte ' 鏈接程序的主版本號 Public MinorLinkerVersion As Byte ' 鏈接程序的次版本號 Public SizeOfCode As UInteger ' 所有含代碼的節的總大小 Public SizeOfInitializedData As UInteger ' 所有含已初始化數據的節的總大小 Public SizeOfUninitializedData As UInteger ' 所有含未初始化數據的節的大小 Public AddressOfEntryPoint As UInteger ' 程序執行入口RVA Public BaseOfCode As UInteger ' 代碼的區塊的起始RVA 'Public BaseOfData As UInteger ' 數據的區塊的起始RVA Public ImageBase As ULong ' 程序的首選裝載地址 Public SectionAlignment As UInteger ' 內存中的區塊的對齊大小 Public FileAlignment As UInteger ' 文件中的區塊的對齊大小 Public MajorOperatingSystemVersion As UShort ' 要求操作系統最低版本號的主版本號 Public MinorOperatingSystemVersion As UShort ' 要求操作系統最低版本號的副版本號 Public MajorImageVersion As UShort ' 可運行於操作系統的主版本號 Public MinorImageVersion As UShort ' 可運行於操作系統的次版本號 Public MajorSubsystemVersion As UShort ' 要求最低子系統版本的主版本號 Public MinorSubsystemVersion As UShort ' 要求最低子系統版本的次版本號 Public Win32VersionValue As UInteger ' 莫須有字段,不被病毒利用的話一般為0 Public SizeOfImage As UInteger ' 映像裝入內存后的總尺寸 Public SizeOfHeaders As UInteger ' 所有頭 + 區塊表的尺寸大小 Public CheckSum As UInteger ' 映像的校檢和 Public Subsystem As UShort ' 可執行文件期望的子系統 Public DllCharacteristics As UShort ' DllMain()函數何時被調用,默認為 0 Public SizeOfStackReserve As ULong ' 初始化時的棧大小 Public SizeOfStackCommit As ULong ' 初始化時實際提交的棧大小 Public SizeOfHeapReserve As ULong ' 初始化時保留的堆大小 Public SizeOfHeapCommit As ULong ' 初始化時實際提交的堆大小 Public LoaderFlags As UInteger ' 與調試有關,默認為 0 Public NumberOfRvaAndSizes As UInteger ' 下邊數據目錄的項數,這個字段自Windows NT 發布以來一直是16 Public IMAGE_DIRECTORY_ENTRY_EXPORT As IMAGE_DATA_DIRECTORY '導出函數表 Public IMAGE_DIRECTORY_ENTRY_IMPORT As IMAGE_DATA_DIRECTORY '導入函數表 Public IMAGE_DIRECTORY_ENTRY_RESOURCE As IMAGE_DATA_DIRECTORY '資源目錄 Public IMAGE_DIRECTORY_ENTRY_EXCEPTION As IMAGE_DATA_DIRECTORY '異常目錄 Public IMAGE_DIRECTORY_ENTRY_SECURITY As IMAGE_DATA_DIRECTORY '安全目錄 Public IMAGE_DIRECTORY_ENTRY_BASERELOC As IMAGE_DATA_DIRECTORY '重定位基本表 Public IMAGE_DIRECTORY_ENTRY_DEBUG As IMAGE_DATA_DIRECTORY '調試目錄 Public IMAGE_DIRECTORY_ENTRY_COPYRIGHT As IMAGE_DATA_DIRECTORY '描述字符串 Public IMAGE_DIRECTORY_ENTRY_ARCHITECTURE As IMAGE_DATA_DIRECTORY '機器值 Public IMAGE_DIRECTORY_ENTRY_GLOBALPTR As IMAGE_DATA_DIRECTORY '線程本地存儲 Public IMAGE_DIRECTORY_ENTRY_TLS As IMAGE_DATA_DIRECTORY 'TLS目錄 Public IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG As IMAGE_DATA_DIRECTORY '載入配置目錄 Public IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT As IMAGE_DATA_DIRECTORY '綁定入口 Public IMAGE_DIRECTORY_ENTRY_IAT As IMAGE_DATA_DIRECTORY '導入地址表 Public IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT As IMAGE_DATA_DIRECTORY '延遲入口 Public IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR As IMAGE_DATA_DIRECTORY 'COM描述符 End Structure <StructLayout(LayoutKind.Sequential)> Public Structure IMAGE_DATA_DIRECTORY Public VirtualAddress As UInteger '地址 Public Size As UInteger '大小 End Structure <StructLayout(LayoutKind.Sequential)> Private Structure IMAGE_EXPORT_DIRECTORY Public Characteristics As UInteger '未使用,為0 Public TimeDateStamp As UInteger '文件生成時間 Public MajorVersion As UShort '未使用,為0 Public MinorVersion As UShort '未使用,為0 Public Name As UInteger '這是這個PE文件的模塊名 Public Base As UInteger '基數,加上序數就是函數地址數組的索引值 Public NumberOfFunctions As UInteger '導出函數的個數 Public NumberOfNames As UInteger '以名稱方式導出的函數的總數(有的函數沒有名稱只有序數) Public AddressOfFunctions As UInteger 'RVA from base of image Nt頭基址加上這個偏移得到的數組中存放所有的導出地址表 Public AddressOfNames As UInteger 'RVA from base of image Nt頭基址加上這個偏移得到的數組中存放所有的名稱字符串 Public AddressOfNameOrdinals As UInteger 'RVA from base of image Nt頭基址加上這個偏移得到的數組中存放所有的函數序號,並不一定是連續的,但一般和導出地址表是一一對應的 End Structure Private Structure IMAGE_COR20_HEADER Public cb As UInteger Public MajorRuntimeVersion As UShort Public MinorRuntimeVersion As UShort Public MetaData As IMAGE_DATA_DIRECTORY 'IMAGE_DATA_DIRECTORY MetaData; '符號表和開始信息 Public Flags As UInteger Public EntryPointTokenAndRVA As IntPtr 'union DWORD EntryPointToken;DWORD EntryPointRVA; Public Resource As IMAGE_DATA_DIRECTORY '綁定信息 IMAGE_DATA_DIRECTORY Resource; Public StrongNameSignature As IMAGE_DATA_DIRECTORY '綁定信息 IMAGE_DATA_DIRECTORY StrongNameSignature; Public CodeMagagerTable As IMAGE_DATA_DIRECTORY '//常規的定位和綁定信息 'IMAGE_DATA_DIRECTORY CodeMagagerTable; Public VTableFixups As IMAGE_DATA_DIRECTORY 'IMAGE_DATA_DIRECTORY VTableFixups; Public ExprotAddressTableJumps As IMAGE_DATA_DIRECTORY 'IMAGE_DATA_DIRECTORY ExprotAddressTableJumps; Public MagageNativeHeader As IMAGE_DATA_DIRECTORY 'IMAGE_DATA_DIRECTORY MagageNativeHeader; End Structure Private Structure MetaDataInfo Public BSJB As UInteger '424A5342h,就是4個固定的ASCII碼,代表.NET四個創始人的首位字母縮寫 Public MajorVersion As UShort '元數據的主版本,一般為1 Public MinorVersion As UShort '元數據的副版本,一般為1 Public ExtraData As UInteger '保留數據0 Public Length As UInteger '接下來版本字符串的長度,包含尾部0,且按4字節對其 <MarshalAs(UnmanagedType.ByValArray, SizeConst:=16)> Public VersionString() As Byte 'UTF8格式的編譯環境版本號 'Public Flags As String '保留0 'Public Streams As UShort 'NStream的個數(流的個數) End Structure ''' <summary> ''' 讀取64位模塊NTHandle結構 ''' </summary> ''' <param name="pHandle">進程句柄</param> ''' <param name="ModuleBaseAddress">模塊地址</param> ''' <returns></returns> Public Shared Function Doit(ByVal pHandle As IntPtr, ByVal ModuleBaseAddress As Long) As IMAGE_NT_HEADERS64 '確認PE格式 If ReadUShort(pHandle, ModuleBaseAddress) <> &H5A4D Then Return New IMAGE_NT_HEADERS64 '首先讀NT頭地址,即PE簽名地址 Dim E_lfanew As IntPtr = ModuleBaseAddress + ReadUInteger(pHandle, ModuleBaseAddress + &H3C) '然后讀取NT頭內容 Dim NTHandle As New IMAGE_NT_HEADERS64 Dim Size As Integer = Marshal.SizeOf(NTHandle) Dim lpNTHandle As IntPtr = Marshal.AllocHGlobal(Size) ReadToGlobal(pHandle, E_lfanew, Size, lpNTHandle) NTHandle = Marshal.PtrToStructure(lpNTHandle, GetType(IMAGE_NT_HEADERS64)) Marshal.FreeHGlobal(lpNTHandle) Return NTHandle End Function Private Shared Function ReadUShort(ByVal pHandle As IntPtr, ByVal lAddress As IntPtr) As UShort Dim tmpArr(1) As Byte Dim lOldProtect As Integer VirtualProtectEx(pHandle, lAddress, 1, &H40, lOldProtect) ReadProcessMemory(pHandle, lAddress, tmpArr, 2, 0) VirtualProtectEx(pHandle, lAddress, 1, lOldProtect, lOldProtect) Return BitConverter.ToUInt16(tmpArr, 0) End Function Private Shared Function ReadUInteger(ByVal pHandle As IntPtr, ByVal lAddress As IntPtr) As UInteger Dim tmpArr(3) As Byte Dim lOldProtect As Integer VirtualProtectEx(pHandle, lAddress, 1, &H40, lOldProtect) ReadProcessMemory(pHandle, lAddress, tmpArr, 4, 0) VirtualProtectEx(pHandle, lAddress, 1, lOldProtect, lOldProtect) Return BitConverter.ToUInt32(tmpArr, 0) End Function Private Shared Function ReadULong(ByVal pHandle As IntPtr, ByVal lAddress As IntPtr) As ULong Dim tmpArr(7) As Byte Dim lOldProtect As Integer VirtualProtectEx(pHandle, lAddress, 1, &H40, lOldProtect) ReadProcessMemory(pHandle, lAddress, tmpArr, 8, 0) VirtualProtectEx(pHandle, lAddress, 1, lOldProtect, lOldProtect) Return BitConverter.ToUInt64(tmpArr, 0) End Function Private Shared Function ReadBytes(ByVal pHandle As IntPtr, ByVal lAddress As IntPtr, length As Integer) As Byte() Dim tmpArr(length - 1) As Byte Dim lOldProtect As Integer VirtualProtectEx(pHandle, lAddress, 1, &H40, lOldProtect) ReadProcessMemory(pHandle, lAddress, tmpArr, length, 0) VirtualProtectEx(pHandle, lAddress, 1, lOldProtect, lOldProtect) Return tmpArr End Function Private Shared Sub ReadToGlobal(ByVal pHandle As IntPtr, ByVal lAddress As IntPtr, length As Integer, lGlobal As IntPtr) Dim lOldProtect As Integer VirtualProtectEx(pHandle, lAddress, 1, &H40, lOldProtect) ReadProcessMemory(pHandle, lAddress, lGlobal, length, 0) VirtualProtectEx(pHandle, lAddress, 1, lOldProtect, lOldProtect) End Sub Private Shared Function ReadString(ByVal pHandle As IntPtr, ByVal lAddress As IntPtr) As String Dim lpString As IntPtr = Marshal.AllocHGlobal(1024) ReadToGlobal(pHandle, lAddress, 1024, lpString) Dim result As String = Marshal.PtrToStringAnsi(lpString) Marshal.FreeHGlobal(lpString) Return result End Function ''' <summary> ''' 解析64位模塊導出函數 ''' </summary> ''' <param name="pHandle">進程句柄</param> ''' <param name="ModuleBaseAddress">模塊地址</param> ''' <param name="NtHandle">Nt頭信息</param> ''' <returns></returns> Public Shared Function GetExportFunctionInfo(ByVal pHandle As IntPtr, ByVal ModuleBaseAddress As Long, NtHandle As IMAGE_NT_HEADERS64) As ExportOrImportsFunctionInfo() If NtHandle.OptionalHeader.IMAGE_DIRECTORY_ENTRY_EXPORT.VirtualAddress = UInteger.MinValue Then Return Nothing '從NtHandle中讀IMAGE_EXPORT_DIRECTORY結構地址 Dim IEDAddress As IntPtr = NtHandle.OptionalHeader.IMAGE_DIRECTORY_ENTRY_EXPORT.VirtualAddress + ModuleBaseAddress '讀IMAGE_EXPORT_DIRECTORY結構 Dim IED As New IMAGE_EXPORT_DIRECTORY Dim size As Integer = Marshal.SizeOf(IED) Dim lpIED As IntPtr = Marshal.AllocHGlobal(size) ReadToGlobal(pHandle, IEDAddress, size, lpIED) IED = Marshal.PtrToStructure(lpIED, GetType(IMAGE_EXPORT_DIRECTORY)) Dim ModleName As String = ReadString(pHandle, IED.Name + ModuleBaseAddress) Marshal.FreeHGlobal(lpIED) '從IMAGE_EXPORT_DIRECTORY結構遍歷函數入口、識別函數名、識別函數編號 Dim result(IED.NumberOfFunctions - 1) As ExportOrImportsFunctionInfo For i As Integer = 0 To IED.NumberOfFunctions - 1 result(i) = New ExportOrImportsFunctionInfo result(i).LibName = ModleName result(i).FunctionIndex = i + IED.Base result(i).FunctionRVA = IED.AddressOfFunctions + 4 * i + ModuleBaseAddress result(i).FunctionAddress = ReadUInteger(pHandle, result(i).FunctionRVA) + ModuleBaseAddress Next For i As Integer = 0 To IED.NumberOfNames - 1 Dim lpName = ModuleBaseAddress + ReadUInteger(pHandle, IED.AddressOfNames + i * 4 + ModuleBaseAddress) Dim lNameOrdinal = ReadUShort(pHandle, IED.AddressOfNameOrdinals + i * 2 + ModuleBaseAddress) If lNameOrdinal <= IED.NumberOfNames Then result(lNameOrdinal).FunctionName = ReadString(pHandle, lpName) End If Next Return result End Function Private Structure IMAGE_IMPORT_DESCRIPTOR Public OriginalFirstThunk As UInteger ' INA Public TimeDateStamp As UInteger ' 0 If Not bound, -1 if bound, And real date\time stamp in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (New BIND) O.W. date/time stamp of DLL bound to (Old BIND) Public ForwarderChain As UInteger ' -1 If no forwarders Public Name As UInteger ' Dll Name Public FirstThunk As UInteger ' RVA To IAT (If bound this IAT has actual addresses) End Structure 'Public Structure IMAGE_THUNK_DATA ' Public ForwarderString As UInteger ' Public FunctionAddr As UInteger ' Public Ordinal As UInteger '序號 ' Public AddressOfData As UInteger '指向IMAGE_IMPORT_BY_NAME 'End Structure 'Public Structure IMAGE_IMPORT_BY_NAME ' Public Hint As UShort 'ordinal ' Public Name As Byte 'Function name String 'End Structure ''' <summary> ''' 解析64位模塊導入函數 ''' </summary> ''' <param name="pHandle">進程句柄</param> ''' <param name="ModuleBaseAddress">模塊地址</param> ''' <param name="NtHandle">Nt頭信息</param> ''' <returns></returns> Public Shared Function GetImportsFunctionInfo(ByVal pHandle As IntPtr, ByVal ModuleBaseAddress As Long, NtHandle As IMAGE_NT_HEADERS64) As ExportOrImportsFunctionInfo() If NtHandle.OptionalHeader.IMAGE_DIRECTORY_ENTRY_IMPORT.VirtualAddress = 0 Then Return Nothing '從NtHandle中讀出IMAGE_IMPORT_DESCRIPTOR結構地址 Dim IIDAddress As ULong = ModuleBaseAddress + NtHandle.OptionalHeader.IMAGE_DIRECTORY_ENTRY_IMPORT.VirtualAddress Dim result As New List(Of ExportOrImportsFunctionInfo) Dim DllIdx As UInteger Do '讀IMAGE_IMPORT_DESCRIPTOR結構 Dim IID As New IMAGE_IMPORT_DESCRIPTOR Dim size As Integer = Marshal.SizeOf(IID) Dim lpIID As IntPtr = Marshal.AllocHGlobal(size) ReadToGlobal(pHandle, IIDAddress + DllIdx * size, size, lpIID) IID = Marshal.PtrToStructure(lpIID, GetType(IMAGE_IMPORT_DESCRIPTOR)) Marshal.FreeHGlobal(lpIID) If IID.FirstThunk = 0 Then Exit Do 'IMAGE_IMPORT_DESCRIPTOR結構的Name屬性是函數所在DLL Dim dllname As String = ReadString(pHandle, IID.Name + ModuleBaseAddress) Dim functionIdx As UInteger = 0UL Do Dim newImports As New ExportOrImportsFunctionInfo newImports.LibName = dllname 'FirstThunk屬性指向函數實際地址數組,數組最后一個元素為0 newImports.FunctionRVA = IID.FirstThunk + ModuleBaseAddress + functionIdx * 8 newImports.FunctionAddress = ReadULong(pHandle, newImports.FunctionRVA) If newImports.FunctionAddress = 0 Then Exit Do Else 'OriginalFirstThunk指向的IMAGE_THUNK_DATA結構地址(INT),當最高位為1時,按編號導出,去除最高位即可;當最高位為0時,指向一個IMAGE_IMPORT_BY_NAME結構。 Dim IMAGE_THUNK_DATA As ULong = ReadULong(pHandle, IID.OriginalFirstThunk + ModuleBaseAddress + functionIdx * 8) If (IMAGE_THUNK_DATA >> 63) = 1 Then newImports.FunctionIndex = (IMAGE_THUNK_DATA And &H7FFFFFFFFFFFFFFFUL) Else newImports.FunctionIndex = ReadUShort(pHandle, IMAGE_THUNK_DATA + ModuleBaseAddress) newImports.FunctionName = ReadString(pHandle, IMAGE_THUNK_DATA + ModuleBaseAddress + 2) End If result.Add(newImports) End If functionIdx += 1 Loop DllIdx += 1 Loop Return result.ToArray End Function ''' <summary> ''' 讀取磁盤上一個程序集所需的CLR版本號,這個函數可以讀取使用任何版本CLR的程序集。 ''' 若使用Assembly.Load而后獲取則受到當前程序集所用CLR版本限制,若目標程序集所需CLR更高則引發錯誤。 ''' </summary> ''' <param name="AssemblyFullPath">程序集的完整名(路徑+文件名+擴展名)</param> ''' <returns>若為空說明不是托管程序集</returns> Public Shared Function GetAssemblyNeedCLRVersion(AssemblyFullPath As String) As String '這個函數直接讀取磁盤文件然后分析,所以過程和前幾個函數有區別 '把磁盤文件加載到內存 Dim pHandle As IntPtr = Process.GetCurrentProcess.Handle Dim buff As Byte() = IO.File.ReadAllBytes(AssemblyFullPath) Dim ModuleBaseAddress As UInteger = Marshal.AllocHGlobal(buff.Length) Marshal.Copy(buff, 0, ModuleBaseAddress, buff.Length) '讀nt頭 Dim NtHandle As IMAGE_NT_HEADERS64 = GetNtHandle64.Doit(Process.GetCurrentProcess.Handle, ModuleBaseAddress) '分析IMAGE_COR20_HEADER If NtHandle.OptionalHeader.IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT.VirtualAddress = 0 Then Return String.Empty '因為這里沒有實際加載只是讀了磁盤文件,所以需要再次重定位 Dim ICHAddress As UInteger = ModuleBaseAddress + NtHandle.OptionalHeader.IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT.VirtualAddress - NtHandle.OptionalHeader.BaseOfCode + NtHandle.OptionalHeader.SizeOfHeaders Dim size As Integer = Marshal.SizeOf(GetType(IMAGE_COR20_HEADER)) Dim lpGlobal As IntPtr = Marshal.AllocHGlobal(size) ReadToGlobal(pHandle, ICHAddress, size, lpGlobal) Dim ICH As IMAGE_COR20_HEADER = Marshal.PtrToStructure(lpGlobal, GetType(IMAGE_COR20_HEADER)) Marshal.FreeHGlobal(lpGlobal) 'IMAGE_COR20_HEADER中的版本號並不是所需CRL版本號,繼續讀取MetaDataInfo成員才能得到 '這里也需要重定位 Dim MDIAddress As UInteger = ModuleBaseAddress + ICH.MetaData.VirtualAddress - NtHandle.OptionalHeader.BaseOfCode + NtHandle.OptionalHeader.SizeOfHeaders Try If Marshal.ReadInt32(MDIAddress) <> &H424A5342 Then Return String.Empty End If Catch ex As Exception Return String.Empty End Try size = Marshal.SizeOf(GetType(MetaDataInfo)) lpGlobal = Marshal.AllocHGlobal(size) ReadToGlobal(pHandle, MDIAddress, size, lpGlobal) Dim MDI As MetaDataInfo = Marshal.PtrToStructure(lpGlobal, GetType(MetaDataInfo)) Marshal.FreeHGlobal(lpGlobal) Dim result As String = UTF8.GetString(MDI.VersionString, 0, MDI.Length) '釋放內存中的程序集 Marshal.FreeHGlobal(ModuleBaseAddress) Return result End Function End Class
和32位的功能相同。
二、遠線程注入DLL
因為有些時候構建一個簡短的ASM就能達成目標,所以我首先封裝了一個遠線程調用ASM函數的類,接下來利用這個類就可以很容易注入DLL了。32位的曾經發過,所以這里只討論64位的情況。64位進程中對函數的調用和32位不太一樣,它把參數逐次壓入RCX,RDX,R8,R9,如果有更多參數,則放在給前4個(已經放入寄存器了)預留的堆棧空間之后,即第五個參數從RSP+20h(4*8)開始。為了能夠在調用API時傳入較復雜的結構(含有指針,指針中還有指針等情況),我實現了兩種不同的參數類型,它們都能夠很好的完成這項任務:
1、subparam,這個類實際上就是一個元素為object的list:
Public Data As New List(Of Object) Sub AddData(val As Boolean) Data.Add(val) End Sub Sub AddData(val As Byte) Data.Add(val) End Sub Sub AddData(val As Short) Data.Add(val) End Sub Sub AddData(val As UShort) Data.Add(val) End Sub Sub AddData(val As Integer) Data.Add(val) End Sub Sub AddData(val As UInteger) Data.Add(val) End Sub Sub AddData(val As Long) Data.Add(val) End Sub Sub AddData(val As ULong) Data.Add(val) End Sub Sub AddData(val As Single) Data.Add(val) End Sub Sub AddData(val As Double) Data.Add(val) End Sub Sub AddData(val As String) Data.Add(val) End Sub Sub AddData(val As Byte()) Data.Add(val) End Sub Sub AddData(val As SubParam) Data.Add(val) End Sub Sub AddData(val As Object) Throw New Exception("SubParam不支持參數類型:" & val.GetType.ToString) End Sub End Class
可以看到,我支持了很多參數類型,其中包括subparam本身,這樣它就可以被解析為指針。也就支持了一些復雜的參數類型。這對實現圖形界面是非常有利的。
2、支持任意類
除了subparam之外,還支持另外一種形式:把任何一個VB.NET類都解釋為指針,這樣也支持了一些復雜的參數類型,這對於外部調用是非常有利的。無論是哪一種情況,它們的解析方法都是一樣的(和前面一篇發的NOI題目“文件圖”相似,只是沒有使用遞歸使用了stack):
Private Sub DeClass(mParam As Object) Dim ParamType As Type = mParam.GetType If ParamType.GetFields.Count = 0 Then '根對象為空指針 Code.AddRange({0, 0, 0, 0, 0, 0, 0, 0}) Else '根對象不為空 Code.AddRange(BitConverter.GetBytes(CLng(AllocBaseAddress.ToInt64 + Data.Count + ObjectAddressOffsetDef))) '對根對象內容進行解析。 Dim Sps As New Stack(Of KeyValuePair(Of Type, Integer)) '將根SubParam壓棧,其指針在Data數組中的地址為-1,因為這個指針實際上已經寫入Code段。 Sps.Push(New KeyValuePair(Of Type, Integer)(ParamType, -1)) Dim CurSubParam As KeyValuePair(Of Type, Integer) = Nothing '當前正在處理的SubParam Dim curMemoryAddress As Long '當前實際內存地址(指針應指向的內存地址) Dim curSubParamBytesList As New List(Of KeyValuePair(Of Byte(), Integer)) '當前SubParam中Byte(),String暫存 Dim curSubParamDataElement As Object '當前SubParam中的數組元素 Dim st As Type '當前SubParam中的數組元素的類型 '以下添加的任何數據都在Data中 Do While Sps.Count > 0 '初始化將要使用的變量 curMemoryAddress = IntPtr.Zero st = Nothing curSubParamDataElement = Nothing curSubParamBytesList.Clear() '彈出一個SubParam-OffsetOnData對進行處理 CurSubParam = Sps.Pop '將當前位置的實際內存地址寫到從OffsetOnData開始的Data段(為-1的是根SubParam,指針已經寫入到Code段) If CurSubParam.Value <> -1 Then '當前實際內存地址 curMemoryAddress = AllocBaseAddress.ToInt64 + Data.Count + ObjectAddressOffsetDef '寫入到DATA中從OffsetOnData開始的位置 Data.RemoveRange(CurSubParam.Value, 8) Data.InsertRange(CurSubParam.Value, BitConverter.GetBytes(curMemoryAddress)) End If '遍歷SubParam中的數據,依次寫入,對於Byte(),String先寫入空指針並記錄空指針在Code段的地址,等寫完全部字段再寫入數據到Data,並填寫空指針為實際內存地址。 '對於SubParam,也是先寫入空指針,然后將SubParam-OffsetOnData對入棧。 If CurSubParam.Key.GetFields.Count = 0 Then '將空對象解釋為空指針 Data.AddRange({0, 0, 0, 0, 0, 0, 0, 0}) Else For i As Integer = 0 To CurSubParam.Key.GetFields.Count - 1 curSubParamDataElement = CurSubParam.Key.GetFields()(i).GetValue(mParam) st = curSubParamDataElement.GetType If (st Is GetType(Boolean)) Then 'Boolean類型轉換為Long添加到數據 Data.AddRange(BitConverter.GetBytes(CLng(curSubParamDataElement))) ElseIf (st Is GetType(Byte)) Then '值類型直接添加到數據 Data.Add(curSubParamDataElement) ElseIf (st Is GetType(Short)) OrElse (st Is GetType(UShort)) OrElse (st Is GetType(Integer)) OrElse (st Is GetType(UInteger)) OrElse (st Is GetType(Long)) OrElse (st Is GetType(ULong)) OrElse (st Is GetType(Single)) OrElse (st Is GetType(Double)) Then '值類型直接添加到數據 Data.AddRange(BitConverter.GetBytes(curSubParamDataElement)) ElseIf st Is GetType(Byte()) Then '數組暫存 curSubParamBytesList.Add(New KeyValuePair(Of Byte(), Integer)(CType(curSubParamDataElement, Byte()), Data.Count)) '暫時寫入空指針占位 Data.AddRange({0, 0, 0, 0, 0, 0, 0, 0}) ElseIf st Is GetType(String) Then '字符串暫存 curSubParamBytesList.Add(New KeyValuePair(Of Byte(), Integer)(Unicode.GetBytes(curSubParamDataElement), Data.Count)) '暫時寫入空指針占位 Data.AddRange({0, 0, 0, 0, 0, 0, 0, 0}) ElseIf st.IsClass Then 'SubParam入棧 Sps.Push(New KeyValuePair(Of Type, Integer)(curSubParamDataElement.GetType, Data.Count)) '暫時寫入空指針占位 Data.AddRange({0, 0, 0, 0, 0, 0, 0, 0}) End If Next '將暫存的Byte(),String寫入指針和數據 For i As Integer = 0 To curSubParamBytesList.Count - 1 '和解釋SubParam一樣,先填充之前的空指針。 '當前實際內存地址 curMemoryAddress = AllocBaseAddress.ToInt64 + Data.Count + ObjectAddressOffsetDef '寫入到DATA中從OffsetOnData開始的位置 Data.RemoveRange(curSubParamBytesList(i).Value, 8) Data.InsertRange(curSubParamBytesList(i).Value, BitConverter.GetBytes(curMemoryAddress)) '然后把數據寫到當前地址 Data.AddRange(curSubParamBytesList(i).Key) Next End If Loop End If End Sub
這樣,就可以構建一個復雜的參數。例如,在后面遠線程給64位進程注入托管程序集的代碼中有這樣一段:
Private Class UinString Public Length As UShort 'buffer中字符串長度(不含chrw(0)) Public MaximumLength As UShort 'buffer大小 Public x64Patch As Integer '4字節0 Public File As String End Class
Dim result As IntPtr '解析Kernel32模塊地址 Dim TargetModuleAddress As IntPtr = GetModuleAddress(TargetProcess.Handle, "ntdll.dll") If TargetModuleAddress = IntPtr.Zero Then Throw New Exception("無法解析目標進程ntdll.dll模塊地址。") Return IntPtr.Zero End If '解析LdrLoadDll函數地址 Dim TargetFunctionAddress As IntPtr = GetFunctionAddress(TargetProcess.Handle, TargetModuleAddress, "LdrLoadDll") If TargetFunctionAddress = IntPtr.Zero Then Throw New Exception("無法解析目標進程LdrLoadDll函數地址。") Return IntPtr.Zero End If '加載相應版本的LoadClrxx.dll Dim LoadClrDll As String = String.Empty Dim LoadClrDllAddress As IntPtr CallRemoteFunctionByAddress64 = New CallRemoteFunctionByAddress64(TargetProcess.Handle) LoadClrDll = "LoadClr64.Dll" Dim LoadClr64 As String = (My.Application.Info.DirectoryPath & "\" & LoadClrDll).Replace("\", "\\") 'LoadClrDllAddress = CallRemoteFunctionByAddress64.DoIt(TargetFunctionAddress, LoadClr64) '創建參數 Dim p3 As New UinString Dim str As String = LoadClr64 & ChrW(0) p3.MaximumLength = Unicode.GetByteCount(str) p3.Length = (p3.MaximumLength - 2) p3.x64Patch = (0) p3.File = str '調用函數 LoadClrDllAddress = CallRemoteFunctionByAddress64.DoIt(TargetFunctionAddress, 0L, 0L, p3, New Byte() {0, 0, 0, 0, 0, 0, 0, 0}) CallRemoteFunctionByAddress64.Clear()
這段代碼遠線程調用了目標進程的LdrLoadDll函數來注入DLL,通過跟蹤可以知道這個函數的參數中DLL文件名用UinString結構存儲,並且64位中bufflen后面有4字節0。這個結構相對比較復雜,調用時參數指向結構地址,而結構中string類型實際上也是一個指向字符緩沖的指針。所以,需要使用一個較為復雜的結構來進行實現,subparam可以做到,但是為了編碼看起來貌似更易懂,我使用了類的方式。
3、構建整個ASM函數和調用它
除了較復雜的參數支持之外,我們還需要構建一個完整函數,遠線程運行這個函數就會用我們指定的參數調用相應的函數。這與以前發過的32位的沒有太大區別——除了支持了復雜的參數以外。隨着操作系統的更新換代,很多API函數都不再被支持,或者被“圍堵”,所以,遠線程調用這里也有一些區別:
If Environment.OSVersion.Version.Major >= 6 Then 'NtCreateThreadEx函數地址 Dim pFunc As IntPtr = GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtCreateThreadEx") '通過NtCreateThreadEx函數地址創建委托 Dim Del As NtCreateThreadEx = Marshal.GetDelegateForFunctionPointer(pFunc, GetType(NtCreateThreadEx)) '通過委托調用NtCreateThreadEx result = Del(pThreadHwnd, &H1FFFFF, IntPtr.Zero, RemoteProcessHandle, AllocBaseAddress, IntPtr.Zero, False, 0, 0, 0, IntPtr.Zero) Else pThreadHwnd = CreateRemoteThread(RemoteProcessHandle, 0, 0, AllocBaseAddress, 0, 0, 0) End If
對於不同的系統版本,采用了不同的API函數來創建遠線程,其中NtCreateThreadEx是之前公開的代碼中沒有的,無論它的C原型是什么,用下面的委托可以順利調用它:
Private Delegate Function NtCreateThreadEx(ByRef hThread As IntPtr, ByVal ACCESS_MASK As Integer, ByVal ObjectAttributes As IntPtr, ByVal ProcessHandle As IntPtr, ByVal lpRemoteStartAddress As IntPtr, ByVal StartContext As IntPtr, ByVal Flags As Boolean, ByVal StackZeroBits As Integer, ByVal SizeOfStackCommit As Integer, ByVal SizeOfStackReserve As Long, ByVal AttributeList As IntPtr) As Integer
在64位中,還有一些小問題。主要是遠線程函數返回值,我做了一些測試,但無論如何結果是令人沮喪的——它依然是一個32位值,指望它返回加載的DLL基地址就呵呵了。當然,我們完全可以在加載DLL時完成各種工作而不再與注入程序發生任何數據交換。實際上,我們還是有辦法取得完整的返回值的:在構建的ASM函數之后,加入一小段內容,把EAX寫入到為目標進程申請的內存中,我們就可以得到這個返回值,在我的代碼中把它寫入到最后8字節:
'9、add rsp,StackSize 恢復棧指針 48 83 C4 78 'a、mov rdx,AllocBaseAddress+MEM_SIZE-8 申請的內存最后8字節寫入rdx 48 BA 1111111111111111 'a、mov [rdx],rax 返回值寫入申請的內存最后8字節 48 89 02 'b、ret 函數返回 C3
好了,就寫到這里,相信根據我的提示,你也能寫出更好的代碼。