• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            微塵--KeepMoving

            為了忘卻的記憶
            posts - 3, comments - 2, trackbacks - 0, articles - 13
              C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

            作者:MSDN
            譯者:李馬 (http://home.nuc.edu.cn/~titilima)

            預定義段

               一個Windows NT的應用程序典型地擁有9個預定義段,它們是.text、.bss、.rdata、.data、.rsrc、.edata、.idata、.pdata和.debug。一些應用程序不需要所有的這些段,同樣還有一些應用程序為了自己特殊的需要而定義了更多的段。這種做法與MS-DOS和Windows 3.1中的代碼段和數(shù)據(jù)段相似。事實上,應用程序定義一個獨特的段的方法是使用標準編譯器來指示對代碼段和數(shù)據(jù)段的命名,或者使用名稱段編譯器選項-NT——就和Windows 3.1中應用程序定義獨特的代碼段和數(shù)據(jù)段一樣。
               以下是一個關于Windows NT PE文件之中一些有趣的公共段的討論。

            可執(zhí)行代碼段,.text

               Windows 3.1和Windows NT之間的一個區(qū)別就是Windows NT默認的做法是將所有的代碼段(正如它們在Windows 3.1中所提到的那樣)組成了一個單獨的段,名為“.text”。既然Windows NT使用了基于頁面的虛擬內(nèi)存管理系統(tǒng),那么將分開的代碼放入不同的段之中的做法就不太明智了。因此,擁有一個大的代碼段對于操作系統(tǒng)和應用程序開發(fā)者來說,都是十分方便的。
               .text段也包含了早先提到過的入口點。IAT亦存在于.text段之中的模塊入口點之前。(IAT在.text段之中的存在非常有意義,因為這個表事實上是一系列的跳轉(zhuǎn)指令,并且它們的跳轉(zhuǎn)目標位置是已固定的地址。)當Windows NT的可執(zhí)行映像裝載入進程的地址空間時,IAT就和每一個導入函數(shù)的物理地址一同確定了。要在.text段之中查找IAT,裝載器只用將模塊的入口點定位,而IAT恰恰出現(xiàn)于入口點之前。既然每個入口擁有相同的尺寸,那么向后退查找這個表的起始位置就很容易了。

            數(shù)據(jù)段,.bss、.rdata、.data

               .bss段表示應用程序的未初始化數(shù)據(jù),包括所有函數(shù)或源模塊中聲明為static的變量。
               .rdata段表示只讀的數(shù)據(jù),比如字符串文字量、常量和調(diào)試目錄信息。
               所有其它變量(除了出現(xiàn)在棧上的自動變量)存儲在.data段之中。基本上,這些是應用程序或模塊的全局變量。

            資源段,.rsrc

               .rsrc段包含了模塊的資源信息。它起始于一個資源目錄結(jié)構(gòu),這個結(jié)構(gòu)就像其它大多數(shù)結(jié)構(gòu)一樣,但是它的數(shù)據(jù)被更進一步地組織在了一棵資源樹之中。以下的IMAGE_RESOURCE_DIRECTORY結(jié)構(gòu)形成了這棵樹的根和各個結(jié)點。

            //WINNT.H
            typedef struct _IMAGE_RESOURCE_DIRECTORY {
            ULONG Characteristics;
            ULONG TimeDateStamp;
            USHORT MajorVersion;
            USHORT MinorVersion;
            USHORT NumberOfNamedEntries;
            USHORT NumberOfIdEntries;
            } IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
              
            請看這個目錄結(jié)構(gòu),你將會發(fā)現(xiàn)其中竟然沒有指向下一個結(jié)點的指針。但是,在這個結(jié)構(gòu)中有兩個域NumberOfNamedEntries和NumberOfIdEntries代替了指針,它們被用來表示這個目錄附有多少入口。附帶說一句,我的意思是目錄入口就在段數(shù)據(jù)之中的目錄后邊。有名稱的入口按字母升序出現(xiàn),再往后是按數(shù)值升序排列的ID入口。
               一個目錄入口由兩個域組成,正如下面IMAGE_RESOURCE_DIRECTORY_ENTRY結(jié)構(gòu)所描述的那樣:
            // WINNT.H
            typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
            ULONG Name;
            ULONG OffsetToData;
            } IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
            
            根據(jù)樹的層級不同,這兩個域也就有著不同的用途。Name域被用于標識一個資源種類,或者一種資源名稱,或者一個資源的語言ID。OffsetToData與常常被用來在樹之中指向兄弟結(jié)點——即一個目錄結(jié)點或一個葉子結(jié)點。
               葉子結(jié)點是資源樹之中最底層的結(jié)點,它們定義了當前資源數(shù)據(jù)的尺寸和位置。IMAGE_RESOURCE_DATA_ENTRY結(jié)構(gòu)被用于描述每個葉子結(jié)點:
            // WINNT.H
            typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
            ULONG OffsetToData;
            ULONG Size;
            ULONG CodePage;
            ULONG Reserved;
            } IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
            
            OffsetToData和Size這兩個域表示了當前資源數(shù)據(jù)的位置和尺寸。既然這一信息主要是在應用程序裝載以后由函數(shù)使用的,那么將OffsetToData作為一個相對虛擬的地址會更有意義一些。——幸甚,恰好是這樣沒錯。非常有趣的是,所有其它的偏移量,比如從目錄入口到其它目錄的指針,都是相對于根結(jié)點位置的偏移量。
               要更清楚地了解這些內(nèi)容,請參考圖2。

            圖2.一個簡單的資源樹結(jié)構(gòu)
               圖2描述了一個非常簡單的資源樹,它包含了僅僅兩個資源對象:一個菜單和一個字串表。更深一層地來說,它們各自都有一個子項。然而,你仍然可以看到資源樹有多么復雜——即使它像這個一樣只有一點點資源。
               在樹的根部,第一個目錄有一個文件中包含的所有資源種類的入口,而不管資源種類有多少。在圖2中,有兩個由樹根標識的入口,一個是菜單的,另一個是字串表的。如果文件中擁有一個或多個對話框資源,那么根結(jié)點會再擁有一個入口,因此,就有了對話框資源的另一個分支。
               WINUSER.H中標識了基本的資源種類,我將它們列到了下面:
            //WINUSER.H
            /*
            * 預定義的資源種類
            */
            #define RT_CURSOR MAKEINTRESOURCE(1)
            #define RT_BITMAP MAKEINTRESOURCE(2)
            #define RT_ICON MAKEINTRESOURCE(3)
            #define RT_MENU MAKEINTRESOURCE(4)
            #define RT_DIALOG MAKEINTRESOURCE(5)
            #define RT_STRING MAKEINTRESOURCE(6)
            #define RT_FONTDIR MAKEINTRESOURCE(7)
            #define RT_FONT MAKEINTRESOURCE(8)
            #define RT_ACCELERATOR MAKEINTRESOURCE(9)
            #define RT_RCDATA MAKEINTRESOURCE(10)
            #define RT_MESSAGETABLE MAKEINTRESOURCE(11)
              
            在樹的第一層級,以上列出的MAKEINTRESOURCE值被放置在每個種類入口的Name處,它標識了不同的資源種類。
               每個根目錄的入口都指向了樹中第二層級的一個兄弟結(jié)點,這些結(jié)點也是目錄,并且每個都擁有它們自己的入口。在這一層級,目錄被用來以給定的種類標識每一個資源種類。如果你的應用程序中有多個菜單,那么樹中的第二層級會為每個菜單都準備一個入口。
               你可能意識到了,資源可以由名稱或整數(shù)標識。在這一層級,它們是通過目錄結(jié)構(gòu)的Name域來分辨的。如果如果Name域最重要的位被設置了,那么其它的31個位就會被用作一個到IMAGE_RESOURCE_DIR_STRING_U結(jié)構(gòu)的偏移量。
            // WINNT.H
            typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
            USHORT Length;
            WCHAR NameString[1];
            } IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
              
            這個結(jié)構(gòu)僅僅是由一個2字節(jié)長的Length域和一個UNICODE字符Length組成的。
               另一方面,如果Name域最重要的位被清空,那么它的低31位就被用于表示資源的整數(shù)ID。圖2示范的就是菜單資源作為一個命名的資源,以及字串表作為一個ID資源。
               如果有兩個菜單資源,一個由名稱標識,另一個由資源標識,那么它們二者就會在菜單資源目錄之后擁有兩個入口。有名稱的資源入口在第一位,之后是由整數(shù)標識的資源。目錄域NumberOfNamedEntries和NumberOfIdEntries將各自包含值1,表示當前的1個入口。
               在第二層級的下面,資源樹就不再更深一步地擴展分支了。第一層級分支至表示每個資源種類的目錄中,第二層級分支至由標識符表示的每個資源的目錄中,第三層級是被個別標識的資源與它們各自的語言ID之間一對一的映射。要表示一個資源的語言ID,目錄入口結(jié)構(gòu)的Name域就被用來表示資源的主語言ID和子語言ID了。Windows NT的Win32 SDK開發(fā)包中列出了默認的值資源,例如對于0x0409這個值來說,0x09表示主語言LANG_ENGLISH,0x04則被定義為子語言的SUBLANG_ENGLISH_CAN。所有的語言ID值都定義于Windows NT Win32 SDK開發(fā)包的文件WINNT.H中。
               既然語言ID結(jié)點是樹中最后的目錄結(jié)點,那么入口結(jié)構(gòu)的OffsetToData域就是到一個葉子結(jié)點(即前面提到過的IMAGE_RESOURCE_DATA_ENTRY結(jié)構(gòu))的偏移量。
               再回過頭來參考圖2,你會發(fā)現(xiàn)每個語言目錄入口都對應著一個數(shù)據(jù)入口。這個結(jié)點僅僅表示了資源數(shù)據(jù)的尺寸以及資源數(shù)據(jù)的相對虛擬地址。
               在資源數(shù)據(jù)段(.rsrc)之中擁有這么多結(jié)構(gòu)有一個好處,就是你可以不存取資源本身而直接可以從這個段收集很多信息。例如,你可以獲得有多少種資源、哪些資源(如果有的話)使用了特別的語言ID、特定的資源是否存在以及單獨種類資源的尺寸。為了示范如何利用這一信息,以下的函數(shù)說明了如何決定一個文件中包含的不同種類的資源:
            // PEFILE.C
            int WINAPI GetListOfResourceTypes(LPVOID lpFile, HANDLE hHeap, char **pszResTypes)
            {
            PIMAGE_RESOURCE_DIRECTORY prdRoot;
            PIMAGE_RESOURCE_DIRECTORY_ENTRY prde;
            char *pMem;
            int nCnt, i;
            /* 獲得資源樹的根目錄 */
            if ((prdRoot = (PIMAGE_RESOURCE_DIRECTORY)ImageDirectoryOffset
            (lpFile, IMAGE_DIRECTORY_ENTRY_RESOURCE)) == NULL)
            return 0;
            /* 在堆上分配足夠的空間來包括所有類型 */
            nCnt = prdRoot->NumberOfIdEntries * (MAXRESOURCENAME + 1);
            *pszResTypes = (char *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY,
            nCnt);
            if ((pMem = *pszResTypes) == NULL)
            return 0;
            /* 將指針指向第一個資源種類的入口 */
            prde = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)((DWORD)prdRoot +
            sizeof (IMAGE_RESOURCE_DIRECTORY));
            /* 在所有的資源目錄入口類型中循環(huán) */
            for (i = 0; i < prdRoot->NumberOfIdEntries; i++)
            {
            if (LoadString(hDll, prde->Name, pMem, MAXRESOURCENAME))
            pMem += strlen(pMem) + 1;
            prde++;
            }
            return nCnt;
            }
              
            這個函數(shù)將一個資源種類名稱的列表寫入了由pszResTypes標識的變量中。請注意,在這個函數(shù)的核心部分,LoadString是使用各自資源種類目錄入口的Name域來作為字符串ID的。如果你查看PEFILE.RC,你會發(fā)現(xiàn)我定義了一系列的資源種類的字符串,并且它們的ID與它們在目錄入口中的定義完全相同。PEFILE.DLL還有有一個函數(shù),它返回了.rsrc段中的資源對象總數(shù)。這樣一來,從這個段中提取其它的信息,借助這些函數(shù)或另外編寫函數(shù)就方便多了。

            導出數(shù)據(jù)段,.edata

               .edata段包含了應用程序或DLL的導出數(shù)據(jù)。在這個段出現(xiàn)的時候,它會包含一個到達導出信息的導出目錄。
            // WINNT.H
            typedef struct _IMAGE_EXPORT_DIRECTORY {
            ULONG Characteristics;
            ULONG TimeDateStamp;
            USHORT MajorVersion;
            USHORT MinorVersion;
            ULONG Name;
            ULONG Base;
            ULONG NumberOfFunctions;
            ULONG NumberOfNames;
            PULONG *AddressOfFunctions;
            PULONG *AddressOfNames;
            PUSHORT *AddressOfNameOrdinals;
            } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
              
            導出目錄中的Name域標識了可執(zhí)行模塊的名稱。NumberOfFunctions域和NumberOfNames域表示模塊中有多少導出的函數(shù)以及這些函數(shù)的名稱。
               AddressOfFunctions域是一個到導出函數(shù)入口列表的偏移量。AddressOfNames域是到一個導出函數(shù)名稱列表起始處偏移量的地址,這個列表是由null分隔的。AddressOfNameOrdinals是一個到相同導出函數(shù)順序值(每個值2字節(jié)長)列表的偏移量。
               三個AddressOf...域是當模塊裝載時進程地址空間中的相對虛擬地址。一旦模塊被裝載,那么要獲得進程地質(zhì)空間中的確切地址的話,就應該在相對虛擬地址上加上模塊的基地址。可是,在文件被裝載前,仍然可以決定這一地址:只要從給定的域地址中減去段頭部的虛擬地址(VirtualAddress),再加上段實體的偏移量(PointerToRawData),這個結(jié)果就是映像文件中的偏移量了。以下的例子解說了這一技術(shù):
            // PEFILE.C
            int WINAPI GetExportFunctionNames(LPVOID lpFile, HANDLE hHeap, char **pszFunctions)
            {
            IMAGE_SECTION_HEADER sh;
            PIMAGE_EXPORT_DIRECTORY ped;
            char *pNames, *pCnt;
            int i, nCnt;
            /* 獲得.edata域中的段頭部和指向數(shù)據(jù)目錄的指針 */
            if ((ped = (PIMAGE_EXPORT_DIRECTORY)ImageDirectoryOffset
            (lpFile, IMAGE_DIRECTORY_ENTRY_EXPORT)) == NULL)
            return 0;
            GetSectionHdrByName (lpFile, &sh, ".edata");
            /* 決定導出函數(shù)名稱的偏移量 */
            pNames = (char *)(*(int *)((int)ped->AddressOfNames -
            (int)sh.VirtualAddress + (int)sh.PointerToRawData +
            (int)lpFile) - (int)sh.VirtualAddress +
            (int)sh.PointerToRawData + (int)lpFile);
            /* 計算出要為所有的字符串分配多少內(nèi)存 */
            pCnt = pNames;
            for (i = 0; i < (int)ped->NumberOfNames; i++)
            while (*pCnt++);
            nCnt = (int)(pCnt.pNames);
            /* 在堆上為函數(shù)名稱分配內(nèi)存 */
            *pszFunctions = HeapAlloc (hHeap, HEAP_ZERO_MEMORY, nCnt);
            /* 將所有字符串復制到緩沖區(qū) */
            CopyMemory((LPVOID)*pszFunctions, (LPVOID)pNames, nCnt);
            return nCnt;
            }
            請注意,在這個函數(shù)之中,變量pNames是由決定偏移量地址和當前偏移量位置的方法來賦值的。偏移量的地址和偏移量本身都是相對虛擬地址,因此在使用之前必須進行轉(zhuǎn)換——函數(shù)之中體現(xiàn)了這一點。雖然你可以編寫一個類似的函數(shù)來決定順序值或函數(shù)入口點,但是我為什么不為你做好呢?——GetNumberOfExportedFunctions、GetExportFunctionEntryPoints和GetExportFunctionOrdinals已經(jīng)存在于PEFILE.DLL之中了。

            導入數(shù)據(jù)段,.idata

               .idata段是導入數(shù)據(jù),包括導入庫和導入地址名稱表。雖然定義了IMAGE_DIRECTORY_ENTRY_IMPORT,但是WINNT.H之中并無相應的導入目錄結(jié)構(gòu)。作為代替,其中有若干其它的結(jié)構(gòu),名為IMAGE_IMPORT_BY_NAME、IMAGE_THUNK_DATA與IMAGE_IMPORT_DESCRIPTOR。在我個人看來,我實在不知道這些結(jié)構(gòu)是如何和.idata段發(fā)生關聯(lián)的,所以我花了若干個小時來破譯.idata段實體并且得到了一個更簡單的結(jié)構(gòu),我名之為IMAGE_IMPORT_MODULE_DIRECTORY。
            // PEFILE.H
            typedef struct tagImportDirectory
            {
            DWORD dwRVAFunctionNameList;
            DWORD dwUseless1;
            DWORD dwUseless2;
            DWORD dwRVAModuleName;
            DWORD dwRVAFunctionAddressList;
            } IMAGE_IMPORT_MODULE_DIRECTORY, *PIMAGE_IMPORT_MODULE_DIRECTORY;
            
            和其它段的數(shù)據(jù)目錄不同的是,這個是作為文件中的每個導入模塊重復出現(xiàn)的。你可以將它看作模塊數(shù)據(jù)目錄列表中的一個入口,而不是一個整個數(shù)據(jù)段的數(shù)據(jù)目錄。每個入口都是一個指向特定模塊導入信息的目錄。
               IMAGE_IMPORT_MODULE_DIRECTORY結(jié)構(gòu)中的一個域dwRVAModuleName是一個相對虛擬地址,它指向模塊的名稱。結(jié)構(gòu)中還有兩個dwUseless參數(shù),它們是為了保持段的對齊。PE文件格式規(guī)范提到了一些東西,關于導入標記、時間/日期標志以及主/次版本,但是在我的實驗中,這兩個域自始而終都是空的,所以我仍然認為它們沒有什么用處。
               基于這個結(jié)構(gòu)的定義,你便可以獲得可執(zhí)行文件中導入的所有模塊和函數(shù)名稱了。以下的函數(shù)示范了如何獲得特定的PE文件中的所有導入函數(shù)名稱:
            //PEFILE.C
            int WINAPI GetImportModuleNames(LPVOID lpFile, HANDLE hHeap, char **pszModules)
            {
            PIMAGE_IMPORT_MODULE_DIRECTORY pid;
            IMAGE_SECTION_HEADER idsh;
            BYTE *pData;
            int nCnt = 0, nSize = 0, i;
            char *pModule[1024];
            char *psz;
            pid = (PIMAGE_IMPORT_MODULE_DIRECTORY)ImageDirectoryOffset
            (lpFile, IMAGE_DIRECTORY_ENTRY_IMPORT);
            pData = (BYTE *)pid;
            /* 定位.idata段頭部 */
            if (!GetSectionHdrByName(lpFile, &idsh, ".idata"))
            return 0;
            /* 提取所有導入模塊 */
            while (pid->dwRVAModuleName)
            {
            /* 為絕對字符串偏移量分配緩沖區(qū) */
            pModule[nCnt] = (char *)(pData +
            (pid->dwRVAModuleName-idsh.VirtualAddress));
            nSize += strlen(pModule[nCnt]) + 1;
            /* 增至下一個導入目錄入口 */
            pid++;
            nCnt++;
            }
            /* 將所有字符串賦值到一大塊的堆內(nèi)存中 */
            *pszModules = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, nSize);
            psz = *pszModules;
            for (i = 0; i < nCnt; i++)
            {
            strcpy(psz, pModule[i]);
            psz += strlen (psz) + 1;
            }
            return nCnt;
            }
            
            這個函數(shù)非常好懂,然而有一點值得指出——注意while循環(huán)。這個循環(huán)當pid->dwRVAModuleName為0的時候終止,這就暗示了在IMAGE_IMPORT_MODULE_DIRECTORY結(jié)構(gòu)列表的末尾有一個空的結(jié)構(gòu),這個結(jié)構(gòu)擁有一個0值,至少dwRVAModuleName域為0。這便是我在對文件的實驗中以及之后在PE文件格式中研究的行為。
               這個結(jié)構(gòu)中的第一個域dwRVAFunctionNameList是一個相對虛擬地址,這個地址指向一個相對虛擬地址的列表,這些地址是文件中的一些文件名。如下面的數(shù)據(jù)所示,所有導入模塊的模塊和函數(shù)名稱都列于.idata段數(shù)據(jù)中了:
            E6A7 0000 F6A7 0000 08A8 0000 1AA8 0000 ................
            28A8 0000 3CA8 0000 4CA8 0000 0000 0000 (...<...L.......
            0000 4765 744F 7065 6E46 696C 654E 616D ..GetOpenFileNam
            6541 0000 636F 6D64 6C67 3332 2E64 6C6C eA..comdlg32.dll
            0000 2500 4372 6561 7465 466F 6E74 496E ..%.CreateFontIn
            6469 7265 6374 4100 4744 4933 322E 646C directA.GDI32.dl
            6C00 A000 4765 7444 6576 6963 6543 6170 l...GetDeviceCap
            7300 C600 4765 7453 746F 636B 4F62 6A65 s...GetStockObje
            6374 0000 D500 4765 7454 6578 744D 6574 ct....GetTextMet
            7269 6373 4100 1001 5365 6C65 6374 4F62 ricsA...SelectOb
            6A65 6374 0000 1601 5365 7442 6B43 6F6C ject....SetBkCol
            6F72 0000 3501 5365 7454 6578 7443 6F6C or..5.SetTextCol
            6F72 0000 4501 5465 7874 4F75 7441 0000 or..E.TextOutA..
            
            以上的數(shù)據(jù)是EXEVIEW.EXE示例程序.idata段的一部分。這個特別的段表示了導入模塊列表和函數(shù)名稱列表的起始處。如果你開始檢查數(shù)據(jù)中的這個段,你應該認出一些熟悉的Win32 API函數(shù)以及模塊名稱。從上往下讀的話,你可以找到GetOpenFileNameA,緊接著是COMDLG32.DLL。然后你能發(fā)現(xiàn)CreateFontIndirectA,緊接著是模塊GDI32.DLL,以及之后的GetDeviceCaps、GetStockObject、GetTextMetrics等等。
               這樣的式樣會在.idata段中重復出現(xiàn)。第一個模塊是COMDLG32.DLL,第二個是GDI32.DLL。請注意第一個模塊只導出了一個函數(shù),而第二個模塊導出了很多函數(shù)。在這兩種情況下,函數(shù)和模塊的排列的方法是首先出現(xiàn)一個函數(shù)名,之后是模塊名,然后是其它的函數(shù)名(如果有的話)。
               以下的函數(shù)示范了如何獲得指定模塊的所有函數(shù)名。
            // PEFILE.C
            int WINAPI GetImportFunctionNamesByModule(LPVOID lpFile, HANDLE hHeap,
            char *pszModule, char **pszFunctions)
            {
            PIMAGE_IMPORT_MODULE_DIRECTORY pid;
            IMAGE_SECTION_HEADER idsh;
            DWORD dwBase;
            int nCnt = 0, nSize = 0;
            DWORD dwFunction;
            char *psz;
            /* 定位.idata段的頭部 */
            if (!GetSectionHdrByName(lpFile, &idsh, ".idata"))
            return 0;
            pid = (PIMAGE_IMPORT_MODULE_DIRECTORY)ImageDirectoryOffset
            (lpFile, IMAGE_DIRECTORY_ENTRY_IMPORT);
            dwBase = ((DWORD)pid. idsh.VirtualAddress);
            /* 查找模塊的pid */
            while (pid->dwRVAModuleName && strcmp (pszModule,
            (char *)(pid->dwRVAModuleName+dwBase)))
            pid++;
            /* 如果模塊未找到,就退出 */
            if (!pid->dwRVAModuleName)
            return 0;
            /* 函數(shù)的總數(shù)和字符串長度 */
            dwFunction = pid->dwRVAFunctionNameList;
            while (dwFunction && *(DWORD *)(dwFunction + dwBase) &&
            *(char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2))
            {
            nSize += strlen ((char *)((*(DWORD *)(dwFunction +
            dwBase)) + dwBase+2)) + 1;
            dwFunction += 4;
            nCnt++;
            }
            /* 在堆上分配函數(shù)名稱的空間 */
            *pszFunctions = HeapAlloc (hHeap, HEAP_ZERO_MEMORY, nSize);
            psz = *pszFunctions;
            /* 向內(nèi)存指針復制函數(shù)名稱 */
            dwFunction = pid->dwRVAFunctionNameList;
            while (dwFunction && *(DWORD *)(dwFunction + dwBase) &&
            *((char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2)))
            {
            strcpy (psz, (char *)((*(DWORD *)(dwFunction + dwBase)) +
            dwBase+2));
            psz += strlen((char *)((*(DWORD *)(dwFunction + dwBase))+
            dwBase+2)) + 1;
            dwFunction += 4;
            }
            return nCnt;
            }
              
            就像GetImportModuleNames函數(shù)一樣,這一函數(shù)依靠每個信息列表的末端來獲得一個置零的入口。這在種情況下,函數(shù)名稱列表就是以零結(jié)尾的。
               最后一個域dwRVAFunctionAddressList是一個相對虛擬地址,它指向一個虛擬地址表。在文件裝載的時候,這個虛擬地址表會被裝載器置于段數(shù)據(jù)之中。但是在文件裝載前,這些虛擬地址會被一些嚴密符合函數(shù)名稱列表的虛擬地址替換。所以在文件裝載之前,有兩個同樣的虛擬地址列表,它們指向?qū)牒瘮?shù)列表。

            調(diào)試信息段,.debug

               調(diào)試信息位于.debug段之中,同時PE文件格式也支持單獨的調(diào)試文件(通常由.DBG擴展名標識)作為一種將調(diào)試信息集中的方法。調(diào)試段包含了調(diào)試信息,但是調(diào)試目錄卻位于早先提到的.rdata段之中。這其中每個目錄都涉及了.debug段之中的調(diào)試信息。調(diào)試目錄的結(jié)構(gòu)IMAGE_DEBUG_DIRECTORY被定義為:
            // WINNT.H
            typedef struct _IMAGE_DEBUG_DIRECTORY {
            ULONG Characteristics;
            ULONG TimeDateStamp;
            USHORT MajorVersion;
            USHORT MinorVersion;
            ULONG Type;
            ULONG SizeOfData;
            ULONG AddressOfRawData;
            ULONG PointerToRawData;
            } IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
            
            這個段被分為單獨的部分,每個部分為不同種類的調(diào)試信息數(shù)據(jù)。對于每個部分來說都是一個像上邊一樣的調(diào)試目錄。不同的調(diào)試信息種類如下:
            // WINNT.H
            #define IMAGE_DEBUG_TYPE_UNKNOWN 0
            #define IMAGE_DEBUG_TYPE_COFF 1
            #define IMAGE_DEBUG_TYPE_CODEVIEW 2
            #define IMAGE_DEBUG_TYPE_FPO 3
            #define IMAGE_DEBUG_TYPE_MISC 4
              
            每個目錄之中的Type域表示該目錄的調(diào)試信息種類。如你所見,在上邊的表中,PE文件格式支持很多不同的調(diào)試信息種類,以及一些其它的信息域。對于那些來說,IMAGE_DEBUG_TYPE_MISC信息是唯一的。這一信息被添加到描述可執(zhí)行映像的混雜信息之中,這些混雜信息不能被添加到PE文件格式任何結(jié)構(gòu)化的數(shù)據(jù)段之中。這就是映像文件中最合適的位置,映像名稱則肯定會出現(xiàn)在這里。如果映像導出了信息,那么導出數(shù)據(jù)段也會包含這一映像名稱。
               每種調(diào)試信息都擁有自己的頭部結(jié)構(gòu),該結(jié)構(gòu)定義了它自己的數(shù)據(jù)。這些結(jié)構(gòu)都列于WINNT.H之中。關于IMAGE_DEBUG_DIRECTORY一件有趣的事就是它包括了兩個標識調(diào)試信息的域。第一個是AddressOfRawData,為相對文件裝載的數(shù)據(jù)虛擬地址;另一個是PointerToRawData,為數(shù)據(jù)所在PE文件之中的實際偏移量。這就使得定位指定的調(diào)試信息相當容易了。
               作為最后的例子,請你考慮以下的函數(shù)代碼,它從IMAGE_DEBUG_MISC結(jié)構(gòu)中提取了映像名稱。
            //PEFILE.C
            int WINAPI RetrieveModuleName(LPVOID lpFile, HANDLE hHeap, char **pszModule)
            {
            PIMAGE_DEBUG_DIRECTORY pdd;
            PIMAGE_DEBUG_MISC pdm = NULL;
            int nCnt;
            if (!(pdd = (PIMAGE_DEBUG_DIRECTORY)ImageDirectoryOffset(lpFile,
            IMAGE_DIRECTORY_ENTRY_DEBUG)))
            return 0;
            while (pdd->SizeOfData)
            {
            if (pdd->Type == IMAGE_DEBUG_TYPE_MISC)
            {
            pdm = (PIMAGE_DEBUG_MISC)((DWORD)pdd->PointerToRawData + (DWORD)lpFile);
            nCnt = lstrlen(pdm->Data) * (pdm->Unicode ? 2 : 1);
            *pszModule = (char *)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, nCnt+1);
            CopyMemory(*pszModule, pdm->Data, nCnt);
            break;
            }
            pdd ++;
            }
            if (pdm != NULL)
            return nCnt;
            else
            return 0;
            }
            
            你看到了,調(diào)試目錄結(jié)構(gòu)使得定位一個特定種類的調(diào)試信息變得相對容易了些。只要定位了IMAGE_DEBUG_MISC結(jié)構(gòu),提取映像名稱就如同調(diào)用CopyMemory函數(shù)一樣簡單。
               如上所述,調(diào)試信息可以被剝離到單獨的.DBG文件中。Windows NT SDK包含了一個名為REBASE.EXE的程序可以實現(xiàn)這一目的。例如,以下的語句可以將一個名為TEST.EXE的調(diào)試信息剝離:
               rebase -b 40000 -x c:\samples\testdir test.exe
               調(diào)試信息被置于一個新的文件中,這個文件名為TEST.DBG,位于c:\samples\testdir之中。這個文件起始于一個單獨的IMAGE_SEPARATE_DEBUG_HEADER結(jié)構(gòu),接著是存在于原可執(zhí)行映像之中的段頭部的一份拷貝。在段頭部之后,是.debug段的數(shù)據(jù)。也就是說,在段頭部之后,就是一系列的IMAGE_DEBUG_DIRECTORY結(jié)構(gòu)及其相關的數(shù)據(jù)了。調(diào)試信息本身保留了如上所描述的常規(guī)映像文件調(diào)試信息。

            PE文件格式總結(jié)

               Windows NT的PE文件格式向熟悉Windows和MS-DOS環(huán)境的開發(fā)者引入了一種全新的結(jié)構(gòu)。然而熟悉UNIX環(huán)境的開發(fā)者會發(fā)現(xiàn)PE文件格式與COFF規(guī)范很相像(如果它不是以COFF為基礎的話)。
               整個格式的組成:一個MS-DOS的MZ頭部,之后是一個實模式的殘余程序、PE文件標志、PE文件頭部、PE可選頭部、所有的段頭部,最后是所有的段實體。
               可選頭部的末尾是一個數(shù)據(jù)目錄入口的數(shù)組,這些相對虛擬地址指向段實體之中的數(shù)據(jù)目錄。每個數(shù)據(jù)目錄都表示了一個特定的段實體數(shù)據(jù)是如何組織的。
               PE文件格式有11個預定義段,這是對Windows NT應用程序所通用的,但是每個應用程序可以為它自己的代碼以及數(shù)據(jù)定義它自己獨特的段。
               .debug預定義段也可以分離為一個單獨的調(diào)試文件。如果這樣的話,就會有一個特定的調(diào)試頭部來用于解析這個調(diào)試文件,PE文件中也會有一個標志來表示調(diào)試數(shù)據(jù)被分離了出去。

            PEFILE.DLL函數(shù)描述

               PEFILE.DLL主要由一些函數(shù)組成,這些函數(shù)或者被用來獲得一個給定的PE文件中的偏移量,或者被用來把文件中的一些數(shù)據(jù)復制到一個特定的結(jié)構(gòu)中去。每個函數(shù)都有一個需求——第一個參數(shù)是一個指針,這個指針指向PE文件的起始處。也就是說,這個文件必須首先被映射到你進程的地址空間中,然后映射文件的位置就可以作為每個函數(shù)第一個參數(shù)的lpFile的值來傳入了。
               我意在使函數(shù)的名稱使你能夠一見而知其意,并且每個函數(shù)都隨一個詳細描述其目的的注釋而列出。如果在讀完函數(shù)列表之后,你仍然不明白某個函數(shù)的功能,那么請參考EXEVIEW.EXE示例來查明這個函數(shù)是如何使用的。以下的函數(shù)原型列表可以在PEFILE.H中找到:
            // PEFILE.H
            /* 獲得指向MS-DOS MZ頭部的指針 */
            BOOL WINAPI GetDosHeader(LPVOID, PIMAGE_DOS_HEADER);
            /* 決定.EXE文件的類型 */
            DWORD WINAPI ImageFileType(LPVOID);
            /* 獲得指向PE文件頭部的指針 */
            BOOL WINAPI GetPEFileHeader(LPVOID, PIMAGE_FILE_HEADER);
            /* 獲得指向PE可選頭部的指針 */
            BOOL WINAPI GetPEOptionalHeader(LPVOID, PIMAGE_OPTIONAL_HEADER);
            /* 返回模塊入口點的地址 */
            LPVOID WINAPI GetModuleEntryPoint(LPVOID);
            /* 返回文件中段的總數(shù) */
            int WINAPI NumOfSections(LPVOID);
            /* 返回當可執(zhí)行文件被裝載入進程地址空間時的首選基地址 */
            LPVOID WINAPI GetImageBase(LPVOID);
            /* 決定文件中一個特定的映像數(shù)據(jù)目錄的位置 */
            LPVOID WINAPI ImageDirectoryOffset(LPVOID, DWORD);
            /* 獲得文件中所有段的名稱 */
            int WINAPI GetSectionNames(LPVOID, HANDLE, char **);
            /* 復制一個特定段的頭部信息 */
            BOOL WINAPI GetSectionHdrByName(LPVOID, PIMAGE_SECTION_HEADER, char *);
            /* 獲得由空字符分隔的導入模塊名稱列表 */
            int WINAPI GetImportModuleNames(LPVOID, HANDLE, char **);
            /* 獲得一個模塊由空字符分隔的導入函數(shù)列表 */
            int WINAPI GetImportFunctionNamesByModule(LPVOID, HANDLE, char *, char **);
            /* 獲得由空字符分隔的導出函數(shù)列表 */
            int WINAPI GetExportFunctionNames(LPVOID, HANDLE, char **);
            /* 獲得導出函數(shù)總數(shù) */
            int WINAPI GetNumberOfExportedFunctions(LPVOID);
            /* 獲得導出函數(shù)的虛擬地址入口點列表 */
            LPVOID WINAPI GetExportFunctionEntryPoints(LPVOID);
            /* 獲得導出函數(shù)順序值列表 */
            LPVOID WINAPI GetExportFunctionOrdinals(LPVOID);
            /* 決定資源對象的種類 */
            int WINAPI GetNumberOfResources (LPVOID);
            /* 返回文件中所使用的所有資源對象的種類 */
            int WINAPI GetListOfResourceTypes(LPVOID, HANDLE, char **);
            /* 決定調(diào)試信息是否已從文件中分離 */
            BOOL WINAPI IsDebugInfoStripped(LPVOID);
            /* 獲得映像文件名稱 */
            int WINAPI RetrieveModuleName(LPVOID, HANDLE, char **);
            /* 決定文件是否是一個有效的調(diào)試文件 */
            BOOL WINAPI IsDebugFile(LPVOID);
            /* 從調(diào)試文件中返回調(diào)試頭部 */
            BOOL WINAPI GetSeparateDebugHeader(LPVOID, PIMAGE_SEPARATE_DEBUG_HEADER);
              除了以上所列的函數(shù)之外,本文中早先提到的宏也定義在了PEFILE.H中,完整的列表如下:
            /* PE文件標志的偏移量 */
            #define NTSIGNATURE(a) ((LPVOID)((BYTE *)a + \
            ((PIMAGE_DOS_HEADER)a)->e_lfanew))
            /* MS操作系統(tǒng)頭部標識了雙字的NT PE文件標志;PE文件頭部就緊跟在這個雙字之后 */
            #define PEFHDROFFSET(a) ((LPVOID)((BYTE *)a + \
            ((PIMAGE_DOS_HEADER)a)->e_lfanew + \
            SIZE_OF_NT_SIGNATURE))
            /* PE可選頭部緊跟在PE文件頭部之后 */
            #define OPTHDROFFSET(a) ((LPVOID)((BYTE *)a + \
            ((PIMAGE_DOS_HEADER)a)->e_lfanew + \
            SIZE_OF_NT_SIGNATURE + \
            sizeof(IMAGE_FILE_HEADER)))
            /* 段頭部緊跟在PE可選頭部之后 */
            #define SECHDROFFSET(a) ((LPVOID)((BYTE *)a + \
            ((PIMAGE_DOS_HEADER)a)->e_lfanew + \
            SIZE_OF_NT_SIGNATURE + \
            sizeof(IMAGE_FILE_HEADER) + \
            sizeof(IMAGE_OPTIONAL_HEADER)))
              
            要使用PEFILE.DLL,你只用包含PEFILE.H文件并在應用程序中鏈接到這個DLL即可。所有的這些函數(shù)都是互斥性的函數(shù),但是有些函數(shù)的功能可以相互支持以獲得文件信息。例如,GetSectionNames可以用于獲得所有段的名稱,這樣一來,為了獲得一個擁有獨特段名稱(在編譯期由應用程序開發(fā)者定義的)的段頭部,你就需要首先獲得所有名稱的列表,然后再對那個準確的段名稱調(diào)用函數(shù)GetSectionHeaderByName了。現(xiàn)在,你可以享受我為你帶來的這一切了!
            久久精品99久久香蕉国产色戒 | 久久久久亚洲国产| 狠狠色丁香久久婷婷综合蜜芽五月| 久久99国产精品久久99小说| 久久精品国产亚洲AV不卡| 久久久精品人妻一区二区三区四 | 久久男人AV资源网站| AV无码久久久久不卡蜜桃| 久久精品国产半推半就| 日韩一区二区三区视频久久| 久久免费的精品国产V∧| 久久久久九九精品影院| 丰满少妇人妻久久久久久| 久久综合久久鬼色| 久久精品国产精品亚洲毛片| 人妻无码αv中文字幕久久琪琪布| 91久久精一区二区三区大全| 久久婷婷国产剧情内射白浆 | 欧美色综合久久久久久| 99久久精品影院老鸭窝| 精品久久久久久无码不卡| 国产精品美女久久久网AV| 无码久久精品国产亚洲Av影片| 久久久久久毛片免费看| 日本免费久久久久久久网站 | 亚洲午夜久久久精品影院| 18岁日韩内射颜射午夜久久成人| 国产AⅤ精品一区二区三区久久| 99久久精品国产麻豆| 中文无码久久精品| 久久久久久精品成人免费图片| 久久国产综合精品五月天| 久久精品国产99国产精品| 99精品久久精品| 久久福利青草精品资源站免费| 久久男人Av资源网站无码软件| 日本欧美久久久久免费播放网| 欧美亚洲色综久久精品国产| 狠狠色丁香久久婷婷综合| 亚洲伊人久久大香线蕉综合图片| 狠狠色噜噜色狠狠狠综合久久|