首页
社区
课程
招聘
[原创]PE文件之旅(C语言描述) 第二篇 DLL的秘密 -- 输出表
发表于: 2005-1-6 22:47 10123

[原创]PE文件之旅(C语言描述) 第二篇 DLL的秘密 -- 输出表

2005-1-6 22:47
10123

大家好,现在继续我们的PE文件之旅第二篇, DLL的秘密 -- 输出表。
        首先看一下预备知识。输出表在<<加密解密II>>等资料中并没有太多的笔墨,居我的接触,它确实比较直观,没有象输入表那样复杂的结构,所以这也可能是介绍的少的缘故。关于输出表的结构可以参考<<Matt Pietrek's PE Tutorial>>。
       
        首先定位输出表。在IMAGE_OPTIONAL_HEADER.DataDirectory结构数组中,注意这也是个结构数组。它的每一个元素都是IMAGE_DATA_DIRECTORY结构。该结构定义如下:

        typedef struct _IMAGE_DATA_DIRECTORY {
           DWORD   VirtualAddress;
           DWORD   Size;
        } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
DataDirectory结构数组的第一个元素描述的就是输出表,第二个元素描述的是输入表,呵呵,这个是我们下一篇研究对象。所以,这里的VirtualAddress就是输出表的偏移地址,Size就不用说了,输出表的大小。

注意:以下代码的一些变量在前一篇文章<<PE文件之旅 -- Sections>>中已经定义过,这里不再重新定义,请大家参考前面的文章。
核心代码:
[CODE]// 首先定位IMAGE_OPTIONAL_HEADER32结构
IMAGE_OPTIONAL_HEADER32 myOptionalHeader;
fseek(pFile, (e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER)), SEEK_SET);     // 如此定位的原因请参考PE文件结构图
fread(&myOptionalHeader, sizeof(IMAGE_OPTIONAL_HEADER32), 1, pFile);

// 数据目录表的第一个元素为输出表
printf("输出表的偏移地址:%08x\n", myOptionalHeader.DataDirectory[0].VirtualAddress);
printf("输出表的大小:%04x\n", myOptionalHeader.DataDirectory[0].Size);

