【问题标题】:Port Win32 DLL hook to Linux将 Win32 DLL 挂钩移植到 Linux
【发布时间】:2011-02-08 15:53:27
【问题描述】:

我有一个程序 (NWSader),它连接到第二个程序的 OpenGL 调用 (NWN) 以执行后处理效果等。

NWSader 最初是为 Windows 构建的,通常是现代版本 (win32),并使用 DLL 导出(让 Windows 加载它并获取一些 OpenGL 函数)和 Detours(连接到其他函数)。我正在使用 Win 在检查 sysdir 之前在当前目录中查找任何 DLL 的技巧,因此它会加载我的。我有使用这种方法重定向的 DLL:

#pragma comment(linker, "/export:oldFunc=nwshader.newFunc)

将它们发送到我自己的 DLL 中的不同命名函数。然后我进行任何处理并从系统 DLL 中调用原始函数。

我需要将 NWSader 移植到 Linux(NWN 存在于两种版本中)。据我所知,我需要做的是一个共享库(.so 文件)。如果这是在 NWN 可执行文件之前预加载的(我找到了一个 shell 脚本来处理这个),我的函数将被调用。唯一的问题是我需要调用原始函数(我认为我会为此使用各种 DLL 动态加载方法)并且需要能够对内部函数进行类似 Detour 的挂钩。

目前我正在构建 Ubuntu 9.10 x64(带有 32 位编译器标志)。我在 Google 上找不到太多可以帮助解决此问题的信息,但我不知道 *nix 社区所指的确切内容。我可以编写 C++ 代码,但我更习惯于 Windows。作为 OpenGL,唯一需要修改以与 Linux 兼容的部分是挂钩代码和调用。有没有简单易行的方法,还是会涉及到重新创建 Detours 并动态加载原始函数地址?

