Trellix Insights: Malware Using CLFS Log Files for Stealth
Technical Articles ID:
KB95150
Last Modified: 2022-09-07 10:47:34 Etc/GMT
Last Modified: 2022-09-07 10:47:34 Etc/GMT
Environment
IMPORTANT: This Knowledge Base article discusses a specific threat that is being automatically tracked by Trellix Insights technology. The content is intended for use by Trellix Insights users, but is provided for general knowledge to all customers. Contact us for more information about Trellix Insights.
Summary
The PRIVATELOG and STASHLOG malware families were discovered in 2021 and use the Common Log File System (CLFS) to hide a second-stage payload. CLFS is used by the Kernel Transaction Manager (KTM) in Microsoft Windows for both Transactional NTFS (TxF) and Transactional Registry (TxR) operations.
Most strings used by the malware are obfuscated with each string encrypted with a unique byte stream. Hiding artifacts, hijacking execution flow, DLL side-loading, and process Doppelganging are used for persistence, privilege escalation, and defense evasion.
Our Threat Research team gathers and analyzes information from multiple open and closed sources before disseminating intelligence reports. This campaign was researched by FireEye and shared publicly.
How to use this article:
- If a Threat Hunting table has been created, use the rules contained to search for malware related to this campaign.
- Review the product detection table and confirm that your environment is at least on the specified content version.
To download the latest content versions, go to the Security Updates page. - Scroll down and review the "Product Countermeasures" section of this article. Consider implementing them if they are not already in place.
- Review
KB91836 - Countermeasures for entry vector threats . - Review KB87843 - Dynamic Application Containment rules and best practices.
- Review KB82925 - Identify what rule corresponds to an Adaptive Threat Protection and Threat Intelligence Exchange event.
meta: author = "adrien.bataille@mandiant.com" description = "This rule looks for CLFS containers containing possible data used by PRIVATELOG. As this rule may loop on file content, preferably use on regtrans-ms files only or with caution." condition: filesize < 100MB and filesize >= 512KB and uint16(0) == 0x0015 // signature and uint8(2) != 0 // fixup value upper byte and uint8(3) == 0 // always 0 and uint16(4) == uint16(6) and uint16(4) != 0 // num sectors and uint32(8) == 0 // always 0 and uint32(16) == 1 // always 1 and uint32(20) == 0 // always 0 and uint32(40) == 0x70 // size of record header // size of data at least 0x28 for first record and uint32(0x70+0x18) - 0x28 >= 0x28 // payloadHeader.numblocks (payloadHeader at 0x70+uint16(0x70+0x22)) and (uint16(0x70+uint16(0x70+0x22)+0x10) == 0x2 or uint16(0x70+uint16(0x70+0x22)+0x10) == 0x3 or uint16(0x70+uint16(0x70+0x22)+0x10) == 0x4 or uint16(0x70+uint16(0x70+0x22)+0x10) == 0x5) // this is a size, assume it is less than our filesize and uint32(0x70+uint16(0x70+0x22)+0xC) < filesize // confirm malware using 2 different methods and ( // look for hardcoded magic in first log record uint32(0x70+uint16(0x70+0x22)) == 0x00686365 or // loop through each possible sector to look for a blockheader struct for any i in (0 .. (filesize \ 512) - 1): ( // look for record header, num sectors and size of record uint16(i*512)==0x0015 and uint16(i*512+4) == uint16(i*512+6) and uint16(i*512+4) != 0 and uint32(i*512+40) == 0x70 and uint32(i*512+0x88) > 0x28 and uint32(i*512+0x88) < filesize and // look for magic and blockheader.blocksize in payload uint32(i*512+0x70+uint16(i*512+0x70+0x22)) == 2 and uint32(i*512+0x70+uint16(i*512+0x70+0x22)+4) == uint32(i*512+0x88)-0x30 ) ) } |
|
meta: author = "adrien.bataille@mandiant.com" description = "This rule looks for CLFS containers with records containing high entropy. As this rule may loop on file content, preferably use on regtrans-ms files only or with caution." condition: filesize < 100MB and filesize >= 512KB and uint16(0) == 0x0015 // signature and uint8(2) != 0 // fixup value upper byte and uint8(3) == 0 // always 0 and uint16(4) == uint16(6) and uint16(4) != 0 // num sectors and uint32(8) == 0 // always 0 and uint32(16) == 1 // always 1 and uint32(20) == 0 // always 0 and uint32(40) == 0x70 // size of record header and for any i in (0 .. (filesize \ 512) - 1) : ( // look for record header, num sectors and size of record uint16(i*512)==0x0015 and uint16(i*512+4) == uint16(i*512+6) and uint16(i*512+4) != 0 and uint32(i*512+40) == 0x70 and uint32(i*512+0x88) > 0x200 and uint32(i*512+0x88) < filesize // look for high entropy in the record[8:] to account for possible block header and math.entropy(i*512+0x70+uint16(i*512+0x70+0x22)+8, i*512+0x70+uint16(i*512+0x70+0x22)+uint32(i*512+0x88)-0x28) > 7.95 ) } |
|
meta: author = "adrien.bataille@mandiant.com" description = "Detects PRIVATELOG and STASHLOG variants based on strings and imports" md5 = "91b08896fbda9edb8b6f93a6bc811ec6" strings: $hvid = "Global\\HVID_" ascii $apci = "Global\\APCI#" wide condition: uint16(0) == 0x5A4D and uint32(uint32(0x3C)) == 0x00004550 and ( all of them and ( pe.imports("clfsw32.dll","CreateLogMarshallingArea") and pe.imports("kernel32.dll", "VirtualProtect") and pe.imports("ktmw32.dll", "CreateTransaction") and pe.imports("kernel32.dll", "CreateFileTransactedA") ) ) } |
|
meta: author = "adrien.bataille@mandiant.com" description = "Detects possible PRIVATELOG and STASHLOG variants based on strings or imports. This rule is purposefully loose so there may be a higher FP rate." md5 = "91b08896fbda9edb8b6f93a6bc811ec6" strings: $hvid = "Global\\HVID_" ascii $apci = "Global\\APCI#" wide condition: uint16(0) == 0x5A4D and uint32(uint32(0x3C)) == 0x00004550 and ( any of them or ( pe.imports("clfsw32.dll","CreateLogMarshallingArea") and pe.imports("kernel32.dll", "VirtualProtect") and pe.imports("ktmw32.dll", "CreateTransaction") and pe.imports("kernel32.dll", "CreateFileTransactedA") ) ) } |
|
meta: author = "adrien.bataille@mandiant.com" description = "Detects possible hijack of legitimate prntvpt.dll based on missing export" md5 = "91b08896fbda9edb8b6f93a6bc811ec6" condition: uint16(0) == 0x5A4D and uint32(uint32(0x3C)) == 0x00004550 and pe.exports("PTOpenProviderEx") and not pe.exports("MergeAndValidatePrintTicketThunk") } |
This Knowledge Base article discusses a specific threat that's being tracked. The list of IOCs will change over time; check Trellix Insights for the latest IOCs.
Campaign IOC
Type | Value |
Minimum Content Versions
Content Type | Version |
Detection Summary
IOC | Scanner | Detection |
IOC | Scanner | Detection |