判断调用者是从EXE还是DLL中调用的

4
我需要确定调用者代码是来自EXE还是DLL。

DLL

:动态链接库
#ifdef DLL_EXPORTS
    __declspec(dllexport) void say_hello();
    __declspec(dllexport) void getCurrentModuleName();
#else
    __declspec(dllimport) void say_hello();
    __declspec(dllexport) void getCurrentModuleName();
#endif

#include <cstdio>
#include <windows.h>
#include <Dbghelp.h>
#include <iostream>
#include <tchar.h>
#include "dll.h"
#include "Psapi.h"

__declspec(naked) void *GetStackPointer()
{
    __asm
    {
        mov eax, esp
        ret
    }
}

void getCurrentModuleName()
{
    BOOL result = SymInitialize(GetCurrentProcess(), NULL , TRUE);
    DWORD64 dwBaseAddress = SymGetModuleBase64(GetCurrentProcess(), (DWORD64)GetStackPointer());
    TCHAR szBuffer[50];
    GetModuleBaseName(GetCurrentProcess(), (HMODULE) dwBaseAddress, szBuffer, sizeof(szBuffer));
    std::wcout << _T("--->") << szBuffer << std::endl;
}

void say_hello() {
    getCurrentModuleName();
}

EXE

#include <windows.h>
#include <cstdio>
#include "dll.h"

int main() {
    printf ("ENTERING EXE CODE...\n");

    getCurrentModuleName();

    printf ("ENTERING DLL CODE...\n");

    say_hello();

    getchar();
}

这里是输出结果。
ENTERING EXE CODE...
--->exe.exe
ENTERING DLL CODE...
--->exe.exe

I wish I can get

ENTERING EXE CODE...
--->exe.exe
ENTERING DLL CODE...
--->dll.dll

作为最后一个调用者的代码来自于DLL本身(例如,在DLL中的say_hello函数)。
有没有办法我可以实现这个?
5个回答

3

GetStackAddress返回的是ESP的值,它是对堆栈的引用。堆栈是针对每个线程分配的,与进程中加载的任何模块无关。

你需要做的是从堆栈中提取返回地址的值-它将是调用模块中的一个地址。

考虑到函数中通常的前缀代码为:

push ebp
mov  ebp,esp
sub  esp, bytes_of_local_variables

ESP的值可能会有些随机,但[EBP]应该指向前一个EBP,而[EBP+4]应该指向当前帧的返回地址。

因此,你可以尝试这样做:

__declspec(naked) void *GetReturnAddressAssumingStandardFramePointers()
{
    __asm
    {
        mov eax, [ebp+4]
        ret
    }
}

请确保调用该函数的功能不使用 /Oy 编译。


但是,naked函数不插入任何prolog或epilog代码。我非常确定你想要使用mov eax,[esp]。 (即,FPO对于naked函数无关)。 - Alex Budovski
我们使用 naked 来确保 GetReturnAddress... 不插入任何序言代码:我们感兴趣的序言代码是 GetCurrentModuleName 的:我们想要找出是谁调用了 GetCurrentModuleName,而不是谁调用了 GetRAASFP(我们已经知道答案:它总是 GetCurrentModuleName)。 - Chris Becke
由于我无法控制模块,无法确定它们是否编译时省略了帧指针。你有没有可以遍历的便携式方法,而不会基于帧指针的可用性做出假设? - Cheok Yan Cheng
дҪ еҶҷдәҶ getCurrentModuleName - иҝҷдёӘеҮҪж•°и°ғз”ЁдәҶ GetRASSFFPгҖӮжүҖд»Ҙе®ғжҳҜ getCurrentModuleName зҡ„е Ҷж Ҳеё§иў«д»Јз ҒжҹҘиҜўгҖӮе…¶д»–жЁЎеқ—зҡ„жһ„е»әж–№ејҸ并дёҚйҮҚиҰҒгҖӮ - Chris Becke

1
在这种情况下,使用函数的返回地址,您可以通过直接查看堆栈来找到它。其余的答案仍然适用。

是的。_ReturnAddress()内在函数可以提供它,而无需进行黑客攻击。此外,VirtualQuery()会获取基地址,您可以将其转换为HMODULE。然后,GetModuleFileName()会获取dll/exe名称。 - Hans Passant
除了该区域的基地址可能不是模块的基地址之外... - wj32

0

在 DLL 中的 getCurrentModuleName() 函数内部,您可以获取堆栈指针,但是您需要从 getCurrentModuleName() 函数开头处获取返回地址,以便了解调用 getCurrentModuleName() 的位置。


0
使用EnumProcessModules()函数。对于每个模块调用GetModuleInformation()函数。使用函数指针比较当前执行的函数的地址与MODULEINFO结构体中的lpBaseOfDll和SizeOfImage成员。如果它在范围内,那么就知道这是当前的模块。如果是,则使用GetModuleBaseName函数检索模块的名称。

对于这种情况,函数指针始终在dll地址空间中,无论它是从哪里调用的。但任务是找出调用它的模块。 - DReJ

0

这是解决方案。限制在于,它只能追踪到62个帧。

// Must have in order for us to turned address into module name.
SymInitialize(GetCurrentProcess(), NULL , TRUE);
// Limitation of RtlCaptureStackBackTrace.
const int kMaxCallers = 62; 
void* callers[kMaxCallers];
int count = RtlCaptureStackBackTrace(0, kMaxCallers, callers, NULL);
for (int i = 0; i < count; i++) {
    TCHAR szBuffer[50];
    DWORD64 dwBaseAddress = SymGetModuleBase64(GetCurrentProcess(), (DWORD64)callers[i]);
    GetModuleBaseName(GetCurrentProcess(), (HMODULE) dwBaseAddress, szBuffer, sizeof(szBuffer));
    std::wcout << _T("--->") << szBuffer << std::endl;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接