2

我正在设置一个钩子,让我的 DLL 模块进入资源管理器的进程/线程空间,并从那里实例化一个 COM 类,该类子类化一些窗口、接收一些事件等。

由于我的对象通过子类化和事件接收器完成所有工作,因此一旦实例化对象,我就不需要钩子 proc。为了性能,我想尽快停止钩子。

唯一的问题是立即取消挂钩(在我的对象刚刚创建之后)会导致 DLL 模块卸载,进而导致访问冲突和崩溃。所以我想知道是否有一种优雅的方法可以解开钩子,同时确保 DLL 模块至少在我的对象被销毁之前保留在内存中。

到目前为止,我发现唯一可行的解​​决方案是调用GetModuleHandleEx我自己加载的模块GET_MODULE_HANDLE_EX_FLAG_PIN。这确保模块在 Explorer 进程的生命周期内保持加载。但我不确定那是不是味道不好。

有没有其他安全的方法来确保在我的对象执行完 DLL 之前不会卸载 DLL FinalRelease?(顺便说一句,我不认为调用LoadLibrary然后FreeLibrary从内部FinalRelease工作。这仍然会导致访问冲突。)感谢您的任何意见。

4

1 回答 1

1

当然,在 dll 为 self 设置了一些回调(线程过程、工作项、窗口过程、I/O 回调等)之后 - 它不能被卸载,直到回调离开 dll 主体

当然,如果您在 api 中设置回调并且在 api 返回后将不再调用回调,这当然不是问题。所以回调只能在 api 调用中调用。例如EnumWindows

但现在让我们看看情况,当回调可以在任何时间设置后被调用。例如窗口过程

为此 - 首先模块必须始终有额外的引用,直到回调处于活动状态(可以被调用或内部调用)并且在回调返回之后 - 取消引用模块。但我们不能直接通过调用FreeLibrary( LdrUnloadDll) 来执行此操作,因为当我们返回 dll 时 - 它可能已经被卸载

所以需要改用callasmjmp指令。但这可能只能从 asm 代码中完成。回调存根必须在 asm 中实现,一开始它必须“旋转”堆栈 - 交换堆栈中的 agruments(如果存在)并返回地址。在堆栈顶部设置返回地址。用 c/c++ 或其他语言调用“真实”回调,然后在它返回后call __imp_FreeLibrary使用jmp __imp_FreeLibrary. FreeLibrary当然覆盖回调的返回码。但有些回调确实没有使用返回码(比如 I/O 回调)。一些(如 Windows 程序)使用返回码,但并非针对所有消息。通常我们会在窗口被破坏时卸载,最后消息已经返回代码,无论如何。所以我们不能打电话FreeLibrary在一般情况下每次回调之后。LdrAddrefDll/LdrUnloadDll 也很昂贵,需要经常这样做。需要在 dll 中使用中间引用计数器(“fastref”)并FreeLibrary仅在内部计数器变为 0 时调用。这不会损坏返回代码并且会更快。

通常回调也可以与某些对象相关联。并且此对象的生存时间比回调更长 - 在回调处于活动状态之前,不得销毁对象。所以改为为每个回调引用/取消引用 dll(这并不总是可能的。windowproc 回调不可能 - 因为新的回调可以随时出现 - 我们不能在每个回调之前引用 dll) - 创建对象时需要引用 dll (在构造函数内部)并在对象被销毁(在析构函数内部)时取消引用 dll。

首先实现快速引用 dll 过程

对于 x64:

extern __imp_LdrUnloadDll:QWORD
extern __imp_LdrAddRefDll:QWORD
extern __ImageBase:BYTE


.DATA?

    align 4
@@UsageCount    DD ?

.code

@@FastReferenceDll proc
    lock inc[@@UsageCount]
    ret
@@FastReferenceDll endp

?ReferenceDll@@YAXXZ proc
    mov eax,1
    lock xadd[@@UsageCount],eax
    test eax,eax
    jz @@AddRefDll
    ret
@@AddRefDll:
    lea rdx, __ImageBase
    xor ecx,ecx
    jmp __imp_LdrAddRefDll
?ReferenceDll@@YAXXZ endp

?DereferenceDll@@YAXXZ proc
    lock dec[@@UsageCount]
    jz @@UnloadDll
    ret
@@UnloadDll:
    lea rcx, __ImageBase
    jmp __imp_LdrUnloadDll
?DereferenceDll@@YAXXZ endp

end

和 x86:

.686

.MODEL flat

extern __imp__LdrAddRefDll@8:DWORD
extern __imp__LdrUnloadDll@4:DWORD
extern ___ImageBase:BYTE

.DATA?

    align 4
@@UsageCount    DD ?
    
.CODE

@@FastReferenceDll proc
    lock inc[@@UsageCount]
    ret
@@FastReferenceDll endp

