1. 程式人生 > >PE檔案學習筆記(三):匯出表(Export Table)解析

PE檔案學習筆記(三):匯出表(Export Table)解析

資料目錄(Data Directory)有16個_IMAGE_DATA_DIRECTORY結構體元素,該結構體陣列是可選PE頭中最後一個成員。這十六個元素分別儲存了不同資訊,分別是:匯入表匯出表、資源、異常資訊、安全證書、重定位表、除錯資訊、版權所有、全域性指標、TLS、載入配置、繫結匯入IAT、延遲匯入、COM資訊、最後一個保留未使用。和程式執行時息息相關的表有:匯出表、匯入表、重定位表、IAT表的燈,這幾種也是PE解析中重點研究的幾張表。

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

該結構體標記了各個表(元素)在記憶體中的VirtualAddress與Size。VirtualAddress是記憶體中的偏移地址,我們要直接在檔案中根據VirtualAddress找到對應的表,就需要進行判斷。判斷VirtualAddress在哪個節,並且計算在節中的偏移量,即RVA->FOA的轉換。

1、匯出表基本結構:

根據_IMAGE_DATA_DIRECTORY結構體陣列的第1個元素索引處匯出表。一般情況下,dll的函式匯出供其他人使用,exe將別人的dll的函式匯入執行。
所以,一般.exe沒有匯出表(但是並非說.exe一定沒有匯出表)。

匯出表結構:

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;    //未使用
    DWORD   TimeDateStamp;      //時間戳
    WORD    MajorVersion;       //未使用
    WORD    MinorVersion;       //未使用
    DWORD   Name;               //指向改匯出表文件名字串
    DWORD   Base;               //匯出表的起始序號
    DWORD   NumberOfFunctions;  //匯出函式的個數(更準確來說是AddressOfFunctions的元素數,而不是函式個數)
DWORD NumberOfNames; //以函式名字匯出的函式個數 DWORD AddressOfFunctions; //匯出函式地址表RVA:儲存所有匯出函式地址(表元素寬度為4,總大小NumberOfFunctions * 4) DWORD AddressOfNames; //匯出函式名稱表RVA:儲存函式名字串所在的地址(表元素寬度為4,總大小為NumberOfNames * 4) DWORD AddressOfNameOrdinals; //匯出函式序號表RVA:儲存函式序號(表元素寬度為2,總大小為NumberOfNames * 2) } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

注意:
裡面的地址均是RVA,而如果我們不想轉換成ImageBuffer就一定要進行RVA->FOA轉換,根據FOA直接在FileBuffer中尋找匯出表。每個dll都有一個匯出表。而每個匯出表有三個子匯出表(地址AddressOfFunctions、名字AddressOfNames、序號AddressOfOrdinals)。NumberOfFunctions是函式序號最大值與最小值之間的差值,NumberOfNames是函式以名字匯出的個數,二者可以不一樣大。一個函式必定有地址,但不一定有名字(如果是以無名字的方式匯出,eg:func @12 NONAME)。
我們自定義一個dll庫,在匯出時採用.def檔案的方式匯出:

//dll.def
EXPORTS
Plus    @1          
Sub     @3 NONAME   
div     @5 NONAME   
mul     @6  

則即解析得到的資訊如下(“——–”代表沒有名字,Ordina顯示的值與記憶體裡的值差一個Base,因為在用程式碼進行解析時已經把Base算過了,而記憶體中沒有動):

Offset to Export Table:[0002DF10]
    Characteristics:       [00000000]
    TimeDateStamp:         [59945264]
    MajorVersion:          [0000]
    MinorVersion:          [0000]
    NameAddr:              [0002DF5C]
    NameString:            [dll.dll]
    Base:                  [00000001]
    NumberOfFunctions:     [00000006]
    NumberOfNames:         [00000002]
    AddressOfFunctions:    [0002DF38]
    AddressOfNames:        [0002DF50]
    AddressOfNameOrdinals: [0002DF58]
    Ordina  func_FOA    name_FOA    FunctionName
    0001    00001005    0002DF68    plus
    0003    00001019    --------    --------
    0005    00015060    --------    --------
    0006    00001014    0002DF64    mul

匯出的函式有兩個函式我們宣告為noname,故其在匯出表中不存在名字。則其匯出表的NumberOfNames = 2,NumberOfFunctions = (6-1+1) = 6。即地址表長度為6寬度為4,Size為24;名字表長度為2,寬度為4,Size為8;序號表長度為2,寬度為2,Size為4。對應的記憶體圖如下(名字表的地址是按照從小到大排的,地址表有與我們.def中指定了序號,因此是亂序的,如果不指定,編譯器自動分配的一般也是有序的):
這裡寫圖片描述
雖然匯出時div、sub沒有序號與名字,但是二者是有地址的。並且由於序號計算地址表時,地址表中有些值沒有對映,則填充為0。而檔案中如下所示(匯出表在檔案中開始地址0002DF10):
這裡寫圖片描述

