导出表(Export Table)是一个在可执行文件或动态链接库(DLL)中的数据结构,用于描述该文件中导出的函数、变量和其他符号。导出表通常位于DLL动态链接库中。
本节必须掌握的知识点:
导入表数据结构
PE 中的导入表
IAT 函数地址表
手工重构导入表
5.1.1 导出表数据结构
■导出表由以下三个主要部分组成:
●导出地址表(Export Address Table,EAT):导出地址表是一个指向导出函数地址的指针数组。每个导出函数在导出地址表中有一个对应的指针。在运行时,当其他模块调用这个DLL中的导出函数时,将使用该表中的指针来定位和调用函数。
●导出名称表(Export Name Table,ENT):导出名称表是一个字符串数组,包含了所有导出函数的名称。每个导出函数名称在导出名称表中都有一个对应的字符串。导出地址表中的指针与导出名称表中的字符串是一一对应的。
●导出函数序号表(Export Function Ordinal Table)是PE文件中导出表的一部分,用于映射导出函数的序号和地址。导出函数序号表是一个由WORD类型的数组组成,每个元素对应一个导出函数。序号表的长度等于导出函数的数量。
导出表的结构和内容由编译器和链接器在构建可执行文件或DLL时生成。导出表允许其他模块(可执行文件、DLL或其他)通过导入表来引用和调用这些导出的函数和符号。
在Windows平台上,您可以使用一些工具来查看PE文件的导出表,例如Dependency Walker、dumpbin工具或PE浏览器等。
注意
导出表只包含被明确定义为导出的符号。如果某个符号未被明确导出,它将不会出现在导出表中。您可以使用关键字 __declspec(dllexport) 或者通过.def文件来指定哪些符号应该被导出。
■导出表数据结构
导入表描述符IMAGE_IMPORT_DESCRIPTOR的个数与调用的动态链接库个数相等,而导出表描述符IMAGE_EXPORT_DIRECTORY的个数只有一个。
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 导出表的特征标志
DWORD TimeDateStamp; // 时间戳
WORD MajorVersion; // 主版本号
WORD MinorVersion; // 次版本号
DWORD Name; // 模块名称的RVA
DWORD Base; // 导出函数序号的基准值
DWORD NumberOfFunctions; // 导出函数的数量
DWORD NumberOfNames; // 导出函数名称的数量
DWORD AddressOfFunctions; // 导出函数地址表的RVA
DWORD AddressOfNames; // 导出函数名称表的RVA
DWORD AddressOfNameOrdinals; // 导出函数序号表的RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
这个结构用于描述PE文件中的导出表。下面是对各个成员的注释:
Characteristics:导出表的特征标志,例如是否按序号导出。
TimeDateStamp:导出表的时间戳,表示导出表生成的时间。
MajorVersion 和 MinorVersion:导出表的版本号,用于指示导出表的版本信息。
Name:指向导出模块名称的指针(RVA)。
Base:导出函数的序号基准值。导出函数编号的起始值。DLL中的第一个导出函数并不是从0开始的,某导出函数的编号等于从AddressOfFunctions开始的顺序号加上这个值。
NumberOfFunctions:导出函数的数量。
NumberOfNames:导出函数名称的数量。
AddressOfFunctions:导出函数地址表的指针(RVA),包含导出函数的地址。该指针指向了全部导出函数的入口地址的起始。从入口地址开始为双字数组,数组的个数由字段IMAGE_EXPORT_DIRECTORY.NumberOfFimctions决定。导出函数的每一个地址按函数的编号顺序依次往后排开。在内存中,我们可以通过函数编号来定位某个函数的地址。
AddressOfNames:导出函数名称表的指针(RVA),包含导出函数的名称。该指针指向的位置是一连串的双字值,这些双字值均指向了对应的定义了函数名的函数的字符串地址。这一连串的双字个数为 NumberOfNames。
AddressOfNameOrdinals:导出函数序号表的指针(RVA),用于匹配导出函数的名称和地址。该值也是一个指针,与AddressOfNames是一一对应关系,所不同的是,AddressOfNames指向的是字符串的指针数组,而AddressOfNameOrdinals则指向了该函数在AddressOfFunctions中的索引值。
注意
索引值是一个字,而非双字。该值与函数编号是两个不同的概念,两者之间的关系为:
索引值=编号 - nBase
导入表描述各个字段之间的关系可以用下图来描述:
图5-1 PE导出表结构
5.1.2 定位导出表
导出表位于PE文件的数据目录第0项中,位于导入表之前。接下来我们通过一个实例具体分析导出表的结构。
首先我们要写一个包含导出表的DLL动态链接库,然后再写一个EXE执行文件调用导出函数。
实验三十三:延迟加载版本2 示例
以下是一个使用延迟加载版本2的C语言示例代码:
●源代码:
winResult.h
/*
; winResult.dll 导出函数:
; 1、AnimateOpen(DWORD)
; 窗口抖动进入效果
; 2、AnimateClose(DWORD)
; 窗口抖动退出效果
; 3、FadeInOpen(DWORD)
; 窗口淡入效果,仅运行在2000/XP以上操作系统
; 4、FadeOutClose(DWORD)
; 窗口淡出效果,仅运行在2000/XP以上操作系统
; ******************************************************************** /
*/
#pragma once
#include <windows.h>
#ifdef _cplusplus //如果C++模式编译
#ifdef API_EXPORT
#define EXPORT extern "C" __declspec(dllexport)
#else
#define EXPORT extern "C" __declspec(dllimport)
#endif
#else
#ifdef API_EXPORT
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __declspec(dllimport)
#endif
#endif
EXPORT void AnimateOpen(HWND );
EXPORT void AnimateClose(HWND );
EXPORT void FadeInOpen(HWND );
EXPORT void FadeOutClose(HWND );
winResult.c
/*------------------------------------------------------------------------
FileName:winResult.c
实验33:一个简单的动态链接库例子
(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <windows.h>
#define API_EXPORT
#include "winResult.h"
#define MAX_XYSTEPS 50
#define DELAY_VALUE 50 //动画效果使用的步长
#define X_STEP_SIZE 10
#define Y_STEP_SIZE 9
#define X_START_SIZE 20
#define Y_START_SIZE 10
#define LMA_ALPHA 2
#define LMA_COLORKEY 1
//入口和退出点
int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
{
return TRUE ;
}
//私有函数:本函数在dll内部使用
DWORD TopXY(DWORD wDim,DWORD sDim)
{
return (sDim / 2 - wDim / 2);
}
//窗口抖动进入效果
EXPORT void AnimateOpen(HWND hWin)
{
RECT rect;
DWORD Xsize, Ysize,Xplace,Yplace;
int sWth,sHth,counts;
GetWindowRect (hWin, &rect);
Xsize = X_START_SIZE;
Ysize = Y_START_SIZE;
sWth = GetSystemMetrics (SM_CXSCREEN );
Xplace = TopXY(Xsize, sWth);
sHth = GetSystemMetrics (SM_CYSCREEN );
Yplace = TopXY(Ysize, sHth);
counts = MAX_XYSTEPS;
while (counts--)
{
MoveWindow (hWin, Xplace, Yplace, Xsize, Ysize, FALSE );
ShowWindow (hWin, SW_SHOWNA );
Sleep (DELAY_VALUE);
ShowWindow (hWin, SW_HIDE );
Xsize += X_STEP_SIZE;
Ysize += Y_STEP_SIZE;
Xplace = TopXY(Xsize, sWth);
Yplace = TopXY(Ysize, sHth);
}
Xsize = rect.right - rect.left ;
Ysize = rect.bottom - rect.top ;
Xplace = TopXY(Xsize,sWth);
Yplace = TopXY(Ysize,sHth);
MoveWindow (hWin,Xplace,Yplace,Xsize,Ysize,TRUE );
ShowWindow (hWin, SW_SHOW );
}
//窗口抖动退出效果
EXPORT void AnimateClose(HWND hWin)
{
RECT rect;
DWORD Xsize, Ysize, Xplace, Yplace;
int sWth, sHth, counts;
ShowWindow (hWin, SW_HIDE );
GetWindowRect (hWin, &rect);
Xsize = rect.right - rect.left ;
Ysize = rect.bottom - rect.top ;
sWth = GetSystemMetrics (SM_CXSCREEN );
Xplace = TopXY(Xsize, sWth);
sHth = GetSystemMetrics (SM_CYSCREEN );
Yplace = TopXY(Ysize, sHth);
counts = MAX_XYSTEPS;
while (counts--)
{
MoveWindow (hWin, Xplace, Yplace, Xsize, Ysize, FALSE );
ShowWindow (hWin, SW_SHOWNA );
Sleep (DELAY_VALUE);
ShowWindow (hWin, SW_HIDE );
Xsize -= X_STEP_SIZE;
Ysize -= Y_STEP_SIZE;
Xplace = TopXY(Xsize, sWth);
Yplace = TopXY(Ysize, sHth);
}
}
//窗口淡入效果,仅运行在2000/XP以上操作系统
EXPORT void FadeInOpen(HWND hWin)
{
const TCHAR User32[] = TEXT ("user32.dll");
const CHAR SLWA[] = "SetLayeredWindowAttributes";//函数名使用ASCII码字符
FARPROC pSLWA;
int Value = 90;
LONG stl = GetWindowLong (hWin, GWL_EXSTYLE );//检索扩展的窗口样式
// 增加"分层窗口"样式
stl |= WS_EX_LAYERED ;
SetWindowLong (hWin,GWL_EXSTYLE ,stl);
//获取SetLayeredWindowAttributes在user32.dll中的地址
pSLWA = GetProcAddress (GetModuleHandle (User32), (LPCSTR )SLWA);
//设置分层窗口的不透明度和透明度颜色键
pSLWA(hWin,0,0,LMA_ALPHA);
ShowWindow (hWin, SW_SHOWNA );
while (Value != 255)
{
pSLWA(hWin,Value,Value,LMA_COLORKEY + LMA_ALPHA);
Sleep (DELAY_VALUE);
Value += 15;
}
pSLWA(hWin,0,255,LMA_ALPHA);
}
//窗口淡出效果,仅运行在2000/XP以上操作系统
EXPORT void FadeOutClose(HWND hWin)
{
const TCHAR User32[] = TEXT ("user32.dll");
const CHAR SLWA[] = "SetLayeredWindowAttributes";
FARPROC pSLWA;
int Value = 255;
LONG stl = GetWindowLong (hWin, GWL_EXSTYLE );//检索扩展的窗口样式
// 增加"分层窗口"样式
stl |= WS_EX_LAYERED ;
SetWindowLong (hWin, GWL_EXSTYLE , stl);
//获取SetLayeredWindowAttributes在user32.dll中的地址
pSLWA = GetProcAddress (GetModuleHandle (User32), (LPCSTR )SLWA);
//设置分层窗口的不透明度和透明度颜色键
pSLWA(hWin, 0,255, LMA_ALPHA);
while (Value != 0)
{
pSLWA(hWin, Value, Value, LMA_COLORKEY + LMA_ALPHA);
Sleep (DELAY_VALUE);
Value -= 15;
}
}
FirstWindow.c
/*------------------------------------------------------------------------
FileName:FirstWindow.c
实验33:执行文件
;功能:简单窗口程序
;具有窗口的大部分基本特性,其中显示和退出使用了渐入和渐出效果
;该程序主要演示自己制作的dll的函数调用
(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <windows.h>
#include "resource.h"
#include "winResult.h"
#pragma comment(lib,"winResult.lib")
HWND hWinMain; //窗口句柄
LRESULT CALLBACK WndProc(HWND , UINT , WPARAM , LPARAM );
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nShowCmd)//默认填写
{
const TCHAR szClassName[] = TEXT ("MyClass");
const TCHAR szCaptionMain[] = TEXT ("窗口特效演示");
MSG msg; //MSG 结构变量
WNDCLASSEX wndclass; //WNDCLASSEX结构变量
wndclass.style = CS_HREDRAW | CS_VREDRAW ;//重叠窗口
wndclass.lpfnWndProc = WndProc;//回调函数指针
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hbrBackground = (HBRUSH )GetStockObject (WHITE_BRUSH );//窗口背景色
wndclass.hCursor = LoadCursor (NULL , IDC_ARROW );
wndclass.hIcon = LoadIcon (hInstance, MAKEINTRESOURCE (IDI_ICON1));
wndclass.lpszMenuName = NULL ;//菜单
wndclass.hInstance = hInstance;//实例句柄
wndclass.cbSize = sizeof(WNDCLASSEX );
wndclass.hIconSm = NULL ;
wndclass.lpszClassName = szClassName;
if (!RegisterClassEx (&wndclass))
{
MessageBox (0, TEXT ("This program requires Windows NT!"),
szClassName, MB_ICONERROR );
return 0;
}
hWinMain = CreateWindowEx (WS_EX_CLIENTEDGE ,
szClassName,//窗口类名
szCaptionMain,//窗口标题
WS_OVERLAPPEDWINDOW , //窗口样式
GetSystemMetrics (SM_CXSCREEN ) / 4,
GetSystemMetrics (SM_CYSCREEN ) / 4,
GetSystemMetrics (SM_CXSCREEN ) / 2,
GetSystemMetrics (SM_CYSCREEN ) / 2,
NULL , // 父窗口句柄
NULL , // 窗口菜单句柄
hInstance, // 程序实例句柄
NULL ); // 创建参数
//ShowWindow(hWinMain, nShowCmd); //显示窗口
FadeInOpen(hWinMain);//外部链接库函数
UpdateWindow (hWinMain);
while (GetMessage (&msg, NULL , 0, 0))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
const TCHAR szText[] = TEXT ("Welocome to 编程达人!");
switch (message)
{
case WM_PAINT :
hdc = BeginPaint (hwnd, &ps);
GetClientRect (hwnd,&rect);
DrawText (hdc,szText,-1,&rect,DT_SINGLELINE | DT_CENTER |DT_VCENTER );
EndPaint (hwnd, &ps);
return 0;
case WM_CLOSE :
FadeOutClose(hwnd);//外部链接库函数
DestroyWindow (hwnd);
return 0;
case WM_DESTROY :
PostQuitMessage (0);
return 0;
}
return DefWindowProc (hwnd, message, wparam, lparam);
}
运行
图5-2 FirstWindow窗口程序
●winResult.dll中导出表的定位:
数据目录项:
00000170 10 25 00 00 90 00 00 00 A0 25 00 00 64 00 00 00 .%......?..d...
00000180 00 40 00 00 F8 00 00 00 00 00 00 00 00 00 00 00 .@..?..........
00000190 00 00 00 00 00 00 00 00 00 50 00 00 7C 01 00 00 .........P..|...
000001A0 F0 20 00 00 70 00 00 00 00 00 00 00 00 00 00 00 ?..p...........
000001B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000001C0 60 21 00 00 40 00 00 00 00 00 00 00 00 00 00 00 `!..@...........
000001D0 00 20 00 00 8C 00 00 00 00 00 00 00 00 00 00 00 . ..?..........
000001E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
●导出表位于数据目录项的第0项。导出表RVA为00002510H,大小为90H。
节表:
000001F0 2E 74 65 78 74 00 00 00 A4 0F 00 00 00 10 00 00 .text...?......
00000200 00 10 00 00 00 04 00 00 00 00 00 00 00 00 00 00 ................
00000210 00 00 00 00 20 00 00 60 2E 72 64 61 74 61 00 00 .... ..`.rdata..
00000220 62 09 00 00 00 20 00 00 00 0A 00 00 00 14 00 00 b.... ..........
00000230 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40 ............@..@
00000240 2E 64 61 74 61 00 00 00 8C 03 00 00 00 30 00 00 .data...?...0..
00000250 00 02 00 00 00 1E 00 00 00 00 00 00 00 00 00 00 ................
00000260 00 00 00 00 40 00 00 C0 2E 72 73 72 63 00 00 00 ....@..?rsrc...
00000270 F8 00 00 00 00 40 00 00 00 02 00 00 00 20 00 00 ?...@....... ..
00000280 00 00 00 00 00 00 00 00 00 00 00 00 40 00 00 40 ............@..@
00000290 2E 72 65 6C 6F 63 00 00 7C 01 00 00 00 50 00 00 .reloc..|....P..
000002A0 00 02 00 00 00 22 00 00 00 00 00 00 00 00 00 00 ....."..........
000002B0 00 00 00 00 40 00 00 42 00 00 00 00 00 00 00 00 ....@..B........
●导出表位于.rdata节区,FOA地址为:2510H-2000H+1400H=1910H。
导出表描述符:
00001910 00 00 00 00 FF FF FF FF 00 00 00 00 60 25 00 00 ........`%..
00001920 01 00 00 00 04 00 00 00 04 00 00 00 38 25 00 00 ............8%..
00001930 48 25 00 00 58 25 00 00 F0 10 00 00 10 10 00 00 H%..X%..?......
00001940 C0 11 00 00 A0 12 00 00 6E 25 00 00 7B 25 00 00 ?..?..n%..{%..
00001950 87 25 00 00 92 25 00 00 00 00 01 00 02 00 03 00 ?..?..........
00001960 77 69 6E 52 65 73 75 6C 74 2E 64 6C 6C 00 41 6E winResult.dll.An
00001970 69 6D 61 74 65 43 6C 6F 73 65 00 41 6E 69 6D 61 imateClose.Anima
00001980 74 65 4F 70 65 6E 00 46 61 64 65 49 6E 4F 70 65 teOpen.FadeInOpe
00001990 6E 00 46 61 64 65 4F 75 74 43 6C 6F 73 65 00 00 n.FadeOutClose..
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 00 00 00 00
DWORD TimeDateStamp; // FF FF FF FF
WORD MajorVersion; // 00 00
WORD MinorVersion; // 00 00
DWORD Name; // 模块名称的RVA:00002560H
DWORD Base; // 导出函数序号的基准值:01
DWORD NumberOfFunctions; // 导出函数的数量:04
DWORD NumberOfNames; // 导出函数名称的数量:04
DWORD AddressOfFunctions; // 导出函数地址表的RVA:00002538H
DWORD AddressOfNames; // 导出函数名称表的RVA:00002548H
DWORD AddressOfNameOrdinals; // 导出函数序号表的RVA:00002558H
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
●导出函数地址表的RVA:00002538H对应的FOA地址为00001938。
F0 10 00 00 10 10 00 00 C0 11 00 00 A0 12 00 00
●导出函数名称表的RVA:00002548H对应的FOA地址为00001948。
6E 25 00 00 7B 25 00 00 87 25 00 00 92 25 00 00
0000196E:AnimateClose
0000197B:AnimateOpen
00001987:FadeInOpen
00001992:FadeOutClose
●导出函数序号表的RVA:00002558H对应的FOA地址为00001958。
00 00 01 00 02 00 03 00
●导出函数地址验证
第一步:将FirstWindow.exe拖入OD调试器,找到FadwInOpen函数的调用,F2下断点,如图5-3所示:
图5-3 FadwInOpen函数的调用
第二步:按下F7单步步入,进入FadwInOpen函数,入口地址为0x579511C0,如图5-4所示:
图5-4 FadwInOpen函数的入口
第三步:打开内存映射窗口,找到winResult.dll模块,模块基址为0x57950000,如图5-5所示:
图5-5winResult.dll模块
FadwInOpen函数入口地址0x579511C0 = 0x57950000(基址)+0x 000011C0(RVA)。