gpt4 book ai didi

c++ - 如何安全地防止在Wininet中调用状态回调函数?

转载 作者:行者123 更新时间:2023-11-30 04:48:22 26 4
gpt4 key购买 nike

我们正在DLL中使用WinInet进行异步网络调用。

当应用程序退出时,我们使用InetSetStatusCallback(connect_handle, NULL);删除针对未决请求的已注册回调函数。但是,偶尔在DLL卸载后仍会调用回调函数,从而导致应用程序崩溃。

症状与该博客的上一个常见问题解答完全相似:WinHTTP Questions: About Callbacks

我试图找出一种安全删除所有挂起请求的回调函数的方法,以便在DLL卸载后WinInet不会调用它们。

最佳答案

在将指针传递给回调函数之后,当然不能卸载包含此回调函数的模块,直到可以调用回调为止。如果回调函数位于EXE模块中,则绝对不会有问题,因为它将永远不会被卸载。但是如果是DLL,则需要在设置回调之前添加对DLL的引用,以防止DLL卸载。这很容易,可以通过 GetModuleHandleExW 函数完成

HMODULE hmod;
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (PCWSTR)&__ImageBase, &hmod);

好的,但是如何不让DLL卸载呢?当我们的回调不再被调用时,我们需要取消引用它(调用 FreeLibrary )。什么时候会?

InternetSetStatusCallback 为某些句柄设置了一个回调函数。当然,应该通过 InternetCloseHandle 和以下方式关闭此句柄:

It is safe to call InternetCloseHandle in a callback for the handle being closed. If there is a status callback registered for the handle being closed, and the handle was created with a non-NULL context value, an INTERNET_STATUS_HANDLE_CLOSING callback will be made. This indication will be the last callback made from a handle and indicates that the handle is being destroyed.

If asynchronous requests are pending for the handle or any of its child handles, the handle cannot be closed immediately, but it will be invalidated. Any new requests attempted using the handle will return with an ERROR_INVALID_HANDLE notification. The asynchronous requests will complete with INTERNET_STATUS_REQUEST_COMPLETE. Applications must be prepared to receive any INTERNET_STATUS_REQUEST_COMPLETE indications on the handle before the final INTERNET_STATUS_HANDLE_CLOSING indication is made, which indicates that the handle is completely closed.



因此 INTERNET_STATUS_HANDLE_CLOSING -将是从句柄进行的最后回调的 最终调用。恰在此时,我们需要取消引用DLL模块-不再调用回调。不需要更多引用。

好。什么时候清楚。但是如何?我们不能从这一点直接调用FreeLibrary,因为如果DLL驻留在最后一个引用上-它将在FreeLibrary调用中卸载,当我们返回时-我们将返回emply空间并崩溃。

我们也不能调用 FreeLibraryAndExitThread -因为我们在任意线程上。但是存在不正确的,但是在实际解决方案上起作用:
ULONG WINAPI SafeUnload(void*)
{
//Sleep(*);
FreeLibraryAndExitThread((HMODULE)&__ImageBase, 0);
}

if (HANDLE hThread = CreateThread(0, 0, SafeUnload, 0, 0, 0))
{
CloseHandle(hThread);
}

我们可以自己创建一个新线程,并从该线程调用FreeLibraryAndExitThread(此处调用正确)。但是我怎么说-理论上这是不正确的解决方案。这里存在种族-FreeLibraryAndExitThread调用可以在
从我们从 CreateThread调用返回之前执行并卸载DLL 。结果我们在CreateThread之后崩溃(我们尝试执行已经卸载的代码)。当然这不太可能,但是..

我们可以在调用Sleep之前插入FreeLibraryAndExitThread(有些延迟),但是无论如何-理论上可以在任何延迟下进行竞赛。再次选择延迟的具体值是多少?并且DLL这次将延迟卸载。不正确,也不好。尽管这是相对简单的“解决方案”,但我不使用它。

正确的解决方案是-调用(更准确地说是跳转)到FreeLibrary而不返回该跳转的位置。但必须返回到调用回调的位置。这在c / c++中是不可能的,但在asm代码中是可能的。当然,每个平台目标都需要单独的asm文件(x86,x64最低要求)

因此,接下来是正确而优雅的解决方案: INTERNET_STATUS_CALLBACK 回调函数无论如何都必须与某个类相关联,我们在其中保存了句柄上下文。这必须是类和DWORD_PTR中的静态函数dwContext-与hInternet关联的应用程序定义的上下文值必须指向类的实例(实际上是此指针)。通常这是通过以下方式完成的:
class REQUEST_CONTEXT 
{
// ...
static void WINAPI _StatusCallback(
__in HINTERNET hRequest,
__in DWORD_PTR dwContext,
__in DWORD dwInternetStatus,
__in LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
)
{
reinterpret_cast<REQUEST_CONTEXT*>(dwContext)->StatusCallback(
hRequest,
dwInternetStatus,
lpvStatusInformation,
dwStatusInformationLength
);
}

void StatusCallback(
__in HINTERNET hRequest,
__in DWORD dwInternetStatus,
__in LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
);
};

在这里,我们需要在asm代码中实现_StatusCallback以便在此处调用FreeLibrary。并将StatusCallback的返回值从void更改为BOOL-TRUE-如果这是最终调用(dwInternetStatus == INTERNET_STATUS_HANDLE_CLOSING),则返回,否则返回FALSE。

