吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7411|回复: 10
收起左侧

[调试逆向] Import Table And Export Table [Review]

[复制链接]
JoyChou 发表于 2014-3-18 23:02
本帖最后由 JoyChou 于 2014-3-22 21:32 编辑

万地高楼平地起。

夯实基础,复习下基础的东西。

0x1. 输入表

一、什么是输入
可执行文件使用来自于其他dll的代码或数据时,称为输入。

二、函数调用

我们都知道,dll调用有两种方式。分别为隐式和显示。
隐式即PE加载器替我们实现利用LoadLibrary和GetProcAdddress获取API地址,
我们直接调用类似MessageBox 的API即可。
显示即我们自己写LoadLibrary和GetProcAdddress来获取dll中导出的API地址。

三、输入表结构

在NT头偏移0x80处就是IMAGE_IMPORT_DESCRIPTOR结构
编程的可以时候可以通过下面的代码获得IID的指针。

[C++] 纯文本查看 复制代码
PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)(dwImageBase + \
        pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);



每一个dll对应一个IID结构。其结构如下

[C++] 纯文本查看 复制代码
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            
        DWORD   OriginalFirstThunk;         
    } DUMMYUNIONNAME;
    DWORD   TimeDateStamp;                                            
    DWORD   ForwarderChain;               
    DWORD   Name;
    DWORD   FirstThunk;                  
} IMAGE_IMPORT_DESCRIPTOR;



成员介绍:
OriginalFirstThunk:指向INT(Import Name Tabe)
Name:指向dll的名字
FirstThunk:指向IAT(Impot Address Table)

INT和IAT都是IMAGE_THUNK_DATA结构体数组,这个数组实质就是一个DWORD类型,不同时候有着不同的含义。我们可以把这个结构体直接看成是一个DWORD的值。

[C++] 纯文本查看 复制代码
IMAGE_THUNK_DATA STRUC
    union u1
        ForwarderString DWORD ? ; 指向一个转向者字符串的RVA
        Function DWORD ? ; 被输入的函数的内存地址
        Ordinal DWORD ? ; 被输入的API 的序数值
        AddressOfData DWORD ? ; 指向 IMAGE_IMPORT_BY_NAME
    ends
IMAGE_THUNK_DATA ENDS


比较重要的是:

1. 当IMAGE_THUNK_DATA最高位为1时,表示函数以序号方式输入(MFC模块就是这样),低31位被看作是一个函数序号。

1.jpg


2.当IMAGE_THUNK_DATA最高位为0时,调试函数以字符串类型的函数名方式输入,此时,DWORD的值指向一个IMAGE_IMPORT_BY_NAME结构。这样就可以找到函数名了。
[C++] 纯文本查看 复制代码
IMAGE_IMPORT_BY_NAME STRUCT
    Hint WORD ? 
    Name BYTE ?
IMAGE_IMPORT_BY_NAME ENDS



四、IAT

为什么有两个并行的指针数组指向IMAGE_IMPORT_BY_NAME结构呢?

第一个由OriginalFirstThunk指向的INT,不可改写
第二个由FirstThunk指向的IAT指向。

PE装载器首先搜索OriginalFirstThunk找到函数名,调用GetProcAddress函数得到函数入口地址,然后用函数入口地址取代FirstThunk指向的IAT。此时IAT就形成了。
如果OriginalFirstThunk为0,那系统就只能根据FirstThunk进行IAT填充。

下面这图,程序是被装载到内存中或者程序被dump下来的IAT。
如果出现在磁盘上,没有运行的话,IAT和INT是一样的内容,即等价的。

2.jpg


代码遍历输入表: 程序的注释可以帮助更加了解输入表结构
[C++] 纯文本查看 复制代码
#include "stdafx.h"
#include <Windows.h>

