Analyzing Desktops, Heaps, and Ransomware with Volatility

Monday, September 24, 2012

Michael Ligh


This post introduces a new plugin for Volatility named deskscan - it enumerates desktops, desktop heap allocations, and associated threads. In the GUI landscape, a desktop is essentially a container for application windows and user interface objects.

Malware utilizes desktops in various ways, from launching applications in alternate desktops (i.e. so the current logged-on user doesn't see) to ransomware that locks users out of their own desktop.

We'll see some examples of both in this post. Before we begin, here is brief summary of the ways you might use the deskscan plugin in your forensic or malware investigations:

  • Find rogue desktops used to hide applications from logged-on users 
  • Detect desktops created by ransomware 
  • Link threads to their desktops 
  • Analyze the desktop heap for memory corruptions 
  • Profile dekstop heap allocations to locate USER objects 

Data Structures

The tagDESKTOP structure represents desktops. Here is an example from Windows 7 x64:  

>>> dt("tagDESKTOP") 'tagDESKTOP' (224 bytes) 0x0   : dwSessionId                    ['unsigned long'] 0x8   : pDeskInfo                      ['pointer64', ['tagDESKTOPINFO']] 0x10  : pDispInfo                      ['pointer64', ['tagDISPLAYINFO']] 0x18  : rpdeskNext                     ['pointer64', ['tagDESKTOP']] 0x20  : rpwinstaParent                 ['pointer64', ['tagWINDOWSTATION']] 0x28  : dwDTFlags                      ['unsigned long'] 0x30  : dwDesktopId                    ['unsigned long long'] 0x38  : spmenuSys                      ['pointer64', ['tagMENU']] 0x40  : spmenuDialogSys                ['pointer64', ['tagMENU']] 0x48  : spmenuHScroll                  ['pointer64', ['tagMENU']] 0x50  : spmenuVScroll                  ['pointer64', ['tagMENU']] 0x58  : spwndForeground                ['pointer64', ['tagWND']] 0x60  : spwndTray                      ['pointer64', ['tagWND']] 0x68  : spwndMessage                   ['pointer64', ['tagWND']] 0x70  : spwndTooltip                   ['pointer64', ['tagWND']] 0x78  : hsectionDesktop                ['pointer64', ['void']] 0x80  : pheapDesktop                   ['pointer64', ['tagWIN32HEAP']] 0x88  : ulHeapSize                     ['unsigned long'] 0x90  : cciConsole                     ['_CONSOLE_CARET_INFO'] 0xa8  : PtiList                        ['_LIST_ENTRY'] 0xb8  : spwndTrack                     ['pointer64', ['tagWND']] 0xc0  : htEx                           ['long'] 0xc4  : rcMouseHover                   ['tagRECT'] 0xd4  : dwMouseHoverTime               ['unsigned long'] 0xd8  : pMagInputTransform             ['pointer64', ['_MAGNIFICATION_INPUT_TRANSFORM']]

Key Points

  • dwSessionId can be used to link a desktop to its owning session (it will match _MM_SESSION_SPACE.SessionId). 
  • pDeskInfo points to a tagDESKTOPINFO – this is where information on the desktop’s global hooks is stored. The tagDESKTOPINFO.spwnd field also identifies the active foreground window. 
  • rpdeskNext can be used to enumerate all desktops in the same window station. 
  • rpwinstaParent identifies the window station to which the desktop belongs.
  • pheapDesktop points to the desktop heap – it can be parsed in the same way as a process heap.
  • PtiList is a list of tagTHREADINFO structures, one for each thread attached to the desktop. 

The DeskScan Plugin

The deskscan plugin scans for window stations and then walks tagWINDOWSTATION.rpdeskList. It is also possible to simply scan for desktop objects (pool tagDesk).

The output below shows WinSta0\Default, WinSta0\Disconnect, and WinSta0\Winlogon in session 2.  

$ python -f rdp.mem --profile=Win2003SP2x86 deskscan Volatile Systems Volatility Framework 2.1_alpha ************************************************** Desktop: 0x8001038, Name: WinSta0\Default, Next: 0x8737bc10 SessionId: 2, DesktopInfo: 0xbc6f0650, fsHooks: 2128 spwnd: 0xbc6f06e8, Windows: 238 Heap: 0xbc6f0000, Size: 0x300000, Base: 0xbc6f0000, Limit: 0xbc9f0000  7808 (notepad.exe 6236 parent 5544)  7760 (csrss.exe 7888 parent 432)  5116 (csrss.exe 7888 parent 432)  8168 (PccNTMon.exe 5812 parent 5132)  3040 (cmd.exe 5544 parent 5132)  6600 (csrss.exe 7888 parent 432)  7392 (explorer.exe 5132 parent 8120)  5472 (explorer.exe 5132 parent 8120)  548 (PccNTMon.exe 5812 parent 5132)  6804 (mbamgui.exe 5220 parent 5132)  2008 (ctfmon.exe 4576 parent 5132)  3680 (PccNTMon.exe 5812 parent 5132)  2988 (VMwareTray.exe 3552 parent 5132)  1120 (explorer.exe 5132 parent 8120)  4500 (explorer.exe 5132 parent 8120)  7732 (explorer.exe 5132 parent 8120)  6836 (explorer.exe 5132 parent 8120)  7680 (winlogon.exe 3272 parent 432)  7128 (rdpclip.exe 6772 parent 3272)  5308 (rdpclip.exe 6772 parent 3272) ************************************************** Desktop: 0x737bc10, Name: WinSta0\Disconnect, Next: 0x8a2f2068 SessionId: 2, DesktopInfo: 0xbc6e0650, fsHooks: 0 spwnd: 0xbc6e06e8, Windows: 25 Heap: 0xbc6e0000, Size: 0x10000, Base: 0xbc6e0000, Limit: 0xbc6f0000 ************************************************** Desktop: 0xa2f2068, Name: WinSta0\Winlogon, Next: 0x0 SessionId: 2, DesktopInfo: 0xbc6c0650, fsHooks: 0 spwnd: 0xbc6c06e8, Windows: 6 Heap: 0xbc6c0000, Size: 0x20000, Base: 0xbc6c0000, Limit: 0xbc6e0000  6912 (winlogon.exe 3272 parent 432)  1188 (winlogon.exe 3272 parent 432)  8172 (winlogon.exe 3272 parent 432) ************************************************** [snip]

Regarding the output above, please note the following:

  • Default is the default desktop for applications in the interactive window station. It is what you see 99.9% of the time you’re logged into a Windows system. Thus it makes sense that explorer.exe runs in this desktop (also note there are 6 different Explorer threads all running in this same desktop). 
  • The number of windows in the Default desktop is also much higher than the others (238 compared to 25 in Disconnect and 6 in Winlogon). In order to allow the creation of so many more windows (and other objects), it makes sense that the Default desktop’s heap size is also much higher (0x300000 compared to 0x10000 and 0x20000). 
  • The only threads in the Winlogon desktop actually belong to winlogon.exe. 
  • The only desktop with global hooks is Default (fsHooks: 2128). This value can be parsed to report on installed windows message hooks. 

If you pass the –v/--verbose option to the deskscan plugin, it will output details on the desktop’s heap allocations. As described in Windows Hook ofDeath: Kernel Attacks through Usermode Callbacks by Tarjei Mandt, the win32k!ghati symbol is an array of structures, one for each USER object type, telling you if the objects are allocated from the desktop heap, shared heap, or session pool.

Windows (tagWND) and hooks (tagHOOK) are two examples of objects you can find on the desktop heap. Thus, if you know the size of a tagWND (0x128 on Windows 7 x64) and the size of a heap header for that platform, you can walk the desktop heap allocations and narrow down the possible addresses of window structures.   

Here’s an example of using the deskscan plugin in verbose mode:  

$ python -f win7x64.dd --profile=Win7SP1x64 deskscan –verbose [snip] ************************************************** Desktop: 0x7e031700, Name: WinSta0\Default, Next: 0xfffffa8003631dc0 SessionId: 1, DesktopInfo: 0xfffff900c0600a70, fsHooks: 0 spwnd: 0xfffff900c0600b90, Windows: 371 Heap: 0xfffff900c0600000, Size: 0x1400000, Base: 0xfffff900c0600000, Limit: 0xfffff900c1a00000   Alloc: 0xfffff900c0600010, Size: 0xa6 Previous: 0x0   Alloc: 0xfffff900c0600a70, Size: 0x10 Previous: 0xa6   Alloc: 0xfffff900c0600b70, Size: 0x2 Previous: 0x10   Alloc: 0xfffff900c0600b90, Size: 0x13 Previous: 0x2   Alloc: 0xfffff900c0600cc0, Size: 0xb Previous: 0x13   Alloc: 0xfffff900c0600d70, Size: 0x2 Previous: 0xb   Alloc: 0xfffff900c0600d90, Size: 0x14 Previous: 0x2   Alloc: 0xfffff900c0600ed0, Size: 0xb Previous: 0x14 [snip]
As you can see, the base of the desktop heap can be found at 0xfffff900c0600000. Desktop heaps use the same structures as the Win32/NT API (i.e. _HEAP and _HEAP_ENTRY), so parsing them is very similar to the way you enumerate process heap allocations. In the following example, we show how its possible to locate potential tagWND structures using this method.  

$ python -f win7x64.dd --profile=Win7SP1x64 volshell Volatile Systems Volatility Framework 2.1_alpha Current context: process System, pid=4, ppid=0 DTB=0x187000 Welcome to volshell! Current memory image is: file:///Users/Michael/Desktop/win7x64.dd To get help, type 'hh()'   # First switch into the context of a process in Session 1.  >>> cc(pid = 880) Current context: process explorer.exe, pid=880, ppid=736 DTB=0x6c3e000   # Acquire a process address space, which allows us to  # instantiate objects and dereference pointers using  # the right DTB for the _MM_SESSION_SPACE  >>> session_space = self.eproc.get_process_address_space()   # Determine the size of a heap entry header  >>> header_size = session_space.profile.get_obj_size("_HEAP_ENTRY")   # Determine the size of a tagWND structure for this platform  # aligned to the heap granularity  >>> import volatility.plugins.common as common >>> wnd_size = common.pool_align(session_space, "tagWND", header_size)   # Create the heap object for WinSta0\Default  >>> desktop_heap = obj.Object("_HEAP", \                    offset = 0xfffff900c0600000, \                    vm = session_space)   # Walk the heap looking for appropriately sized allocations  >>> for seg in desktop_heap.segments(): ...   for entry in seg.heap_entries(): ...     if (entry.Size - 1) * header_size == wnd_size: ...       print "? tagWND at", hex(entry.obj_offset + header_size) ...  ? tagWND at 0xfffff900c0600d90L ? tagWND at 0xfffff900c06017a0L ? tagWND at 0xfffff900c0601e10L ? tagWND at 0xfffff900c0601f90L ? tagWND at 0xfffff900c0602360L ? tagWND at 0xfffff900c0602f80L [snip]
Of course, its possible to see false positives with this method, since all we’re matching on is the size of an allocation, especially if two or more objects were the same size. In fact, this is just a proof-of-concept explaining how the desktop heap can be leveraged if it became necessary. For example, later we’ll describe how the userhandles plugin can locate all USER objects, including tagWND, regardless of whether they’re allocated from the desktop heap, shared heap, or session pool. However, if the USER handle table is not accessible for some reason, at least we could use the heap allocations as a backup.  

Tigger Malware

Tigger’s backdoor component accepts commands over the network. If the attacker wants to run applications on the victim machine, the malware creates a hidden desktop to run them in. You may notice that if you try to CreateProcess(“ipconfig.exe”, …) even if the SW_HIDE flag is set, a console window may flash instantaneously and alert the user that something suspicious is running in the background. Additionally, if an attacker opens a non-visible Internet Explorer instance (for example with COM APIs) in the logged on user’s Default desktop, the application’s threads (and thus its window messages) are subject to hooking and monitoring by any other tools in the desktop. 

The code below is reverse-engineered from the DLL that Tigger injects into explorer.exe. It uses all the same flags, APIs, and variables that the real malware used. Here’s how it works:

  1. Create a new desktop named system_temp_ if it doesn’t already exist 
  2. Generate a temporary file name that will be used to capture the redirected output of console commands
  3. Set STARTUPINFO.lpDesktop to WinSta0\system_temp_
  4. Set dwFlags to indicate the STARTUPINFO structure also has preferences for window visibility and standard output and standard error handles for the process to be created 
  5. Create the new process (full path to the process szCmd is passed into the function as an argument – originally from an attacker over the network)
  6. Wait for the process to complete 
  7. Read the process’s output (if any) from the specified output file and return it in a buffer  

LPSTR RunCmdInSecretDesktop(TCHAR *szCmd, BOOL bWait) {  DWORD dwFlags = (DESKTOP_SWITCHDESKTOP |                  DESKTOP_WRITEOBJECTS | DESKTOP_CREATEWINDOW);  HDESK hDesk = NULL;  SECURITY_ATTRIBUTES SecurityAttributes;  STARTUPINFO StartupInfo;  PROCESS_INFORMATION ProcessInfo;  DWORD ddSize = 0;  TCHAR lpszPath[MAX_PATH];  TCHAR lpszFile[MAX_PATH];  HANDLE hFile = INVALID_HANDLE_VALUE;    hDesk = OpenDesktop(_T("system_temp_"), 0, FALSE, dwFlags);    if (hDesk == NULL)    hDesk = CreateDesktop(_T("system_temp_"),              NULL, NULL, 0, dwFlags, NULL);    if (hDesk == NULL)   return NULL;    SecurityAttributes.nLength = sizeof(SecurityAttributes);  SecurityAttributes.bInheritHandle = TRUE;  SecurityAttributes.lpSecurityDescriptor = NULL;    GetTempPath(MAX_PATH, lpszPath);  GetTempFileName(lpszPath, NULL, GetTickCount(), lpszFile);    hFile = CreateFile(   lpszFile,    GENERIC_READ | GENERIC_WRITE,    FILE_SHARE_READ | FILE_SHARE_WRITE,    &SecurityAttributes,    CREATE_ALWAYS,    NULL, NULL);    if (hFile == INVALID_HANDLE_VALUE) {   CloseDesktop(hDesk);   return NULL;  }    memset(&StartupInfo, 0, sizeof(StartupInfo));  GetStartupInfo(&StartupInfo);    StartupInfo.cb = sizeof(StartupInfo);  StartupInfo.lpDesktop = _T("Winsta0\\system_temp_");  StartupInfo.wShowWindow = 1;  StartupInfo.dwFlags = (STARTF_USESTDHANDLES |                                     STARTF_USESHOWWINDOW);  StartupInfo.hStdOutput = hFile;  StartupInfo.hStdError = hFile;    LPTSTR szDup = _tcsdup(szCmd);    if (CreateProcess(NULL, szDup, NULL, NULL, TRUE,              CREATE_NEW_CONSOLE, NULL, NULL,              &StartupInfo, &ProcessInfo))  {   if (bWait)      WaitForSingleObject(ProcessInfo.hProcess, INFINITE);  }    CloseHandle(ProcessInfo.hProcess);  CloseHandle(ProcessInfo.hThread);  CloseHandle(hFile);  CloseDesktop(hDesk);    return GetFileData(lpszFile, &ddSize); }

At the end of the day, Tigger can covertly execute console and GUI applications without the user noticing and without being detected by security products that only monitor windows and window messages broadcasted in the user’s Default desktop. Of course, some of the more robust antivirus suites now monitor all desktops, but many of them still do not.    

Unfortunately, since the function does call CloseDesktop at the end, this can cause the tagDESKTOP object in memory to be freed (unless other threads have references to it). That’s why Volatility leverages object scanning through physical memory to try and detect traces of past activity.   

ACCDFISA Ransomware

The ACCDFISA malware (Anti Cyber Crime Department of Federal Internet Security Agency), described on the Emsisoft blog is a ransomware that creates a new desktop to display the ransom notice; and effectively locks users out of the system until they enter a special code. For example, one of the variants will display the message shown below after an infected system is booted:   

With no obvious way to get back to the real desktop, users are forced to comply with the attacker’s demands, or figure out some other way around it. As shown in the next screenshot, to create this screen-lock effect, the malware uses CreateDesktopA to create a new desktop named My Desktop 2 and then switches to it with SwitchDesktop.   

The artifacts this malware leaves in physical memory should not be surprising – a suspiciously named desktop with a single process (besides the typical csrss.exe) associated with the desktop. In the output below, notice the desktop is WinSta0\My Desktop 2 and the only thread attached to the desktop besides those from csrss.exe is thread ID 308 from svchost.exe. As you can imagine, a single thread running alone in a desktop is not typical.    

$ python –f ACCFISA.vmem deskscan Volatile Systems Volatility Framework 2.1_alpha [snip] ************************************************** Desktop: 0x24675c0, Name: WinSta0\My Desktop 2, Next: 0x820a47d8 SessionId: 0, DesktopInfo: 0xbc310650, fsHooks: 0 spwnd: 0xbc3106e8, Windows: 111 Heap: 0xbc310000, Size: 0x300000, Base: 0xbc310000, Limit: 0xbc610000  652 (csrss.exe 612 parent 564)  648 (csrss.exe 612 parent 564)  308 (svchost.exe 300 parent 240)

Let’s view a tree of the windows in the suspicious desktop. This plugin will be introduced in a future post. So far we know svchost.exe is most likely the malware process, but we’ll need a bit more evidence to explicitly link it with the ransomware message.  

$ python -f ACCFISA.vmem wintree Volatile Systems Volatility Framework 2.1_alpha [snip] ************************************************** Window context: 0\WinSta0\My Desktop 2 [snip] .#100e2  csrss.exe:612 - .#100e4  csrss.exe:612 - #100de (visible) csrss.exe:612 - .Anti-Child Porn Spam Protection (18 U.S.C. ? 2252) (visible) svchost.exe:300 WindowClass_0 ..Send Code (visible) svchost.exe:300 Button ..#100ee (visible) svchost.exe:300 Edit ..Your Id #: 1074470467 Our special service email: (visible) svchost.exe:300 Static ..Your ID Number and our contacts (please write down this data): (visible) svchost.exe:300 Static ..#100e8 (visible) svchost.exe:300 Static

As you can see, all of the windows for the ransom message are owned by svchost.exe with process ID 300. Now you can start your initial investigation based on this specific process. For example, using dlllist shows it’s not a real svchost.exe, rather its hosted out of the C:\wnhsmlud directory:  

$ python -f ACCFISA.vmem dlllist -p 300 Volatile Systems Volatility Framework 2.1_alpha ************************************************************************ svchost.exe pid:    300 Command line : "C:\wnhsmlud\svchost.exe" Service Pack 3   Base             Size Path ---------- ---------- ---- 0x00400000    0x2f000 C:\wnhsmlud\svchost.exe 0x7c900000    0xb2000 C:\WINDOWS\system32\ntdll.dll 0x7c800000    0xf6000 C:\WINDOWS\system32\kernel32.dll 0x77c10000    0x58000 C:\WINDOWS\system32\MSVCRT.dll [snip]

With information on the executable path name, you can search for it in the registry. In the following output, it is easily locatable in the registry hives cached in kernel memory:

$ python -f ACCFISA.vmem printkey -K "Microsoft\Windows\CurrentVersion\Run" Volatile Systems Volatility Framework 2.1_alpha Legend: (S) = Stable   (V) = Volatile ---------------------------- Registry: \Device\HarddiskVolume1\WINDOWS\system32\config\software Key name: Run (S) Last updated: 2012-07-23 01:57:05   Subkeys:   Values: REG_SZ        VMware Tools    : (S) "C:\Program Files\VMware\VMware Tools\VMwareTray.exe" REG_SZ        VMware User Process : (S) "C:\Program Files\VMware\VMware Tools\VMwareUser.exe" REG_SZ        SunJavaUpdateSched : (S) "C:\Program Files\Common Files\Java\Java Update\jusched.exe" REG_SZ        svchost         : (S) C:\wnhsmlud\svchost.exe


Using Volatility's deskscan command, you can analyze memory dumps in ways never seen before. Even better, this is just one of the new plugins in the win32k suite that will be released in Volatility 2.2. 

Possibly Related Articles:
Viruses & Malware
Information Security
malware Forensics Hacking Tools Network Security Ransomware Analysis Volatility
Post Rating I Like this!
The views expressed in this post are the opinions of the Infosec Island member that posted this content. Infosec Island is not responsible for the content or messaging of this post.

Unauthorized reproduction of this article (in part or in whole) is prohibited without the express written permission of Infosec Island and the Infosec Island member that posted this content--this includes using our RSS feed for any purpose other than personal use.