📜 ⬆️ ⬇️

Unregistering the window hook


Holmes And tell me, my friend Watson, have you ever checked out the window hook, especially the global one?

Watson . Hmm ... what could be easier, dear Holmes.
::UnhookWindowsHookEx( hhookMy); 

H. Do not tell, Watson, do not tell. After this call, the DLL module containing the hook function is still loaded into all processes into which it was loaded. The system unloads this DLL only after some time. Namely, at the moment when through the message queues of all threads (having such a queue), at least one window message will pass. And so for each process on the desktop.

C. Here, wow! Do you happen to be kidding me, Holmes? This is User32. One of the main parts in Win32, which in turn is based on all other technologies in Windows. The same .Net, for example. And suddenly this is not quite deterministic behavior.
')
And CreateFile , incidentally, does not create the file deferred too - for example, until the first record in the file? And CreateProcess , by chance, not ...

H. No, Watson, this time I'm not joking. CreateFile and CreateProcess is our Kernel32 - is another matter.

Believe me, buddy. Pursuing my investigations, I spent a lot of time studying the internal structure of the User32 module and confidently asserted: the authors wrote (and support) it is not at all with the same quality as Kernel32.

C. Is this just another development team?

H. Yes, obviously so.

C. Perhaps this behavior is still justified. And really, is it really important when the hook DLL is unloaded?

H. Imagine that our application has updated itself via the Internet and is restarted. After restarting, it turns out that the old version of the DLL is loaded into some processes, and a new one is loaded into some processes. And in this case, the old version will not be unloaded even when the messages pass through the queues.

C. Horrible!

But wait. If the application has updated its files, then it has renamed the old version of the hook DLL or moved it somewhere in the temporary directory. Then a new version is launched ... It also sends the address of the DLL to SetWindowsHookEx , the file path of which is now different from the DLL that, as you say, remains loaded.

You want to say that the system will create a hook aimed at the wrong DLL that you pass to SetWindowsHookEx ? It can not be!

H. In those processes from which the DLL did not have time to unload, this is exactly what will happen, I swear by my tuned keyboard.

Apparently, User32 internally stores the path that the DLL had at the time of the first creation of the hook. In the second creation, he believes that this is the same DLL and does not need to unload and reload it.

C. Hmm ... And how to be with all this? What if after UnhookWindowsHookEx just wait, say, a second. Window messages occur quite often.

H. This is if the user does something with the window. And, for example, minimized windows behave quietly. There are no guarantees that a message will appear in each queue within a second. Or within an hour.

C. What to do? How do you want to unload DLLs from all processes?

(Holmes is silent and, smoking, looks at Watson.)

C. Will you, Holmes. I’m sure you’ve come up with the right way out. Share, please.

H. Let our hook function, finding that it is being executed for the first time in some thread, will create a file stream in a special temporary file. And right in the name of this thread will indicate the thread id in which it is executed. Like that:
 using namespace std; namespace k = Kernel32; ... if( nullptr == tls_plistiterHookedThreadFileStreamHandle) //      . { _listHookedThreadFileStreamHandles.push_front( k::CreateFile( k::FormatMessage( L"%1:%2!4u! %3!4u!", sHookedThreadListFilePath, k::GetCurrentThreadId(), k::GetCurrentProcessId() ), GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE )); tls_plistiterHookedThreadFileStreamHandle = new list< HANDLE>::iterator( _listHookedThreadFileStreamHandles.begin()); } 

Then the main EXE will be able to FindNextStream through these streams ( FindNextStream ) and send a WM_NULL message to each thread that User32 has associated with our hook. As a result, User32 will unload the DLL.

DLL, unloading, should close all descriptors from the list _listHookedThreadFileStreamHandles .

Thus, we also get a good criterion for the fact that the deed is done - success when trying to delete our temporary file.

C. Holmes, but why such difficulties? Here is the SendMessage with the argument HWND_BROADCAST .

H. SendMessage does not send messages to absolutely all threads that have a queue. However, this will probably work as well. Almost always.

(Screaming.) Mrs. Hudson, serve tea, please!

The next day

C. Holmes, one gentleman reported that SendMessage( HWND_BROADCAST) does not help, at least in his case.

H. Hm

Source: https://habr.com/ru/post/174565/


All Articles