PE

来源:互联网 发布:七牛域名和自定义域名 编辑:程序博客网 时间:2024/04/27 08:38

基地址:  PE映象到内存的起始地址 = 0x10000;
相对虚拟地址: (RVA)对于基地址的偏移量;
RVA + 基地址 = 线性地址;

文件偏移量:  文件中地址用偏移量表示,第一个字节偏移量0,后面依次递增.;
  SoftICE/W32Dasm  显示的地址是内存地址,或称之为虚拟地址(VA);
  Hiew/Hex/Workshop显示的地址是文件地址,称之为偏移量(File offset)或物理地址(文件);

虚拟地址 = 逻辑地址 => 线性地址 (内存地址);

////////////////////////////////////////////////////////////////////////////////////

ex:;
(内存 各节按4096字节对齐);
已知执行开始处 RVA = 0x1560;
".code"节 RVA = 0x1000;
0x1560位于".code"节 偏移量0x560处 (0x1560 - 0x1000 = 0x560);
(文件 各节按512字节边界对齐);
".code"节 偏移量 = 0x800;
文件中".code"节开始 = 0x800 + 0x560 = 0xd60;

 

////////////////////////////////////////////////////////////////////////////////////

 

DLL 同样是PE格式
导出表: 里面包含函数的名称、序号和入口地址,提供给执行文件调用的。(简单理解是是dll才有使用)

Windows装载器将文件装入内存并将导入表中登记的DLL文件一并装入,
再根据DLL文件中的函数导出信息对被执行文件的IAT表进行修正


PE执行文件 (简单理解是执行文件使用,dll也包含导入表)
导入表: 包含需要用到的dll文件名和函数名,运行时根基信息定位找到dll函数


lib 导入库,有此文件就可以让程序自动载入DLL
 包含dll文件名和函数名
 lib会将内容装入导入表

 


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

 


// ************************ WINNT.H ************************

#define IMAGE_DOS_SIGNATURE             0x5A4D      // MZ (DOS头)
#define IMAGE_OS2_SIGNATURE             0x454E      // NE (OS头)
#define IMAGE_OS2_SIGNATURE_LE          0x454C      // LE (OS头)
#define IMAGE_NT_SIGNATURE              0x00004550  // PE00 (NT头)