DWORD dwFileOffset;
if (myOptionalHeader.DataDirectory[0].VirtualAddress != 0)
{
        // OnConvertVAToRawA()函数是进行VirtualAddress到文件偏移转换的函数,具体代码见下面
        dwFileOffset = OnConvertVAToRawA(myOptionalHeader.DataDirectory[0].VirtualAddress);
        if (-1 == dwFileOffset)
        {
                printf("输出表定位错误\n");
        }
        else
        {
                printf("输出表的文件偏移为:%08x\n", dwFileOffset);

                // ------------------ 这里已经获得了输出表的文件偏移,输出表是由IED数组开始 ------------------ //
                int nNumberOfFunctions = 0;
                int nNumberOfNames     = 0;
                // 由输出表的偏移(VA)计算出文件偏移(RawAddress),然后定位到这里就是输出表了
                // 输出表由IMAGE_EXPORT_DIRECTORY结构开始,
                IMAGE_EXPORT_DIRECTORY *pIED = (IMAGE_EXPORT_DIRECTORY *)malloc(sizeof(IMAGE_EXPORT_DIRECTORY)); // Memory 17
                fseek(pFile, dwFileOffset, SEEK_SET);
                fread(pIED, sizeof(IMAGE_EXPORT_DIRECTORY), 1, pFile);

                printf("IMAGE_EXPORT_DIRECTORY结构如下: \n");
                printf("Characteristics:%08x(没有用途,总是为0)\n", pIED->Characteristics);
                printf("TimeDateStamp:%08x(文件被产生时刻)\n", pIED->TimeDateStamp);
                printf("MajorVersion:%08x(没有用途,总是为0)\n", pIED->MajorVersion);
                printf("MinorVersion:%08x(没有用途,总是为0)\n", pIED->MinorVersion);
                printf("Name:%08x(RVA,指向一个DLL文件名称)\n", pIED->Name);

                // 读取DLL名称
                char chDllName[64] = {0};   // 这里多留点空间给函数名字,防止出错,我所见过的函数名称最长也就30个char,呵呵
                // 将指向DLL文件名称的VA转化为文件偏移
                DWORD dwTemp = OnConvertVAToRawA(pIED->Name);
                // 读取DLL文件名称
                fseek(pFile, dwTemp, SEEK_SET);
                fread(chDllName, 64, 1, pFile);
                printf("DLL文件名称为:%s\n", chDllName);

                printf("Base:%08x(起始序号)\n", pIED->Base);
                // 被此模块输出的函数的起始序号
                WORD wBase = (WORD)pIED->Base;

                printf("NumberOfFunctions:%d(输出函数个数)\n", pIED->NumberOfFunctions);
                // AddressOfFunctions 数组(稍后描述)中的元素个数。此值同时也是输出函数的个数。
                // 通常这个值和 NumberOfNames 字段相同,但也可以不同。
                nNumberOfFunctions = pIED->NumberOfFunctions;

                printf("NumberOfNames:%d(以名称输出的函数个数)\n", pIED->NumberOfNames);
                // AddressOfNames 数组(稍后描述)中的元素个数。此值表示以名称输出的函数个数。
                // 通常(但不总是)和输出函数的总数相同。
                nNumberOfNames = pIED->NumberOfNames;

                // 这是一个 RVA (Relative Virtual Address )值,指向一个由函数地址所构成的数组。
                // 我所谓的函数地址是指此一模块中的每一个输出函数的进入点的 RVA 值。
                printf("AddressOfFunctions:%08x(RVA, 指向一个由函数地址构成的数组)\n", pIED->AddressOfFunctions);
                // 这里将其转化为文件的偏移地址
                DWORD dwAddressOfFunctions = OnConvertVAToRawA(pIED->AddressOfFunctions);

                // 这是一个 RVA 值,指向一个由字符串指针所构成的数组。
                // 字符串的内容是此一模块中的每一个"以名称输出的输出函数"的名称
                printf("AddressOfNames:%08x(RVA, 指向一个由字符串指针所构成的数组)\n", pIED->AddressOfNames);
                // 转化为文件偏移地址
                DWORD dwNameAddressOfFunctions = OnConvertVAToRawA(pIED->AddressOfNames);

                // 这是一个 RVA 值,指向一个 WORD 数组。
                // WORD 的内容是此一模块中的每一个"以名称输出的输出函数"的序号。别忘了要加上 Base 字段中的起始序号。
                printf("AddressOfNameOrdinals:%08x(RVA, 指向一个DWORD数组)\n", pIED->AddressOfNameOrdinals);
                // 转化为文件偏移地址
                DWORD dwAddressOfNameOrdinals = OnConvertVAToRawA(pIED->AddressOfNameOrdinals);

                if (pIED != NULL)             // release Memory 17
                {
                        free(pIED);
                        pIED = NULL;
                }

                // 动态开辟内存用来存储函数的VirtualAddress
                DWORD *pdwAddressOfFunctions = (DWORD *)calloc(nNumberOfFunctions, sizeof(DWORD));     // Memory 18
                fseek(pFile, dwAddressOfFunctions, SEEK_SET);
                fread(pdwAddressOfFunctions, sizeof(DWORD), nNumberOfFunctions, pFile);
                // 动态开辟内存用来存储函数的RawAddress
                DWORD *pRowAddressOfFunctions = (DWORD *)calloc(nNumberOfFunctions, sizeof(DWORD));    // Memory 19
                for (int i = 0; i < nNumberOfFunctions; i++, pdwAddressOfFunctions++, pRowAddressOfFunctions++)
                {
                        *pRowAddressOfFunctions = OnConvertVAToRAwA(*pdwAddressOfFunctions);
                }
                // 恢复指针
                pdwAddressOfFunctions  -= nNumberOfFunctions;
                pRowAddressOfFunctions -= nNumberOfFunctions;

                // 动态开辟内存用来存储函数名称地址
                DWORD *pdwNameAddressOfFunctions = (DWORD *)calloc(nNumberOfNames, sizeof(DWORD));    // Memory 20
                fseek(pFile, dwNameAddressOfFunctions, SEEK_SET);
                fread(pdwNameAddressOfFunctions, sizeof(DWORD), nNumberOfNames, pFile);
                // VA TO RAWA
                for (i = 0; i < nNumberOfNames; i++, pdwNameAddressOfFunctions++)
                {
                        *pdwNameAddressOfFunctions = OnConvertVAToRowA(*pdwNameAddressOfFunctions);
                }
                // 恢复指针
                pdwNameAddressOfFunctions -= nNumberOfNames;

                // 二重指针,用来存储函数名称指针,因为函数个数不固定
                DWORD **pFunctionsName = (DWORD **)calloc(nNumberOfNames, sizeof(char *));            // Memory 21
                for (i = 0; i < nNumberOfNames; i++, pFunctionsName++)
                {
                        // 这块内存是用来存放函数名称的
                        char *pchFunctionName = (char *)malloc(sizeof(char) * 64);
                        *pFunctionsName = (DWORD *)pchFunctionName;
                }
                // 恢复指针
                pFunctionsName -= nNumberOfNames;

                // 读出函数名称
                for (i = 0; i < nNumberOfNames; i++, pdwNameAddressOfFunctions++, pFunctionsName++)
                {
                        fseek(pFile, *pdwNameAddressOfFunctions, SEEK_SET);
                        fread(*pFunctionsName, 64, 1, pFile);
                }
                // 恢复指针
                pdwNameAddressOfFunctions -= nNumberOfNames;
                pFunctionsName            -= nNumberOfNames;

                if (pdwNameAddressOfFunctions != NULL)                          // release Memory 20
                {
                        free(pdwNameAddressOfFunctions);
                        pdwNameAddressOfFunctions = NULL;
                }

                // 读出函数的序号
                WORD *pwFunctionsOrdinals = (WORD *)calloc(nNumberOfNames, sizeof(WORD));                // Memory 22
                fseek(pFile, dwAddressOfNameOrdinals, SEEK_SET);
                for (i = 0; i < nNumberOfNames; i++, pwFunctionsOrdinals++)
                {
                        fread(pwFunctionsOrdinals, sizeof(WORD), 1, pFile);
                }
                // 恢复指针
                pwFunctionsOrdinals -= nNumberOfNames;

                //     这里需要注意的是AddressOfFunctions所指的数组。这是一个DWORD数组,每一个DWORD都是
                // 一个输出函数的RVA地址。每一个输出函数的序号都与函数在此数组中的位置一致。假使起始序号
                // 为1,那么输出序号为1者其函数地址在此数组中排在第一个位置。输出序号为2者其函数地址在此
                // 函数地址中则排在第二个位置,依次类推。

                // 由以上可知,我们必须对AddressOfNameOrdinals所指向的WORD数组进行升序排序,同时进行的还有
                // 函数名称存放位置的排序,因为Ordinals和函数名称存放的位置是对应的。

                // 这里采用效率较高的快速排序(详细代码见下面)
                QuickSort(pwFunctionsOrdinals, pFunctionsName, nNumberOfNames);

                // 打印信息
                for (i = 0; i < nNumberOfNames; i++, pwFunctionsOrdinals++, pFunctionsName++, pRowAddressOfFunctions++, pdwAddressOfFunctions++)
                {
                        printf("Ordinals: %04x\n", *pwFunctionsOrdinals + wBase);
                        printf("函数%d: %s\n", i + 1, *pFunctionsName);
                        printf("RowAddress: %08x\n", *pRowAddressOfFunctions);
                        printf("VirtulAddress: %08x\n", *pdwAddressOfFunctions);
                }

                // 恢复指针
                pwFunctionsOrdinals    -= nNumberOfNames;
                pFunctionsName         -= nNumberOfNames;
                pRowAddressOfFunctions -= nNumberOfNames;
                pdwAddressOfFunctions  -= nNumberOfNames;

                if (pdwAddressOfFunctions != NULL)                  // release Memory 18
                {
                        free(pdwAddressOfFunctions);
                        pdwAddressOfFunctions = NULL;
                }

                if (pRowAddressOfFunctions != NULL)                 // release Memory 19
                {
                        free(pRowAddressOfFunctions);
                        pRowAddressOfFunctions = NULL;
                }

                for (i = 0; i < nNumberOfNames; i++, pFunctionsName++)    // release Memory 21
                {
                        if (*pFunctionsName != NULL)
                        {
                                free(*pFunctionsName);
                                *pFunctionsName = NULL;
                        }
                }
                pFunctionsName -= nNumberOfNames;

                if (pFunctionsName != NULL)                              // release prince 21
                {
                        free(pFunctionsName);
                        pFunctionsName = NULL;
                }

                if (pwFunctionsOrdinals != NULL)                         // release prince 22
                {
                        free(pwFunctionsOrdinals);
                        pwFunctionsOrdinals = NULL;
                }
        }
}
else
{
        printf("此文件无输出表\n");
}