所以开始解决
// helper for get complex c++ names for use in asm code
#if 0
#define ASM_FUNCTION {__pragma(message(__FUNCDNAME__" proc\r\n" __FUNCDNAME__ " endp"))}
#define CPP_FUNCTION __pragma(message("extern " __FUNCDNAME__ " : PROC ; " __FUNCSIG__))
#else
#define ASM_FUNCTION
#define CPP_FUNCTION
#endif

基类骨架:
class REQUEST_CONTEXT 
{
// ...
static void WINAPI _StatusCallback(
__in HINTERNET hRequest,
__in DWORD_PTR dwContext,
__in DWORD dwInternetStatus,
__in LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
) ASM_FUNCTION;

BOOL StatusCallback(
__in HINTERNET hRequest,
__in DWORD dwInternetStatus,
__in LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
);

ULONG SendRequest(PCWSTR pwszObjectName);

void OnRequestComplete(HINTERNET hRequest, INTERNET_ASYNC_RESULT* pres);
};
StatusCallback实现:
BOOL REQUEST_CONTEXT::StatusCallback(
__in HINTERNET hRequest,
__in DWORD dwInternetStatus,
__in LPVOID lpvStatusInformation,
__in DWORD dwStatusInformationLength
)
{
CPP_FUNCTION;

switch (dwInternetStatus)
{
case INTERNET_STATUS_HANDLE_CLOSING:
Release();
return TRUE;
case INTERNET_STATUS_REQUEST_COMPLETE:
OnRequestComplete(hRequest, (INTERNET_ASYNC_RESULT*)lpvStatusInformation);
break;
}

return FALSE;
}

我们如何注册回调(在我的SendRequest内的代码中,用于从 HttpOpenRequestW 返回的句柄)
ULONG SendRequest(PCWSTR pwszObjectName)
{
// ... some code ...
AddRef();

HMODULE hmod;
if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (PCWSTR)&__ImageBase, &hmod))
{
if (HINTERNET hRequest = HttpOpenRequestW(..., (DWORD_PTR)this))
{
set_handle(hRequest);

if (INTERNET_INVALID_STATUS_CALLBACK != InternetSetStatusCallback(hRequest, _StatusCallback))
{
INTERNET_ASYNC_RESULT ar;
ar.dwResult = HttpSendRequestW(hRequest, 0, 0, 0, 0);
ar.dwError = ar.dwResult ? NOERROR : GetLastError();

if (ar.dwError != ERROR_IO_PENDING)
{
OnRequestComplete(hRequest, &ar);
}

return NOERROR;
}
}

FreeLibrary(hmod);
}

Release();

return GetLastError();
}

所以在这里我们调用GetModuleHandleExW在设置回调之前添加对DLL的引用。如果设置回调失败-我们将FreeLibrary用作引用DLL。请注意,此处安全正确地调用FreeLibrary,因为调用SendRequest的人必须具有DLL的引用-当然,在此调用期间不能卸载DLL。因此,当我们从此函数调用FreeLibrary时-确保不释放对DLL的最后一个引用(我们从GetModuleHandleExW调用中释放引用,但在此函数执行期间在DLL上存在其他引用。此函数的调用者(直接或间接)关心这个)。在dwContext中,我们传递了该指针。

现在是最后一部分-_StatusCallback实现。

对于x64(ml64 / c / Cp $ {InputFileName)-> $ {InputName).obj)
extern __imp_FreeLibrary:QWORD
extern __ImageBase:BYTE

; void REQUEST_CONTEXT::StatusCallback(void *,unsigned long,void *,unsigned long)
extern ?StatusCallback@REQUEST_CONTEXT@@QEAAHPEAXK0K@Z : PROC

.CODE

?_StatusCallback@REQUEST_CONTEXT@@SAXPEAX_KK0K@Z proc
xchg rcx,rdx
mov rax,[rsp + 28h]
sub rsp,38h
mov [rsp + 20h],rax
call ?StatusCallback@REQUEST_CONTEXT@@QEAAHPEAXK0K@Z
add rsp,38h
test eax,eax
jnz @@1
ret
@@1:
lea rcx, __ImageBase
jmp __imp_FreeLibrary
?_StatusCallback@REQUEST_CONTEXT@@SAXPEAX_KK0K@Z endp

end

对于x86(ml / c / Cp $ {InputFileName)-> $ {InputName).obj)
.MODEL FLAT

extern __imp__FreeLibrary@4:DWORD
extern ___ImageBase:BYTE

; int __thiscall REQUEST_CONTEXT::StatusCallback(void *,unsigned long,void *,unsigned long)
extern ?StatusCallback@REQUEST_CONTEXT@@QAEHPAXK0K@Z : PROC

.CODE

?_StatusCallback@REQUEST_CONTEXT@@SGXPAXKK0K@Z proc
mov ecx,[esp + 8]
push [esp + 20]
push [esp + 20]
push [esp + 20]
push [esp + 16]
call ?StatusCallback@REQUEST_CONTEXT@@QAEHPAXK0K@Z
test eax,eax
jnz @@1
ret 4*5
@@1:
mov eax,[esp]
lea edx, ___ImageBase
add esp,4*4
mov [esp],eax
mov [esp + 4],edx
jmp __imp__FreeLibrary@4
?_StatusCallback@REQUEST_CONTEXT@@SGXPAXKK0K@Z endp

end

关于c++ - 如何安全地防止在Wininet中调用状态回调函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55862504/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com