在DLL中调用未导出的函数

时间:2010-05-27 03:09:13

标签: c++ dll assembly function

我有一个加载DLL的程序,我需要调用它包含的一个非导出函数。通过在调试器中搜索或其他方式,我有什么办法可以做到这一点吗?在有人要求之前,是的,我有原型和功能的东西。

6 个答案:

答案 0 :(得分:10)

是的,至少有一点,但这不是一个好主意。

在C / C ++中,所有函数指针都是内存中的地址。所以如果你能以某种方式找到这个函数的地址,你可以调用它。

让我问一些问题,你怎么知道这个DLL包含这个功能?你有源代码吗?否则我不知道你怎么知道这个函数存在或者是否可以安全地调用。但是如果你有源代码,那么只需公开该函数。如果DLL编​​写器没有公开这个函数,他们就不会期望你调用它,并且可以随时更改/删除实现。

除了警告之外,如果您有调试符号或MAP file可以找到DLL中的偏移量,您可以找到函数地址。如果除了DLL之外没有任何东西,那么就无法知道DLL中该函数的位置 - 它不存储在DLL本身中。

获得偏移后,您可以将其插入代码中,如下所示:

const DWORD_PTR funcOffset = 0xDEADBEEF;
typedef void (*UnExportedFunc)();

....
void CallUnExportedFunc() {
     // This will get the DLL base address (which can vary)
     HMODULE hMod = GetModuleHandle("My.dll"); 
     // Calcualte the acutal address 
     DWORD_PTR funcAddress = (DWORD_PTR)hMod + funcOffset;
     // Cast the address to a function poniter
     UnExportedFunc func = (UnExportedFunc)funcAddress;
     // Call the function
     func();
}

还要意识到这个函数的偏移会在每次重建DLL时改变,所以这非常脆弱,让我再说一遍,不是一个好主意。

答案 1 :(得分:8)

我意识到这个问题相当古老,但shf301在这里有正确的想法。我要添加的唯一内容是在目标库上实现模式搜索。如果您有IDAOllyDbg,则可以搜索该函数并查看该函数起始地址周围的二进制/十六进制数据。

在大多数情况下,会出现某种很少改变的二进制签名。签名可能包含可能在构建之间发生变化的通配符,但最终在搜索此模式时应至少有一次成功命中,除非构建之间发生了极大的变化(此时,您可以为此找出新的签名)特别版)。


实现二进制模式搜索的方式如下:

bool bCompare(const PBYTE pData, const PBYTE bMask, const PCHAR szMask)
{
    for(;*szMask;++szMask,++pData,++bMask)
            if(*szMask=='x' && *pData!=*bMask)  
                    return 0;
    return (*szMask) == NULL;
}

DWORD FindPattern(DWORD dwAddress, DWORD dwLen, PBYTE bMask, PCHAR szMask)
{
    for(DWORD i=0; i<dwLen; i++)
            if (bCompare((PBYTE)(dwAddress+i),bMask,szMask))  
                    return (DWORD)(dwAddress+i);
    return 0;
}


用法示例:

typedef void (*UnExportedFunc)();

//...
void CallUnExportedFunc()
{
    // This will get the DLL base address (which can vary)
    HMODULE hMod = GetModuleHandleA( "My.dll" );

    // Get module info
    MODULEINFO modinfo = { NULL, };
    GetModuleInformation( GetCurrentProcess(), hMod, &modinfo, sizeof(modinfo) );

    // This will search the module for the address of a given signature
    DWORD dwAddress = FindPattern(
        hMod, modinfo.SizeOfImage,
        (PBYTE)"\xC7\x06\x00\x00\x00\x00\x89\x86\x00\x00\x00\x00\x89\x86",
        "xx????xx????xx"
    );

    // Calculate the acutal address 
    DWORD_PTR funcAddress = (DWORD_PTR)hMod + dwAddress;

    // Cast the address to a function poniter
    UnExportedFunc func = (UnExportedFunc)funcAddress;

    // Call the function
    func();
}


这种方式的工作方式是通过GetModuleHandle传入加载库的基地址,指定要搜索的长度(以字节为单位),要搜索的二进制数据,以及指定二进制文件的哪些字节的掩码字符串有效('x'),哪些是被忽略的('?')。然后,该函数将遍历已加载模块的内存空间,搜索匹配项。在某些情况下,可能会有多个匹配,在这种情况下,明智的做法是使您的签名更加明显,只有一个匹配。

