前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PE文件和COFF文件格式分析——导出表的应用——一种插件模型

PE文件和COFF文件格式分析——导出表的应用——一种插件模型

作者头像
方亮
发布2019-01-16 10:27:57
6140
发布2019-01-16 10:27:57
举报
文章被收录于专栏:方亮

        可能在很多人想想中,只有DLL才有导出表,而Exe不应该有导出表。而在《PE文件和COFF文件格式分析——导出表》中,我却避开了这个话题。我就是想在本文中讨论下载Exe中存在导出表的场景。(转载请指明出于breaksoftware的csdn博客

        首先要说的是Exe是可以有导出表的,我用我写的PE分析工具扫描了我电脑上所有文件。发现有导出表的Exe文件还不少。比如chrome.exe。

        还有OllyDBG.EXE。

        我一开始还不能理解为什么要在Exe中搞导出函数。后来查了相关资料,发现这样做是为了方便开发插件,这让我一下焕然大悟。

        现在思考一个过程,我们的Exe程序的逻辑可能需要若干Dll中函数来辅助。如下图

        A.exe需要B.dll、C.dll和D.dll辅助。现在我们要支持插件,那么我们需要提供一些接口函数供插件使用。比如我们要提供B1()、C1()和D1()供插件使用,那如何设计呢?

        最简单的办法就是不做设计,插件要动态LoadLibrary我们的B.dll、C.dll和D.dll,然后把各个函数导出来用。看似很方便,但是如果我们工程不止是3个Dll呢?暴露的函数也不止这三个呢?我想做这个系统的插件的同学想到要Load那么多DLL就会感觉烦!而且还有一个严重的问题,哪天我们想给B.dll改个名字,叫E.dll,那么使用B.dll的插件都不能正常工作了。可以见得这是个非常不稳定的方案,因为它关联的因素太多了。

        那我们收敛一下方案,我们做个空壳DLL(使用《PE文件和COFF文件格式分析——导出表》介绍的类似于Kernel32.dll的AddVectoredExceptionHandler导出方法,这个方法的应用我会在之后写篇文章介绍),该DLL就导出B.dll中B1()、C.dll中C1()和D.dll中D1()的入口地址。然后插件就加载这个DLL,调用该DLL中的方法。如下图

        这样就很好解决了之前的不足。貌似离最佳不远了,但是想想,是不是还可以优化呢?我们这么设计要多维护一个DLL(PluginHelper.dll),这个也就引入了一个不稳定因素。那么这个DLL可以省掉么?省掉后导出的那些函数放哪儿?

        经过考虑,PluginHelper.dll的功能放在哪个DLL文件中都不合适。那只能放在A.exe中了。是的!我们让A.exe导出函数,反正我们A.exe也是要加载B.dll、C.dll和D.dll,这样还可以省下PluginHelper.dll加载如上DLL的过程。现在我写了一个工程,模拟这种插件模型。

        ExeMain是我们的主程序,DllOne和DllTwo是ExeMain需要加载的DLL,它们也提供了插件需要暴露给插件的函数的实现。Plugin是个插件。

       我们先看下DLLOne和DllTwo的导出函数

代码语言:javascript
复制
LIBRARY	"DllOne"
EXPORTS
	Ret1
代码语言:javascript
复制
LIBRARY	"DllTwo"
EXPORTS
	Ret2

        那么在Exe中如何暴露这两个函数呢?看Exe中的代码

代码语言:javascript
复制
typedef int (WINAPI* RetNFunc)();

extern "C" __declspec(dllexport) int MainRet1();
extern "C" __declspec(dllexport) int MainRet2();

int MainRet1(){
    int nRet = 0;
    HMODULE hDllOne = LoadLibraryA("DllOne.dll");
    do {
        if ( NULL == hDllOne ) {
            break;
        }
        RetNFunc pFunc = (RetNFunc)GetProcAddress( hDllOne, "Ret1" );
        nRet = pFunc();
        FreeLibrary( hDllOne );
        hDllOne = NULL;
    } while (0);

    return nRet;
}

int MainRet2(){
    int nRet = 0;
    HMODULE hDllTwo = LoadLibraryA("DllTwo.dll");
    do {
        if ( NULL == hDllTwo ) {
            break;
        }
        RetNFunc pFunc = (RetNFunc)GetProcAddress( hDllTwo, "Ret2" );
        pFunc();
        FreeLibrary( hDllTwo );
        hDllTwo = NULL;
    } while (0);

    return nRet;
}

        我们看下Exe的导出函数表

        至于插件的调用,我这儿不准备搞复杂的设计,我这儿将直接Load插件DLL,并调用DLL中的导出方法(该方法的调用约定是提前确定好的)。调用方法是

代码语言:javascript
复制
typedef void (WINAPI* PluginFunc)();
int _tmain(int argc, _TCHAR* argv[])
{
    HMODULE hPlugin = LoadLibraryA("Plugin.dll");
    do {
        if ( NULL == hPlugin ) {
            break;
        }
        PluginFunc pFunc = (PluginFunc)GetProcAddress( hPlugin, "PluginMain" );
        pFunc();
        FreeLibrary( hPlugin );
        hPlugin = NULL;
    } while (0);
    system("pause");
	return 0;
}

        那么插件该如何写呢?插件的逻辑代码如下

代码语言:javascript
复制
LIBRARY	"Plugin"
EXPORTS
	PluginMain
代码语言:javascript
复制
typedef int (WINAPI* RetNFunc)();

void PluginMain() {
    RetNFunc pFun1 = (RetNFunc)GetProcAddress( GetModuleHandle(NULL), "MainRet1" );
    if ( NULL != pFun1 ) {
        printf( "MainRet1: %d\n", pFun1() );
    }

    RetNFunc pFun2 = (RetNFunc)GetProcAddress( GetModuleHandle(NULL), "MainRet2" );
    if ( NULL != pFun2 ) {
        printf( "MainRet2: %d\n", pFun2());
    }
}

        因为插件DLL已经被Exe加载,所以此处GetModuleHandle(NULL)会得到该进程Exe的模块句柄,GetProcAddress该句柄的导出方法,就可以获得了Exe中导出的函数入口地址了。

        看!这样的插件模型是不是非常简单而且紧凑而且易用。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2012年09月06日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档