// VA到RAWA的转换程序(原理请参考<<加密解密II>>中关于输出表的部分)
DWORD OnConvertVAToRawA(DWORD dwFileOffset)
{
        IMAGE_SECTION_HEADER *pmySectionHeader = (IMAGE_SECTION_HEADER *)calloc(nSectionCount, sizeof(IMAGE_SECTION_HEADER)); // prince 14
        fseek(pFile, (e_lfanew + 4 + sizeof(IMAGE_FILE_HEADER) + sizeof(IMAGE_OPTIONAL_HEADER32)), SEEK_SET);
        fread(pmySectionHeader, sizeof(IMAGE_SECTION_HEADER), nSectionCount, pFile);

        DWORD dwFilePos;
        DWORD dwOffset;
        DWORD *pdwVA   = (DWORD *)malloc(sizeof(DWORD) * nSectionCount);   // prince 15
        DWORD *pdwRowA = (DWORD *)malloc(sizeof(DWORD) * nSectionCount);   // prince 16
        for (int i = 0; i < nSectionCount; i++, pmySectionHeader++, pdwVA++, pdwRowA++)
        {
                *pdwVA   = pmySectionHeader->VirtualAddress;
                *pdwRowA = pmySectionHeader->PointerToRawData;
        }

        pmySectionHeader -= nSectionCount;

        pdwVA                        -= nSectionCount;
        pdwRowA                        -= nSectionCount;

        for (i = 0; i < nSectionCount; i++, pdwVA++, pdwRowA++)
        {
                if ((dwFileOffset >= *pdwVA) && (dwFileOffset < *(pdwVA + 1)))
                {
                        dwOffset  = *pdwVA - *pdwRowA;
                        dwFilePos = dwFileOffset - dwOffset;
                       
                        pdwVA   -= i;
                        pdwRowA -= i;
                        if (pdwVA != NULL)                       // release prince 15
                        {
                                free(pdwVA);
                                pdwVA = NULL;
                        }
                        if (pdwRowA != NULL)                     // release prince 16
                        {
                                free(pdwRowA);
                                pdwRowA = NULL;
                        }
                       
                        if (pmySectionHeader != NULL)            // release prince 14
                        {
                                free(pmySectionHeader);
                                pmySectionHeader = NULL;
                        }
                       
                        return dwFilePos;
                }
        }
        pdwVA   -= nSectionCount;
        pdwRowA -= nSectionCount;

        if (pmySectionHeader != NULL)            // release prince 14
        {
                free(pmySectionHeader);
                pmySectionHeader = NULL;
        }

        if (pdwVA != NULL)                       // release prince 15
        {
                free(pdwVA);
                pdwVA = NULL;
        }

        if (pdwRowA != NULL)                     // release prince 16
        {
                free(pdwRowA);
                pdwRowA = NULL;
        }

        return -1;
}

