Keylogger Data Stored in an ADS

    Published: 2025-07-15. Last Updated: 2025-07-15 07:32:44 UTC
    by Xavier Mertens (Version: 1)
    0 comment(s)

    If many malware samples try to be "filess" (read: they try to reduce their filesystem footprint to the bare minimum), another technique remains interesting: Alternate Data Streams or "ADS"[1]. This NTFS feature allows files to contain multiple data streams, enabling hidden or additional metadata to be stored alongside the main file content without being visible in standard file listings. A common usage of ADS is the "Mark of the Web"[2] that helps to flag files as suspicious or not depending on their origin.

    I found a simple Python keylogger that implements an ADS to store the captured keystrokes:

    hidden_dir = os.path.join(os.environ['APPDATA'], 'Microsoft\\Windows\\Cache')
    os.makedirs(hidden_dir, exist_ok=True)
    log_host_file = os.path.join(hidden_dir, "syscache.dat")
    log_file = log_host_file + ":logdata"

    A second layer of protection is implemented to hide the file using SetFileAttributesW()[3] with the flag 0x02:

    try:
        FILE_ATTRIBUTE_HIDDEN = 0x02
        ctypes.windll.kernel32.SetFileAttributesW(log_host_file, FILE_ATTRIBUTE_HIDDEN)
    except Exception as e:
        print(f"Failed to hide host file: {e}")

    The script is a classic keylogger but it also implements a clipboard monitor to capture all text content:

    try:
        win32clipboard.OpenClipboard()
        if win32clipboard.IsClipboardFormatAvailable(win32clipboard.CF_TEXT):
            data = win32clipboard.GetClipboardData()
            win32clipboard.CloseClipboard()
    

    The script (SHA256:9927159c39a0201e2fcd558c4716fc5cab7e1c6ab69a311f7a21cab3c5667980) has a low VT score (only 3/64) even if not obfuscated. The script does not have an exfiltration mechanism, therefore I presume that another one will take care of this!

    How to detect if files have ADS on your file system? This can be achieve with a few lines of PowerShell:

    Get-ChildItem -Recurse -Path C:\ | ForEach-Object {
        $streams = Get-Item $_.FullName -Stream * -ErrorAction SilentlyContinue
        if ($streams.Count -gt 1) { 
            $streams 
        }
    }

    Example:

    PS C:\Users\REM> C:\Users\REM\Documents\ads_search.ps1
    
    PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\Users\REM\Desktop\PURCHASE_ORDER.exe::$DATA
    PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\Users\REM\Desktop
    PSChildName   : PURCHASE_ORDER.exe::$DATA
    PSDrive       : C
    PSProvider    : Microsoft.PowerShell.Core\FileSystem
    PSIsContainer : False
    FileName      : C:\Users\REM\Desktop\PURCHASE_ORDER.exe
    Stream        : :$DATA
    Length        : 1044992
    
    PSPath        : Microsoft.PowerShell.Core\FileSystem::C:\Users\REM\Desktop\PURCHASE_ORDER.exe:Zone.Identifier
    PSParentPath  : Microsoft.PowerShell.Core\FileSystem::C:\Users\REM\Desktop
    PSChildName   : PURCHASE_ORDER.exe:Zone.Identifier
    PSDrive       : C
    PSProvider    : Microsoft.PowerShell.Core\FileSystem
    PSIsContainer : False
    FileName      : C:\Users\REM\Desktop\PURCHASE_ORDER.exe
    Stream        : Zone.Identifier
    Length        : 608

    [1] https://infosecwriteups.com/ntfs-filesystem-alternate-data-stream-ads-c0e4a2402563
    [2] https://en.wikipedia.org/wiki/Mark_of_the_Web
    [3] https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesw
     

    Xavier Mertens (@xme)
    Xameco
    Senior ISC Handler - Freelance Cyber Security Consultant
    PGP Key

    0 comment(s)
    ISC Stormcast For Tuesday, July 15th, 2025 https://isc.sans.edu/podcastdetail/9526

      Comments


      Diary Archives