①匯出表中AddressOfFunction指向的地址表大小根據 NumberOfFunctions 決定:地址表大小 = NumberOfFunctions * 4
②而AddressOfNames指向的名字表大小不由 NumberOfFunctions 決定,而由NumberOfNames決定:名字表大小 = NumberOfNames * 4
③AddressOfNameOrdinals指向的序號表中的值是非準確的,應該均加上Base才是真正的序號(Base等於序號表中最小的值)。而序號表大小 = NumberOfNames * 2
地址表可能大於等於名字表,也有可能小於名字表,因為一個函式可能沒有名字,也可能有多個名字。但是一般情況下,名字表均不會大於地址表。並且一個函式必然有地址,不一定有名字,名字表和序號表一一對應。

2、匯出表解析:

知道一個函式名字func,如何找到其在PE檔案中的地址?步驟(根據三張子表查詢):

①在名字表遍歷RVA地址,轉換成FOA地址,然後根據FOA比較FOA指向的字串與func是否相等,不相等則判斷下一個。
②如果相等則獲取到其在名字表中的索引(下標),根據該索引獲取對應的序號表中同一下標索引到的序號值value。
③value作為地址表的索引,索引到的值即為func()的地址。
也就是我們上面圖中所描述的。

RVA->FOA的轉換函式如下:

//輸入RVA(記憶體相對偏移地址),返回FOA(檔案偏移地址)
DWORD PETool::RVAToFOA(DWORD imageAddr)
{
    /*
     * 相對虛擬地址轉檔案偏移地址
     * ①獲取Section數目
     * ②獲取SectionAlignment
     * ③判斷需要轉換的RVA位於哪個Section中(section[n]),
     * offset = 需要轉換的RVA-VirtualAddress,計算出RVA相對於本節的偏移地址
     * ④section[n].PointerToRawData + offset就是RVA轉換後的FOA
    */

    if(imageAddr > imageSize){
        printf("RVAToFOA in_addr is error!%08X\n",imageAddr);
        exit(EXIT_FAILURE);
    }
    if(imageAddr < section_header[0].PointerToRawData){
        return imageAddr;//在頭部(包括節表與對齊)則直接返回
    }
    IMAGE_SECTION_HEADER * section = section_header;
    DWORD offset = 0;
    for(int i = 0; i < sectionNum; i++){
        DWORD lower = section[i].VirtualAddress;//該節下限
        DWORD upper = section[i].VirtualAddress+section[i].Misc.VirtualSize;//該節上限
        if(imageAddr >= lower && imageAddr <= upper){
            offset = imageAddr - lower + section[i].PointerToRawData;//計算出RVA的FOA
            break;
        }
    }
    return offset;
}

匯出表的解析實現如下(省略檔案到記憶體的讀入):