void QuickSort(WORD *pwSourceData1, DWORD **pdwSourceData2, int nNumberOfArray)
{
        SortRun(pwSourceData1, pdwSourceData2, 0, nNumberOfArray - 1);
}

void SortRun(WORD *pwSourceData1, DWORD **pdwSourceData2, int nLeft, int nRight)
{
        WORD wMiddle, wTemp;
        DWORD *pdwTemp;
        int i, j;

        wMiddle  = pwSourceData1[(nLeft + nRight) / 2];
        i = nLeft;
        j = nRight;

        while (i < j)
        {
                while ((pwSourceData1[i] < wMiddle) && (i < j))
                {
                        i++;
                }
                while ((pwSourceData1[j] > wMiddle) && (j > i))
                {
                        j--;
                }

                if (i <= j)
                {
                        wTemp = pwSourceData1[i];
                        pwSourceData1[i] = pwSourceData1[j];
                        pwSourceData1[j] = wTemp;

                        pdwTemp = *(pdwSourceData2 + i);
                        *(pdwSourceData2 + i) = *(pdwSourceData2 + j);
                        *(pdwSourceData2 + j) = pdwTemp;

                        i++;
                        j--;
                }
        }

        // 如果左边还有元素,对左边进行快速排序
        if (nLeft < j)
        {
                SortRun(pwSourceData1, pdwSourceData2, 0, j);
        }

        // 如果右边还有元素,对右边进行快速排序
        if (nRight > i)
        {
                SortRun(pwSourceData1, pdwSourceData2, i, nRight);
        }
}

        呼~,好累啊!也感谢你坚持看完。接下来的一篇就是大家最关心,也是最重要的输出表了,敬请关注下一篇:PE文件之旅 -- 加壳与脱壳的战场 输入表。

                                                                 prince 2005.01.06