int main(int argc, char *argv[])
{
    HANDLE hFile = CreateFile(
        argv[1], 
        GENERIC_READ | GENERIC_WRITE, 
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);

    HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE | SEC_IMAGE, 0, 0, NULL);
    DWORD dwImageBase = (DWORD)MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, NULL, NULL, 0);
    // 得到当前基地址
    //DWORD dwImageBase = (DWORD)GetModuleHandle(NULL);
    // 得到DOS头
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwImageBase;
    // 得到NT头
    PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(dwImageBase + pDosHeader->e_lfanew);
    // 得到IID头
    PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)(dwImageBase + \
        pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

    PIMAGE_THUNK_DATA pThunk = NULL;

    printf("-----------Import Table----------");
    while (pIID->Name)
    {
        DWORD n = 0;
        printf("----------------------------------\n");
        printf("DllName: %s\n", pIID->Name + dwImageBase);

        // 有些程序的OriginalFirstThunk为0,所以此时要用到FirstThunk
        if (pIID->OriginalFirstThunk)
        {
            pThunk = (PIMAGE_THUNK_DATA)(dwImageBase + pIID->OriginalFirstThunk);
        }
        else
        {
            pThunk = (PIMAGE_THUNK_DATA)(dwImageBase + pIID->FirstThunk);
        }

        while( *(DWORD*)pThunk)
        {

            // 判断最高位是否为1
            // 如果是1,表示函数以序号方式输入
            // 此时,低31位被看作是函数序号
            if (pThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG32)
            {
                // ul是联合体,随便哪个,值肯定只有一个,只是表示意义不同
                // 像MFC的模块就是以序号输入,只需要和FFFF &运算
                // 因为ul.Ordinal的值都是类似80001D56的值,只有第一位为1,第二三四位都为0
                // 为了程序的可读性,还是写成7FFFFFFFh
                printf("Ordinal = %08X \r\n", pThunk->u1.Ordinal & 0x7FFFFFFF);
            }
            else
            {
                PIMAGE_IMPORT_BY_NAME pFuncName = (PIMAGE_IMPORT_BY_NAME)(pThunk->u1.Function);
                printf("FuncName: %s\n", dwImageBase + pFuncName->Name);
                // 得到IAT的地址,这里必须通过ImageBase
                printf("Addr = %08X\n", pNtHeader->OptionalHeader.ImageBase + pIID->FirstThunk + n);
            }
            pThunk++;
            n += 4;
        }
        pIID++;
    }

    CloseHandle(hFile);
    UnmapViewOfFile((LPVOID)dwImageBase);
    return 0;
}



3.jpg







0x2. 导出表

关于导出表,可以参考这篇文章:http://bbs.pediy.com/showthread.php?t=122632
关于PE文件导出表看上面的文章已经足够了,《加密与解密》上写的不是很清楚。

自己先写一个DLL,代码如下
[C++] 纯文本查看 复制代码
#include "stdafx.h"
#include <stdio.h>

BOOL APIENTRY DllMain( HANDLE hModule, 
                      DWORD ul_reason_for_call, 
                      LPVOID lpReserved)
{
    return TRUE;
}
void fnDll1()
{  
    printf("1\n");
}
void fnDll2()
{  
    printf("2\n");
}

void fnDll3()
{
    printf("3\n");
}



def文件:
[C++] 纯文本查看 复制代码
LIBRARY 
EXPORTS
  fnDll1  @ 3 NONAME  
  fnDll2  @ 4 
  fnDll3  @ 5



解析代码:
[C++] 纯文本查看 复制代码
#include "stdafx.h"
#include <Windows.h>

