核心分析PE獲取DLL匯出函式地址

whatday發表於2013-08-10

環境:VS2012+WIN8 64

型別:C++編寫的WDM驅動程式

測試:VM WIN7

用途:主要用於驅動程式中得到WIN32 API地址,也可得到自定義的DLL中的函式匯出地址,記錄核心檔案相關操作以便以後檢視。

說明:此段程式碼來源於網路,經修改除錯而成。


標頭檔案 HelloWDM.h

#if __cplusplus
extern "C"
{
#endif
#include <wdm.h>
#include <windef.h>
#ifdef __cplusplus
}
#endif

//winnt.h中的定義 由於是WDM不能引用該檔案 所以只有複製過來
#define SEC_IMAGE         0x1000000  

//PE相關結構
typedef struct _SECTION_IMAGE_INFORMATION
{
     PVOID TransferAddress;
     ULONG ZeroBits;
     ULONG MaximumStackSize;
     ULONG CommittedStackSize;
     ULONG SubSystemType;
     union
     {
          struct
          {
               WORD SubSystemMinorVersion;
               WORD SubSystemMajorVersion;
          };
          ULONG SubSystemVersion;
     };
     ULONG GpValue;
     WORD ImageCharacteristics;
     WORD DllCharacteristics;
     WORD Machine;
     UCHAR ImageContainsCode;
     UCHAR ImageFlags;
     ULONG ComPlusNativeReady: 1;
     ULONG ComPlusILOnly: 1;
     ULONG ImageDynamicallyRelocated: 1;
     ULONG Reserved: 5;
     ULONG LoaderFlags;
     ULONG ImageFileSize;
     ULONG CheckSum;
} SECTION_IMAGE_INFORMATION, *PSECTION_IMAGE_INFORMATION;

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    //
    // NT additional fields.
    //

    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[16];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

HelloWDM.cpp檔案

#include "HelloWDM.h"