写好的WIN32程序:
附件:PEExport.rar


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 7
支持
分享
最新回复 (15)
雪    币: 10619
活跃值: (2314)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
爽哦
2005-1-6 22:51
0
雪    币: 159
活跃值: (339)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
3
关注中...
2005-1-6 23:48
0
雪    币: 392
活跃值: (909)
能力值: ( LV9,RANK:690 )
在线值:
发帖
回帖
粉丝
4
风格很眼熟啊,呵呵
2005-1-7 09:13
0
雪    币: 61
活跃值: (160)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
5
支持先!!!
2005-1-7 09:24
0
雪    币: 603
活跃值: (617)
能力值: ( LV12,RANK:660 )
在线值:
发帖
回帖
粉丝
6
最初由 cyclotron 发布
风格很眼熟啊,呵呵


你怀疑我是抄袭?
2005-1-7 10:13
0
雪    币: 12323
活跃值: (4040)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
谢谢提供!
2005-1-7 10:58
0
雪    币: 392
活跃值: (909)
能力值: ( LV9,RANK:690 )
在线值:
发帖
回帖
粉丝
8
最初由 prince 发布


你怀疑我是抄袭?

不是这个意思啦~~~
当我没说。。。
2005-1-7 11:24
0
雪    币: 223
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
呵呵 好像见过啊
2005-1-7 11:49
0
雪    币: 603
活跃值: (617)
能力值: ( LV12,RANK:660 )
在线值:
发帖
回帖
粉丝
10
最初由 cyclotron 发布

不是这个意思啦~~~
当我没说。。。


我花了将近一个月的零散时间,写了1000多行代码,而且之前我想找这样的现成的代码没有找到所以才写的,我认为我的努力使我提高了很多,也增长了很多的知识,所以想把喜悦的成果分享给大家,让跟我有同样想法的小菜们得到他们想要的东西,能够把破解的知识用于编程,这才是追求顶尖技术的Top Cracker所应该拥有的素质。That's All.

PS: 我会继续写下去的。
2005-1-7 11:49
0
雪    币: 97697
活跃值: (200824)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
11
支持!!!
2005-1-7 17:01
0
雪    币: 6075
活跃值: (2236)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
12
支持
其实我觉得把一段内存数据用框框画出来指示一下更有助于理解
2005-1-8 08:19
0
雪    币: 603
活跃值: (617)
能力值: ( LV12,RANK:660 )
在线值:
发帖
回帖
粉丝
13
最初由 forgot 发布
支持
其实我觉得把一段内存数据用框框画出来指示一下更有助于理解


文件结构在内存中的样子好多资料里都画出来了,我自己开辟的内存倒是应该画一下。
其实只是查看的话,可以读出数据就显示出来,这样就不用开辟那么多内存来保存数据了。
2005-1-8 20:41
0
雪    币: 201
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
感谢prince的无私精神
2005-6-29 14:12
0
雪    币: 229
活跃值: (168)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
15
大力支持!
2005-6-29 14:54
0
雪    币: 240
活跃值: (78)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
16
有时间的话彻底看看这个
http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx
就什么都清楚了!
2005-6-29 22:19
0
游客
登录 | 注册 方可回帖
返回
//