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

好了,就写到这里,相信根据我的提示,你也能写出更好的代码。

 

posted @ 2017-02-11 22:57  zcsor~流浪dè风  Views(1370)  Comments(6Edit  收藏  举报