// MS-DOS头(64(0x40)个字节) ---------------------------------
typedef struct _IMAGE_DOS_HEADER {  // DOS下的.EXE文件头
 USHORT e_magic;         // 魔数
 USHORT e_cblp;          // 文件最后一页的字节数
 USHORT e_cp;            // 文件的页数
 USHORT e_crlc;          // 重定位
 USHORT e_cparhdr;       // 段中头的大小
 USHORT e_minalloc;      // 需要的最少额外段
 USHORT e_maxalloc;      // 需要的最多额外段
 USHORT e_ss;            // 初始的(相对的)SS寄存器值
 USHORT e_sp;            // 初始的SP寄存器值
 USHORT e_csum;          // 校验和
 USHORT e_ip;            // 初始的IP寄存器值
 USHORT e_cs;            // 初始的(相对的)CS寄存器值
 USHORT e_lfarlc;        // 重定位表在文件中的地址
 USHORT e_ovno;          // 交叠数
 USHORT e_res[4];        // 保留字
 USHORT e_oemid;         // OEM识别符(用于e_oeminfo成员)
 USHORT e_oeminfo;       // OEM信息; e_oemid中指定的
 USHORT e_res2[10];      // 保留字
 LONG   e_lfanew;        // PE文件签名(偏移量),4字节
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

// PE文件头结构 -----------------------------------------------
typedef struct _IMAGE_FILE_HEADER {
 USHORT  Machine;                 //机器
 USHORT  NumberOfSections;        //节数
 ULONG   TimeDateStamp;           //时间日期戳
 ULONG   PointerToSymbolTable;    //符号表指针
 ULONG   NumberOfSymbols;         //符号数
 USHORT  SizeOfOptionalHeader;    //可选头的大小
 USHORT  Characteristics;         //特性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
#define IMAGE_SIZEOF_FILE_HEADER 20 //PE文件头结构大小

// PE可选头 -------------------------------------------------
typedef struct _IMAGE_OPTIONAL_HEADER {
 //标准域
 USHORT  Magic;                   //魔数
 UCHAR   MajorLinkerVersion;      //链接器主版本号
 UCHAR   MinorLinkerVersion;      //链接器小版本号
 ULONG   SizeOfCode;              //代码大小
 ULONG   SizeOfInitializedData;   //已初始化数据大小
 ULONG   SizeOfUninitializedData; //未初始化数据大小
 ULONG   AddressOfEntryPoint;     //入口点地址
 ULONG   BaseOfCode;              //代码基址  已载入的映像文件中代码(“.text”节)的相对偏移量
 ULONG   BaseOfData;              //数据基址  已载入的映像文件中未初始化数据(“.bss”节)的相对偏移量
 //NT增加的域
 ULONG   ImageBase;                  //映像文件基址, Win32 SDK默认地址0x00400000, 但可改变
 ULONG   SectionAlignment;           //节对齐
 ULONG   FileAlignment;              //文件对齐
 USHORT  MajorOperatingSystemVersion;//操作系统主版本号
 USHORT  MinorOperatingSystemVersion;//操作系统小版本号
 USHORT  MajorImageVersion;          //映像文件主版本号
 USHORT  MinorImageVersion;          //映像文件小版本号
 USHORT  MajorSubsystemVersion;      //子系统主版本号
 USHORT  MinorSubsystemVersion;      //子系统小版本号
 ULONG   Reserved1;                  //保留项1
 ULONG   SizeOfImage;                //映像文件大小
 ULONG   SizeOfHeaders;              //所有头的大小
 ULONG   CheckSum;                   //校验和
 USHORT  Subsystem;                  //子系统
 USHORT  DllCharacteristics;         //DLL特性
 ULONG   SizeOfStackReserve;         //保留栈的大小
 ULONG   SizeOfStackCommit;          //指定栈的大小
 ULONG   SizeOfHeapReserve;          //保留堆的大小
 ULONG   SizeOfHeapCommit;           //指定堆的大小
 ULONG   LoaderFlags;                //加载器标志
 ULONG   NumberOfRvaAndSizes;        //RVA的数量和大小
 IMAGE_DATA_DIRECTORY DataDirectory  [IMAGE_NUMBEROF_DIRECTORY_ENTRIES];   //数据目录数组
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

// PE的可选头->DataDirectory->16个数据目录
typedef struct _IMAGE_DATA_DIRECTORY {
 ULONG   VirtualAddress;       //偏移地址
 ULONG   Size;                 //大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

// 各个目录项 只使用11个 每一个宏都是一个_IMAGE_DATA_DIRECTORY结构
// 输出目录
#define IMAGE_DIRECTORY_ENTRY_EXPORT         0
// 输入目录
#define IMAGE_DIRECTORY_ENTRY_IMPORT         1
// 资源目录
#define IMAGE_DIRECTORY_ENTRY_RESOURCE       2
// 异常目录
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION      3
// 安全目录
#define IMAGE_DIRECTORY_ENTRY_SECURITY       4
// 基址重定位表
#define IMAGE_DIRECTORY_ENTRY_BASERELOC      5
// 调试目录
#define IMAGE_DIRECTORY_ENTRY_DEBUG          6
// 描述字符串
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT      7
// 机器值(MIPS GP)
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR      8
// TLS(线程本地存储)
#define IMAGE_DIRECTORY_ENTRY_TLS            9
// 载入配置目录
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10

// 节头(40字节/每个) ------------------------------------------------------
// 可以有N个节头, 每个结构都相同
typedef struct _IMAGE_SECTION_HEADER {
 UCHAR   Name[IMAGE_SIZEOF_SHORT_NAME];           //名字数组
 union {                                          //共用体标志
  ULONG   PhysicalAddress;      //物理地址
  ULONG   VirtualSize;       //虚拟大小
 } Misc;
 ULONG   VirtualAddress;                          //虚拟地址
 ULONG   SizeOfRawData;                           //原始数据的大小
 ULONG   PointerToRawData;                        //原始数据指针
 ULONG   PointerToRelocations;                    //重定位指针
 ULONG   PointerToLinenumbers;                    //行数指针
 USHORT  NumberOfRelocations;                     //重定位数目
 USHORT  NumberOfLinenumbers;                     //行数数目
 ULONG   Characteristics;                         //特征
              /*
              Characteristics
              值                  定义
              0x00000020          代码节
              0x00000040          已初始化数据节
              0x00000080          未初始化数据节
              0x04000000          不可缓存节
              0x08000000          不可分页节
              0x10000000          共享节
              0x20000000          可执行节
              0x40000000          可读节
              0x80000000          可写节
 */
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
#define IMAGE_SIZEOF_SHORT_NAME 8 //节头大小

// 输出数据节(导出表) .edata --------------------------------------------
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;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ************************ PE.C ************************

// PE文件签名 实际内存镜像地址 = e_lfanew偏移量 + 文件内存镜像基址
#define NTSIGNATURE(a) ((LPVOID)((BYTE *)a + ((PIMAGE_DOS_HEADER)a)->e_lfanew))

// 判断是否NT签名
DWORD  WINAPI ImageFileType (LPVOID lpFile)
{
 /* DOS文件签名先出现。 */
 if (*(USHORT *)lpFile == IMAGE_DOS_SIGNATURE)
 {
  /* 从DOS头开始确定PE文件头的位置。 */
  if (LOWORD (*(DWORD *)NTSIGNATURE (lpFile)) == IMAGE_OS2_SIGNATURE ||
   LOWORD (*(DWORD *)NTSIGNATURE (lpFile)) == IMAGE_OS2_SIGNATURE_LE)
  {
   /* 返回OS头 */
   return (DWORD)LOWORD(*(DWORD *)NTSIGNATURE (lpFile));
  }
  else if (*(DWORD *)NTSIGNATURE (lpFile) == IMAGE_NT_SIGNATURE)
  {
   /* 返回NT头,只有NT才进行处理 */
   return IMAGE_NT_SIGNATURE;
  }
  else
  {
   /* 返回DOS头 */
   return IMAGE_DOS_SIGNATURE;
  }
 }
 /* 未知的文件类型。 */
 else return 0;
}

// PE文件头 实际内存镜像地址 = PE文件签名 实际内存镜像地址 + NT签名size
DWORD SIZE_OF_NT_SIGNATURE // SIZE_OF_NT_SIGNATURE定义为DWORD大小 == NT签名大小 sizeof(IMAGE_NT_SIGNATURE)
#define PEFHDROFFSET(a) ((LPVOID)((BYTE *)a + ((PIMAGE_DOS_HEADER)a)->e_lfanew + SIZE_OF_NT_SIGNATURE))

// 取得PE文件头结构
PIMAGE_FILE_HEADER pfh;
pfh = (PIMAGE_FILE_HEADER)PEFHDROFFSET (lpFile); // lpFile: 可执行文件基址指针

// 取得PE文件头->节的数量    PIMAGE_FILE_HEADER->NumberOfSections
int WINAPI NumOfSections (LPVOID lpFile)
{
 /* 文件头中有多少个节 */
 return (int)((PIMAGE_FILE_HEADER) PEFHDROFFSET (lpFile))->NumberOfSections);
 // PE文件头结构中的其他字段也是这样取出
}

// PE可选头 实际内存镜像地址 = PE文件头 实际内存镜像地址 + PE文件头size
#define OPTHDROFFSET(a) ((LPVOID)((BYTE *)a + ((PIMAGE_DOS_HEADER)a)->e_lfanew + SIZE_OF_NT_SIGNATURE + sizeof (IMAGE_FILE_HEADER)))

// 取得PE可选头->应用程序入口地址    IMAGE_OPTIONAL_HEADER->AddressOfEntryPoint
// 也是输入地址表(Import Address Table,IAT)结束位置 ,因为IAT必定在程序入口地址的前面
LPVOID WINAPI GetModuleEntryPoint (LPVOID lpFile)
{
 // 取得PE可选头结构
 PIMAGE_OPTIONAL_HEADER poh;
 poh = (PIMAGE_OPTIONAL_HEADER)OPTHDROFFSET (lpFile);
 
 if (poh != NULL)
  return (LPVOID)poh->AddressOfEntryPoint;
 else
  return NULL;
 // PE可选头结构中的其他字段也是这样取出
}

// 节头就在PE可选头后面 = PE可选头 实际内存镜像地址 + PE可选头size
#define SECHDROFFSET(a) ((LPVOID)((BYTE *)a + ((PIMAGE_DOS_HEADER)a)->e_lfanew + SIZE_OF_NT_SIGNATURE + sizeof (IMAGE_FILE_HEADER) + sizeof (IMAGE_OPTIONAL_HEADER)))

// 取节名相同的节头信息
// lpFile执行文件载入基址, sh空结构, szSection指定的节名
BOOL WINAPI GetSectionHdrByName (LPVOID lpFile, IMAGE_SECTION_HEADER *sh, char *szSection)
{
 PIMAGE_SECTION_HEADER psh;
 // 取得有多少个节
 int nSections = NumOfSections (lpFile);
 int i;
 // 节头起始地址
 if ((psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET (lpFile)) != NULL)
 {
  /* 按名字循环寻找节 */
  for (i=0; i<nSections; i++)
  {
   if (!strcmp (psh->Name, szSection))
   {
    /* 将节名相同的节头信息复制到 sh */
    CopyMemory ((LPVOID)sh, (LPVOID)psh, sizeof (IMAGE_SECTION_HEADER));
    return TRUE;
   }
   else psh++;
  }
 }
 // 其他节取法相同
 return FALSE;
}

// 定位数据目录
LPVOID  WINAPI ImageDirectoryOffset (LPVOID lpFile, DWORD dwIMAGE_DIRECTORY)
{
 PIMAGE_OPTIONAL_HEADER poh;
 PIMAGE_SECTION_HEADER psh;
 int nSections = NumOfSections (lpFile);
 int i = 0;
 LPVOID VAImageDir;
 
 /* 先验证所求数据目录项的数字 */
 if (dwIMAGE_DIRECTORY >= poh->NumberOfRvaAndSizes)
  return NULL;
 
 /* 可选头偏移量指针 */
 poh = (PIMAGE_OPTIONAL_HEADER)OPTHDROFFSET (lpFile);
 /* 节头偏移量指针 */
 psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET (lpFile);
 
 /* 定位可选头 数据目录(DataDirectory) 相对虚拟地址。 */
 VAImageDir = (LPVOID)poh->DataDirectory[dwIMAGE_DIRECTORY].VirtualAddress;
 
 /* 定位节身 循环n个节身 */
 while (i++<nSections)
 {
 /*
 lpFile :内存镜像的可执行文件的基址的指针
 psh->VirtualAddress :内存中节身的虚拟地址(起始地址)
 psh->SizeOfRawData :该节头size
 psh->VirtualAddress + psh->SizeOfRawData :节头结束位置
 psh->PointToRawData :文件中节身的偏移量
 因为数据目录在节头之内,所以:(节头起始起始位置 <= 数据目录 < 节头结束位置)
  */
  if (psh->VirtualAddress <= (DWORD)VAImageDir && psh->VirtualAddress + psh->SizeOfRawData > (DWORD)VAImageDir)
   break;
  psh++;
 }
 
 if (i > nSections)
  return NULL;
 
  /*
  lpFile + psh->PointerToRawData 可执行文件内存基址 + 文件中节身的偏移量 = 节身在内存中实际地址
  如果数据目录的虚拟地址不等于节身的偏移量,就要相减 VAImageDir - psh->VirtualAddress ,得出的差再加上上面“节头在内存中实际地址”
 */
 /* 内存中节身(数据目录)的实际地址 */
 return (LPVOID)(((int)lpFile + (int)VAImageDir - psh->VirtualAddress) + (int)psh->PointerToRawData);
}

// 导出表(.edata)取AddressOfNames值 (还有另外三个函数:GetNumberOfExportedFunctions, GetExportFunctionEntryPoints,GetExportFunctionOrdinals)
int WINAPI GetExportFunctionNames (LPVOID lpFile, HANDLE hHeap, char **pszFunctions)
{
 IMAGE_SECTION_HEADER sh;
 PIMAGE_EXPORT_DIRECTORY ped;
 char *pNames, *pCnt;
 int i, nCnt;
 
 /* 取数据目录的指针 */
 if ((ped = (PIMAGE_EXPORT_DIRECTORY)ImageDirectoryOffset(lpFile, IMAGE_DIRECTORY_ENTRY_EXPORT)) == NULL)
  return 0;
 /* 取.edata节头 */
 GetSectionHdrByName (lpFile, &sh, ".edata");
 
 /* 确定输出函数名字的偏移量。 */
 /*
 (int)ped->AddressOfNames - (int)sh.VirtualAddress + (int)sh.PointerToRawData + (int)lpFile
 第一句,取得导出表结构里面AddressOfNames的值,但是AddressOfNames本身就是一个虚拟地址,所以要进行第二次计算
 AddressOfNames值 - (int)sh.VirtualAddress + (int)sh.PointerToRawData + (int)lpFile
 第二句计算方法相同,详细逻辑参考函数ImageDirectoryOffset
 */
 pNames = (char *)(*(int *)((int)ped->AddressOfNames - (int)sh.VirtualAddress + (int)sh.PointerToRawData + (int)lpFile) - (int)sh.VirtualAddress + (int)sh.PointerToRawData + (int)lpFile);
 
 /* 计算所有字符串需分配多少内存。 */
 pCnt = pNames;
 /* NumberOfNames包含多个名字,计算每个名字长度累加 */
 for (i=0; i<(int)ped->NumberOfNames; i++)
  while (*pCnt++);
  nCnt = (int)(pCnt - pNames);
  
  /* 从堆中为函数名字分配内存。 */
  *pszFunctions = HeapAlloc (hHeap, HEAP_ZERO_MEMORY, nCnt);
  
  /* 复制所有字符串到缓存区中。 */
  CopyMemory ((LPVOID)*pszFunctions, (LPVOID)pNames, nCnt);
  
  return nCnt;
}

/*
由于WINNT.H没有导入表的结构,自定义一个
typedef struct tagImportDirectory
{
DWORD    dwRVAFunctionNameList;      //函数名字列表的RVA
DWORD    dwUseless1;                 //未用1
DWORD    dwUseless2;                 //未用2
DWORD    dwRVAModuleName;            //模块名字的RVA
DWORD    dwRVAFunctionAddressList;   //函数地址列表的RVA
}IMAGE_IMPORT_MODULE_DIRECTORY,
* PIMAGE_IMPORT_MODULE_DIRECTORY;

  每个结构代表一个模块(DLL),包含若干个函数
  而一个PE中可以包含若干个模块(DLL),即是可以有若干个导入表结构
  由数据目录得到的导入表结构地址,代表第一个结构的入口,其他的结构跟随其后
 
 第三个字段:dwRVAFunctionAddressList
 载入内存之前与dwRVAFunctionNameList相同,载入内存后才是真正的函数地址列表RVA
*/

// 导入表(.data)取dwRVAModuleName值(模块名)
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;
 
 /* 取节头内存地址 */
 if (!GetSectionHdrByName (lpFile, &idsh, ".idata"))
  return 0;
 
 /* 提取所有的输入模块,模块名以0结束0 */
 while (pid->dwRVAModuleName)
 {
  /* pData节身 + (模块名偏移地址 - 节身偏移地址) = 模块名实际内存地址 */
  pModule[nCnt] = (char *)(pData + (pid->dwRVAModuleName - idsh.VirtualAddress));
  /* 为字符串的绝对偏移量分配缓冲区。 */
  nSize += strlen (pModule[nCnt]) + 1;
  /* 增量到下一个输入目录项。*/
  pid++;
  nCnt++;
 }
 /* 复制所有字符串到堆内存的一个块当中。 */
 *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;
}

// 导入表(.data)取特定模块的函数名 dwRVAFunctionNameList
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;
 
 /* 取节头内存地址 */
 if (!GetSectionHdrByName (lpFile, &idsh, ".idata"))
  return 0;
 
 /* 取节身内存地址 */
 pid = (PIMAGE_IMPORT_MODULE_DIRECTORY)ImageDirectoryOffset
  (lpFile, IMAGE_DIRECTORY_ENTRY_IMPORT);
 
  /*
  原句:(DWORD)pid + pid->dwRVAModuleName - idsh.VirtualAddress
  拆开后dwBase的值供其他地方使用
 */
 dwBase = ((DWORD)pid - idsh.VirtualAddress);
 /* 找出特定模块(pszModule)名的pid 函数名列表以0结束 */
 while (pid->dwRVAModuleName && strcmp (pszModule, (char *)(pid->dwRVAModuleName+dwBase)))
  pid++;
 
 /* 如果找不到模块则退出。 */
 if (!pid->dwRVAModuleName)
  return 0;
 
 /* 计算函数名的数量以及字符串的长度。 */
 dwFunction = pid->dwRVAFunctionNameList;
 /*
  (dwFunction + dwBase)函数名偏移量的实际地址
  (dwFunction + dwBase) + dwBase 因为函数名本来只是偏移量,所以要再计算一次才得出函数名的实际地址
 */
 while (dwFunction && *(DWORD *)(dwFunction + dwBase) && *(char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2))
 {
  nSize += strlen ((char *)((*(DWORD *)(dwFunction + dwBase)) + dwBase+2)) + 1;
  dwFunction += 4;
  nCnt++;
 }
 
 /* 为函数名在堆中分配内存。 */
 *pszFunctions = HeapAlloc (hHeap, HEAP_ZERO_MEMORY, nSize);
 psz = *pszFunctions;
 
 /* 复制函数名到内存指针。 */
 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;
}