AppCompatCache Deep Dive
Let’s set some background first.
Back in Windows XP and prior, the mere existence of AppCompatCache (aka Shimcache) could be used to prove execution. A program wasn’t shimmed unless it was actually executed. This changed in Windows 7, 8, and 8.1 (presumably Vista as well, but nobody used it) where a program could be shimmed due to multiple reasons, such as just viewing it in file explorer. However, there was an additional Insert Flag that, with a specific value, could still be used to reliably show evidence of execution.
Then came Windows 10 (and later 11), and another massive re-write of the way Shimcache worked. The insert flag was removed, and many tools, such as Eric Zimmerman’s AppCompatCacheParser would show "NA" in the Execution data column. Most writeups concerning this artifact still state that Shimcache cannot be used to reliably show evidence of execution on Windows 10 and later. In my opinion this is still the correct way to approach the artifact until more research can be done. However, astute observers will notice that running AppCompatCacheParser on a modern copy of Windows 11 (23H2 as of this writing) will result in Yes/No values in the execution column, indicating there is a way to show evidence of execution.
Update:
Part 2 - https://nullsec.us/building-appcompatcacheparser/
Part 3 - https://nullsec.us/appcompatcache-part-3/
So what happened?
Is the insert flag back? Did it ever disappear? Or is something else being used to show execution?
I don’t like clickbait, so the short answer is this. No, the insert flag isn’t back. However, the last 4 bytes of the Data field in a Shimcache entry is being used to show execution.
Windows 10 Shimcache Entry
When trying to answer what changed, my first thought was to start by trying to figure out the structure of the Windows 10/11 Shimcache. If I knew what was being referenced and possible values, and compaired that to Windows 7, maybe I would have a better understanding of how the Execution flag returned and what it means. I used Registry Explorer to export the AppCompatCache value data from my system, then opened that in 010 Editor.
The file path is pretty easy to see, right in the middle in Unicode. The rest of it doesn’t have any immediately clear meaning. I spent a little while just seeing if there was a pattern anywhere I could see, it’d be nice if there was a simple Boolean value that indicated execution, but I couldn’t find one (foreshadowing here folks). I did pretty easily determine that 31 30 74 73
or 10ts
looked like the entry header though. You can’t see the pattern in this screenshot, but that value appeared a little before every file path.
Looking at AppCompatCacheParser Source
Around the same time I’m also looking at the source code for AppCompatCacheParser, specifically the following files:
- https://github.com/EricZimmerman/AppCompatCacheParser/blob/master/AppCompatCache/Windows10.cs
- https://github.com/EricZimmerman/AppCompatCacheParser/blob/master/AppCompatCache/Windows7.cs
- https://github.com/EricZimmerman/AppCompatCacheParser/blob/master/AppCompatCache/AppCompatCache.cs
The first is the code for parsing a Windows 10 registry hive. We can start at the very top of that statement and walk through the index to map out each value. We can also see that the guess on the signature, 10ts
, was correct (line 45). There’s a helpful comment on line 79 that tells us "if the last 4 bytes of the Data [section] is 1, it indicates execution"
// [...SNIP...]
ce.Data = rawBytes.Skip(index).Take(ce.DataSize).ToArray();
index += ce.DataSize;
//if the last 4 bytes of Data is 1, it indicates execution
var exec= BitConverter.ToInt32(ce.Data, ce.Data.Length - 4) == 1;
ce.Executed = AppCompatCache.Execute.No;
if (exec)
{
ce.Executed = AppCompatCache.Execute.Yes;
}
// [...SNIP...]
The second is the code for parsing a Windows 7 hive. We can see it is called an InsertFlag on line 56. I’ve snipped out bits for brevity, but it’s looking for this flag directly after the section storing the LastModifiedTimeUTC and just before an additional 4 bytes of unknown data, followed by the DataSize.
// [...SNIP...]
// skip 4 unknown (insertion flags?)
ce.InsertFlags = (AppCompatCache.InsertFlag) BitConverter.ToInt32(rawBytes, index);
index += 4;
// [...SNIP...]
if ((ce.InsertFlags & AppCompatCache.InsertFlag.Executed) == AppCompatCache.InsertFlag.Executed)
{
ce.Executed = AppCompatCache.Execute.Yes;
}
else
{
ce.Executed = AppCompatCache.Execute.No;
}
// [...SNIP...]
lastly, number three has the code containing the AppCompatCache.InsertFlag.Executed definitions on line 36. We can see that everything except the Executed value is unknown.
// [...SNIP...]
public enum InsertFlag
{
Unknown1 = 0x00000001,
Executed = 0x00000002,
Unknown4 = 0x00000004,
// [...SNIP...]
}
So we know that the structure is completely different. If you want to explore some other structures, there’s test files located here https://github.com/EricZimmerman/AppCompatCacheParser/tree/master/AppCompatCacheParserTest/TestFiles. Even though the records contain mostly the same information, the Execution vs Insert flag is in completely different locations. The flags also have completely different values. So we have the first part of our answer. No, this is not an insert flag, it seems to be something completely new.
Windows 10 AppCompatCache Structure
Googling some keywords and 10ts
header value identified earlier will result in a number of hits. One site in particular caught my attention because the structure was mapped out visually. Sometimes you have to draw it in crayon for me. It’s a Korean blog post by @hskang00 located here: https://velog.io/@hskang00/%EB%94%94%EC%A7%80%ED%84%B8-%ED%8F%AC%EB%A0%8C%EC%8B%9D-50-Shimcache - built in browser translation helped a lot. There’s also a link to a paper here https://www.koreascience.or.kr/article/CFKO202125036451369.pdf. I translated this one using https://www.onlinedoctranslator[.]com/en/translationform. I’m not going to hyperlink that because I’m not sure of the legitimacy of free tools like this, but for a public PDF I accepted the risk, and it seemed to work just fine. Most of what you need is in the table on the velog.io site though.
Here’s the table translated to save some time.:
Offset | Length | Mean |
0-3 | 4 | Signature |
45389 | 4 | Unidentified |
8-1B | 4 | Entry Length |
1C-1D | 2 | Path Length |
1E-? | Variable | Path Values |
?-?+8 | 8 | Last modified time |
?+9-?+10 | 1 | Data Length |
?+11-?+11+Data Length | Variable | Data Values |
?+11+Data Length-?+11+Data Length + 3 | 3 | Padding |
We have 52 bytes of file header before the entries start. Then for each entry, 4 bytes of signature (10ts), followed by 4 unidentified bytes, followed by 4 bytes indicating the entry length, etc.
Rather than recreate the screenshot and table, I did a 15 minute crash course in 010 Editor templates and mapped out the values. It’s ugly, there’s probably a way to actually search for and start at the beginning 10ts
values, etc. It works though, and now I could easily pick out all of the Execution values, color coded Purple below. The only real difference between mine and @hskang00 is the addition of the Execution bit, which I grabbed by backing up the DataValues by two and giving Execution it’s own field.
If you want to try the template, I’ll include it at the end of this article so you don’t have to retype it. I’d appreciate it if you credited me, but have no objections to someone improving it, redistributing it, or submitting it as an official template.
In addition to values of 00 00
(not executed?) and 01 00
(executed), I also found values of 02 00
, 4C 01
, and 64 86
. I’m not sure what those indicate, So now we know the structure. This doesn’t give us anything new really. There isn’t an embedded field definition that says “this means executed”, and we could have followed Zimmerman’s source code and typed it out to find the structure. Something about being able to visually examine actual data helps me though.
Practical Results
A quick examination of the executed flag from AppCompatCacheParser on a fairly clean build of Windows 11 shows that it does look like everything with a Yes was executed, either by myself, or as part of an upgrade/install that was expected (items executing out of %temp%). However, there were a number of things that had a No executed that should have been executed, for example lsass.exe, cmd.exe, and explorer.exe. All of which were executed, either by myself or the operating system. If you are guessing that maybe built in executables don’t get flagged or something, there’s others that fall into the same bucket, such as msiexec.exe, conhost.exe, and regsvr32.exe, that were marked as Executed. Maybe this is where the extra unknown values come in?
I feel like the takeaway from this is that something marked with a Yes has very likely been executed. However, something marked with a No doesn’t mean it hasn’t been executed. As I said in the beginning though, I still wouldn’t rely on this until more definitive evidence comes forward. At least now we know where the flag comes from though.
Wrapping up
This "execution flag" appears to have been in place for some time, possibly since the beginning of Windows 10. I haven't gone back that far, but I have gone back to Windows 11 RTM, which does have the execution flag. I know I've used AppCompatCacheParser against Windows 11 before and seen NA, so the flag was there, just not being processed at that time. Looking at the Github history, it appears the Windows 10 (and 11) parser has been around since April 2015, but the flag was only added on March 2023.
References and Resources
- @hskang00 - Digital Forensics #50 (Shimcache) and associated paper "A SimCache Structural Analysis and A Detection tool forAnti-Forensics Tool Execution Evidence on Windows 10"
- Eric Zimmerman’s AppCompatCacheParser and Registry Explorer
- 13Cubed’s Shimcache Series
- 010 Editor
010 Editor Template
//------------------------------------------------
//--- 010 Editor v15.0 Binary Template
//------------------------------------------------
struct FILE {
struct HEADER {
char type[52];
} header <bgcolor=cLtGray>;
typedef struct {
char Signature[4] <bgcolor=cLtRed>;
char Unidentified[4] <bgcolor=cLtGreen>;
int EntryLength <bgcolor=cLtBlue>;
ushort PathLength <bgcolor=cBlue>;
char Path[PathLength] <bgcolor=cLtAqua>;
int64 LastModTime <bgcolor=cLtYellow>;
ushort DataLength <bgcolor=cGreen>;
char DataValues[DataLength-2] <bgcolor=cLtPurple>;
ushort Execution <bgcolor=cPurple>;
char Padding[2] <bgcolor=cRed>;
} RECORD;
RECORD record[1024] <optimize=false>;
} file;