In this blog post we lay out a real-life examination of computer memory which enabled us to identify a keylogger that was running, what files were responsible for running it, and how it managed to ensure it was started every time the machine booted up. Not only did this provide us with previously unknown indicators of compromise, but also specific details with which we could assist the client in their remediation efforts.
During some hard disk forensics, one of our examiners found a text file which was clearly a log of a keylogger application. I won't share the gory details of what it contained - I'm sure you can imagine. By performing a search for the file name, the examiner found a hit in C:\Windows\MEMORY.DMP. This file stores debug information when a system failure occurs. The examiner passed this one over to me to see if I could do anything to help identify the keylogger.
I've anonymised the username for the purposes of this blog, replacing the username of the currently logged in user with 'theuser', but the file we're interested in is:
As is probably obvious, the file name is made up of logged-in-user-name + _tmp.dat.
In this case, as is the default, Windows had created a 'Kernel Memory Dump'. That is, not a full memory dump, but enough to help troubleshoot system failures. Unfortunately, at least to the best of my knowledge, there's not too much that can be done with a Kernel Memory Dump. However, that didn't deter us. The hard disk also contained a hiberfil.sys, which is the file that Windows uses when it goes into hibernation; it essentially contains a copy of RAM at the time the machine enters the hibernation state.
There are a few memory forensics tools out there, but the one I've seen prove itself time and time again is Volatility from the Volatility Foundation. Volatility is written in python, is free and is open source.
Armed with the latest version of Volatility (2.4, at the time of writing), I set about examining the hiberfil.sys. The first hurdle was compression - hiberfil.sys files are compressed, so Volatility has to decompress the file on the fly in order to analyse it. This is slow. Luckily, Volatility provides the imagecopy plugin which allows us to convert one type of memory dump (e.g. hiberfil) into a raw, uncompressed format - much faster.
C:\>python vol.py -f hiberfil.sys --profile=Win7SP1x64 imagecopy -O hiberfil.dd
Is the file into which the keystrokes are being logged actually referenced in memory?
With an uncompressed image, and contemporaneous notes underway, a good starting point was to see if the file was open in memory. For that, we use filescan:
C:\>python vol.py -f hiberfil.dd --profile=Win7SP1x64 filescan
And in the output, we look for our file:
Offset(P) Pointers Handles Access Name ---------- -------- ------- ------ ---- --SNIP-- 0x1b66ef20 16 0 -W--w- \Device\HarddiskVolume1\Users\theuser\AppData\Local\Temp\theuser_tmp.dat --SNIP--
This is good news. The file was indeed open, for write access, when the memory dump was taken. At least we know we're not wasting our time.
Which processes are accessing the file?
The next step was to see which processes were accessing the file. A useful blog post from Andreas Schuster says that Volatility should be able to resolve the process(es) by following the _FILE_OBJECT structure. However, the blog post relates to quite an old version of Volatility and it doesn't seem to apply to the current version. Or it might be that it only works with handles, which in this case we didn't have. Regardless, there's always something else to try.
By using strings from Sysinternals we can find the positions (offsets) within the raw memory dump where the paths can be seen, and save them to a file:
C:\>strings -o -n 9 hiberfil.dd | findstr /I /L "\theuser_tmp.dat" >strings.txt
And the output, in strings.txt, looks something like this:
--SNIP-- 785366187:\Device\HarddiskVolume1\Users\theuser\AppData\Local\Temp\theuser_tmp.dat 795296032:\Device\HarddiskVolume1\Users\theuser\AppData\Local\Temp\theuser_tmp.dat 821237504:C:\Users\theuser\AppData\Local\Temp\theuser_tmp.dat 829851904:C:\Users\theuser\AppData\Local\Temp\theuser_tmp.dat --SNIP--
We can then use this strings file as an input to Volatility's strings plugin. The strings plugin takes a text file where each line contains a decimal offset and string to find, for example: 123456:some_text, and returns the process ID and virtual address where the string is found.
C:\>python vol.py -f hiberfil.dd --profile=Win7SP1x64 strings -s strings.txt >strings-output.txt
Gives us the following in strings-output.txt:
--SNIP-- 1125860523 [2000:008d74ab] \Device\HarddiskVolume1\Users\theuser\AppData\Local\Temp\theuser_tmp.dat 1160780915 [4628:1051ec73] c:\users\theuser\appdata\local\temp\theuser_tmp.dat 1167748112 [kernel:fa8001c4ec10] heuser\AppData\Local\Temp\theuser_tmp.dat:6E53BFF5-0001-412B-8407-E3AEDE763511:$DATA 1336598656 [kernel:f8a007b1b080] \Users\theuser\AppData\Local\Temp\theuser_tmp.dat 1542979888 [4628:0322d130] C:\Users\theuser\AppData\Local\Temp\theuser_tmp.dat 1575054952 [3056:025c3e68] \Device\HarddiskVolume1\Users\theuser\AppData\Local\Temp\theuser_tmp.dat --SNIP--
By working through the file, a unique list of processes accessing the strings can be drawn up:
The process IDs can be easily found by running the pslist plugin and checking the PID value:
C:\>python vol.py -f hiberfil.dd --profile=Win7SP1x64 pslist
The output gives us:
Offset(V) Name PID PPID Thds Hnds Sess Wow64 Start Exit ------------------ -------------------- ------ ------ ------ -------- ------ ------ ------------------------------ ------------ 0xfffffa8001719b30 System 4 0 146 2056 ------ 0 2014-09-15 07:35:03 UTC+0000 0xfffffa800305d040 smss.exe 320 4 2 33 ------ 0 2014-09-15 07:35:03 UTC+0000 0xfffffa8004eeeb30 csrss.exe 456 448 10 1052 0 0 2014-09-15 07:35:10 UTC+0000 --SNIP-- 0xfffffa800639cb30 Rtvscan.exe 3056 612 24 531 0 1 2014-09-15 07:35:34 UTC+0000 --SNIP-- 0xfffffa8002d63490 ccSvcHst.exe 2000 612 36 408 0 1 2014-09-15 07:35:28 UTC+0000 --SNIP-- 0xfffffa80032a0b30 svchost.exe 2816 612 4 102 0 0 2014-09-15 07:35:33 UTC+0000 --SNIP-- 0xfffffa8003a766f0 explorer.exe 4628 4460 46 1118 1 0 2014-09-15 07:36:12 UTC+0000 --SNIP--
So, kernel, ccSvcHst, Rtvscan and explorer all have references to the file path in their process memory space.
ccSvcHst and Rtvscan are both components of the Symantec endpoint protection software installed on the system. It's reasonable that the AV would be listing the file for a variety of reasons, but explorer is slightly odd. Explorer is spawned when one uses an open or save file dialog, but that seems unlikely for a malicious file. The explorer process definitely warrants a closer look.
A closer look at explorer.exe
Checking the md5 of explorer.exe we can be happy that explorer itself hasn't been tampered with. Perhaps a DLL is being injected. Of course, Volatility provides a plugin with which we can check what DLLs a process has loaded; the appropriately named dlllist.
C:\>python vol.py -f hiberfil.dd --profile=Win7SP1x64 dlllist --pid=4628
************************************************************************ explorer.exe pid: 4628 Command line : C:\Windows\Explorer.EXE Service Pack 1 Base Size LoadCount Path ------------------ ------------------ ------------------ ---- 0x00000000ffe90000 0x2c0000 0xffff C:\Windows\Explorer.EXE 0x00000000777b0000 0x1a9000 0xffff C:\Windows\SYSTEM32\ntdll.dll 0x0000000077690000 0x11f000 0xffff C:\Windows\system32\kernel32.dll 0x000007fefd900000 0x6b000 0xffff C:\Windows\system32\KERNELBASE.dll --SNIP-- 0x000007fef5930000 0x15000 0x1 C:\Windows\Installer\WinInstall.dll --SNIP--
There were actually 215 DLLs loaded by explorer. (In case you're thinking 215 sounds suspiciously high, it's not. If you've got 'Process Explorer' to hand, check your own explorer.exe - I bet you've got around 200 loaded.)
So, scrolling down the list preparing myself to have to check the md5 of each, I noticed the one between the snips above: C:\Windows\Installer\WinInstall.dll. This folder is immediately suspicious: there aren't normally DLLs in the root of the Installer folder.
The easy option here would be to fire up the hard disk image and take a look at the file, but:
- we're showing off memory skills here, and
- I don't have the hard disk drive image.
But that's fine, because Volatility also includes a dlldump plugin. No prizes for guessing that it dumps DLLs.
C:\>python vol.py -f hiberfil.dd --profile=Win7SP1x64 dlldump --pid=4628 --dump-dir=4628
Process(V) Name Module Base Module Name Result ------------------ -------------------- ------------------ -------------------- ------ 0xfffffa8003a766f0 explorer.exe 0x00000000ffe90000 Explorer.EXE OK: module.4628.74a766f0.ffe90000.dll 0xfffffa8003a766f0 explorer.exe 0x00000000777b0000 ntdll.dll OK: module.4628.74a766f0.777b0000.dll 0xfffffa8003a766f0 explorer.exe 0x0000000002130000 apphelp.dll OK: module.4628.74a766f0.2130000.dll 0xfffffa8003a766f0 explorer.exe 0x0000000180000000 igfxpph.dll OK: module.4628.74a766f0.180000000.dll --SNIP-- 0xfffffa8003a766f0 explorer.exe 0x000007fef5930000 WinInstall.dll OK: module.4628.74a766f0.7fef5930000.dll --SNIP--
This article isn't the place to get into the analysis of the DLL, but if we do strings against the file we get some pretty damning clues...
--SNIP-- [Right SHIFT] [Left SHIFT] [SCROLL LOCK] [NUM LOCK] [F12] [F11] [F10] --SNIP-- Global\Klogger --SNIP-- [d/d/%d d:d:d] (%s) RegisterRawInputDevices User32.dll GetRawInputData user32.dll %s\%s_tmp.dat KLogger --SNIP-- E:\Code\Keylog\KloggerDll\x64\Release\KloggerDll.pdb --SNIP--
It's easy enough to see:
- Keylogger key mappings, e.g. the right shift key becomes [Right SHIFT]
- A timestamp and message format string: [d/d/%d d:d:d] (%s)
- The name of two Windows API functions to do with getting raw input.
- A format string for our file name: %s\%s_tmp.dat
- And generally, terms to do with 'logger'.
How is the DLL injected into explorer?
All that's left for us to do now is to see how the DLL is being injected into the explorer process.
I fully expected this to be a key/value pair in the Registry, so because we're staying in memory, let's use Volatility's dumpregistry plugin to, well, dump the Registry files from memory to disk:
C:\>python vol.py -f hiberfil.dd --profile=Win7SP1x64 dumpregistry --dump-dir=reg
We end up with a file listing like this:
Volume in drive C has no label. Volume Serial Number is 1234-5678 Directory of C:\reg 14/01/2015 09:21 <DIR> . 14/01/2015 09:21 <DIR> .. 23/12/2014 12:10 8,192 registry.0xfffff8a00000f010.no_name.reg 23/12/2014 13:57 22,675,456 registry.0xfffff8a000023290.SYSTEM.reg 23/12/2014 12:10 282,624 registry.0xfffff8a00006b410.HARDWARE.reg 23/12/2014 12:10 66,224,128 registry.0xfffff8a001bf7410.SOFTWARE.reg 23/12/2014 12:10 40,960 registry.0xfffff8a004071010.SECURITY.reg 23/12/2014 12:10 36,864 registry.0xfffff8a0040dc410.SAM.reg 23/12/2014 12:10 245,760 registry.0xfffff8a004153410.NTUSERDAT.reg 23/12/2014 12:10 249,856 registry.0xfffff8a00431d410.NTUSERDAT.reg 23/12/2014 12:10 4,128,768 registry.0xfffff8a004ef8010.ntuserdat.reg 23/12/2014 12:10 6,733,824 registry.0xfffff8a005405010.UsrClassdat.reg 23/12/2014 12:10 5,808,128 registry.0xfffff8a00712f010.Syscachehve.reg 23/12/2014 12:10 286,720 registry.0xfffff8a007c9f010.DEFAULT.reg 12 File(s) 106,721,280 bytes 2 Dir(s) 69,660,389,376 bytes free
Next step, lets see if we can quickly identify which of these files contains a reference to WinInstall.dll:
C:\>strings.exe reg\*.reg | findstr /I /L WinInstall.dll
FINDSTR: Line 662056 is too long.
So one line is too long, that's normal, but there are no results. That is unexpected.
What about the folder in which the file resides:
C:\>strings.exe reg\*.reg | findstr /R C:\\Windows\\Installer\\.*dll
C:\reg\registry.0xfffff8a000023290.SYSTEM.reg: C:\Windows\Installer\adspack.dll C:\reg\registry.0xfffff8a000023290.SYSTEM.reg: C:\Windows\Installer\adspack.dll FINDSTR: Line 662056 is too long.
So now we have a SECOND DLL in the C:\Windows\Installer folder?! That's suspicious. Is THAT dll loaded by any processes?
Let's see if 'adspack.dll' appears anywhere.
C:\>python vol.py -f hiberfil.dd --profile=Win7SP1x64 dlllist >dlllist_all.txt
--SNIP-- ************************************************************************ svchost.exe pid: 2816 Command line : C:\Windows\system32\svchost.exe -k netsvcs Service Pack 1 Base Size LoadCount Path ------------------ ------------------ ------------------ ---- 0x00000000ffdb0000 0xb000 0xffff C:\Windows\system32\svchost.exe 0x00000000777b0000 0x1a9000 0xffff C:\Windows\SYSTEM32\ntdll.dll 0x0000000077690000 0x11f000 0xffff C:\Windows\system32\kernel32.dll --SNIP-- 0x000007fef85e0000 0x25000 0x1 c:\windows\installer\adspack.dll --SNIP--
Our mystery dll is loaded by svchost - a service?
Let's grab a copy of this file:
C:\>python vol.py -f hiberfil.dd --profile=Win7SP1x64 dlldump --pid=2816 --dump-dir=2816 Process(V) Name Module Base Module Name Result ------------------ -------------------- ------------------ -------------------- ------ 0xfffffa80032a0b30 svchost.exe 0x00000000ffdb0000 svchost.exe OK: module.2816.752a0b30.ffdb0000.dll 0xfffffa80032a0b30 svchost.exe 0x00000000777b0000 ntdll.dll OK: module.2816.752a0b30.777b0000.dll --SNIP-- 0xfffffa80032a0b30 svchost.exe 0x000007fef85e0000 adspack.dll OK: module.2816.752a0b30.7fef85e0000.dll --SNIP--
And take a look at it in MiTeC's EXE Explorer.
A 'ServiceMain' function is exported...
...and there's a resource called 'DLL', which IS WinInstall.dll:
Analysis of this DLL shows that it runs as a service, drops WinInstall.dll and injects it into explorer.
We already know from above that this DLL is referenced in the SYSTEM hive, so it's a quick check (with MiTeC's Windows Registry Recovery) to see precisely where:
So, as we can see, a service called Ias is started at boot using adspack.dll.
- On start-up, a service called 'Ias' is started the binary for which is C:\Windows\Installer\adspack.dll.
- This dll drops another dll, C:\Windows\Installer\WinInstall.dll, which is a keylogger.
- This keylogger is injected into the explorer.exe process.
Contact and Follow-up
Adam is part of our Response team in Context's Cheltenham office. See the Contact page for how to get in touch.
Appendix: Get Lucky! A keylogger has to log key strokes.
As we're hunting a keylogger, we can consider something a keylogger has to do: log keystrokes that can't be represented by a printable character. For example: backspace, enter, home, end, etc. Often, as was the case in this instance, keyloggers represent these unprintable characters as the literal name within square brackets, for example: [ENTER]. We could have looked for that with strings again:
C:\>strings -o -n 11 hiberfil.dd | findstr /I /L "[BACKSPACE]" >backspace.txt
Which gives an output of:
OK! So, we have a couple of matches. Back to our strings plugin:
C:\>python vol.py -f hiberfil.dd --profile=Win7SP1x64 strings -s backspace.txt 834782000 [4628:7fef593b330] [BACKSPACE] 1165946328 [2816:7fef85fcdd8][BACKSPACE]
Nice! So, we have two processes that contain the string:
- 2816 svchost.exe
- 4628 explorer.exe
So, explorer.exe has hit again, and svchost.exe is of course the process responsible for launching services. This would have identified our two processes too, but those unprintable characters could've been encoded in any kind of way - looks like our attackers were just a little lazy.