This post documents a potential technique for stealthy inter-process communication on Windows. The idea is based on the apparent absence of Etw (and EtwTi) instrumentation of system calls involving the Windows Kernel Transaction Manager, and the ability for processes to receive handles to transaction objects without accessing instrumented system calls.
Overview
While re-reading Matt Hand’s “Evading EDR”, I was following along with the methodology described involving graph analysis of function call trees. The premise is to convert a Ghidra analysis of call graphs in a binary to a JSON format suitable for use in Neo4j, allowing for robust querying.
The example provided in Chapter 12 demonstrates identifying Nt family functions with edges to known EtwTi functions. Reading this gave me the idea to write a query that does something slightly different: Identify system call functions without known calls to Etw functions.
The query for this is relatively simple:
MATCH (f:Function)
WHERE f.name STARTS WITH 'Nt'
AND NOT f.name CONTAINS '$'
AND NOT EXISTS {
CALL {
WITH f
MATCH (f)-[:CALLS*1..25]->(t:Function)
WHERE t.name STARTS WITH 'Etw'
RETURN t
}
}
RETURN f;
This will simply traverse the call graph from Nt-family functions and report on those which do not have an outgoing ETW call somewhere inside.
On my Windows 11 26100 reversing machine, this query identified 47 records. Nearly all are related to the Windows Kernel Transaction Manager (KTM). After briefly reviewing the Microsoft documentation, I started to wonder if the KTM service was suitable for anything useful for offensive purposes, as it appears to be ignored by Etw.
While I haven’t gone into depth to verify that no form of security instrumentation exists for these system calls in the kernel, the graph analysis results are promising. With this in mind, I set out with the purpose of at least determining if some sort of inter-process communication could be implemented using KTM. If true, it may merit a closer look and potentially some utility in same-host offensive scenarios. As a side note, I haven’t extensively surveyed the previous work in the area of KTM, so it’s possible that someone has stumbled upon this before, but it still seems quite niche and potentially under-explored.
While reviewing the documentation, an initial objective was to determine how one would work with the transaction manager in the first place. Fortunately, it is relatively straightforward and decently documented.
The first step is to simply create a transaction object with NtCreateTransaction. This can be done from unprivileged user-mode contexts. While reading the function documentation, I noticed that it takes a parameter of type PUNICODE_STRING, potentially allowing for the storage of a decently large amount of information inside the kernel. If there is a way to recover this, it could be used as an odd form of generic inter-process communication.
At this point I set some criteria for the technology for further investigation:
- The technique must allow for some sort of data to be sent to the kernel and recovered by another process.
- There must be a way to use only the uninstrumented system calls.
- All system calls must be shown as uninstrumented by the graph query.
- All operations must be from an unprivileged context.
...