吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 7501|回复: 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++] 纯文本查看 复制代码
1
2
PIMAGE_IMPORT_DESCRIPTOR pIID = (PIMAGE_IMPORT_DESCRIPTOR)(dwImageBase + \
        pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);



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

[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
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++] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
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++] 纯文本查看 复制代码
1
2
3
4
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++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#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++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
#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++] 纯文本查看 复制代码
1
2
3
4
5
LIBRARY
EXPORTS
  fnDll1  @ 3 NONAME 
  fnDll2  @ 4
  fnDll3  @ 5



解析代码:
[C++] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#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, 2025-4-3 01:24

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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