12.4. Memory Scanning in User ModeThe first question of memory scanning is how to access a particular process' data in memory. As discussed previously, process A cannot interfere with process B. How can a user-mode scanner read the contents of all the other processes? The answer is an API called ReadProcessMemory(). This API is usually used by debuggers to control the execution of the traced application by the debugger. The ReadProcessMemory() API needs a handle to a process, which can be gotten by the OpenProcess() API and the PROCESS_VM_READ access right. OpenProcess() needs the ID of a process. From where do we get a process ID?The answer was not obvious for quite some time because the actual DLL (PSAPI.DLL) in which documented process enumeration APIs have been placed is not part of the standard Windows NT environment. (It was introduced later in Windows 2000.) The lack of PSAPI.DLL and the missing documentation suggested to me how NT actually does this itself. Because Task Manager and several other applications can display all the running processes and their IDs, it was obvious that it is possible to do so without PSAPI.DLL. In fact, it turns out that most APIs in PSAPI.DLL are just wrappers around native service APIs placed in NTDLL.DLL, such as NtQuerySystemInformation().The native API set is not documented by Microsoft and is mostly used by subsystems. Most applications do not link to NTDLL.DLL directly for this reason. In fact, Microsoft suggests using the documented interfaces. However, Task Manager (TASKMGR.EXE) is linked to NTDLL.DLL directly, even if the information could be obtained by using performance data. Oh, well!Task Manager uses the NtQuerySystemInformation() native API to get a list of all running processes and their process IDs. A user-mode application can link itself to NTDLL.DLL or simply use GetProcAddress() to get the address of the API to call it.When the process ID of a particular process is available, ReadProcessMemory() can be used to read the actual address space of that particular application. To do so, a memory scanner should know the exact location of the used pages of an application. Fortunately, the VirtualQueryEx() function provides information about the range of pages within the virtual address space of a specified process. It needs an open handle to a process and returns the attributes and the sizes of regions.It also needs PROCESS_QUERY_INFORMATION access for this operation. Free and reserved pages can easily be eliminated with this function, and those should not be accessed, but the rest must be checked. This can be done by using the ReadProcessMemory() API on those pages. 12.4.1. The Secrets of NtQuerySystemInformation()NtQuerySystemInformation() (NtQSI) is not documented by Microsoft, and it is not necessary to use it because a user-mode application can link itself to PSAPI.DLL, which in turn will call NtQSI. As we will see later on, however, this function can be useful in a kernel-mode implementation of a memory scanner, so it is worth talking about it a bit.NtQSI has four 32-bit (DWORD or ULONG) parameters.The first parameter could be named SystemInformationClass. This parameter specifies the type of information to be returned by the function. (It has several possible values; 5 specifies the running process list query.)The second parameter is the address of the returned buffer, which should be allocated by the caller; let's call this SystemInformationBuffer.The third parameter is the allocated size in bytes. The fourth parameter is an optional value, PULONG BytesWritten, which is the number of bytes returned in the caller.NtQSI() returns an NTSTATUS value. When the returned value is not STATUS_SUCCESS (0), it is usually STATUS_INFO_LENGTH_MISMATCH, which means that the allocated buffer length does not match the length required for the specified information class. Therefore, NtQSI() must be called with bigger and bigger buffers in a loop until the information can be placed into the allocated buffer completely by the Windows NT kernel.On correct return, the necessary information is placed in the buffer in the form of a linked list. The first DWORD value specifies the relative pointer of the next process block information from the start of the buffer. The DWORD value at offset 0x44 of each block is the process ID (of course this position is dependent on the platform and is different on IA64). With this ID, several additional APIs can be called, which is why it is the most important.After all of this, here is the "hand-made" definition for NtQuerySystemInformation(): Other important information, such as the loaded images (EXE and DLLs) and their base addresses, can be examined by other native API calls by using this process ID, such as RtlQueryProcessDebugInformation(), which uses allocated buffers created by RtlCreateQueryDebugBuffer() and deallocated by RtlDestroyQueryDebugBuffer() APIs. Of course, these are all undocumented native APIs. 12.4.2. Common Processes and Special System RightsOn a typical Windows NTbased system, several processes are already running, even if the user has not logged in. The most important of these processes are the System Idle Process, the System Process, SMSS.EXE, CSRSS.EXE, WINLOGON.EXE, and SERVICES.EXE. A Windows NT scanner should scan all of these address spaces and also any other running processes executed by the user.7 (such as the Advanced Windows NT Third Edition), it is stated that some of the processes are secure processes and therefore cannot be opened for QUERY_INFORMATION or VM_WRITE operations. (These processes include WINLOGON.EXE, CLIPSRV.EXE, and EVENTLOG.EXE.) Such processes first need an additional system security privilege to be adjusted. (This information is missing from the Microsoft documentation.)In particular, the SeDebugPrivilege privilege value must be adjusted to the SE_PRIVILEGE_ENABLED attribute. The SeDebugPrivilege is available to administrators and equivalent users or to anyone who has been granted this privilege by an administrator. However, even under an administrator account, the default attribute of this privilege is not enabled, so OpenProcess() will fail to open secured processes. To enable this privilege, the OpenProcessToken() function must specify TOKEN_ADJUST_PRIVILEGES, and then LookupPrivilegeValue() can be used to check whether the user has the privilege at all. If the user has the rights to do it, this privilege can be set to SE_PRIVILEGE_ENABLED by the AdjustTokenPrivileges() API.It makes very good sense to protect some standard applications this way. For instance, Windows NT simply crashes if WINLOGON.EXE stops working. A modification caused by any user-mode application inside a random location of WINLOGON.EXE's address space could cause the system to crash! Of course, this would not be great. In any case, WINLOGON.EXE can even be written in memory when this privilege is enabled, but the privilege would have to be granted to all users first to scan such applications in memory. If WINLOGON.EXE were infected, the infected process could not be detected. Giving debug privileges to all users would definitely not make the system more secure. This is why a memory scanner is much better developed as a kernel-mode driver, where PROCESS_ALL_ACCESS is easily gained because drivers are running with the highest rights on a Windows NT machine. 12.4.3. Viruses in the Win32 SubsystemThis section introduces the different ways that viruses can become active as part of a particular process. Most 32-bit user-mode applications run in the Win32 subsystem, which is the most important subsystem of Windows NT. It is created and used by default and unlike the other subsystems, cannot be disabled. This is the subsystem in which Win32 viruses can be active.The Win32 subsystem consists of the following major components: CSRSS.EXE (the environment subsystem process); the kernel-mode device driver WIN32K.SYS; and subsystem DLLs (such as USER32.DLL, ADVAPI32.DLL, GDI32.DLL, and KERNEL32.DLL), which translate documented Win32 API functions into the appropriate undocumented kernel-mode system service calls to NTOSKRNL.EXE and WIN32K.SYS. There is one other very important part of the Win32 subsystem: NTDLL.DLL, primarily for subsystem DLLs. NTDLL.DLL is used in the other subsystems of Windows NT also and by native applications that do not run in a subsystem. (Listing 12.2 shows some of the system processesthe loaded DLLs with their base addresses and sizes.) Listing 12.2. Some System Executables with Their DLLs
12.4.4. Win32 Viruses That Allocate Private PagesSome Win32 viruses allocate private pages for themselves with the PAGE_EXECUTE_READWRITE attribute. When the infected application is loaded, the virus code is activated from the executed application code. The virus then allocates new pages for its own use and moves its code there. Write access to those pages is important for the virus because it stores data in itself that must change, and read-only pages cannot be written to.8 allocates a 12,232-byte block that is represented as three pages (3*4,096=12,888 bytes) from the address space of the infected process (see Listing 12.3). Because Cabanas uses the MEM_TOP_DOWN flag when it allocates memory with the VirtualAlloc() function, the actual three pages will be available at the very end of the user address space, usually somewhere around the 0x7FFA0000 address. Listing 12.3. W32/Cabanas at the Very End of the User Address SpaceCabanas hooks some of the KERNEL32.DLL APIs to itself by patching the import table entries of the host program to its own routines. Whenever the host application calls any of the hooked APIs, the virus has the chance to replicate on the fly to another application or to call its directory stealth routines.9 virus allocates 132,605 bytes from the address space of the infected process (more exactly: 33 pages, 135,168 bytes) because it needs a lot of memory for its polymorphic engine and for its communication modules.W32/Parvo does not use the MEM_TOP_DOWN flag, so its allocated pages will be reserved from the first free gap of the user address space that is large enough (at address 0x002F0000 in the infected NOTEPAD.EXE in this particular example, as shown in Listing 12.4). Listing 12.4. W32/Parvo Inside NOTEPAD's Address SpaceThe virus code will be active with the name of the original infected and executed application. Only one copy of the virus is active at a time. The original host will be executed as the child process of the infected application under a random name, as shown in Listing 12.5.Because the host program will be executed almost immediately, the virus can silently infect other applications from its own process and propagate itself to other locations with its communication module, based on the use of WSOCK32.DLL APIs. Listing 12.5. W32/Parvo Runs Original NOTEPAD.EXE as JRWK.EXE
12.4.5. Native Windows NT Service VirusesA new class of Windows NT viruses activate by dropping executable images loaded as a native Windows NT service, as done by WNT/RemEx3 (commonly known as the RemoteExplorer). The RemEx virus runs as a user-mode service called ie403r.sys, as shown in Listing 12.6. The virus sleeps for a while and then wakes up and tries periodically to infect other applications. Listing 12.6. WNT/RemEx Running as ie403r.sys Service
12.4.6. Win32 Viruses That Use a Hidden Window ProcedureA few viruses such as { W32,W97M} /Beast.41472.A10 install a hidden window procedure for their own use and use a timer. Timers were available back in 16-bit Windows versions, and they were sometimes used to simulate multithreaded functionality. As explained in Chapter 3, "Malicious Code Environments," this virus runs as a complete process and uses OLE APIs to inject embedded macros and executable code (the binary virus code itself) into Office 97 documents. Because the virus can infect Office 97 documents from its active process, a macro virus-specific scanner and disinfector has a hard time removing it from documents if it cannot detect and terminate the virus in memory first. 12.4.7. Win32 Viruses That Are Part of the Executed Image ItselfW32/Heretic.1986.A was the first virus to infect KERNEL32.DLL correctly under Windows NT. KERNEL32.DLL is used by most applications; most of the crucial Win32 APIs are exported from it. When KERNEL32.DLL is infected, most executed applications will be attached to it because they need to call APIs from it.Heretic patches the export address table of KERNEL32.DLL so that the CreateProcessA() and CreateProcessW() functions will point to the last section of the DLL where the virus code is placed, as shown in Listing 12.7. Listing 12.7. W32/Heretic.1986.A Modifies the Export Address of CreateProcess APIsWhen these functions are called by the host program, the virus has the chance to infect other applications on the fly. The virus enlarges the last section (.reloc) of KERNEL32.DLL and puts its code there, modifying the characteristics of that section to both MEM_EXECUTE and MEM_WRITE types. Listing 12.8 shows the virus code in memory at the end of an infected KERNEL32.DLL. Listing 12.8. W32/Heretic.1986.A at the End of Infected KERNEL32.DLL in MemoryAnother class of Win32 viruses stay active as part of an infected executable image, as done by the W32/Niko.5178 virus. (See Listing 12.9 for an illustration). The W32/Niko virus is activated from an infected portable executable (PE) application. The virus adds itself to the last section of the PE application and modifies the characteristics of the last section to MEM_WRITE. This allows the virus code to be modified in memory. The virus does not allocate memory for its full code but only for small data blocks whenever they are needed. Listing 12.9. W32/Niko.5178 Virus in an Infected ASD.EXE Application in Page 0x0040F000Niko is one of the first computer viruses to be multithreaded. The virus creates two threads for its own use, as shown in Listing 12.10. One is the trigger thread, which is supposed to display a message on a particular day; the other is the infection thread. The host program is executed after the virus creates the threads.As long as the host program is running, the virus's infection thread will also be active. If the host application (main thread) terminates, all threads of the process will be killed by Windows NT, so the virus will be no longer active. The virus can replicate to other files only from those applications that are running and used for a longer time. In such a situation, the infection thread will infect other applications from the background. Listing 12.10. W32/Niko.5178 Virus Creates Two Threads (68 and 123 in This Example)
|