2005-06-01
Abstract
The security and resource implications of adware - particularly in the corporate environment - are becoming an increasing concern for users. While AV vendors continue the tricky process of determining what should and should not be detected, adware itself is becoming increasingly advanced - both in the way it hooks the system and in the way it prevents itself from being removed. Here, presents Virus Bulletin's first adware analysis.
Copyright © 2005 Virus Bulletin
Can you imagine a world where the police force has no right to investigate and law courts have no right to pass judgement? You might imagine that it would feel like being a rabbit in a laboratory cage whose behaviour and habits are closely monitored - with no means of escape.
This comparison came to my mind during the analysis of the privilege attack, combined with the 'winlogon notification package' technique, employed by Adware/Look2Me.
In order to run in the address space of Windows Explorer and Internet Explorer, it used to be pretty common for adware to install itself in the form of Shell extensions and/or Browser Helper Objects. Being registered as an in-process COM object for the shell/browser gave the adware certain advantages: process invisibility, the ability to bypass the firewall, direct access to the browsing session events, the ability to be up and running as long as the shell is alive, and so on.
Another legitimate in-process model is provided by Microsoft in the form of the 'winlogon notification package' (Windows 2000/XP), which provides the additional advantage of running the code under the System account. Another advantage relates to the ability to define the local security policy on a stage when the authentication package creates a new logon session. This allows the adware to shape the token that LSA creates for the authenticated user, and therefore affect the rights of all processes running under its account.
When the system starts, winlogon is launched before the system shell. To locate its own extensions, winlogon enumerates the subkeys of the following registry key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify
It then loads the located extensions and uses their exports to construct the interface for handling the following system events:
Lock
Shutdown
Startup
Logoff
StartScreenSaver
StopScreenSaver
Logon
StartShell
Unlock
Winlogon extensions are also loaded and run in safe mode, just like shell extensions.
When run, Look2Me drops its DLL component and installs it as 'winlogon notification package'. The filename for the DLL module is pseudo-random: it is composed using the filename letters of the files located in the %system% directory.
In order to register itself as winlogon extension, Look2Me creates the following registry key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify\[RandomKey]
It then creates the following values in the key to establish the interface with winlogon.exe:
Asynchronous = 0x00000000
DllName = '%System%\[DLL filename, which is random]'
Impersonate = 0x00000000
Logon = 'WinLogon'
Logoff = 'WinLogoff'
Shutdown = 'WinShutdown'
[Random Key] is picked up by enumerating the subkeys of the following registry key randomly:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion
If it cannot figure out what needs to be used for [Random Key], it will use the string 'Guardian' for this key. Look2Me then registers the dropped DLL as an in-process COM object, by creating the following registry keys:
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\[random CLSID]
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\[random CLSID]\ID
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\[random CLSID]\IDex
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\[random CLSID]\Implemented Categories
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\[random CLSID]\Implemented Categories\
{00021492-0000-0000-C000-00000000046}
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\[random CLSID]\InprocServer32
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\[random CLSID]\Version
Then, Look2Me enumerates all explorer.exe windows and sends them an 'enable' message. At the next step, it terminates explorer.exe.
As soon as the shell is restarted, the adware's DLL module will be loaded into its address space. At this moment, Look2Me runs within the shell as its extension - however, the next time the computer starts, Look2Me will run as a winlogon extension within its address space.
Any process that is started under the Administrator account is supplied with a copy of the Administrator's access token, to inherit its rights and privileges. For security reasons, not all of the rights and privileges are enabled by default (for example, to prevent termination of critical processes - e.g. a user cannot terminate winlogon.exe in the task manager). However, a process running in the Administrator account may still adjust the Administrator's privileges with the AdjustTokenPrivileges() API.
To use a real world analogy, consider a policeman who presents his documents at the entrance to a restricted area. He is identified as a policeman and so is allowed to do many things that others are not allowed, or are not expected to do. He is supplied with a particular set of rights and privileges, that are normal for the police force. For example, he can monitor other people closely and even ask them to show him their documents. However, without a search warrant, the policeman is not allowed to search a house. For security reasons, this permission is not enabled by default, but the system gives the policeman the instruments to obtain it (e.g. via a court application).
To deactivate a userland Administrator process, Look2Me opens the Policy object on the local system and enumerates all accounts in the LSA Policy object's database that hold the SeDebugPrivilege privilege.
Then, it removes this privilege from every account that has this privilege enabled in its access token. Primarily this will affect the LSA account BUILTINS/Administrators, which has SeDebugPrivilege enabled by default.
After a reboot, no user-mode process that runs in the Administrator context will be able to enable the SeDebugPrivilege privilege. The AdjustTokenPrivileges() API still succeeds, but GetLastError() returns ERROR_NOT_ALL_ASSIGNED, which means that SeDebugPrivilege did not accept the attribute SE_PRIVILEGE_ENABLED.
Returning to our real world analogy, no policeman entering the restricted area would have any of the privileges that are normal for the police force. SeDebugPrivilege is a critical privilege that is vital for the successful removal of unwanted software. Without this privilege, the user-mode process acts more like a guest, with no right to perform critically important actions.
Look2Me locks its file by opening it in an exclusive mode, so that CreateFile will fail with a sharing violation. As a result, no user-mode process is able to open and scan it.
Generally speaking, if the process has the debug privilege then the locked file could be unlocked by duplicating and closing its handle.
One method of doing this is to enumerate all open handles with NtQuerySystemInformation and NT_HANDLE_LIST system information class, pass the located kernel object pointers to the installed kernel-mode driver, and let it provide the user-mode part of the application with the object names by utilising the ObQueryNameString() API.
Look2Me modules would be identified by locating adware threads and their addresses in the running processes. The file-type objects that are opened and owned by the parent winlogon process would also be known by the name that is returned by the driver. This would allow handles for Look2Me module files to be found, and then closed. This method of closing handles is implemented in the Sysinternals tools Handle and System Process. However, to duplicate a handle, the parent process needs to be open with the PROCESS_DUP_HANDLE access right, and that still requires the debug privilege.
Note that, without the debug privilege, the modules can be enumerated by utilizing the NtOpenProcess(), NtQueryInformationProcess() and NtReadVirtualMemory() APIs and by inspecting Process Environment Blocks and the obtained module lists. Remember that if the debug privilege is removed and the inspected process is the system process, then RtlQueryProcessDebugInformation() fails with the DEBUG_ACCESS_DENIED status code returned. This API should not be used to enumerate Look2Me modules within winlogon.exe.
Look2Me then spawns several threads that are responsible for different actions. Some of them install monitors on the registry keys by using the RegNotifyChangeKeyValue() API and passing it the handles of the monitored keys. The monitoring threads then fall into an infinite waiting state with WaitForSingleObject() until the change notification event is triggered.
For example, as soon as any subkey/value of the following registry key is altered Look2Me will restore it immediately:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify
To summarize the privilege attack description, let's have a look at the Figure 1 below and define the removal issues associated with this technique.
In order to detect and delete the adware file, the user-mode process needs winlogon.exe to be started without the Look2Me module loaded as its extension. This can be achieved if the Look2Me registry entries are removed and the system is restarted.
However, the Look2Me registry entries cannot be removed because of the spawned monitoring threads. As soon as these entries are altered or deleted, Look2Me restores them immediately.
To prevent registry entries from being recreated, the Look2Me threads need to be terminated or suspended.
The problem with this is that no user-mode process will have the privilege to call OpenProcess() with a powerful access mode (e.g. PROCESS_ALL_ACCESS, PROCESS_TERMINATE) to manipulate the Look2Me process/threads. In this case, OpenProcess() will fail and the GetLastError() will return an Access is denied error.
To manipulate processes and threads, SeDebugPrivilege must first be restored.
A user-mode process may allocate and initialize SID for the BUILTINS/Administrators and then enumerate its rights. If it detects that the Administrator has no SeDebugPrivilege enabled, it may grant this privilege with the LsaAddAccountRights() API.
The next thing the user-mode process will need to do is to adjust its own SeDebugPrivilege to the SE_PRIVILEGE_ENABLED attribute. However, in order for this privilege to be truly enabled in the access token of the BUILTINS/Administrators account (so that it can be inherited by the Administrator processes), the system must be rebooted.
As the system reboot is invoked, the Logoff system event notification will call the Look2Me Winlogoff() API. Then, with a new logon session, winlogon.exe will start up, load Look2Me, and call its exported WinLogon() API again, notifying it about the Logon system event. Both times Look2Me fires up and strips SeDebugPrivilege from all accounts again, so that BUILTINS/Administrators will not have this privilege enabled in the newly created logon session. This leads to the dead loop.
Let's consider what can still be done in this situation.
Our user-mode application is still capable of dropping its own component and installing it as 'winlogon notification package'. For this purpose, the application may register the exported APIs to handle the system events Logon, Startup, and StartShell. The Asynchronous value may need to be set to one to have its APIs called by winlogon.exe in the separate threads.
Next, our process should reboot the system. After the reboot, the system events Logon, Startup, and StartShell will be spawned in separate threads within our winlogon extension. A race condition with Look2Me might be expected in this case. Every thread would need to wait until the privilege is found to be stripped, then restore it, and quit.
The privileges defined at this stage will shape the token of the authenticated user and they will be inherited by other processes.
The user-mode process should then be able to enumerate running processes. For every process it will be able to enumerate its threads, read them, and detect the Look2Me threads by signature.
Once the entry points of the detected adware threads and the address ranges of the loaded modules are known, it is easy to find out within which modules the threads were spawned. As the Look2Me modules are identified inside every scanned process, their fullpath filenames will need to be collected for further reference.
In addition to this, every detected Look2Me thread needs to be terminated or suspended (either will work). Thread deactivation will block the automatic recreation of the adware registry entries.
As a result of the fact that the CLSID of Look2Me and the registry entries are random, the user-mode process needs to enumerate all registry keys and their values under the following registry keys:
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify
To locate the keys that should be removed, the values '(default)' and 'DllName' need to be inspected in the following registry keys (respectively) to determine whether they are set to any of the module fullpath filenames that were collected in the previous step:
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\[Enumerated CLSID]\InprocServer32
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Notify\[Enumerated Subkey]
The located keys belong to Look2Me and they must be deleted.
In addition, the CLSID of Look2Me can also be collected to remove the values from the registry key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved
If our user-mode application acts to reboot or power off, this will invoke Logoff and Shutdown system events, which in turn, will spawn new Look2Me threads inside the modules that are still loaded. This will repeat the whole payload again.
How can the system be shut down with no Logoff and Shutdown system events triggered? ExitWindowsEx() with EWX_POWEROFF and EWX_FORCE is not a cure - it still invokes the events mentioned.
One solution would be to patch the exports of the Look2Me module within the winlogon.exe process. A simpler method would be to 'power off' the machine by terminating winlogon.exe itself. The system crash and the subsequent reboot will run winlogon.exe with no Look2Me loaded (assuming the registry was cleaned properly) so that system scan can successfully be started again to clean the remaining Look2Me files.
The method described here allows the successful removal of Look2Me in user mode. Nevertheless, this piece of adware provides food for thought about the restrictions of user mode and indicates that the next meeting point with 'unwanted' software may take place in the kernel mode - and it seems only to be a question of time until that happens.
Jason Bruce, of SophosLabs, will present a paper on defining the rules for 'acceptable' adware at this year's Virus Bulletin conference. VB2005 takes place 5-7 October 2005 in Dublin, Ireland. The abstract for Jason's paper, as well as the full conference programme and online registration can be found at http://www.virusbtn.com/conference/.