void PETool::print_ExportTable()
{
    fprintf(fp_peMess, "匯出表(export table):\n");
    if(dataDir[0].VirtualAddress == 0){
        fprintf(fp_peMess, "\t不存在匯出表!\n");
        return;
    }
    DWORD offset = RVAToFOA(dataDir[0].VirtualAddress);
    IMAGE_EXPORT_DIRECTORY * exportTb = (IMAGE_EXPORT_DIRECTORY * )(pFileBuffer + offset);

    fprintf(fp_peMess, "\tOffset to Export Table:[%08X]\n",dataDir[0].VirtualAddress);
    fprintf(fp_peMess, "\tCharacteristics:       [%08X]\n", exportTb->Characteristics);
    fprintf(fp_peMess, "\tTimeDateStamp:         [%08X]\n", exportTb->TimeDateStamp);
    fprintf(fp_peMess, "\tMajorVersion:          [%04X]\n", exportTb->MajorVersion);
    fprintf(fp_peMess, "\tMinorVersion:          [%04X]\n", exportTb->MinorVersion);
    fprintf(fp_peMess, "\tNameAddr:              [%08X]\n", exportTb->Name);
    fprintf(fp_peMess, "\tNameString:            [%s]\n", pFileBuffer + RVAToFOA(exportTb->Name));
    fprintf(fp_peMess, "\tBase:                  [%08X]\n", exportTb->Base);
    fprintf(fp_peMess, "\tNumberOfFunctions:     [%08X]\n", exportTb->NumberOfFunctions);
    fprintf(fp_peMess, "\tNumberOfNames:         [%08X]\n", exportTb->NumberOfNames);
    fprintf(fp_peMess, "\tAddressOfFunctions:    [%08X]\n", exportTb->AddressOfFunctions);
    fprintf(fp_peMess, "\tAddressOfNames:        [%08X]\n", exportTb->AddressOfNames);
    fprintf(fp_peMess, "\tAddressOfNameOrdinals: [%08X]\n", exportTb->AddressOfNameOrdinals);

    //列印匯出表
    fprintf(fp_peMess, "\n\tOrdina\tfunc_FOA\tname_FOA\tFunctionName\n");
    DWORD * addrFunc = (DWORD *)(pFileBuffer + RVAToFOA(exportTb->AddressOfFunctions));
    DWORD * addrName = (DWORD *)(pFileBuffer + RVAToFOA(exportTb->AddressOfNames));
    WORD * addrOrdi = (WORD *)(pFileBuffer + RVAToFOA(exportTb->AddressOfNameOrdinals));

    //Base--->NumberOfFunctions
    DWORD i, j;
    for(i = 0; i < exportTb->NumberOfFunctions; i++){//匯出時序號有NumberOfFunctions個
        if(addrFunc[i] == 0){
            continue;//地址值為0代表該序號沒有對應的函式,是空餘的
        }
        for(j = 0; j < exportTb->NumberOfNames; j++){//序號表序號有NumberOfNames個
            if(addrOrdi[j] == i){//序號表的值為地址表的索引
                fprintf(fp_peMess, "\t%04X\t%08X\t%08X\t%s\n", i + exportTb->Base, addrFunc[i], addrName[j], pFileBuffer + addrName[j]);
                break;
            }
        }
        //存在addrOrdi[j]時,i(索引)等於addrOrdi[j](值),不存在,則i依舊有效,i+Base依舊是序號
        if(j != exportTb->NumberOfNames){
            continue;//在序號表中找到
        }
        else{//如果在序號表中沒有找到地址表的索引,說明函式匯出是以地址匯出的,匿名函式
            fprintf(fp_peMess, "\t%04X\t%08X\t%s\t%s\n", i + exportTb->Base, addrFunc[i], "--------", "--------");
        }
    }
}

相關推薦

PE檔案學習筆記匯出Export Table解析

資料目錄(Data Directory)有16個_IMAGE_DATA_DIRECTORY結構體元素,該結構體陣列是可選PE頭中最後一個成員。這十六個元素分別儲存了不同資訊,分別是:匯入表、匯出表、資源、異常資訊、安全證書、重定位表、除錯資訊、版權所有、全域性指

PE檔案格式學習匯出