同样,你需要在反汇编应用程序中进行初始二进制搜索,以便知道这个签名是什么,但是一旦你有了这个,那么这个方法应该比手动找到函数offset 更好一点时间目标已建成。希望这会有所帮助。

答案 2 :(得分:1)

如果未导出所需的功能,则它不会出现在导出地址表中。假设Visual Studio用于生成此DLL并且您具有其关联的PDB(程序数据库)文件,那么您可以使用Microsoft的DIA(调试接口访问)API来按名称或大约通过签名。

从PDB获得功能(符号)后,您还将拥有其RVA(相对虚拟地址)。您可以将RVA添加到已加载模块的基址,以确定存储函数的内存中的绝对虚拟地址。然后,您可以通过该地址进行函数调用。


或者,如果这只是您需要做的一次性事情(即您不需要程序化解决方案),您可以使用Debugging Tools for Windows工具包中的windbg.exe附加到您的进程并发现您关心的功能的地址。在WinDbg中,您可以使用x命令在模块中“检查符号”。

例如,您可以x mymodule!*foo*查看名称中包含“foo”的所有函数。只要为模块加载符号(PDB),这也将显示非导出功能。使用.hh x获取x命令的帮助。

答案 3 :(得分:0)

如果引用的库没有显式地导出其对象(class / func),我担心没有“安全”的方法。因为你不知道代码存储器中所需的对象在哪里。

但是,通过使用RE工具,您可以在库中找到感兴趣对象的偏移量,然后将其添加到任何已知的导出对象地址以获取“实际”内存位置。之后,准备一个函数原型等,并转换到您的本地结构中以供使用。

答案 4 :(得分:0)

执行此操作的最常用方法(并且它仍然是一个坏主意,正如其他人已经指出的那样)是在加载后在运行时扫描DLL代码,并在该函数中查找已知的,唯一的代码段,然后使用类似于shf301的答案中的代码来调用它。如果你知道DLL永远不会改变,那么基于确定DLL中的偏移量的任何解决方案都应该有效。

要找到代码的唯一部分,请使用反汇编程序来反汇编DLL,除了汇编语言助记符之外还可以显示机器代码(我想不出任何不会这样做的东西)并注意呼叫和jmp指令。

我实际上必须做类似的事情才能将二进制补丁应用到DOS exe;这是一个bug修复,并且代码没有受到版本控制,所以这是修复它的唯一方法。

顺便说一下,我真的很想知道为什么你需要这个。

答案 5 :(得分:0)

即使你能找到函数地址,调用编译器创建的函数通常也不安全,因为编译器认为它正在创建一个&#34; private&#34;仅供内部使用的功能。

使用link-time-optimization enabled的现代编译器可以创建一个函数的专用版本,该函数只执行特定调用者需要它执行的操作。

不要认为看起来像你想要的功能的机器代码块实际上遵循标准的ABI并实现了源代码所说的一切。

在gcc的情况下,它确实对函数的特殊版本使用特殊名称,这些函数不是内联的,而是利用来自多个调用者的特殊情况(如常量传播)。

e.g。在此objdump -drwC输出中(其中-C为demangle):

42944c:e8 cf 13 0e 00致电50a820   429451:48 8b 7b 48 mov rdi,QWORD PTR [rbx + 0x48]   429455:48 89 ee mov rsi,rbp   429458:e8 b3 10 0e 00致电50a510

gcc发出调用同一函数的两个不同克隆的代码,专门用于两个不同的编译时常量。 (这来自http://endless-sky.github.io/,它迫切需要LTO,因为它的XY位置类的简单访问器函数都在Point.cpp中,而不是Point.h中,所以它们只能由LTO内联。)

LTO甚至可以制作.lto_priv静态版本的数据:例如

mov    rcx,QWORD PTR [rip+0x412ff7]        # 83dbe0 <_ZN12_GLOBAL__N_116playerGovernmentE.lto_priv.898>

因此,即使您找到了一个看起来像您想要的功能,从新地方调用它也可能违反了Link-Time-Optimization利用的假设。