A brief look at Windows telemetry: CIT aka Customer Interaction Tracker
- Windows version up to at least version 7 contained a telemetry source called Customer Interaction Tracker
- The CIT database can be parsed to aid forensic investigation
- Finally, we also provide code to parse the CIT database yourself. We have implemented all of these findings into our previously mentioned investigation framework, which enables us to use them on all types of evidence data that we encounter.
About 2 years ago while I was working on a large compromise assessment, I had extra time available to do a little research. For a compromise assessment, we take a forensic snapshot of everything that is in scope. This includes various log or SIEM sources, but also includes a lot of host data. This host data can vary from full disk images, such as those from virtual machines, to smaller, forensically acquired, evidence packages. During this particular compromise assessment, we had host data from about 10,000 machines. An excellent opportunity for large scale data analysis, but also a huge set of data to test new parsers on, or find less common edge cases for existing parsers! During these assignments we generally also take some time to look for new and interesting pieces of data to analyse. We don’t often have access to such a large and varied dataset, so we take advantage of it while we can.
Around this time I also happened to stumble upon the excellent blog posts from Maxim Suhanov over at dfir.ru. Something that caught my eye was his post about the CIT database in Windows. It may or may not stand for “Customer Interaction Tracker” and is one of the telemetry systems that exist within Windows, responsible for tracking interaction with the system and applications. I’d never heard of it before, and it seemed relatively unknown as his post was just about the only reference I could find about it. This, of course, piqued my interest, as it’s more fun exploring new and unknown data sources in contrast to well documented sources. And since I now had access to about 10k hosts, it seemed like as good a time as any to see if I could expore a little bit further than he had.
While Maxim does hypothesise about the purpose of the CIT database, he doesn’t describe much about how it is structured. It’s an LZNT1 compressed blob stored in the Windows registry at
HKLMSoftwareMicrosoftWindows NTCurrentVersionAppCompatFlagsCITSystem that, when decompressed, has some executable paths in there. Nothing seems to be known about how to parse this binary blob. So called “grep forensics”, while having its’ place, doesn’t scale, and you might be missing crucial pieces of information from the unparsed data. I’m also someone who takes comfort in knowing exactly how something is structured, without too many hypotheses and guesses.
In my large dataset I had plenty of CIT databases, so I could compare them and possibly spot patterns on how to parse this blob, so that’s exactly what I set out to do. Fast iteration with dissect.cstruct and a few hours of eyeballing hexdumps later, I came up with some structures on how I thought the data might be stored.
While still incredibly rough, I figured I had a rudimentary understanding of how the CIT was stored. However, at the time it was hardly a practical improvement over just “extracting the strings”, except perhaps that the parsing was a bit more efficient when compared to extracting strings. It did scratch initial my itch on figuring out how it might be stored, but I didn’t want to spend a lot more time on it at the time. I added it as a plugin in our investigation framework called dissect, ran it over all the host data we had and used it as an additional source of information during the remainder of the compromise assessment. I figured I’d revisit some other time.
Some other time turned out to be a lot farther into the future than I had anticipated. On an uneventful friday afternoon a few weeks ago, at the time of writing, and 2 years after my initial look, I figured I’d give the CIT another shot. This time I’d go about it with my usual approach, given that I had more time available now. That approach roughly consists of finding whatever DLL, driver or part of the Windows kernel is responsible for some behaviour, reverse engineering it and writing my own implementation. This is my preferred approach if I have a bit more time available, since it leaves little room for wrongful hypotheses and own interpretation, and grounds your implementation in mostly facts.
My usual approach starts with scraping the disk of one of my virtual machines with some byte pattern, usually a string in various encodings (UTF-8 and UTF-16-LE, the default string encoding in Windows, for example) in search of files that contain those strings or byte patterns. For this we can utilize our dissect framework that, among its many capabilities, allows us to easily search for occurrences of data within any data container, such as (sparse) VMDK files or other types of disk images. We can combine this with a filesystem parser to see if a hit is within the dataruns of a file, and report which files have hits. This process only takes a few minutes and I immediately get an overview of all the files on my entire filesystem that may have a reference to something I’m looking for.
In this case, I used part of the registry path where the CIT database is stored. Using this approach, I quickly found a couple of DLLs that looked interesting, but a quick inspection revealed only one that was truly of interest: generaltel.dll. This DLL, among other things, seems to be responsible for consuming the CIT database and its records, and emitting telemetry ETW messages.
Through reverse engineering the parsing code and looking at how the ETW messages are constructed, we can create some fairly complete looking structures to parse the CIT database.
When compared against the initial guessed structures, we can immediately get a feeling for the overall format of the CIT. Decompressed, the CIT is made up of a small header, a global “system use data”, a global “use data” and a bunch of entries. Each entry has its’ own “use data” as well as references to a file path and optional command line string.
Figuring out how to parse data is the easy part, interpreting this data is oftentimes much harder.
Looking at the structures we came up with, we have something called “use data” that contains some bitmaps, “stats” and “span stats”. Bitmaps are usually straightforward since there are only so many ways you can interpret those, but “stats” and “span stats” can mean just about anything. However, we still have the issue that the “system use data” has multiple bitmaps.
To more confidently interpet the data, it’s best we look at how it’s created. Further reverse engineering brings us to
win32kfull.sys for newer Windows versions (e.g. Windows 10+), and
win32k.sys for older Windows versions (e.g. Windows 7, Server 2012).
In the CIT header, we can see a
AggregationPeriodInS. With some values from a real header, we can confirm that
(BitPeriodInS * 8) * SingleBitmapSize = AggregationPeriondInS. We also have a
PeriodStartLocal field which is usually a nicely rounded timestamp. From this, we can make a fairly confident assumption that for every bit in the bitmap, the application in the entry or the system was used within a
BitPeriodInS time window. This means that the bitmaps track activity over a larger time period in some period size, by default an hour. Reverse engineered code seems to support this, too. Note that all of this is in local time, not UTC.
For the “stats” or “span stats”, it’s not that easy. We have no indication of what these values might mean, other than their integer size. The parsing code seems to suggest they might be tuples, but that may very well be a compiler optimization. We at least know they aren’t offsets, since their values are often far larger than the size of the CIT.
Further reverse engineering
win32k.sys seems to suggest that the “stats” are in fact individual counters, being incremented in functions such as
CitDesktopSwitch, etc. These functions get called from other relevant functions in win32k.sys, like
xxxSwitchDesktop that calls
CitDesktopSwitch. One of the smaller increment functions can be seen below as an example:
The increment events are different between the system use data and the program use data. If we map these increments out to the best of our ability, we end up with the following structures:
There are some interesting tracked statistics here, such as the amount of times someone logged on, locked their system, or how many times they clicked or pressed a key in an application.
We can see similar behaviour for “span stats”, but in this case it appears to be a pair of (count, duration). Similarly, if we map these increments out to the best of our ability, we end up with the following structures:
Finally, when looking for all references to the bitmaps, we can identify the following bitmaps stored in the “system use data”:
We can also identify that the single bitmap linked to each program entry is a bitmap of “foreground” activity for the aggregation period.
In the original source, I suspect these fields are accessed by index with an enum, but mapping them to structs makes for easier reverse engineering. You can also still see some unknowns in there, or unspecified fields such as Foreground0 and Foreground1. This is because the differentiation between these is currently unclear. For example, both counters might be incremented upon a foreground switch, but only one of them when a specific flag or condition is true. The exact condition or meaning of the flag is currently unknown.
During the reverse engineering of the various win32k modules, I noticed something disappointing: the CIT database seems to no longer exist in the same form on newer Windows versions. Some of the same code remains and some new code was introduced, but any relation to the stored CIT database as described up until now seems to no longer exists. Maybe it’s now handled somewhere else and I couldn’t find it, but I also haven’t encountered any recent Windows host that has had CIT data stored on it.
Something else seems to have taken its place, though. We have some stored
PUUActive (Post Update Use Info) data instead. If the running Windows version is a “multi-session SKU”, as determined by the
RtlIsMultiSessionSku API, these values are stored under the key
HKCUSoftwareMicrosoftWindows NTCurrentVersionWinlogon. Otherwise, they are stored under
We can apply the same technique here as we did with the older CIT database, which is to look at how ETW messages are being created from the data. A little bit of reversing later and we get the following structure:
Looks like we lost the information for individual applications, but we still get a lot of usage data.
Once again we can apply the same technique, resulting in the following:
I haven’t looked too deeply into the memoization shown here, but it’s largely irrelevant when parsing the data. We see some of the same fields we also saw in the PUU data, but also a ForegroundDurations array. This appears to be an array of foreground durations in milliseconds for a couple of hardcoded applications:
- Microsoft Internet Explorer
- Microsoft Edge
- Google Chrome
- Microsoft Word
- Microsoft Excel
- Mozilla Firefox
- Microsoft Photos
- Microsoft Outlook
- Adobe Acrobat Reader
- Microsoft Skype
Each application is given an index in this array, starting from 1. Index 0 appears to be reserved for a cumulative time. It is not currently known if this list of applications changes between Windows versions. It’s also not currently known what “DP” stands for.
While looking for some test CIT data, I stumbled upon two other pieces of information stored in the registry under the CIT registry key.
This information is stored at the registry key
HKLMSoftwareMicrosoftWindows NTCurrentVersionAppCompatFlagsCITwin32k, under a subkey of some arbitrary version number. It contains values with the value name being the
ImageFileName of the process, and the value being a flag indicating what types of messages or telemetry this application received during its lifetime. For example, the
POWERBROADCAST flag is set if
NtUserfnPOWERBROADCAST is called on a process, which itself it called from
NtUserMessageCall. Presumably a system message if the power state of the system changed (e.g. a charger was plugged in). Currently known values are:
You can discover which events a process received by masking the stored value with these values. For example, the value
0x30000 can be interpreted as
POWERBROADCAST|DEVICECHANGE, meaning that a process received those events.
This behaviour was only present in a Windows 7
win32k.sys and seems to no longer be present in more recent Windows versions. I have also seen instances where the values 4 and 8 were used, but have not been able to find a corresponding executable that produces these values. In most
win32k.sys the code for this is inlined, but in some the function name
AnswerTelemetryQuestion can be seen.
Another interesting registry key is
HKLMSoftwareMicrosoftWindows NTCurrentVersionAppCompatFlagsCITModule. It has subkeys for certain runtime DLLs (for example,
Microsoft.NET/Framework64/v4.0.30319/clr.dll), and each subkey has values for applications that have loaded this module. The name of the value is once again the
ImageFileName and the value is a standard Windows timestamp of when the value was written.
These values are written by
CitmpLogUsageWorker. This function is called from
CitmpLoadImageCallback, which subsequently is the callback function provided to
PsSetLoadImageNotifyRoutine. The MSDN page for this function says that this call registers a “driver-supplied callback that is subsequently notified whenever an image is loaded (or mapped into memory)”. This callback checks a couple of conditions. First, it checks if the module is loaded from a system partition, by checking the
DO_SYSTEM_SYSTEM_PARTITION flag of the underlying device. Then it checks if the image it’s loading is from a set of tracked modules. This list is optionally read from the registry key
HKLMSystemCurrentControlSetControlSession ManagerAppCompatCache and value
Citm, but has a default list to fall back to. The version of
ahcache.sys that I analysed contained:
The tracked module path is concatenated to the aforementioned registry key to, for example, result in the key
HKLMSoftwareMicrosoftWindows NTCurrentVersionAppCompatFlagsCITModuleMicrosoft.NET/Framework/v1.0.3705/mscorwks.dll. Note the replaced path separators to not conflict with the registry path separator. It does a final check if there are not more than 64 values already in this key, or if the
ImageFileName of the executable exceeds 520 characters. In the first case, the current system time is stored in the
OverflowQuota value. In the second case, the value name
OverflowValue is used.
So far I haven’t found anything that actually removes values from this registry key, so
OverflowQuota effectively contains the timestamp of the last execution to load that module, but which already had more than 64 values. If these values are indeed never removed, it unfortunately means that these registry keys only contain the first 64 executables to load these modules.
This behaviour seems to be present from Windows 10 onwards.
We showed how to parse the CIT database and provide some additional information on what it stores. The information presented may not be perfect, but this was just a couple of days worth of research into CIT. We hope it’s useful to some and perhaps also a showcase of a method to quickly research topics like these.
We also discovered the lack of the CIT database on newer Windows versions, and these new
PUUActive values. We provided some information on what these structures contain and structure definitions to easily parse them.
Finally, we also provide code to parse the CIT database yourself. It’s just the code to parse the CIT contents and doesn’t do anything to access the registry. There’s also no code to parse the other mentioned registry keys, since registry access is very implementation specific between investigation tools, and the values are quite trivial to parse out. We have implemented all of these findings into our investigation framework, which enables us to use them on all types of evidence data that we encounter.
We invite anyone curious on this topic to provide feedback and information for anything we may have missed or misinterpreted.