VB.NET實現32位、64位遠線程運行ASM,注入非托管、托管DLL


  這是一個老話題,遠線程函數給我們提供了機會在其他進程中啟動一個新線程,所以我們可以做很多事情。但事情遠遠沒有結束,如果我們要做的事情非常復雜,那么將面臨編寫大量的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

好了,就寫到這里,相信根據我的提示,你也能寫出更好的代碼。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM