天天看點

PE檔案和COFF檔案格式分析——導出表

        在之前的《PE可選檔案頭》相關博文中我們介紹了可選檔案頭中很多重要的屬性,而其中一個非常重要的屬性是(轉載請指明來源于breaksoftware的CSDN部落格)

IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];            

複制

        該數組儲存了如下節(不一定全包括,要以IMAGE_OPTIONAL_HEADER32(64)::NumberOfRvaAndSizes來确定)的資訊

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
//      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor           

複制

        我們在之後會介紹各個節的結構和相關應用,本文我将介紹該數組中第一個元素(DataDirectory[0])的資訊——導出表資訊。

        首先我介紹下導出表。我們做程式時,新手一般喜歡做的是copy+paste。這個方法在代碼結構不是很複雜的時候還能過的去。如果像微軟這樣的系統也是這麼寫,我想我們的黑客和漏洞挖掘者會非常高興了——因為這樣必定會産生更多的漏洞和bug。因為這樣寫的代碼非常難維護。打個比方,我們有個函數實作了對XML的解析,有ABCDE這麼多業務方去copy了這段代碼。若幹年後某天XML規則發生了改變,我們要修正XML解析算法,這個時候可能由于原來引入該段代碼的員工離職了或者時間久遠等原因,ABCDE各方都不知道自己的邏輯中用了XML,更不知道要去修正為新的算法。于是如何解決呢?ABCDE方應該讓XML解析算法編寫者提供一個.h和.cpp檔案,裡面包含了我們可能會調用的XML算法,然後在各自的代碼中include這個XML算法編寫者維護的目錄下的這個.h檔案,并調用.h中的方法。這樣,以後XML算法即使改了,各業務方也可以保證我們使用的算法是最新的。但是還别高興的太早,還有個問題放在我們面前。如果我們的程式是一個獨立的Exe釋出的話,在後續更新時會帶來些麻煩。比如我們釋出的Exe檔案是1G,可是釋出後我們發現一行代碼寫錯了,于是我改了這行代碼,卻要讓使用者更新一個1G的檔案!!流量啊!怎麼辦?為了便于更新,我們還是把1G檔案合理分割成若幹個檔案,并保證它們可以協同工作。DLL就是這樣被拆分出來的檔案中一個非常重要的組成部分,它裡面的導出函數就如同供其他方調用的XML解析的各種方法。導出表就是用于儲存這些方法的名稱和位址等資訊的地方。

        現在我們來說下導出表節的結構。在DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]中儲存了導出表節的相對虛拟偏移RVA和大小,在之後的章節中我們會發現除了DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]儲存的是RA(相對檔案頭的偏移),其他都是RVA。通過該RVA,我們算出RA,進而得到一個描述導出表頭的結構體資訊,該結構體是

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;           

複制

        Characteristics是保留字段,要求為0。

        TimeDataStamp儲存的生成導出資訊的時間。

        MajorVersion和MinorVersion分别是主版本号和此版本号。這些資訊是我們可以決定的。

        Name字段儲存的該導出檔案的名稱的偏移。這兒要注意一點,這個位址是系統不關心的,我們可以将其指向的位址設定為違法的位址,這樣會幹擾部分PE分析工具的分析結果。

        Base是導出函數的起始序數值,該值一般為1。如我們用View dependencies打開一個檔案,紅色部分就是Base字段相關的

PE檔案和COFF檔案格式分析——導出表

        NumberOfFunctions标志導出函數的函數位址數。該資料是非常重要的,我們要知道該檔案導出了多少個函數就是要依據這個資訊。我們之後會詳細說的。

        NumberOfNames标志導出函數的函數名數量。

        AddressOfFunctions标志導出函數的函數位址表的RVA。

        AddressOfNames标志導出函數的函數名表的RVA。

        AddressOfNameOrdinals标志導出函數的導出序數表的RVA。

        以我電腦上desktmon.dll為例,我們看一下該檔案中該結構的布局

PE檔案和COFF檔案格式分析——導出表

        我們再用一個圖來描述一下PE導出表在View dependencies中顯示的相關關系

