January 2008 Archives
Kaspersky Internet Security v7.0 offers a "proactive defense" feature which, if enabled, intercepts DLL injections and displays a scary-looking yellow window informing the user that an application is an "Invader." The user can choose to terminate the process; deny the action; allow the action to be completed once; or allow this particular process to inject in the future.
Kaspersky detects injections via both CreateRemoteThread and WriteProcessMemory / entry point modification. Because benign solutions often use the latter technique for chaining, the warning messages can be as frequent as every time any process is created on the system. While this certainly damages the user's experience and can lead to confusing scenarios (e.g. the user is warned that Explorer.exe may be malicious and opts to terminate Explorer), there is an additional, more subtle problem with Kaspersky's warning messages.
Specifically, Kaspersky's messages are synchronous. This means that when Explorer creates a process (the most likely scenario for user-initiated applications) the desktop, Start menu and task bar all freeze waiting for the user to click on the Kaspersky warning message. This is more than an annoyance -- synchronous IPC can lead to very insidious deadlocks and should be avoided altogether. For example, we have seen cases where a newly created window will block on Explorer. This doesn't happen all the time, and unfortunately it's not clear to us why it happens in some cases, but any time this does occur the whole system will deadlock as Explorer waits for a warning dialog which is itself waiting for Explorer.
Beyond this, there appears to be a transient bug in Kaspersky's monitoring which causes certain applications to fail silently. This cannot be expressed through policy -- blocking injection alone, at least Celceo's implementation, allows applications to execute normally -- and as a consequence, we expect this is an issue.
We're working with Kaspersky on these issues and on getting our injected DLL whitelisted. In the meantime, we're advising customers to disable the "proactive defense" feature. You can advise your own customers that there are two ways to accomplish this:
- Upon seeing a "Proactive Defense" warning, the user can click the "Add to Trusted zone" option and, in the window that appears, uncheck the "Object" box (so the policy rule only applies to the "Threat type," which is "Invader") and click "OK."
- Open the main Kaspersky Internet Security window and click the "Settings" box. Click the "Proactive Defense" option and, under the "Application Activity Analyzer" section, click the "Settings" button. Select the "intrusion into process (invaders)" item, and at the bottom of the window next to the "Action" text, click the action link until it switches to "Allow." Then click "OK" to save the new policy.
As part of our installer encapsulation technology, we need to determine what package MsiExec is using during an install. Parsing the command line is the naive approach, but unfortunately this doesn't always work. To perform "custom actions" -- that is, to invoke native code contained within the MSI package -- MsiExec spawns new instances of itself through the MSIServer service, running under the System account. In these cases the newly spawned instance does not receive the package's path over the command line. Instead, it receives the parameters "-Embedding <GUID>." The GUID is a one-time-use token -- it changes with each install, and it doesn't reference the package's own ID or other identifying data. As a consequence there is no clear way to translate the GUID back to the package itself.
Instead of using the command line, we intercept calls to CreateFile. The initial MsiExec instance (which receives the package path via the command line) reads the package from disk and writes copies of various parts of it to temporary locations, and then continues its work using the temporary copies. We intercept MsiExec's CreateFile calls to identify the original source package and the path to the new copy (associating the latter with the former). When subsequent instances of MsiExec (including the custom action ones) access package files, we use CreateFile to determine which packages they use. If they work directly from temporary files, we use the mapping we constructed earlier to properly attribute the install work to the original MSI file.
One subtlety involves the contents of the temporary packages. In some cases they are well-formed MSI packages (presumably sub-installers) which we identify by their eight-byte headers, since MSI doesn't preserve file extensions in its temporary files. However, in other cases they are DLL files (again without the ".dll" extension) which MSI loads in order to call specific exported methods -- these are the custom actions specifically. To handle these cases, we had to make sure our mapping included any temporary DLL files created by MsiExec, and we had to recognize their loading through LoadLibrary in order to attribute subsequent work to the correct virtual environment.
McAfee Enterprise v8.0i (but not subsequent versions) uses a strange detour patching approach which can cause problems when other hooks are introduced. This issue was first reported on the Madshi forum and was independently discovered by Eric Fieleke of Liquid Machines (formerly of Tenebril). Because there is disagreement on what exactly goes wrong, I wanted to add my thoughts here.
In constructing detour patches, McAfee inserts a CALL to its hook code at the beginning of the hooked method. The hook code, in executing the original method, JMPs to the trampoline which executes the first five bytes of the original method, and then pops the return address off the stack (so the remaining return address is that of the original caller to the method, rather than the CALL McAfee inserted). It then JMPs to the original method + 5 bytes, which executes the rest of the method and returns properly to the original caller.
Most detour patching schemes (including Microsoft's own) insert a JMP at the beginning of hooked methods. They can then return without the pop-return-address instruction that McAfee needs.
Case 1: McAfee hooks first
When McAfee hooks first, everything works properly. It's easy to argue this conceptually: if the McAfee hook won't disrupt the calling application (when it is the only hook in the app), it can't disrupt a calling hook. But for completeness, here's what happens when McAfee hooks first:
- The application CALLs the doubly hooked method.
- The first instruction JMPs to the non-McAfee hook's code.
- The non-McAfee hook executes. When it wants to invoke the original method, it issues a CALL to its trampoline.
- The first instruction in the trampoline is McAfee's CALL to its own hook code.
- The McAfee hook executes, and then JMPs to its trampoline.
- The trampoline executes the first 5 bytes of the original hooked method.
- Then the trampoline pops the return address off the stack, so the next RET will (correctly) go back to the non-McAfee hook that is trying to execute the original method.
- The trampoline then JMPs to the original method + 5 bytes.
- The original method executes, and then returns to the calling non-McAfee hook.
- The non-McAfee hook completes, and returns to the calling application.
Case 2: McAfee hooks second
In this case, things go wrong. The issue centers on a side effect that McAfee introduces with its CALL. Specifically, when the first five bytes of the hooked method execute the return address is incorrect -- McAfee doesn't pop the return address until after the first five bytes execute. This is a problem when the method is already hooked, and the first five bytes JMP to code that issues a RET. Here's what happens:
- The application CALLs the doubly hooked method.
- The first instruction CALLs the McAfee hook code.
- The McAfee hook executes. When it wants to invoke the original method, it issues a JMP to its trampoline.
- The first instruction in the trampoline is the non-McAfee hook's JMP to its own hook code.
- The non-McAfee hook executes, and then CALLs to its trampoline.
- The trampoline executes the first 5 bytes of the original hooked method, then JMPs to the original method + 5 bytes.
- The original method executes, and then returns (correctly) to the calling non-McAfee hook.
- The non-McAfee hook completes, and then issues a RET.
- But because McAfee hasn't yet popped the return address it introduced with its CALL, the non-McAfee hook returns not to the calling hook but to the location after McAfee's inserted CALL -- that is, to the original method + 5 bytes.
- Now the original method (minus the first five bytes) executes a second time. This time, the register state isn't what it expects.
Conclusion and Notes
If you're hooking methods that McAfee Enterprise 8.0i hooks (including WriteFile), you'll need to make sure you hook after McAfee does. You can check for the presence of McAfee by looking for entapi.dll and checking version numbers.
The Madshi forum mentions that, in cases where someone hooks before McAfee and that hook's instruction is larger than five bytes, McAfee will jump back into garbage. While this is certainly true, it's not unique to McAfee's implementation and it should not apply to detour implementations of which I am aware.
Finally, this issue appears to affect only this specific version of McAfee Enterprise (not previous or subsequent versions). If you're trying to replicate this issue, you will need v8.0i.