int main(int argc, char *argv[])
{
    HANDLE hFile = CreateFile(
        argv[1], 
        GENERIC_READ | GENERIC_WRITE, 
        FILE_SHARE_READ,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);
    DWORD dwFileSize = GetFileSize(hFile, NULL);
    HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE | SEC_IMAGE, 0, dwFileSize, NULL);
    if (hMap == NULL)
    {
        CloseHandle(hFile);
        return -1;
    }
    DWORD dwImageBase = (DWORD)MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, NULL, NULL, dwFileSize);
    if (dwImageBase==NULL)
    {
        CloseHandle(hFile);
        CloseHandle(hMap);
        return -1;
    }
    // 得到当前基地址
    //DWORD dwImageBase = (DWORD)GetModuleHandle(NULL);
    // 得到DOS头
    PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwImageBase;
    // 得到NT头
    PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)(dwImageBase + pDosHeader->e_lfanew);
    // 得到IID头

    PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)(dwImageBase + \
        pNtHeader->OptionalHeader.DataDirectory[0].VirtualAddress);


    int nFuncNum = pExportDir->NumberOfFunctions; 
    int nBase = pExportDir->Base; 
    int iNameOrdinalsIndex = -1; 

    // 导出序号表首地址(WORD大小)
    WORD *pNameOrdinalsTable = (WORD *)(dwImageBase + pExportDir->AddressOfNameOrdinals);

    // 导出函数名称数组首地址(设置为DWORD数组)
    DWORD *pNameAddress = (DWORD *)(pExportDir->AddressOfNames + dwImageBase);

    // EAT数组首地址
    DWORD *pRVAFunc = (DWORD *)(pExportDir->AddressOfFunctions + dwImageBase);

    printf("----------------Export Table  Start---------------------\n");
    printf("\n");
    printf("DllName: %s\n", dwImageBase + pExportDir->Name);

    for (int i = 0; i < nFuncNum; i++) 
    {
        BOOL bFind = FALSE;

        for (int j = 0; j < (int)pExportDir->NumberOfNames; j++) 
        {
            // 导出序号 + base基数
            int iIndex = pNameOrdinalsTable[j] + nBase;
            // 如果能找到,就表示以名称和序号,否则只是以序号
            if (iIndex == i + nBase)
            {
                iNameOrdinalsIndex++;
                bFind = TRUE; // 名称+序号
                break;
            }
        }

        if (bFind)
        {
            // 打印函数名字
            printf("序号:%4d\t", i+nBase);
            printf("RVA: 0x%08X\t", pRVAFunc);
            printf("FuncName: %s\n", pNameAddress[iNameOrdinalsIndex] + dwImageBase);
        }

        // EAT中RVA为0就不输出
        else if(pRVAFunc)
        {
            printf("序号:%4d\t", i+nBase);
            printf("RVA: 0x%08X\t", pRVAFunc);
            printf("FuncName: --\n");
        }
    }

    puts("");
    printf("----------------Export Table  End---------------------\n");
    CloseHandle(hFile);
    CloseHandle(hMap);
    UnmapViewOfFile((LPVOID)dwImageBase);

    return 0;
}




自己程序解析的:
2530137555.jpg



lordpe解析的:
4039250158.jpg


peid解析的:
573794840.jpg


可以看到peid的序号是有问题的。
附件: Import Table And Export Table.zip (197.09 KB, 下载次数: 15)



点评

大赞、一定要大赞  发表于 2014-4-22 21:23

免费评分

参与人数 1威望 +1 收起 理由
Hmily + 1 感谢发布原创作品,吾爱破解论坛因你更精彩.

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

h_one 发表于 2014-3-19 20:39
怒顶jc,,,,,,,.好总结
 楼主| JoyChou 发表于 2014-3-22 21:38
xjun 发表于 2014-3-26 22:30
mark 下,写的很好,让小菜懂了很多,膜拜大牛!

点评

id好熟悉呀,大牛  发表于 2014-3-26 22:34
lyliucn 发表于 2014-3-30 17:43
基础,好好学习学习。
www52pojiecn 发表于 2014-4-9 10:56
棒,想评分给高评,没想到超过时间了。
没办法了,只有留言大赞
xxhaishixx 发表于 2014-4-22 21:22
好贴啊~支持支持再支持
头像被屏蔽
vk929495v 发表于 2014-7-21 18:37
提示: 作者被禁止或删除 内容自动屏蔽
mashan2014 发表于 2014-10-19 00:39
感谢分享!!!!!!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-22 16:14

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表