NanoCore: DNS Cache Collection — Detection
NanoCore reads the local DNS resolver cache through DnsGetCacheDataTable and writes
every A record to the same staging file as the other collectors. It is passive
reconnaissance: no queries are sent, so nothing shows up on the wire.
MITRE ATT&CK: T1005
Technique write-up: NanoCore_DNSLogging
Detection Indicators
| Indicator | Value |
|---|---|
| Log path | KB_<Environment.TickCount>.dat — under C:\Users\Public\ in this build, configuration-dependent |
| Naming | literal KB_ prefix, decimal tick count, .dat extension |
| Location | any user-writable staging directory — Public, %APPDATA%, %TEMP%, ProgramData |
| API | dnsapi!DnsGetCacheDataTable, freed with DnsRecordListFree |
| Filter | record type 1 — A records only |
| Module | dnsapi.dll loaded by a process with no name-resolution role |
| Traffic | none — the cache is read locally, so network telemetry sees nothing |
The KB_<ticks>.dat name is shared by all three NanoCore collection TTPs, so one broadened
file rule covers keystrokes, clipboard and DNS — but see the caveat below before weighting it.
⚠️ The output path is not a reliable indicator on its own. The recreated test case hardcodes
C:\Users\Public\KB_<TickCount>.dat, but where NanoCore stages collected data is configuration-dependent —%APPDATA%is at least as common in the wild, and the naming convention changes between builds. The file rule below is deliberately broadened across every plausible staging directory, and it is supporting evidence. The API-level rules are what actually identify the technique.
Log Sources
- Microsoft Defender XDR (
DeviceFileEvents) - Sysmon EID 11 (file create), EID 23/26 (file delete)
- Microsoft Defender XDR (
DeviceImageLoadEvents) - Sysmon EID 7 (image load)
Detection Engineering with Defender XDR
Same log artefact as the other two collectors.
// Supporting evidence, not proof - see the caveat above.
let staging = dynamic([@"\users\public\", @"\appdata\roaming\", @"\appdata\local\",
@"\programdata\", @"\windows\temp\"]);
DeviceFileEvents
| where tolower(FolderPath) has_any (staging)
| where FileName matches regex @"^KB_[0-9]+\.dat$"
or (FileName endswith ".dat" and tolower(InitiatingProcessFolderPath) has_any (staging))
| project Timestamp, DeviceName, AccountName, ActionType, FileName, FolderPath,
InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessCommandLine
| order by Timestamp desc
Detection Engineering with Sysmon
dnsapi.dll is loaded by almost everything that resolves a name, so this is a hunt
input rather than an alert — narrow it by the image’s own location.
WindowsEvent //(sysmon)
| where EventID == 7 // ImageLoad
| extend Image = tostring(EventData.Image), ImageLoaded = tostring(EventData.ImageLoaded)
| where ImageLoaded endswith "\dnsapi.dll"
| where Image has_any (@"\AppData\", @"\Users\Public\", @"\Temp\")
| project TimeGenerated, Computer, Image, ImageLoaded
Detection Engineering with SIGMA
Full rule: sigma_nanocore_collector_staging_file.yml — one file shared by all three collectors, kept with the keylogger so the rule has a single id
detection:
selection_name:
TargetFilename|re: '(?i)\\KB_[0-9]+\.dat$'
selection_location:
TargetFilename|contains:
- '\Users\Public\'
- '\AppData\Roaming\'
- '\AppData\Local\'
- '\ProgramData\'
condition: all of selection_*
Detection Engineering with YARA and image load
DnsGetCacheDataTable is undocumented and effectively unused outside ipconfig /displaydns,
which makes the import itself a strong signal — stronger than any file artefact.
Rules: nanocore_dns_cache_read.yar ·
sigma_nanocore_dnsapi_load.yml
$api1 = "DnsGetCacheDataTable" $api2 = "DnsRecordListFree" $mod = "dnsapi.dll"
Notes and tuning
This technique is invisible on the network. The cache is already populated; the sample just reads it. Any detection that watches for DNS queries or C2 lookups will miss it entirely, which is worth stating plainly because it is a common assumption.
DnsGetCacheDataTable is undocumented and effectively unused by legitimate software — the
only common consumer is ipconfig /displaydns. A binary outside System32 importing it is a
strong static signal, so add it to your import-based hunting alongside the file rule.
Disclaimer
For educational purposes only!!!