//得到DLL中的指定函式地址 相當於應用層的GetProcAddress函式
DWORD GetDllFunctionAddress(PTSTR lpFunctionName, PTSTR pDllName)
{
    HANDLE hThread, hSection, hFile, hMod;
    SIZE_T size=0;
    NTSTATUS status;
    PVOID BaseAddress = NULL;

    //轉換DLL名稱
    UNICODE_STRING strDllName;
    RtlInitUnicodeString(&strDllName, pDllName);

    OBJECT_ATTRIBUTES objectAttributes={0};
    IO_STATUS_BLOCK iosb={0};

    //初始化 objectAttributes
    InitializeObjectAttributes(&objectAttributes, &strDllName, OBJ_KERNEL_HANDLE, NULL, NULL);

    //開啟檔案
    status=ZwOpenFile(&hFile,  FILE_EXECUTE | SYNCHRONIZE, &objectAttributes, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
    if(!NT_SUCCESS(status))
    {
        return status;
    }
    objectAttributes.ObjectName = 0;

    //建立記憶體塊
    status=ZwCreateSection(&hSection, SECTION_ALL_ACCESS, &objectAttributes, 0, PAGE_READONLY, SEC_IMAGE, hFile); //PAGE_READONLY頁面保護屬性,必須結合SEC_IMAGE屬性
    if(!NT_SUCCESS(status))
    {
        return status;
    }

    //記憶體對映檔案
    status=ZwMapViewOfSection(hSection, 
                            ZwCurrentProcess(), 
                            &BaseAddress, 
                            0, 
                            1024, 
                            0, 
                            &size, 
                            ViewUnmap, 
                            MEM_LARGE_PAGES,        //針對DLL檔案較小是可以用MEM_TOP_DOWN 檔案較大比如USER32.DLL時需要用MEM_LARGE_PAGES
                            PAGE_READWRITE);        
    if(!NT_SUCCESS(status))
    {
        return status;
    }
    //關閉檔案控制程式碼
    ZwClose(hFile);

    //讀取PE頭資訊
    IMAGE_DOS_HEADER* dosheader;
    IMAGE_OPTIONAL_HEADER* opthdr;
    IMAGE_EXPORT_DIRECTORY* pExportTable;
    PDWORD arrayOfFunctionAddresses, arrayOfFunctionNames;
    PWORD arrayOfFunctionOrdinals;
    DWORD functionOrdinal, functionAddress=0;
    PSTR functionName;
    ANSI_STRING anFunName;
    UNICODE_STRING unFunctionName, unFunctionNameSearch;
    //模組控制程式碼
    hMod = BaseAddress;
    //得到DOS頭
    dosheader = (PIMAGE_DOS_HEADER)hMod;
    //得到PE選項頭
    opthdr =(PIMAGE_OPTIONAL_HEADER) ((PBYTE)hMod+dosheader->e_lfanew+24);
    //得到匯出表
    pExportTable =(PIMAGE_EXPORT_DIRECTORY)((PBYTE) hMod + opthdr->DataDirectory[0].VirtualAddress);
    //得到函式地址列表
    arrayOfFunctionAddresses = (PDWORD)( (PBYTE)hMod + pExportTable->AddressOfFunctions);
    //得到函式名稱列表
    arrayOfFunctionNames = (PDWORD)( (PBYTE)hMod + pExportTable->AddressOfNames);
    //得到函式序號
    arrayOfFunctionOrdinals = (PWORD)( (PBYTE)hMod + pExportTable->AddressOfNameOrdinals);
    //匯出表基地址
    DWORD Base = pExportTable->Base;

    //轉換函式名
    RtlInitUnicodeString(&unFunctionNameSearch, lpFunctionName);
    //迴圈匯出表
    for(DWORD x = 0; x < pExportTable->NumberOfNames; x++)            //匯出函式有名稱 編號之分,匯出函式總數=名稱匯出+編號匯出,這裡是迴圈匯出名稱的函式
    {
        //得到函式名 
        functionName = (PSTR)( (PBYTE)hMod + arrayOfFunctionNames[x]);

        //轉化為ANSI_STRING
        RtlInitAnsiString(&anFunName, functionName);
        //轉化為UNICODE_STRING
        RtlAnsiStringToUnicodeString(&unFunctionName, &anFunName, TRUE);
        //列印除錯資訊
        KdPrint(("%d/%d,FunName:%wZ\n", x+1, pExportTable->NumberOfNames, &unFunctionName));
        //比較函式名稱
        if (RtlCompareUnicodeString(&unFunctionName, &unFunctionNameSearch, TRUE) == 0)
        {
            //得到該函式地址
            functionOrdinal = arrayOfFunctionOrdinals[x] + Base - 1;
            functionAddress = (DWORD)( (PBYTE)hMod + arrayOfFunctionAddresses[functionOrdinal]);
            break;
        }
    }

    ZwClose(hSection);

    return functionAddress;
}

以上程式碼雖可以執行但沒有考慮到 ZwMapViewOfSection的資源釋放問題 修改如下:

//HelloWDM.h

#if __cplusplus
extern "C"
{
#endif
#include <wdm.h>
#include <windef.h>
#ifdef __cplusplus
}
#endif

//定義裝置擴充套件
typedef struct _DEVICE_EXTERSION
{
    PDEVICE_OBJECT fdo;
    PDEVICE_OBJECT NextStatckDevice;
    UNICODE_STRING ustrDeviceName;        //裝置名
    UNICODE_STRING ustrSymLinkName;        //符號連結名
    PVOID tmpPoint;                        //記錄臨時指標
}DEVICE_EXTENSION, *PDEVICE_EXTENSION;

//全域性變數 
PDEVICE_EXTENSION gDevExt=NULL;

//winnt.h中的定義 由於是WDM不能引用該檔案 所以只有複製過來
#define SEC_IMAGE         0x1000000  

//PE相關結構
typedef struct _SECTION_IMAGE_INFORMATION
{
     PVOID TransferAddress;
     ULONG ZeroBits;
     ULONG MaximumStackSize;
     ULONG CommittedStackSize;
     ULONG SubSystemType;
     union
     {
          struct
          {
               WORD SubSystemMinorVersion;
               WORD SubSystemMajorVersion;
          };
          ULONG SubSystemVersion;
     };
     ULONG GpValue;
     WORD ImageCharacteristics;
     WORD DllCharacteristics;
     WORD Machine;
     UCHAR ImageContainsCode;
     UCHAR ImageFlags;
     ULONG ComPlusNativeReady: 1;
     ULONG ComPlusILOnly: 1;
     ULONG ImageDynamicallyRelocated: 1;
     ULONG Reserved: 5;
     ULONG LoaderFlags;
     ULONG ImageFileSize;
     ULONG CheckSum;
} SECTION_IMAGE_INFORMATION, *PSECTION_IMAGE_INFORMATION;

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    //
    // NT additional fields.
    //

    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[16];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

//HelloWDM.cpp

//得到DLL中的指定函式地址 相當於應用層的GetProcAddress函式
DWORD GetDllFunctionAddress(PTSTR lpFunctionName, PTSTR pDllName)
{
	HANDLE hSection=NULL, hFile=NULL;
	SIZE_T size=0;
	NTSTATUS status;
	PVOID BaseAddress = NULL;

	//轉換DLL名稱
	UNICODE_STRING strDllName;
	RtlInitUnicodeString(&strDllName, pDllName);

	OBJECT_ATTRIBUTES objectAttributes={0};
	IO_STATUS_BLOCK iosb={0};

	//初始化 objectAttributes
	InitializeObjectAttributes(&objectAttributes, &strDllName, OBJ_KERNEL_HANDLE, NULL, NULL);

	__try
	{
		//開啟檔案
		status=ZwOpenFile(&hFile,  FILE_EXECUTE | SYNCHRONIZE, &objectAttributes, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT);
		if(!NT_SUCCESS(status))
		{
			__leave;
		}
		objectAttributes.ObjectName = 0;

		//建立記憶體塊
		status=ZwCreateSection(&hSection, SECTION_ALL_ACCESS, &objectAttributes, 0, PAGE_READONLY, SEC_IMAGE, hFile); //PAGE_READONLY頁面保護屬性,必須結合SEC_IMAGE屬性
		if(!NT_SUCCESS(status))
		{
			__leave;
		}

		//記憶體對映檔案
		status=ZwMapViewOfSection(hSection, 
			ZwCurrentProcess(), 
			&BaseAddress, 
			0, 
			1024, 
			0, 
			&size, 
			ViewUnmap, 
			MEM_LARGE_PAGES,		//針對DLL檔案較小是可以用MEM_TOP_DOWN 檔案較大比如USER32.DLL時需要用MEM_LARGE_PAGES
			PAGE_READWRITE);		
	}
	__finally
	{
		if(hFile != NULL)
		{
			//關閉檔案控制程式碼
			ZwClose(hFile);		
		}
		if(!NT_SUCCESS(status) && hSection != NULL)
		{
			//關閉記憶體塊
			ZwClose(hSection);
		}
	}
	//如果失敗 直接返回
	if(!NT_SUCCESS(status))
	{
		return 0;
	}

	//讀取PE頭資訊
	IMAGE_DOS_HEADER* dosheader;
	IMAGE_OPTIONAL_HEADER* opthdr;
	IMAGE_EXPORT_DIRECTORY* pExportTable;
	PDWORD arrayOfFunctionAddresses, arrayOfFunctionNames;
	PWORD arrayOfFunctionOrdinals;
	DWORD functionOrdinal, functionAddress=0;
	PSTR functionName;
	ANSI_STRING anFunName;
	UNICODE_STRING unFunctionName, unFunctionNameSearch;
	//模組控制程式碼
	HANDLE hMod = BaseAddress;
	//得到DOS頭
	dosheader = (PIMAGE_DOS_HEADER)hMod;
	//得到PE選項頭
	opthdr =(PIMAGE_OPTIONAL_HEADER) ((PBYTE)hMod+dosheader->e_lfanew+24);
	//得到匯出表
	pExportTable =(PIMAGE_EXPORT_DIRECTORY)((PBYTE) hMod + opthdr->DataDirectory[0].VirtualAddress);
	//得到函式地址列表
	arrayOfFunctionAddresses = (PDWORD)( (PBYTE)hMod + pExportTable->AddressOfFunctions);
	//得到函式名稱列表
	arrayOfFunctionNames = (PDWORD)( (PBYTE)hMod + pExportTable->AddressOfNames);
	//得到函式序號
	arrayOfFunctionOrdinals = (PWORD)( (PBYTE)hMod + pExportTable->AddressOfNameOrdinals);
	//匯出表基地址
	DWORD Base = pExportTable->Base;

	//轉換函式名
	RtlInitUnicodeString(&unFunctionNameSearch, lpFunctionName);
	//迴圈匯出表
	for(DWORD x = 0; x < pExportTable->NumberOfNames; x++)			//匯出函式有名稱 編號之分,匯出函式總數=名稱匯出+編號匯出,這裡是迴圈匯出名稱的函式
	{
		//得到函式名 
		functionName = (PSTR)( (PBYTE)hMod + arrayOfFunctionNames[x]);

		//轉化為ANSI_STRING
		RtlInitAnsiString(&anFunName, functionName);
		//轉化為UNICODE_STRING
		RtlAnsiStringToUnicodeString(&unFunctionName, &anFunName, TRUE);
		//列印除錯資訊
		KdPrint(("%d/%d,FunName:%wZ\n", x+1, pExportTable->NumberOfNames, &unFunctionName));
		//比較函式名稱
		if (RtlCompareUnicodeString(&unFunctionName, &unFunctionNameSearch, TRUE) == 0)
		{
			//得到該函式地址
			functionOrdinal = arrayOfFunctionOrdinals[x] + Base - 1;
			functionAddress = (DWORD)( (PBYTE)hMod + arrayOfFunctionAddresses[functionOrdinal]);
			break;
		}
	}
	//這裡釋放資源返回的地址將無效 所以先存放起來
	//ZwUnmapViewOfSection (NtCurrentProcess(), BaseAddress);
	gDevExt->tmpPoint=BaseAddress;

	ZwClose(hSection);

	return functionAddress;
}

呼叫程式碼如下:

ULONG ulOriginalProcAddr=GetDllFunctionAddress(TEXT("NtOpenProcess"), TEXT("\\SystemRoot\\system32\\ntdll.dll"));
//釋放GetDllFunctionAddress的記憶體塊
if(gDevExt->tmpPoint!=0)
{
    ZwUnmapViewOfSection (NtCurrentProcess(), gDevExt->tmpPoint);
    gDevExt->tmpPoint=0;
}





相關文章