?ReferenceDll@@YGXXZ proc
    mov eax,1
    lock xadd[@@UsageCount],eax
    test eax,eax
    jnz @@nop
    lea eax, ___ImageBase
    push eax
    xor eax,eax
    push eax
    call __imp__LdrAddRefDll@8
@@nop:
    ret
?ReferenceDll@@YGXXZ endp

?DereferenceDll@@YGXXZ proc
    lock dec[@@UsageCount]
    jz @@UnloadDll
    ret
@@UnloadDll:
    lea eax, ___ImageBase
    xchg [esp],eax
    push eax
    jmp __imp__LdrUnloadDll@4
?DereferenceDll@@YGXXZ endp

end

使用 ATL 进行子类化的基类

class MySubClassBaseT : public CWindowImplBaseT<>
{
    ULONG dwRefCount = 1;

    static LRESULT CALLBACK StubWindowProc(
        _In_ HWND hWnd,
        _In_ UINT uMsg,
        _In_ WPARAM wParam,
        _In_ LPARAM lParam)ASM_FUNCTION;

    virtual WNDPROC GetWindowProc()
    {
        // force reference/include WindowProc
        return _ReturnAddress() ? StubWindowProc : WindowProc;
        //return StubWindowProc;
    }

protected:

    virtual void OnFinalMessage(_In_ HWND /*hwnd*/)
    {
        Release();
    }

    virtual ~MySubClassBaseT()
    {
        DereferenceDll();
    }

public:

    MySubClassBaseT()
    {
        ReferenceDll();
    }

    BOOL SubclassWindow(_In_ HWND hWnd)
    {
        if (__super::SubclassWindow(hWnd))
        {
            AddRef();
            return TRUE;
        }

        return FALSE;
    }

    HWND UnsubclassWindow(_In_ BOOL bForce /*= FALSE*/)
    {
        if (HWND hwnd = __super::UnsubclassWindow(bForce))
        {
            m_dwState |= WINSTATE_DESTROYED;
            m_hWnd = hwnd;
            return hwnd;
        }

        return 0;
    }

    void AddRef()
    {
        dwRefCount++;
    }

    void Release()
    {
        if (!--dwRefCount)
        {
            delete this;
        }
    }
};

我们ReferenceDll();在构造函数和DereferenceDll();析构函数中调用。所以直到对象(继承自MySubClassBaseT)没有被销毁,dll才会被卸载。我们还需要替换我们WindowProc实现StubWindowProc的是 asm (因为需要返回jmp

x64:

extern ?WindowProc@?$CWindowImplBaseT@VCWindow@ATL@@V?$CWinTraits@$0FGAAAAAA@$0A@@2@@ATL@@SA_JPEAUHWND__@@I_K_J@Z : PROC

?StubWindowProc@MySubClassBaseT@@CA_JPEAUHWND__@@I_K_J@Z proc
    call @@FastReferenceDll
    sub rsp,28h
    call ?WindowProc@?$CWindowImplBaseT@VCWindow@ATL@@V?$CWinTraits@$0FGAAAAAA@$0A@@2@@ATL@@SA_JPEAUHWND__@@I_K_J@Z
    add rsp,28h
    jmp ?DereferenceDll@@YAXXZ
?StubWindowProc@MySubClassBaseT@@CA_JPEAUHWND__@@I_K_J@Z endp

我们将调用包装ATL::CWindowImplBaseT::WindowProc@@FastReferenceDll/?DereferenceDll@@YAXXZ调用中。这需要在我们调用的析构函数中不直接卸载 dll DereferenceDll();。可以在回调中调用析构函数。因为在回调入口点我们已经有至少 1 个引用,可能会@@FastReferenceDll使用?ReferenceDll@@YAXXZ. 最后我们退出jmp尾调用),如果仍然存在对 dll 或to 的DereferenceDll引用,则不执行任何操作(并且不更改返回代码)并且永远不会返回到 dll 主体(可以在此 api 执行后卸载)。jmpLdrUnloadDll

对于 x86:

?StubWindowProc@MySubClassBaseT@@CGJPAUHWND__@@IIJ@Z proc
    mov eax,[esp]
    xchg [esp+4*4],eax
    xchg [esp+3*4],eax
    xchg [esp+2*4],eax
    xchg [esp+1*4],eax
    mov [esp],eax
    call @@FastReferenceDll
    call ?WindowProc@?$CWindowImplBaseT@VCWindow@ATL@@V?$CWinTraits@$0FGAAAAAA@$0A@@2@@ATL@@SGJPAUHWND__@@IIJ@Z
    jmp ?DereferenceDll@@YGXXZ
?StubWindowProc@MySubClassBaseT@@CGJPAUHWND__@@IIJ@Z endp

在这里,我们需要首先“旋转”堆栈。所以从

arg4
arg3
arg2
arg1
ret

ret
arg4
arg3
arg2
arg1

(对于 x64 不需要这个,因为这里的寄存器中的所有参数,但如果回调有 5 个或更多参数 - 这也已经需要)

用法示例this

于 2021-06-03T00:06:53.133 回答