PE檔案和COFF檔案格式分析——導出表

        初次研究這個結構的同學可能會注意一個問題,該結構中有三個表的RVA(AddressOfFunctions,AddressOfNames,AddressOfNameOrdinals),而隻給出了其中前兩個表的元素個數(NumberOfFunctions,NumberOfNames)。那第三個表——導出序數表的個數是多少?是按導出函數位址表(AddressOfFunctions)中元素個數(NumberOfFunctions)還是按導出函數名稱表(AddressOfNames)中元素個數(NumberOfNames)?還有個問題:為什麼要設定Base屬性?這些問題我們先Mark下。我們先來詳細介紹這三個表。

導出位址表。顧名思義,該表中儲存了函數入口RVA。但是如果僅僅是如此簡單就好了,這個地方儲存的還可能是一個指向字元串的RVA!其結構是以下結構體的一個集合。

// 導出表資訊
typedef struct _IMAGE_Export_Address_Table_
{
    union {
        DWORD dwExportRVA;
        DWORD dwForwarderRVA;
    };
}IMAGE_Export_Address_Table, *pIMAGE_Export_Address_Table;           

複制

        如果它儲存的是導出函數入口位址,那沒什麼好說的。我們說下它儲存的是指向一個字元串的偏移的情況。在我的XP系統下Kernel32.dll中AddVectoredExceptionHandler函數的導出函數位址指向的字元串是NTDLL.RtlAddVectoredExceptionHandler。看到這樣的名字組合,我想你大概能猜出個眉目。AddVectoredExceptionHandler函數,在Kernel32.dll檔案内部是沒有實作的。但是如果有程式需要加載Kernel32.dll并需要調用這個函數,則這樣的寫法會告訴加載器在加載Kernel32.dll時,要将AddVectoredExceptionHandler函數的位址直接改成Ntdll.dll中的RtlAddVectoredExceptionHandler函數位址(即自動加載Ntdll.dll)。這個特性非常有趣吧!我想做加殼的朋友應該對這個場景很熟悉。我之後會介紹利用這個特性去隐性自動加載DLL。最後說一下,我們如何辨識這個字段儲存的是函數的入口位址的RVA還是字元串呢?隻要判斷該偏移不在導出表節中即可:指向的位址在節中就是字元串的RVA;在節外是函數入口的RVA。

導出名稱表。計算機做出來是給人用的,如果給人一堆010101這樣的資料,我想沒誰會有太多興趣去看的。于是出于人性化考慮,人們發明了别名,比如發明了彙編映射二進制指令,進而幫助了解程式邏輯。導出名稱表就是出于這樣的考慮而設計的。其結構是以下結構體的一個集合。

typedef struct _IMAGE_Export_Name_Pointer_Table_ {
    DWORD dwPointer;
}IMAGE_Export_Name_Pointer_Table,*pIMAGE_Export_Name_Pointer_Table;           

複制

        它是指向字元串的RVA,該字元串是以\0結尾的。

        說到這兒,我覺得我們可以停下思考一個問題,是不是隻要有這兩個表就夠了?如果對于我們自己編寫的且非常标準的DLL,隻要有這兩個表的确是夠了。你想,當我們調用GetProcAddress時,我們在導入名稱表中找到該名稱對應的index,然後再傳回導出函數位址表中該index的資料即可。

lpFunc = ExportAddressTable[ExportNameTable.find(FuncName)]           

複制

        但是,PE檔案設計的遠沒有這麼簡單。如果如此簡單,那很多事都好辦了。舉一個特殊的例子來推翻這種簡單的場景: 函數入口位址和函數名之間的關系是1對N(0~n)。我們程式運作起來後,很多時候是要調用其他邏輯,即函數入口。可以說一個函數入口可以唯一标注一個邏輯。而我們經常說的某某API,其實隻是某個函數過程的一個名字。比如我們一個實作XML解析的函數,我們可以叫做ParseXML,也可以叫XMLParse。不管是叫哪個名字,該函數的功能是不變的,它的入口位址是不變的。如果入口位址變了,那就是另外一個函數了。這就是為什麼說函數入口位址和函數名之間是1對N的關系。

PE檔案和COFF檔案格式分析——導出表

        針對以上問題,可能有人會想到,有多少個導出函數名(以導出函數名的數量為标準)就設定多少個導出位址,導出位址表中資料可以重複,比如上圖中ParseXML和XMLParse函數名對應的導出位址都設定成0xXXXXXXXX就行了嘛。如

PE檔案和COFF檔案格式分析——導出表

但是還有個場景:windows平台可以通過序數導入一個函數位址(GerProcAddress的第二個參數傳序數),那麼這就意味着函數可以沒有函數名!!因為序數也可以看成一個函數的編号嘛,雖然這樣非常不友好,但是仍然是一種可行的方法。那麼如果在這種場景下,我們還能以導出函數名的數量為标準麼?不可以了吧,因為函數名表元素數量可能是0!其實這類檔案挺多,如mfc40u.dll,見下圖

PE檔案和COFF檔案格式分析——導出表

        通過以上分析,我們可以得出,我們還是要一個能在導出函數位址表和導出函數名稱表建立紐帶的結構體。這個我們期待的輔助結構體就是我們下面介紹的導出序數表。

  導出序數表。該表儲存的是導出位址表的序數偏移!切記這個重要的概念。那這個偏移是相對什麼偏移的呢?是針對IMAGE_EXPORT_DIRECTORY::Base屬性的。即這個表中儲存的值加上Base,就是導出位址表的序數。其結構是以下結構體的一個集合。

typedef struct _IMAGE_Export_Ordinal_Table_ {
    WORD dwOrdinal;
}IMAGE_Export_Ordinal_Table,*pIMAGE_Export_Ordinal_Table;           

複制

        從這個表的命名(AddressOfNameOrdinals )看,應該可以發現這個表應該和導出名稱表存在一定的關系!是的,它的元素的數量和導出名稱表的元素數量是一樣的。可能有人會疑問,什麼這個表元素的個數不是和導出位址表元素個數一緻呢?因為如上面所說,一個函數過程可以對應多個函數名,如果導出序數表元素個數和導出函數位址表元素個數一樣,則無法讓位址與函數名對應上。比如我們導出位址表有1個函數入口,而我們有2個函數名都指向這個位址,那麼導出序數表個數如果是1,則如何表示這兩個名稱與函數入口的對應呢?如果導出序數表格式是2個,則我們可以讓這兩個元素都“指向”同一個導出函數入口即可。OK,這兒我就解答了上面我們Mark過的那個問題:導出序數表個數和導出名稱表個數一緻。

       那麼這三個表之間具體什麼關系呢?我首先以一個簡單的、正常的檔案為例,這個檔案是上面提到的deskmon.dll。我們看一下View Dependencies的分析結果:

PE檔案和COFF檔案格式分析——導出表

        我們再把它的PE檔案拿出來看下

PE檔案和COFF檔案格式分析——導出表

        我們把各個資訊提取出來看下:

Characteristics;        0x00000000
TimeDateStamp;          0x3B7D74B7
MajorVersion;           0x0000
MinorVersion;           0x0000
Name;                   0x00002E6C
Base;                   0x00000001
NumberOfFunctions;      0x00000002 
NumberOfNames;          0x00000002
AddressOfFunctions;     0x00002E58
AddressOfNames;         0x00002E60
AddressOfNameOrdinals;  0x00002E68           

複制

        可以看到這個Dll的導出位址表有2個元素,導出名稱表和導出序數表也是有2個元素的。用之前《PE檔案和COFF檔案格式分析——RVA和RA互相計算》介紹的算法,我們可以得出

        導出位址表RVA(0x00002E58)對應的RA是0x00002258。兩個元素分别為{ {0,0x0002218},{1,0x00002534}}。和View Dependencis分析結果對比發現,這組資料是一緻的。

        導出名稱表RVA(0x00002E60)對應的RA是0x00002260,其資料是{0,0x00002E78}和{1,0x00002E88}。0x00002E78是函數名的RVA,其對應的RA是0x00002278,即字元串“DllCanUnloadNow”;0x00002E88也是函數名稱的RVA,其RA是0x00002288,即字元串“DllGetClassObject”。于是可以把導出函數名表看成{{0,DllCanUnloadNow},{1,DllGetClassObject}}。這個資料和View Dependencies中資訊一緻。

        導出序數表RVA(0x00002E68)對應的RA是0x00002268,其資料是{{0,0x0000},{1,0x0001}}。但是這并不是最終資料,剛才我在介紹導出序數表時,說過這個表儲存的是相對Base的偏移,該檔案的Base是1,于是真實的資料是{{0,0x0001},{1,0x0002}}。

       我們用圖來說一下這三者的關系。

PE檔案和COFF檔案格式分析——導出表

       比如我們試圖得到DllGetClassObject的函數位址。我們現在名稱表中找到它的index是1。然後在序數表中找到index是1的元素的值0x00000002,。0x00000002要減去Base的值1得到值1。最後在位址表中找到index為1的元素的值,這個值就是DllGetClassObject函數的入口位址。表達式是

i = Search_ExportNamePointerTable (ExportName);
ordinal = ExportOrdinalTable [i];
SymbolRVA = ExportAddressTable [ordinal - OrdinalBase];           

複制

        看了上面的邏輯, 我們在序數表中“加上”Base,然後要通過名稱去找函數入口時又要從序數表中“減去”Base,是不是這兒Base是多餘的?如果單從通過名稱擷取函數位址來看,Base的确是多餘的。那麼如果通過序數來擷取函數位址呢?我構造了一個DLL——DllTestIndex.dll(工程位址)

LIBRARY	"DllTestIndex"
EXPORTS
	Ret1 @2.
	Ret2 @4
	Ret3 @8
	Ret4 @6           

複制

PE檔案和COFF檔案格式分析——導出表

        發現通過序數去得到Ordinal為3的函數位址時會出錯。這兒有兩種可能:

        A GetProcAddress看到函數位址是0x00000000就認為擷取出錯

        B GetProcAddress是發現序數3不在序數表中(該檔案導出序數表為{2,4,6,8},于是傳回出錯。

        那麼到底是那種呢?我将這個檔案修改成如下,即将Ordinal為3的函數位址修改成一個有效的函數位址,得到一個檔案DllTestIndex_Modify

PE檔案和COFF檔案格式分析——導出表

        如果是B原因,則此時我們去擷取Ordinal為3的函數位址還是會失敗。可是結果呢?GetProcAddress成功了,并正确傳回了0x00011069這個函數入口位址。這個實驗證明A原因是對的。這進而證明Base這個字段,對通過函數名尋找函數入口位址的算法的确是多餘的資訊。如果真要找個原因,可能從檔案大小的說起。PE檔案序數表的元素是WORD為機關的,而Base是DWORD。那麼就是說,我們最多可以有0x10000(0x0000~0xFFFF)個導出函數。假如這些函數都在導出序數表中有對應元素,且導出序數表每個元素用DWORD描述,則需要sizeof(DWORD)*0x10000的空間。如果采用Base+WORD的方法,則隻需要sizeof(WORD)*0x10000+sizeof(DWORD)的空間。采用後者最多可以節省0x3FFF8(0x40000-8)byte空間。其實這個空間很小的,可以忽略不計的。

       除了之上那個非常強求的原因,Base就沒用了麼?不是!我們繼續看上面那個例子,從我們DEF檔案中看出,我們希望導出的4個函數的序号分别是2、4、8、6。我們看下PE檔案中的布局

PE檔案和COFF檔案格式分析——導出表

        我們看到資訊如下:

        Base是0x00000002;NumberOfFunctions是0x00000007;導出函數位址分别為0x00011069、0x00000000、0x000110F5、0x00000000、0x000110A5、0x00000000、0x000110C3;導出名稱是按我們在DEF申明的順序是一緻的,分别是:Ret1、Ret2、Ret3、Ret4。導出序數表是0x0000、0x0002、0x0006、0x0004。

PE檔案和COFF檔案格式分析——導出表

        注意View Dependencies的Ordinal列,該列的資訊是函數位址的Index加上Base的值。于是

PE檔案和COFF檔案格式分析——導出表

      當我們如此調用時

typedef int(WINAPI* PRetN)();

void ExprotFunc(LPSTR lpFileName) {
    HMODULE hDll = NULL;
    do {
        printf("%s\n", lpFileName);
        hDll = LoadLibraryA(lpFileName);
        if ( NULL == hDll ) {
            break;
        }
        for ( int nIndex = 0; nIndex <  10; nIndex ++ ) {
            PRetN pRetn = (PRetN)GetProcAddress( hDll, (LPCSTR)(LPVOID)(nIndex) );
            if ( NULL != pRetn ) {
                printf("nIndex is %d: Value is %d\n",nIndex,pRetn());
            }
        }
        FreeLibrary(hDll);
    } while (0);
}

int _tmain(int argc, _TCHAR* argv[])
{
    ExprotFunc("DllTestIndex.dll");
    ExprotFunc("DllTestIndex_Modify.dll");
    system("pause");
	return 0;
}           

複制

        此時GetProcAddress的第二個參數就是上圖中間一列的資訊,即View Dependencies的Ordinals資訊。這兒要特别注意這個Ordinals和導出序數表(AddressOfNameOrdinals指向的表)不是一個東西。這樣我就解答了上面我們Mark的那個問題——Base到底是為什麼設計的?是為了通過序數導出函數而設計的。

        之後我将會介紹幾個對導出表好玩的應用。

        最後貼一段導出表解析的代碼

BOOL CGetPEInfo::GetExportInfo()
{
    BOOL bSuc = ( 0 == m_ExpDir.Characteristics ) ? TRUE : FALSE;
    if ( FALSE == bSuc ) {
        _ASSERT(FALSE);
    }

	m_ExpFullInfo.ExpDir = m_ExpDir;

    std::string wszTime;
    if ( FALSE == GetTime( m_ExpDir.TimeDateStamp, wszTime ) ) {
        _ASSERT(FALSE);
    }

	m_ExpFullInfo.wszTime = wszTime;

    std::string wszDllName;
    DWORD dwNameRA = 0;
    if ( FALSE == GetRAByRVA( m_ExpDir.Name, dwNameRA ) ) {
        _ASSERT(FALSE);
    }
    else {
        LPBYTE lpFileName = m_lpFileStart + dwNameRA;
        if ( lpFileName < m_lpFileStart || lpFileName > m_lpFileEnd ) {
            wszDllName.clear();
        }
        else {
            wszDllName = (LPSTR)lpFileName;
        }
    }
	m_ExpFullInfo.szImgName = wszDllName;

	MapIMAGE_Export_Address_Table MapImageExpAddrTable;
	DWORD dwFunAddrTableRA = 0;
	if ( FALSE ==  GetRAByRVA( m_ExpDir.AddressOfFunctions, dwFunAddrTableRA ) ) {
        if ( 0 != m_ExpDir.AddressOfFunctions ) {
            //_ASSERT(FALSE);
        }
        else {
        }
	}
	else {
		LPBYTE lpStart = m_lpFileStart + dwFunAddrTableRA;
		for ( WORD i = 0; i < m_ExpDir.NumberOfFunctions; i++ ) {
			IMAGE_Export_Address_Table ImgExpAddrTable;
            if ( FALSE == SafeCopy( &ImgExpAddrTable, lpStart, sizeof(IMAGE_Export_Address_Table)) ) {
                break;
            }
			MapImageExpAddrTable[i] = ImgExpAddrTable;
			lpStart += sizeof(IMAGE_Export_Address_Table);
		}
	}

	MapIMAGE_Export_Name_Pointer_Table MapImageExpNamePointerTable;
	DWORD dwFunNameTableRA = 0;
	if ( FALSE == GetRAByRVA( m_ExpDir.AddressOfNames, dwFunNameTableRA ) ) {
        if ( 0 != m_ExpDir.AddressOfNames ) {
            _ASSERT(FALSE);
        }
        else {
        }
	}
	else {
		LPBYTE lpStart = m_lpFileStart + dwFunNameTableRA;
		for ( DWORD i = 0; i < m_ExpDir.NumberOfNames; i++ ) {
			IMAGE_Export_Name_Pointer_Table ImgExpNamePointer;
            if ( FALSE == SafeCopy( &ImgExpNamePointer, lpStart , sizeof(IMAGE_Export_Name_Pointer_Table) ) ) {
                break;
            }
			MapImageExpNamePointerTable[i] = ImgExpNamePointer;
			lpStart += sizeof(IMAGE_Export_Name_Pointer_Table);
		}
	}

	MapIMAGE_Export_Ordinal_Table MapImageExpOrdinalTable;
	DWORD dwOrdinalTableRA = 0;
	if ( FALSE == GetRAByRVA( m_ExpDir.AddressOfNameOrdinals, dwOrdinalTableRA ) ) {
        if (  0 != m_ExpDir.AddressOfNameOrdinals ) {
            _ASSERT(FALSE);
        }
        else {
            //C:\Config.Msi\1ecac1a.rbf
        }
	}
	else {
		LPBYTE lpStart = m_lpFileStart + dwOrdinalTableRA;
		for ( WORD i = 0; i < m_ExpDir.NumberOfNames; i++ ) {
			IMAGE_Export_Ordinal_Table ImgExpOrdinalTable;
            if ( FALSE == SafeCopy( &ImgExpOrdinalTable, lpStart, sizeof(IMAGE_Export_Ordinal_Table) ) ) {
                break;
            }
			MapImageExpOrdinalTable[i] = ImgExpOrdinalTable;
			lpStart += sizeof(IMAGE_Export_Ordinal_Table);
		}
	}

	EXPORT_TABLE_FULL_INFO ExpTableFullInfo;

    if ( 0 != m_ExpDir.Base && 1 != m_ExpDir.Base ) {
        _ASSERT(FALSE);
    }
    
	if ( m_ExpDir.NumberOfNames != m_ExpDir.NumberOfFunctions ) {
		// _ASSERT(FALSE);
	}

	//  應該按位址數量來算
	for ( MapIMAGE_Export_Address_TableIter ImgExpAdIter = MapImageExpAddrTable.begin();
		ImgExpAdIter != MapImageExpAddrTable.end();
		ImgExpAdIter++ )
	{
		std::string strName;
		strName.empty();

		ExpTableFullInfo.wHint = 0xFFFF;
		ExpTableFullInfo.wOrdinal = ImgExpAdIter->first + (WORD)m_ExpDir.Base;

        DWORD dwRVA = ImgExpAdIter->second.dwExportRVA;
        if ( FALSE == IsRVAinSection( dwRVA, IMAGE_DIRECTORY_ENTRY_EXPORT )
            && 0 != dwRVA ) {
                ExpTableFullInfo.bForwarderRVA = FALSE;
                ExpTableFullInfo.dwRVA.dwExportRVA = dwRVA;
        }
        else
        {
            ExpTableFullInfo.dwRVA.dwForwarderRVA = dwRVA;
            ExpTableFullInfo.bForwarderRVA = TRUE;

            DWORD dwForwarderRA;
            if ( FALSE == GetRAByRVA( ExpTableFullInfo.dwRVA.dwForwarderRVA, dwForwarderRA ) ) {
                if (  0 != ExpTableFullInfo.dwRVA.dwForwarderRVA ) {
                    _ASSERT(FALSE);
                }
                else {
                    // C:\4f1b3cac6fdc7b2cb9092b46e7c0fc71\Mobile Partner-dial\Mobile Partner-dial\mfc40u.dll
                    // _ASSERT(FALSE);
                }
                //continue;
            }
            else {
                ExpTableFullInfo.strForwarder = (LPSTR)(m_lpFileStart + dwForwarderRA);
            }
        }

		MapIMAGE_Export_Ordinal_TableIter ImgExpOrdIter = MapImageExpOrdinalTable.begin();
		for ( ;
			ImgExpOrdIter != MapImageExpOrdinalTable.end();
			ImgExpOrdIter++ )
		{
			if ( ImgExpAdIter->first != ImgExpOrdIter->second.dwOrdinal ) {
				continue;
			}

			ExpTableFullInfo.wHint = ImgExpOrdIter->first;

			for ( MapIMAGE_Export_Name_Pointer_TableIter ImgExpNamePointerIter = MapImageExpNamePointerTable.begin();
				ImgExpNamePointerIter != MapImageExpNamePointerTable.end();
				ImgExpNamePointerIter++ )
			{
				if ( ImgExpNamePointerIter->first != ExpTableFullInfo.wHint ) {
					continue;
				}
				DWORD dwNamePointerRA = 0;
				if ( FALSE == GetRAByRVA( ImgExpNamePointerIter->second.dwPointer, dwNamePointerRA ) ) {
                    continue;
				}
				else {
					strName =(LPSTR)(m_lpFileStart + dwNamePointerRA);
				}
				break;
			}
			break;
		}

        ExpTableFullInfo.strFuncName = strName;

		m_ExpFullInfo.vecExpTable.push_back( ExpTableFullInfo );
	}

    return bSuc;
}           

複制