NanoCore: DNS Cache Collection — Detection

NanoCore Image

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

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!!!