# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""Gather Windows Anti-Spyware information.
Supporting:
McAfee Anti-spyware module 8.x
CA PestPatrol Corporate Edition 5.023
Webroot SpySweeper Enterprise Edition 2.5
Lavasoft AdAware SE Enterprise Edition 2005
"""

import os
import platform
import stat
import time
import winreg as _winreg
from win32com.client import GetObject
import re
import win32api
import datetime
import logging

from sigma.util import console

#from sigma.util import config

logger = logging.getLogger("gather.gather_winantisw")

# _ipre matches ip addresses.
_ipre = r"\d+\.\d+\.\d+\.\d+"
assert re.match(_ipre, "192.168.1.6"), "_ipre is broken"

# _optipre matches an IP address or "*".
_optipre = r"(" + _ipre + r"|\*)"
assert re.match(_optipre, "192.168.1.6"), "_optipre (normal case) is broken."
assert re.match(_optipre, "*"), "_optipre (optional case) is broken."

# Match a port number or "*".
_optport = r"(\d+|\*)"
assert re.match(_optport, "123"), "_optport (normal case) is broken."
assert re.match(_optport, "*"), "_optport (optional case) is broken."

windows_linere = re.compile(
    r"\s*(?P<proto>(TCP|UDP))\s+"
    + r"(?P<localip>" + _ipre + r"):(?P<localport>\d+)\s+"
    + r"(?P<foreignip>" + _optipre + r"):(?P<foreignport>" + _optport+r")\s+"
    + r"(?P<state>[^\s]*)\s+(?P<pid>\d+)\s*")

columns = ["proto", "localip", "localport",
           "foreignip", "foreignport", "state", "pid"]

def Gather(collector):
    if os.name != "nt":
        raise RuntimeError("Can only gather Windows process info on Windows")
    table = collector.table("winantisw", ["aswname", "aswversion", "aswdefversion", "aswlastused", "serverport", "serverhost", "criticalobj"])
    rootKey = getattr(_winreg, "HKEY_LOCAL_MACHINE")
    GetAdAware(table, rootKey)
    GetSpySweeper(table, rootKey)
    GetPestPatrol(table)
    GetMcAfeeASW(table, rootKey)
    

def GetMcAfeeASW(table, rootKey):
    prVer = ""
    prName = ""
    defVer = ""
    lastUsed = 0
    serverPort = ""
    serverHost = ""
    criticalObj = 0

    # get the product name and version
    try:
        #registry for McAfee Anti-Spyware app       
        baseKey = _winreg.OpenKey(rootKey, r"SOFTWARE\Network Associates\ePolicy Orchestrator\Application Plugins\VSEMAS__8000")
    except OSError as err:
        return
    
    try:
        try:
            nsubkeys, nvalues, mtime = _winreg.QueryInfoKey(baseKey)
            for i in range(nvalues):
                nm, val, rtype = _winreg.EnumValue(baseKey, i)
                if nm.lower() == 'version':
                    prVer = val
                elif nm.lower() == 'product name':
                    prName = val
        except OSError as err:
            return
    finally:
        _winreg.CloseKey(baseKey)

    # if product name could not be found, return
    if prName == "":
        return

    # get def version
    try:
        baseKey = _winreg.OpenKey(rootKey, r"Software\Network Associates\TVD\VirusScan Enterprise\CurrentVersion")
        try:
            try:
                nsubkeys, nvalues, mtime = _winreg.QueryInfoKey(baseKey)
                for i in range(nvalues):
                    nm, val, rtype = _winreg.EnumValue(baseKey, i)
                    if nm.lower() == 'szvirdefver':
                        defVer = val
                        break
            except OSError as err:
                pass
        finally:
            _winreg.CloseKey(baseKey)
    except OSError as err:
        pass

    try:
        baseKey = _winreg.OpenKey(rootKey, 
            r"Software\Network Associates\TVD\VirusScan Enterprise\CurrentVersion\TaskLastData")
        try:
            nSubkeys1, nValues1, mtime1 = _winreg.QueryInfoKey(baseKey)
            for i in range(nSubkeys1):
                try:
                    subkeyName = _winreg.EnumKey(baseKey, i)
                except OSError as err:
                    continue 
                try:
                    subKey = _winreg.OpenKey(baseKey, subkeyName)
                except OSError as err:
                    continue 
                try:
                    nSubkeys2, nValues2, mtime2 = _winreg.QueryInfoKey(subKey)
                    problems = 0
                    newLastUsed = 0
                    for j in range(nValues2):
                        nm, val, rtype = _winreg.EnumValue(subKey, j)
                        if nm.lower() == "szendtime":
                            # time in registry is in UTC
                            epoch = datetime.datetime(1601, 1, 1)
                            try:
                                newLastUsed = int(time.mktime((epoch + datetime.timedelta(microseconds=int(val)/10)).timetuple())) - time.timezone
                            except:
                                logger.exception(val)
                        if nm.lower().endswith("infected"):
                            problems = problems + val
                    if newLastUsed > lastUsed:
                        lastUsed = newLastUsed
                        criticalObj = problems
                finally:
                    _winreg.CloseKey(subKey)
        finally:
            _winreg.CloseKey(baseKey)
    except:
        pass
    #print time.ctime(lastUsed)
    table.add((prName, prVer, defVer, lastUsed, serverPort, serverHost, criticalObj))


# this fn gets the pid for ppRemoteService.exe    
def GetPestPatrolPID():
    pid = ""
    try:
        objItems = GetObject("WinMgMts://./root/cimv2").InstancesOf("Win32_Process")
        for item in objItems:
            caption = item.Properties_("Caption").Value
            if caption.lower() == "ppremoteservice.exe":
                pid = item.Properties_("ProcessID").Value
                break
    except:
        logger.exception("winantisw gather: process list retrieval failed")
    return str(pid)

def GetPestPatrolServerInfoNetstat():
    ppPID = GetPestPatrolPID()
    afile = console.popen("netstat -ano", "r")
    try:
        for line in afile:
            match = windows_linere.match(line)
            if match:
                proto, localip, localport, foreignip, foreignport, state, pid = \
                        match.group(*columns)
                if pid == ppPID:
                    if foreignip == '*' or foreignip == '0.0.0.0':
                            continue
                    return (foreignip, foreignport)
    finally:
        afile.close()
    return ("", "")

def GetPestPatrolTime(s):
    # Input is of the form 2005/06/08-14:48:47
    assert len(s) == 19
    YYYY = int(s[0:4])
    MM = int(s[5:7])
    DD = int(s[8:10])
    hh = int(s[11:13])
    mm = int(s[14:16])
    ss = int(s[17:19])
    t = (YYYY, MM, DD, hh, mm, ss, -1, -1, -1) # Assume it's local time
    return int(time.mktime(t))

def GetFileVersion(path):
    prName, prVer = "", ""
    if os.path.exists(path):
        try:
            #ver_strings=('ProductName', 'ProductVersion')
            pairs = win32api.GetFileVersionInfo(path, '\\VarFileInfo\\Translation')
            for lang, codepage in pairs:
                #print 'lang: ', lang, 'codepage:', codepage
                strInfo = u'\\StringFileInfo\\%04X%04X\\%s' % (lang, codepage, "ProductName")
                prName = win32api.GetFileVersionInfo(path, strInfo)
                strInfo = u'\\StringFileInfo\\%04X%04X\\%s' %(lang, codepage, "ProductVersion")
                prVer = win32api.GetFileVersionInfo(path, strInfo)
                prVer = prVer.replace(" ", "")
                prVer = prVer.replace(",", ".")
        except:
            pass
    return (prName, prVer)
            
def GetPestPatrol(table):
    clientPort = ""
    serverPort = ""
    serverHost = ""
    objScanned = ""
    criticalObj = 0
    problems = ""
    lastUsed = 0
    lastTime = ""
    fileName = os.path.join(os.getenv("ProgramFiles"),
                            "Common Files\\PestPatrol\\ppRemoteService.exe")
    prName, prVer = GetFileVersion(fileName)
    if prName == "" or prVer == "":
        fileName = os.path.join(os.getenv("SystemRoot"),
                            "System32\\ppRemoteService.exe")
        prName, prVer = GetFileVersion(fileName)
        if prName == "" or prVer == "":
            return
    fileName = os.path.join(os.getenv("ProgramFiles"), "Common Files\\PestPatrol\\ppctl.dll")
    if os.path.exists(fileName):
        compName, compVer = GetFileVersion(fileName)
##    fileName = os.path.join(os.getenv("ProgramFiles"), "Common Files\\PestPatrol\\pploc.dat")
##    if os.path.exists(fileName):
##        stats = os.stat(fileName)
##        pplocDat = stats[6]
##    fileName = os.path.join(os.getenv("ProgramFiles"), "Common Files\\PestPatrol\\ppfile.dat")
##    if os.path.exists(fileName):
##        stats = os.stat(fileName)
##        ppfileDat = stats[6]
##    fileName = os.path.join(os.getenv("ProgramFiles"), "Common Files\\PestPatrol\\ppinfo.dat")
##    if os.path.exists(fileName):
##        stats = os.stat(fileName)
##        ppinfoDat = stats[6]
##    defVer = "%s (%s), %s (%s), %s (%s), %s (%s)" % ("ppctl.dll", compVer, "pploc.dat: ", pplocDat, "ppfile.dat: ", ppfileDat, "ppinfo.dat: ", ppinfoDat)
    defVer = compVer
    fileName = os.path.join(os.getenv("ProgramFiles"), "Common Files\\PestPatrol\\scanHistory.txt")
    if os.path.exists(fileName):
        try:
            f = open(fileName, "r")
            try:
                line = f.readline()
                while line:
                    if line.find(",") >= 0:
                        lastTime, problems = line.split(",", 1)
                    line = f.readline()
            except:
                pass
            f.close()
        except:
            pass
    try:
        if lastTime != None and lastTime != "":
            lastUsed = GetPestPatrolTime(lastTime)
    except:
        logger.exception(lastTime)
    problems = re.sub(r"\n", "", problems)
    problems = re.sub(r"\r", "", problems)
    if problems.isdigit():
        criticalObj = int(problems)

    if os.name == "nt":
        rel = platform.release().lower()
        if rel != "2000":
            serverHost, serverPort = GetPestPatrolServerInfoNetstat()
    table.add((prName, prVer, defVer.strip(), lastUsed, serverPort.strip(), serverHost.strip(), criticalObj))

        
def GetSpySweeper(table, rootKey):
    prVer = ""
    prName = ""
    defVer = ""
    logDir = ""
    clientPort = ""
    serverPort = ""
    serverHost = ""
    objScanned = ""
    criticalObj = 0
    try:
        rootKey1 = _winreg.OpenKey(rootKey, 
            r"Software\Microsoft\Windows\CurrentVersion\Uninstall")
    except OSError as err:
        return
    try:
        prName, prVer = walkregkey(table, rootKey1, 
            r"Software\Microsoft\Windows\CurrentVersion\Uninstall",
            "webroot spy sweeper enterprise client")
        if prName == "":
            return
        try:
            swKey = _winreg.OpenKey(rootKey, r"Software\Webroot\Enterprise\Spy Sweeper")
        except OSError as err:
            return
        try:
            nSubkeys, nValues, mtime = _winreg.QueryInfoKey(swKey)
            for i in range(nValues):
                nm, val, typ = _winreg.EnumValue(swKey, i)
                if nm.lower() == 'version':
                    version = val
                    try:
                        appver, defVer = version.split(",", 1)
                    except:
                        logger.exception(version)
                elif nm.lower() == 'id':
                    logDir = os.path.join(val, "logs")
        finally:
            _winreg.CloseKey(swKey)
        try:
            swKey = _winreg.OpenKey(rootKey, r"Software\Webroot\Enterprise\CommAgent")
            try:
                nSubkeys, nValues, mtime = _winreg.QueryInfoKey(swKey)
                for i in range(nValues):
                    nm, val, typ = _winreg.EnumValue(swKey, i)
                    if nm.lower() == 'su':
                        server = val
                        if server.startswith("http://"):
                            server = server[7:]
                            try:
                                serverHost, serverPort = server.split(":", 1)
                            except:
                                logger.exception(server)
                    if defVer == "":
                        if nm.lower() == 'sdfv':
                            defVer = val
                    
            finally:
                _winreg.CloseKey(swKey)
        except OSError as err:
            pass
    finally:
        _winreg.CloseKey(rootKey1)
    lastUsed, mainLogFile = GetSpySweeperLogFileName(logDir)
    if not GetSpySweeperLogData(mainLogFile):
        lastUsed = 0
    table.add((prName, prVer, defVer.strip(), lastUsed, serverPort.strip(), serverHost.strip(), criticalObj))

def GetSpySweeperLogFileName(logDir):
    lastFile = ""
    lastUsed = 0
    try:
        fileList = os.listdir(logDir)
        for aFile in fileList:
            filePath = os.path.join(logDir, aFile)
            if os.path.isfile(filePath):
                if not filePath.lower().endswith(".txt"):
                    continue
                try:
                    newDate = getFileDate(filePath)
                    if lastUsed < newDate:
                        lastUsed = newDate
                        lastFile = filePath
                except:
                    continue
    except:
        pass
    return (lastUsed, lastFile)

def GetSpySweeperLogData(mainLogFile):
    found = False
    try:
        f = open(mainLogFile, "r")
        #regexp = 'Full Sweep has completed.'
        regexp = 'Sweep Completed'
        rs = re.compile(regexp, re.IGNORECASE)
        try:
            line = f.readline()
            while line:
                m = re.search(rs, line)
                if m:
                    found = True
                    break
                line = f.readline()
        finally:
            f.close() 
    except:
        pass
    return found

def GetAdAware(table, rootKey):
    prVer = ""
    prName = ""
    logDir = ""
    clientPort = ""
    serverPort = ""
    serverHost = ""
    try:
        rootKey1 = _winreg.OpenKey(rootKey, 
            r"Software\Microsoft\Windows\CurrentVersion\Uninstall")
    except OSError as err:
        return
    try:
        prName, prVer = walkregkey(table, rootKey1, 
            r"Software\Microsoft\Windows\CurrentVersion\Uninstall",
            "ad-aware se enterprise 2005 client")
        if prName == "":
            return
        try:
            swKey = _winreg.OpenKey(rootKey, r"Software\Lavasoft\Ad-Axis Client")
        except OSError as err:
            return
        try:
            nSubkeys, nValues, mtime = _winreg.QueryInfoKey(swKey)
            for i in range(nValues):
                nm, val, typ = _winreg.EnumValue(swKey, i)
                if nm.lower() == 'logdir':
                    logDir = val
                elif nm.lower() == 'portno':
                    clientPort = val
                elif nm.lower() == 'serverportno':
                    serverPort = val
                elif nm.lower() == 'serverhost':
                    serverHost = val
        finally:
            _winreg.CloseKey(swKey)
    finally:
        _winreg.CloseKey(rootKey1)

    # get the last file which has the log for starting of scan
    lastScanFile = GetLastFileWithPattern(logDir, "Executing Ad-AwareSE")
    # get the scan time from the last scan log file
    lastUsed = GetLastUsed(lastScanFile)

    # get the last result log file name
    dataLogFile = GetLastFile("%SystemDrive%\\Documents and Settings\\LocalService\\Application Data\\Lavasoft\\Ad-Aware\\Logs")
    # parse the last scan result log
    defVer, objScanned, criticalObj = GetAdAwareLogData(dataLogFile)

    # get the last file which has client version info
    lastVersionFile = GetLastFileWithPattern(logDir, "Starting Ad-Axis client version")
    # get the version from the file
    v = GetLastClientVer(lastVersionFile)
    # use the registry version if file does not have version
    if v != "":
        prVer = v

    table.add((prName, prVer, defVer, lastUsed, serverPort, serverHost, criticalObj))


# helper function to find the last file in the specified dir with specified string
def GetLastFileWithPattern(logDir, pattern):
    lastFile = ""
    lastUsed = 0
    try:
        fileList = os.listdir(logDir)
        for aFile in fileList:
            filePath = os.path.join(logDir, aFile)
            if os.path.isfile(filePath):
                if filePath.lower().endswith("_trace.log"):
                    continue
                if not filePath.lower().endswith(".log"):
                    continue
                try:
                    f = open(filePath, "r")
                    try:
                        lines = f.read()
                        if lines.find(pattern) >= 0:
                            newDate = getFileDate(filePath)
                            if lastUsed < newDate:
                                lastUsed = newDate
                                lastFile = filePath
                    finally:
                        f.close()
                except:
                    continue
    except:
        pass
    return lastFile


# helper function to get the last scan time from the file
def GetLastUsed(logFile):
    lastUsed = 0
    try:
        f = open(logFile, "r")
        regexp = r'(.*)\s+\[DEBUG\]\s+Executing Ad-AwareSE \(\d+\)'
        rs = re.compile(regexp, re.IGNORECASE)
        try:
            line = f.readline()
            while line:
                m = re.search(rs, line)
                if m:
                    try:
                        lastUsed = GetAdAwareTime(m.group(1))
                    except:
                        logger.exception(m.group(1))
                    #line = f.readline()
                    #regexp2 = r'Input logpath:\s(.*)\s\(\d+\)$'
                    #rs2 = re.compile(regexp2, re.IGNORECASE)
                    #while line:
                    #    m = re.search(rs2, line)
                    #    if m:
                    #        resultFile = m.group(1)
                    #        break
                    #    line = f.readline()
                line = f.readline()
        finally:
            f.close() 
    except:
        pass
    return lastUsed


# helper function to find the last file in the specified folder
def GetLastFile(logDir):
    lastFile = ""
    lastUsed = 0
    exlogDir = logDir
    i = logDir.find("%")
    if i >= 0:
         j = logDir.find("%", i+1)
         if j > i:
             key = logDir[i+1: j]
             env = os.getenv(key)
             if env is not None:
                 exlogDir = logDir[:i] + env + logDir[j+1:]
    try:
        fileList = os.listdir(exlogDir)
        for aFile in fileList:
            filePath = os.path.join(exlogDir, aFile)
            if os.path.isfile(filePath):
                if not filePath.lower().endswith(".txt"):
                    continue
                newDate = getFileDate(filePath)
                if lastUsed < newDate:
                    lastUsed = newDate
                    lastFile = filePath
    except:
        pass
    return lastFile


# helper function to get scan result from the log file
def GetAdAwareLogData(logFile):
    defVer = ""
    objScanned = ""
    problems = ""
    criticalObj = 0
    try:
        f = open(logFile, "r")
        regexp1 = r'Ad-Aware SE Build (\d+)'
        rs1 = re.compile(regexp1, re.IGNORECASE)
        regexp2 = r'Using definitions file:(.*)'
        rs2 = re.compile(regexp2, re.IGNORECASE)
        regexp3 = r'Objects scanned:(.*)'
        rs3 = re.compile(regexp3, re.IGNORECASE)
        regexp4 = r'New critical objects:(.*)'
        rs4 = re.compile(regexp4, re.IGNORECASE)
        try:
            line = f.readline()
            while line:
                m = re.search(rs2, line)
                if m:
                    defVer = m.group(1)
                m = re.search(rs3, line)
                if m:
                    objScanned = m.group(1)
                m = re.search(rs4, line)
                if m:
                    problems = m.group(1)
                line = f.readline()
        finally:
            f.close() 
    except:
        pass
    if problems != None and problems.isdigit():
        criticalObj = int(problems)
    return (defVer, objScanned, criticalObj)


# helper function to get client version from the log file
def GetLastClientVer(logFile):
    ver= ""
    try:
        f = open(logFile, "r")
        regexp = r'.*Starting Ad-Axis client version\s+(.*)\s+\(\d+\)'
        rs = re.compile(regexp, re.IGNORECASE)
        try:
            line = f.readline()
            while line:
                m = re.search(rs, line)
                if m:
                    ver = m.group(1)
                line = f.readline()
        finally:
            f.close() 
    except:
        pass
    return ver


# helper function to compute time in seconds from the log time
def GetAdAwareTime(s):
    # Input is of the form 2005-08-16 13:19:18 
    assert len(s.strip()) == 19
    YYYY = int(s[0:4])
    MM = int(s[5:7])
    DD = int(s[8:10])
    hh = int(s[11:13])
    mm = int(s[14:16])
    ss = int(s[17:19])
    t = (YYYY, MM, DD, hh, mm, ss, -1, -1, -1) # Assume it's local time
    return int(time.mktime(t))

 
def walkregkey(table, key, path, name):
    nSubkeys, nValues, mtime = _winreg.QueryInfoKey(key)
    for i in range(nSubkeys):
        try:
            subkeyName = _winreg.EnumKey(key, i)
        except OSError as err:
            continue 
        #subkeyPath = path + "\\" + subkeyName
        try:
            subkey = _winreg.OpenKey(key, subkeyName)
        except OSError as err:
            continue 
        try:
            prName, prVer = walkregval(table, subkey, name)
            if prName != "":
                return (prName, prVer)
        finally:
            _winreg.CloseKey(subkey)
    return ("", "")



def walkregval(table, key, name):
    nSubkeys, nValues, mtime = _winreg.QueryInfoKey(key)
    prName = ""
    prVer = ""
    for i in range(nValues):
        nm, val, typ = _winreg.EnumValue(key, i)
        # Encoding these to bytes on Py3 made every comparison below false
        # (bytes != str), so prName/prVer came back empty on every host.
        if not isinstance(nm, str) or not isinstance(val, str):
            continue
        if nm.lower() == "displayname":
            if val.lower() == name:
                prName = val
        if nm.lower() == "displayversion":
            prVer = val
    return (prName, prVer)


def getFileDate(filename):
    if os.path.exists(filename):
        stats = os.stat(filename)
        return stats[stat.ST_MTIME]
    else:
        return 0


def _test():
    from sigma.gather import gathering
    gathering.test(Gather)

if __name__ == "__main__":
    _test()