1.回顧 上篇文章中介紹過,可選頭中的資料目錄表是一個大小為0x10的陣列,匯出表就是這個陣列中的第一個元素。 我們再回顧下資料目錄表的結構體: struct _IMAGE_DATA_DIRECTORY {     DWORD VirtualAddress;    

PE檔案學習筆記重定位Relocation Table解析

1、重定位表的作用 重定位表(Relocation Table)用於在程式載入到記憶體中時,進行記憶體地址的修正。為什麼要進行記憶體地址的修正?我們舉個例子來說:test.exe可執行程式需要三個動態連結庫dll(a.dll,b.dll,c.dll),假設te

PE檔案學習筆記---匯入匯出

最近在看《黑卡免殺攻防》,對講解的PE檔案匯入表、匯出表的作用與原理有了更深刻的理解,特此記錄。 首先,要知道什麼是匯入表? 匯入表機制是PE檔案從其他第三方程式(一般是DLL動態連結庫)中匯入API,以提供本程式呼叫的機制。而在Windows平臺下,PE檔案中的匯入表結構就

吳恩達機器學習筆記 —— 19 應用舉例照片OCR光學字符識別

參考 https ocr 噪聲 也說 字符 www. 定位 cnblogs http://www.cnblogs.com/xing901022/p/9374258.html 本章講述的是一個復雜的機器學習系統,通過它可以看到機器學習的系統是如何組裝起來的;另外也說明了一

吳恩達機器學習筆記 —— 19 應用舉例照片OCR光學字元識別

本章講述的是一個複雜的機器學習系統,通過它可以看到機器學習的系統是如何組裝起來的;另外也說明了一個複雜的流水線系統如何定位瓶頸與分配資源。 OCR的問題就是根據圖片識別圖片中的文字: 這種OCR識別的問題可以理解成三個步驟: 文字檢測 字元切分 字元識別 文字檢測 文字的檢測可以用行人的檢測來做

C++ Primer學習筆記- 第標準庫型別之四

四、標準庫bitset型別 標準庫中bitset型別用來處理二進位制位的有序集,bitset型別簡化了位集的處理,使用bitset時需要包含標頭檔案#include<bitset>     bitset物件的定義和初始化 bitset也是類模板,不過bits

【從下而上學習Redis】資料結構篇跳躍skiplist

描述 跳躍表(skiplist)是對有序的連結串列增加上附加的前進連結,在列表中的查詢可以快速的跳過部分列表,因此取名跳躍表。跳躍表大概就長得如下圖所示 再舉個形象點的栗子,就像我們如果要去國外,如果徒步去當然會累死,於是我們先坐飛機,下飛機後乘坐汽車,然後再走一段路最

SpringMVC4+thymeleaf3的一個簡單例項篇四form單資料驗證

關於表單資料驗證有很多中方法,這裡我僅介紹JSR303註解驗證。 注意在spring的配置檔案spring-mvc.xml中要有這句程式碼:<mvc:annotation-driven/>

PE檔案格式學習十二TLS

1.介紹 TLS全稱執行緒區域性儲存器,它用來儲存變數或回撥函式。 TLS裡面的變數和回撥函式都在程式入口點(AddressOfEntry)之前執行,也就是說程式在被除錯時,還沒有在入口點處斷下來之前,TLS中的變數和回撥函式就已經執行完了,所以TLS可以用作反除錯之類的操作。

PE檔案格式學習安全

1.介紹 如果一個應用程式有數字簽名,那麼它的安全表就不會為空。它位於異常表的後面。 2.安全表解析 通過資料目錄表裡提供的RVA,我們轉換成offset,找到了安全表的位置,如下: 安全表的結構體如下: typedef struct _WIN_CERTIFIC

PE檔案格式學習異常

1.概述 x86系統採用動態的方式構建SEH結構,相比而言x64系統下采用靜態的方式處理SEH結構,它儲存在PE檔案中,通常在.pdata區段。因此本文的例子採用x64編譯過的程式。 異常表在資源表的後面。 2.異常表解析 資料目錄表的第四個元素指向異常表,RVA指向的是

PE檔案格式學習資源

1.概述 程式內部和外部的介面等元素的二進位制資料統稱為資源,程式把它們放在一個特定的表中,符合資料和程式分離的設計原則。 Windows程式中的資源大致分為六類:選單、對話方塊、點陣圖、游標、圖示、自定義資源 資源表是資料目錄表中的第三個元素,排在匯入表的後面。 2.資

PE檔案格式學習匯入

UPDATE: 在文章的末尾更新了一張圖,在網上找的,有助於理解匯入表的結構 1.概述 匯入表是逆向和病毒分析中比較重要的一個表,在分析病毒時幾乎第一時間都要看一下程式的匯入表的內容,判斷程式大概用了哪些功能。 匯入表是資料目錄表中的第2個元素,排在匯出表的

MATLAB學習筆記符號計算積分+導數

2.3 .1符號微積分  求極限   limit(f,x,a) 求f(x)中x趨近於a的極限值 例如: >> clear >> syms k x >> lim_t=limit((1-1/x)^(k*x),x,inf) lim_t =

PE檔案學習資料目錄之資源

     資源是PE檔案中最複雜的結構了,資源在PE檔案中是以目錄結構的形式存在的,一般情況下分為3層,從根目錄開始分別是資源型別、目錄資源ID與資原始碼頁。      3層目錄結構都是由一個IMAGE_RESOURCE_DIRECTORY結構為頭部,後面跟著一個IMAGE

#學習筆記2# 自動化二讀取csv引數化檔案get請求+連線資料庫

上一篇學習了csv檔案引數化(post方法),不過還有以下方面需要繼續學習完善: 1.get方法請求 2.連線資料庫  3.其他型別的非查詢類介面引數化,今天準備先完成1和2, 3的話要看學習情況,可能會放在下週繼續學習。一、讀取CSV引數化檔案 —— get請求1.新建.c

樹莓派3學習筆記77寸分辨率800 480顯示器配置

樹莓派、顯示器配置樹莓派3學習筆記(7):7寸(分辨率800 480)顯示器配置 樹莓派搭載分辨率為800X480的顯示器在顯示的時候可能會遇到無法全屏顯示的問題, 顯示器只有部分能夠顯示,有一部分是黑邊,對於這一種情況,我們只需進入系統的boot目錄,找到config.txt文件,或者直接在命

TypeScript學習筆記裝飾器Decorators

標註 時裝 als cal () 操作 enume 筆記 文檔 裝飾器簡介 裝飾器(Decorators)為我們在類的聲明及成員上通過元編程語法添加標註提供了一種方式。 需要註意的是:裝飾器是一項實驗性特性,在未來的版本中可能會發生改變。 若要啟用實驗性的裝飾器特性

學習 WebService 第一個簡單的實例SoapUI測試REST項目

方法 資源 ima .com required tle margin 導出 ont 原文地址:SOAPUI測試REST項目(六)——REST服務和WADL ↑↑↑ 原文用的SoapUI,2018-3-19時,這個軟件已經更名為ReadyAPI(集成了SoapUI),因此下文