【问题讨论】:

    标签: c++ linux dll winapi hook


    【解决方案1】:

    库的预加载可以通过LD_PRELOAD 完成。从那里您想查看dlopendlsym 调用以获取原始库中的实际函数。如果您想手工操作,这就是全部。

    您还可以查看修改ltrace 的方式,以便您提供挂钩函数(通过-e 标志)并让ltrace 为您处理簿记。

    [编辑] 手动执行此操作的示例:

    #include <dlfcn.h>
    #include <stdio.h>
    
    int (*orig_puts)(const char *);
    
    int puts (const char * str) {
        void * handle = dlopen("/lib/libc.so.6", RTLD_NOW | RTLD_GLOBAL);
        orig_puts = dlsym(handle,"puts");
        fprintf (stderr,"I have hooked your puts\n");
        return orig_puts(str);
    }
    

    并使用诸如

    之类的程序
    #include <stdio.h>
    
    int main () {
        puts ("Hello World");
        return 0;
    }
    

    你会得到以下结果:

    $ ./a.out
    Hello World
    $ LD_PRELOAD=./libhook ./a.out
    I have hooked your puts
    Hello World
    

    【讨论】:

    • 您不需要dlopen(),因为目标库已经被目标可执行文件链接到。只需 dlsym()RTLD_NEXT 标志。
    • 如果没有dlopen,我不知道该怎么做。我的手册页状态 dlsym 需要从 dlopen 返回的句柄,并且 RTLD_NEXT 保留供将来使用。我添加了一个示例来帮助澄清。
    • @caf 我似乎记得在某些平台上不起作用。 (也许是 Solaris?)
    • @asveikau:我认为RTLD_NEXT 最初来自 Solaris,但我可能错了。
    • @ezpz:在 Linux(由 OP 指定)上,您只需执行 dlsym(RTLD_NEXT, "puts")
    【解决方案2】:

    这听起来像您正在寻找的。您可能已经找到了解决方案,但我想我会继续下去。我使用 linux 并玩 NWN,并且希望能够使用 nwshader。 OGC(在文章中提到)似乎是一种通过中断 Opengl 起作用的多人作弊,很像 nwshader 所做的,但目的不同。

    http://aimbots.net/tutorials/14575-detours-linux-windows.html

    Linux 和 Windows 的弯路

    This is a basic "Hello world" detour example in C++.
    It does not make use of the Microsoft detour library.
    Therefore it works on Windows, Linux and Mac.
    
    I used the detour and undetour functions from OGC, but corrected it for IA64, and I also corrected the bug that made it crash on Fedora.
    Also, it works with C++. If you want to use it with pure C, you need to remove the C++ style typecasts, as well as the template.
    You don't need the template in C anyway, since C lets you convert any pointer to void* without any error or even warning.
    Works with IA-32 & IA-64 & AMD64 x86 processors.
    
    To be fully working, you would need to include a disassembler and adjust relative jumps in the 5+ bytes detourlength. You would also need to take care if you are writing over to the next memory page. (It almost never happens, but it could happen.)
    
    On IA-64, you can maximally jump 4 Gigabytes. That should be sufficient for any normal program, however.
    
    #if ( defined (_WIN32) || defined (_WIN64) )
        #define WIN32_LEAN_AND_MEAN
        #define WIN64_LEAN_AND_MEAN
        #include <windows.h>
    
        #define unprotect(addr,len) (VirtualProtect(addr,len,PAGE_EXECUTE_READWRITE,&oldprot))
        #define GETPAGESIZE()        getpagesize()
    
        DWORD oldprot ;
    
        unsigned long getpagesize (void)
        {
            static long g_pagesize = 0 ;
            if (! g_pagesize)
            {
                SYSTEM_INFO system_info ;
                GetSystemInfo(&system_info) ;
                g_pagesize = system_info.dwPageSize ;
            }
            return (unsigned long) g_pagesize ;
        }
    
        #define CLEAR_SCREEN "cls"
    
    #else // LINUX / UNIX / OS X
        #include <unistd.h>
        #include <sys/mman.h>
    
        #define unprotect(addr,len)  (mprotect(addr,len,PROT_READ|PROT_WRITE|PROT_EXEC))
        #define GETPAGESIZE()         sysconf (_SC_PAGE_SIZE)
        #define CLEAR_SCREEN "reset"
    #endif
    
    
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    
    
        unsigned long uslngPageSize = 0    ;
        unsigned long uslngPageMask = 0    ;
    
    
    
    
    
    
    
    
        #define JMP_OPCODE 0xE9
        #define OPCODE_LENGTH 1
        #define DATATYPE_ADDRESS int
        #define ADDRESS_LENGTH (sizeof(DATATYPE_ADDRESS))
        #define MIN_REQUIRED_FOR_DETOUR (OPCODE_LENGTH + ADDRESS_LENGTH)
        #define INT_DETOUR_FACTOR 1
        #define OPCODE_NOT_DEFINED 0
    
    
    
    // offset[ENGINE][FUNCTION_NAME] ;
    // detourlength[ENGINE][FUNCTION_NAME]
    
    #define HOTPATCH(FUNCTION_NAME) \
        original_##FUNCTION_NAME = TemplateFuncInterceptFunction( \
                                                                 original_##FUNCTION_NAME, \
                                                                 reinterpret_cast<unsigned long> (&FUNCTION_NAME), \
                                                                 reinterpret_cast<unsigned long> (&modified_##FUNCTION_NAME), \
                                                                 static_cast<unsigned long> (FUNCTION_NAME##_COPY) \
                                                                )
    
    #define UNPATCH(FUNCTION_NAME) \
        unpatchfunc( reinterpret_cast<void*>(reinterpret_cast<unsigned long>(&FUNCTION_NAME)), reinterpret_cast<unsigned char*> (reinterpret_cast<unsigned long>( original_##FUNCTION_NAME)), static_cast<unsigned long> (FUNCTION_NAME##_COPY))
    
    
    
    #define NATURALIZE(FUNCTION_NAME) \
        Naturalized_##FUNCTION_NAME = FuncConvertAddress(Naturalized_##FUNCTION_NAME, &FUNCTION_NAME)
    
    
    template <class DataType>
    DataType FuncConvertAddress(const DataType dt_FunctionPointer, unsigned long uslng_FunctionAddress)
    {
        return reinterpret_cast<DataType> (uslng_FunctionAddress) ;
    }
    
    
    
    
    void* FuncGetPage(const unsigned long &uslngVirtualMemoryAddress)
    {
        return reinterpret_cast<void*> (uslngVirtualMemoryAddress & uslngPageMask) ;
    }
    
    
    void* InterceptFunction (void* voidptr_AddressOfDetouredFunction, unsigned long uslng_CopyLength, void* voidptr_AddressOfDetourFunction)
    {
        DATATYPE_ADDRESS Relocation ;
        //printf("copy length: %ld\n", uslng_CopyLength);
        //printf("MIN_REQUIRED_FOR_DETOUR : %d\n", MIN_REQUIRED_FOR_DETOUR );
        void* voidptr_BackupForOriginalFunction = malloc( uslng_CopyLength + MIN_REQUIRED_FOR_DETOUR ) ;
        //printf("Sizeof Backuppointer %ld\n", sizeof(voidptr_BackupForOriginalFunction));
        //printf("Sizeof AddrDetouredFunction %d\n", sizeof(voidptr_AddressOfDetouredFunction));
    
        memcpy( voidptr_BackupForOriginalFunction, voidptr_AddressOfDetouredFunction, uslng_CopyLength) ;
    
        if (OPCODE_NOT_DEFINED)
        {
            printf("Error: OP-Code not defined\n.") ;
            exit(EXIT_FAILURE) ;
        }
    
        memset( reinterpret_cast<void*> (reinterpret_cast<unsigned long> (voidptr_BackupForOriginalFunction) + uslng_CopyLength),
                JMP_OPCODE, OPCODE_LENGTH ) ;
    
    
    
        Relocation = static_cast<DATATYPE_ADDRESS> (reinterpret_cast<unsigned long> (voidptr_AddressOfDetouredFunction)
                      - (reinterpret_cast<unsigned long> (voidptr_BackupForOriginalFunction)
                      + MIN_REQUIRED_FOR_DETOUR)) ;
    
    
        memcpy( reinterpret_cast<void*> ( reinterpret_cast<unsigned long> (voidptr_BackupForOriginalFunction)
                 + uslng_CopyLength + OPCODE_LENGTH), &Relocation, ADDRESS_LENGTH) ;
    
    
    
        unprotect(FuncGetPage(reinterpret_cast <unsigned long> (voidptr_AddressOfDetouredFunction)),uslngPageSize) ;
    
        memset(voidptr_AddressOfDetouredFunction, JMP_OPCODE, OPCODE_LENGTH) ;
    
        Relocation = static_cast<DATATYPE_ADDRESS> ( reinterpret_cast<unsigned long> (voidptr_AddressOfDetourFunction)
                      - (reinterpret_cast<unsigned long> (voidptr_AddressOfDetouredFunction)
                      + MIN_REQUIRED_FOR_DETOUR)) ;
    
        memcpy( reinterpret_cast<void*> (reinterpret_cast<unsigned long> (voidptr_AddressOfDetouredFunction)
                 + OPCODE_LENGTH), &Relocation, ADDRESS_LENGTH) ;
        unprotect(FuncGetPage(reinterpret_cast <unsigned long> (voidptr_BackupForOriginalFunction)),uslngPageSize) ;
    
        return voidptr_BackupForOriginalFunction ;
    }
    
    
    // C++ is typesafe, they said...
    // I say: Yes, but at which price ?
    template <class DataType>
    DataType TemplateFuncInterceptFunction( DataType dt_Original_Function, unsigned long uslng_FunctionAddress,
                                            unsigned long uslng_modified_FunctionName, unsigned long uslng_DetourLength)
    {
        return reinterpret_cast<DataType>
                ( reinterpret_cast<unsigned long>
                    ( InterceptFunction( reinterpret_cast<void*> (uslng_FunctionAddress),
                                         uslng_DetourLength,
                                         reinterpret_cast<void*> (uslng_modified_FunctionName)
                                       )
                    )
                );
    }
    
    
    void SayHello()
    {
        printf("Hello World\n");
    }
    
    
    void modified_SayHello()
    {
        printf("**** World\n");
    }
    
    void (*original_SayHello)();
    //#define SayHello_COPY 9
    #define SayHello_COPY 6
    
    
    
    
    
    
    void unpatchfunc(void* patched_function, unsigned char* original_function, unsigned long uslng_DetourLength)
    {
        //DWORD dw_OldProtect;
        //VirtualProtect(patched_function, uslng_DetourLength, PAGE_EXECUTE_READWRITE, &dw_OldProtect);
        unprotect(FuncGetPage(reinterpret_cast<unsigned long>(patched_function) ), uslngPageSize) ;
        unsigned int intIndex;
        for( intIndex = 0; intIndex < uslng_DetourLength; ++intIndex)
            *( (unsigned char*) patched_function + intIndex) = *(original_function + intIndex) ;
    
        //VirtualProtect(patched_function, uslng_DetourLength, dw_OldProtect, &dw_OldProtect);
        unprotect(FuncGetPage(reinterpret_cast<unsigned long>(patched_function) ), uslngPageSize) ;
        if(original_function!=NULL)
            free( (void*) original_function) ;
    }
    
    
    int main()
    {
        system( CLEAR_SCREEN ) ;
        uslngPageSize = GETPAGESIZE() ;
        uslngPageMask = ( ~(uslngPageSize - 1) ) ;
        printf("PageSize: %ld\n", uslngPageSize) ;
        printf("PageMask: 0x%08lX\n", uslngPageMask) ;
        SayHello() ;
        printf("Hotpatching now!!!\n") ;
        HOTPATCH(SayHello) ;
        printf("Hotpatched:\n") ;
        SayHello() ;
        printf("Backup:\n") ;
        original_SayHello() ;
        printf("Unpatching now\n") ;
        UNPATCH(SayHello);
        // expands to: unpatchfunc( (void*) SayHello, (unsigned char*) original_SayHello, (int) SayHello_COPY) ;
        printf("Unpatched:\n") ;
        SayHello() ;
        printf("EXIT_SUCCESS\n") ;
        return EXIT_SUCCESS ;
    }
    

    编辑:请注意,如果您在 Linux 上的 64 位共享库/dll 中包含此函数,则会出现分段错误。这是因为64位共享库只能用-fPIC编译,这样绕路比较困难,因为每次跳转前都要读PLT。您需要将共享库编译为 32 位共享对象 (-m32) 并使用 32 位可执行文件运行它。

    【讨论】:

    • 酷,我得看看这个。如果它可以很容易地挂接到 Linux 上的 NWN,并且我可以学习在 Linux 上调试 NWSader,我肯定会再次尝试移植它。
    【解决方案3】:

    如您所述,在共享库中编写您自己的oldfunc 并预加载它。还要写一些初始化,在原库上调用dlopen()dlsym()获取指向原oldfunc的函数指针。 (这些函数分别是 LoadLibraryGetProcAddress 的 Unix 等价函数。)

    【讨论】:

      【解决方案4】:

      您可以查看functions redirection in shared ELF libraries 的方法。附上代码。它允许您从特定模块挂钩特定功能。

      【讨论】: