Synaptics病毒

空城:) 发布于 2024-11-18 1043 次阅读 无~ 3236 字 最后更新于 2025-09-09 预计阅读时间: 15 分钟


AI 摘要

几年前我就发现电脑一运行 exe 就会冒出带 “._cache_” 的隐藏文件,进程里还躺着 Synaptics.exe,当时以为是系统组件没在意。 直到最近才确认它是病毒:后台给所有 exe 加 Delphi 壳,U 盘也不放过,释放 C:ProgramDataSynapticsSynaptics.exe 伪装成“Synaptics Pointing Device Driver”(土耳其语版)。运行被壳程序会先落地一个隐藏的 ._cache_*.exe,多数时候还不删。 懒得等杀软更新,我用 Python+C# 撸了个专杀:Python 扫描文件属性里带 “Synaptics Pointing Device Driver” 的 exe 并写日志,C# 工具脱壳还原,再一键删病毒目录,顺手把进程也毙了。目前注册表启动项还没空清,但机器已经干净。

这件事还要从几年前说起,以前就发现电脑上打开exe程序会莫名其妙生成一个文件名带._cache_的隐藏文件,然后进程也有一个Synaptics.exe,我以为这是什么系统的自带的处理程序的软件,就没当回事。

直到前段时间,发现这个是病毒,会在后台运行,把桌面和所有U盘移动硬盘和打开的exe加一个Delphi的壳,然后用户运行加壳感染的文件,会运行病毒,并且在用户的C:\ProgramData\Synaptics\Synaptics.exe创建病毒原文件,图标会和启动的软件图标一样,并且文件说明是Synaptics Pointing Device Driver,土耳其语言。

运行感染文件会释放一个._cache_*.exe的原程序文件,并且是隐藏的,运行完病毒又会删除这个文件,不过大多数时候并没有删除,所以我使用Python+C#写了一个杀毒软件,软件原理是使用Python扫描文件属性带Synaptics Pointing Device Driver的程序,并且输出日志,然后使用C#进行解壳还原,并且使用Python删除C盘的病毒文件,暂时没有经历去删除注册表启动。

import os# os模块
import win32api# win32api模块
import time# 时间模块
from tqdm import tqdm# 进度条
import logging# 导入日志模块
from colorama import init, Fore, Style# 导入颜色模块
import sys# 导入系统模块
import shutil# 文件操作
import subprocess# 调用系统命令
import webbrowser# 打开浏览器

# 初始化colorama
init(autoreset=True)

class Colors:  # 定义颜色类
    HEADER = '\033[95m'# 定义颜色
    OKBLUE = '\033[94m'# 定义颜色
    OKCYAN = '\033[96m'# 定义颜色
    OKGREEN = '\033[92m'# 定义颜色
    WARNING = '\033[93m'# 定义颜色
    FAIL = '\033[91m'# 定义颜色
    ENDC = '\033[0m'# 定义颜色
    BOLD = '\033[1m'# 定义颜色
    UNDERLINE = '\033[4m'# 定义颜色

# 配置日志
logging.basicConfig(filename='scan_log.log', level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

def get_company_name_and_product_name(file_path):  # 获取文件信息
    propNames = ('Comments', 'InternalName', 'ProductName',# 定义文件信息
                 'CompanyName', 'LegalCopyright', 'ProductVersion',
                 'FileDescription', 'LegalTrademarks', 'PrivateBuild',
                 'FileVersion', 'OriginalFilename', 'SpecialBuild')

    props = {'FixedFileInfo': None, 'StringFileInfo': None, 'FileVersion': None}# 定义文件信息

    try:
        fixedInfo = win32api.GetFileVersionInfo(file_path, '\\')# 获取文件信息
        props['FixedFileInfo'] = fixedInfo# 保存文件信息
        props['FileVersion'] = "%d.%d.%d.%d" % (fixedInfo['FileVersionMS'] / 65536,
                                                fixedInfo['FileVersionMS'] % 65536, fixedInfo['FileVersionLS'] / 65536,
                                                fixedInfo['FileVersionLS'] % 65536)# 保存文件版本

        lang, codepage = win32api.GetFileVersionInfo(file_path, '\\VarFileInfo\\Translation')[0]#   获取文件信息

        strInfo = {}# 定义文件信息
        for propName in propNames:
            strInfoPath = u'\\StringFileInfo\\%04X%04X\\%s' % (lang, codepage, propName)
            strInfo[propName] = win32api.GetFileVersionInfo(file_path, strInfoPath)# 保存文件信息

        props['StringFileInfo'] = strInfo# 保存文件信息
    except Exception as e:
        return None, None

    if not props["StringFileInfo"]:# 如果文件信息为空,则返回None
        return None, None
    else:
        return props["StringFileInfo"].get("CompanyName"), props["StringFileInfo"].get("ProductName")# 返回公司名和产品名

def scan_drive_for_exe(drive, company_name, product_name):  # 扫描驱动器
    infected_files = []
    total_files = 0
    total_dirs = 0
    start_time = time.time()# 记录开始时间

    logging.debug(f"开始扫描驱动器 {drive}")

    for root, dirs, files in tqdm(os.walk(drive), desc=f"正在扫描 {drive}", unit='目录', position=0):# 遍历驱动器
        total_dirs += len(dirs)# 计算目录总数
        total_files += len(files)# 计算文件总数
        for filename in files:# 遍历文件
            if filename.lower().endswith('.exe'):# 判断文件是否为.exe文件
                file_path = os.path.join(root, filename)# 获取文件路径
                company, product = get_company_name_and_product_name(file_path)# 获取文件信息
                if company and product and company == company_name and product == product_name:
                    infected_files.append(file_path)#   将感染文件路径添加到列表中
                    logging.warning(f"发现感染文件: {file_path}")
                    print(Fore.RED + f"发现感染文件: {file_path}")

    end_time = time.time()# 记录结束时间
    elapsed_time = end_time - start_time# 计算运行时间
    files_per_second = total_files / elapsed_time if elapsed_time > 0 else 0# 计算每秒处理的文件数

    # 记录日志并打印结果
    if infected_files:
        logging.warning(f"在驱动器 {drive} 上发现 {len(infected_files)} 个感染文件:{infected_files}")
        print(Fore.RED + f"在驱动器 {drive} 上发现 {len(infected_files)} 个感染文件。")
    else:
        logging.info(f"在驱动器 {drive} 上未发现感染文件")
        print(Fore.GREEN + f"在驱动器 {drive} 上未发现感染文件。")

    return infected_files# 返回感染文件列表

def list_available_drives():  # 列出可用驱动器
    drives = []
    for letter in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':# 遍历字母
        drive = f"{letter}:\\".replace("\\", "")# 替换反斜杠为空
        if os.path.exists(drive) and os.path.isdir(drive):# 判断是否为目录
            drives.append(drive)# 将驱动器添加到列表中
    return drives# 返回驱动器列表

def scan_all_drives(company_name, product_name):  # 扫描所有驱动器
    available_drives = list_available_drives()# 列出可用驱动器
    for drive in tqdm(available_drives, desc='扫描所有驱动器', unit='驱动器'):# 遍历驱动器
        scan_drive_for_exe(drive, company_name, product_name)# 扫描驱动器
        synaptics_folder = "C:\\ProgramData\\Synaptics"# 定义 Synaptics 文件夹路径
        synaptics_exe = os.path.join(synaptics_folder, "Synaptics.exe")# 定义 Synaptics.exe 路径
        if os.path.exists(synaptics_folder) and os.path.exists(synaptics_exe):# 判断 Synaptics 文件夹和 Synaptics.exe 是否存在
            logging.warning(f"警告:在 {synaptics_folder} 中找到 Synaptics 文件夹和 Synaptics.exe,可能已感染病毒。")
            print(Fore.YELLOW + f"警告:在 {synaptics_folder} 中找到 Synaptics 文件夹和 Synaptics.exe,可能已感染病毒。")
        else:
            logging.info(f"在 {synaptics_folder} 中未找到 Synaptics.exe,系统看起来是干净的。")
            print(Fore.GREEN + f"在 {synaptics_folder} 中未找到 Synaptics.exe,系统看起来是干净的。")
            logging.info(f"扫描结束")

def process_file(file_path):  # 处理文件
    # 获取 ExtractResource.exe 的路径
    if hasattr(sys, '_MEIPASS'):
        extract_resource_path = os.path.join(sys._MEIPASS, 'ExtractResource.exe')# 获取当前目录
    else:
        extract_resource_path = 'ExtractResource.exe'# 获取当前目录

    # 构建命令行参数
    command = [extract_resource_path, file_path]# 构建命令行参数

    try:
        # 在新窗口中调用 ExtractResource.exe
        process = subprocess.Popen(command, creationflags=subprocess.CREATE_NEW_CONSOLE)# 调用 ExtractResource.exe

        # 输出结果
        logging.info(f"处理文件 {file_path} 成功")
        print(Colors.OKGREEN + f"处理文件 {file_path} 成功")
    except Exception as e:
        # 处理错误
        logging.error(f"处理文件 {file_path} 失败: {e}")
        print(Fore.RED + f"处理文件 {file_path} 失败: {e}")

def show_hidden_files(drive_letter):  # 删除病毒文件
    print(Colors.OKGREEN + f"如果提示删除文件夹时出错:[WinError 3] 系统找不到指定的路径。: 'C:\\ProgramData\\Synaptics'类似的不用管,说明没有残留在电脑的文件" + Colors.ENDC)
    folder_path = r'C:\ProgramData\Synaptics'
    try:
        shutil.rmtree(folder_path)
        print(Colors.OKGREEN + f"成功删除病毒文件夹及其所有内容:{folder_path}" + Colors.ENDC)
    except Exception as e:
        print(Colors.WARNING + f"删除文件夹时出错:{e}" + Colors.ENDC)

def killbd():
    os.system(f'taskkill /f /im Synaptics.exe >NUL 2>&1')  # 尝试杀死进程
    logging.info(f"病毒进程已杀死")
    print(Colors.WARNING + "病毒进程已杀死" + Colors.ENDC)

def wait_for_key_press():
    print(Colors.OKBLUE + "按任意键退出...")  # 显示提示
    input()  # 等待用户输入
    logging.info(f"杀毒结束")
    webbrowser.open('https://autkc.com')  # 打开博客
    os.system(f'start scan_log.log')  # 打开日志
    os.system(f'taskkill /f /im cmd.exe >NUL 2>&1')  # 关闭当前窗口

def clear_screen_and_display_title():  # 清理屏幕并显示标题
    os.system('cls')
    print(Colors.OKBLUE + "====================================================================================================================" + Colors.ENDC)
    print(Colors.OKGREEN + "                                          Synaptics病毒清理程序2.0  by 空城                                         " + Colors.ENDC)
    print(Colors.OKBLUE + "====================================================================================================================" + Colors.ENDC)

def search_exe_files(root_path, company_name, product_name):  # 搜索exe文件
    for dirpath, dirnames, filenames in os.walk(root_path):
        for filename in filenames:# 遍历文件
            if filename.lower().endswith('.exe'):# 判断是否为exe文件
                file_path = os.path.join(dirpath, filename)# 获取文件路径
                company, product = get_company_name_and_product_name(file_path)# 获取文件名
                if company and product and company == company_name and product == product_name:#     判断是否为 infections.txt
                    logging.warning(f"找到感染文件: {file_path}")# 输出警告
                    print(Colors.WARNING + f"找到感染文件: {file_path}")
                    process_file(file_path)# 处理文件
                    # 继续处理下一个文件

def check_dotnet_9_installed():# 检查.net是否安装
    try:
        result = subprocess.run(['dotnet', '--version'], capture_output=True, text=True, check=True)# 调用 dotnet --version 命令
        version = result.stdout.strip()
        if version.startswith('9.'):
            print(Colors.OKGREEN + "检测到 .NET 9.0 已安装。" + Colors.ENDC)
            return True
        else:
            print(Colors.WARNING + f"检测到 .NET 版本 {version},但不是 9.0。" + Colors.ENDC)
            print(Colors.WARNING + "请先安装 .NET 9.0 或更高版本,然后再运行此脚本。" + Colors.ENDC)
            return False
    except subprocess.CalledProcessError as e:
        print(Colors.FAIL + "未检测到 .NET 9.0 环境。" + Colors.ENDC)
        print(Colors.WARNING + "正在尝试安装 .NET 9.0 SDK..." + Colors.ENDC)
        return False
    except FileNotFoundError:
        print(Colors.FAIL + "未检测到 .NET 9.0 环境。" + Colors.ENDC)
        print(Colors.WARNING + "正在尝试安装 .NET 9.0 SDK..." + Colors.ENDC)
        return False
    except Exception as e:
        print(Colors.FAIL + f"检测 .NET 9.0 环境时发生未知错误: {e}" + Colors.ENDC)
        print(Colors.WARNING + "正在尝试安装 .NET 9.0 SDK..." + Colors.ENDC)
        return False

def install_dotnet_9():
    if hasattr(sys, '_MEIPASS'):# 判断是否为打包后的exe
        dotnet_installer = os.path.join(sys._MEIPASS, 'dotnet-sdk-9.0.100-win-x64.exe')# 获取当前目录
    else:
        dotnet_installer = os.path.join(os.path.dirname(__file__), 'dotnet-sdk-9.0.100-win-x64.exe')# 获取当前目录
    if os.path.exists(dotnet_installer):# 判断文件是否存在
        try:
            subprocess.run([dotnet_installer, '/quiet', '/install'], check=True)# 调用 .NET 安装程序
            print(Colors.OKGREEN + "已成功安装 .NET 9.0 SDK。" + Colors.ENDC)
            return True
        except subprocess.CalledProcessError as e:
            print(Colors.FAIL + f"安装 .NET 9.0 SDK 时发生错误: {e}" + Colors.ENDC)
            return False
    else:
        print(Colors.FAIL + "未找到 .NET 9.0 SDK 安装程序。" + Colors.ENDC)
        return False

def main():
    clear_screen_and_display_title()# 清理屏幕并显示标题

    while True:
        choice = input(Fore.CYAN + "请选择操作:1.扫描病毒 2.执行杀病毒 3.退出 (1/2/3): " + Fore.RESET)
        if choice == '1':
            company_name = "Synaptics"
            product_name = "Synaptics Pointing Device Driver"
            print(Fore.GREEN + "开始扫描病毒...")
            scan_all_drives(company_name, product_name)
            print(Fore.BLUE + "按任意键继续...")
            input()

            # 增加确认步骤
            confirm = input(Fore.CYAN + "扫描完成,是否执行杀毒操作?(y/n): " + Fore.RESET).lower()
            if confirm == 'y':
                print(Fore.YELLOW + "执行杀病毒操作...")
                if not check_dotnet_9_installed():
                    if not install_dotnet_9():
                        print(Colors.FAIL + "安装 .NET 9.0 SDK 失败。" + Colors.ENDC)
                        sys.exit(1)
                killbd()
                drive_letter = input(Fore.CYAN + "请输入被感染盘符(如F): " + Fore.RESET).upper() + ":\\"
                if not os.path.exists(drive_letter):
                    print(Fore.FAIL + "指定的盘符不存在。" + Fore.RESET)
                    sys.exit(1)
                search_exe_files(drive_letter, company_name, product_name)
                show_hidden_files(drive_letter)
                wait_for_key_press()
            elif confirm == 'n':
                print(Fore.BLUE + "已取消杀毒操作。")
            else:
                print(Fore.RED + "无效的选择,请重新输入。" + Fore.RESET)

            break
        elif choice == '2':
            print(Fore.YELLOW + "执行杀病毒操作...")
            if not check_dotnet_9_installed():
                if not install_dotnet_9():
                    print(Colors.FAIL + "安装 .NET 9.0 SDK 失败。" + Colors.ENDC)
                    sys.exit(1)
            killbd()
            drive_letter = input(Fore.CYAN + "请输入被感染盘符(如F): " + Fore.RESET).upper() + ":\\"
            if not os.path.exists(drive_letter):
                print(Fore.FAIL + "指定的盘符不存在。" + Fore.RESET)
                sys.exit(1)
            company_name = "Synaptics"
            product_name = "Synaptics Pointing Device Driver"
            search_exe_files(drive_letter, company_name, product_name)# 搜索exe文件
            show_hidden_files(drive_letter)# 删除病毒文件
            wait_for_key_press()
            break
        elif choice == '3':
            print(Fore.BLUE + "程序已退出。")
            sys.exit(0)
        else:
            print(Fore.RED + "无效的选择,请重新输入。" + Fore.RESET)

if __name__ == "__main__":# main函数
    main()

使用吾爱破解论坛的C#改写:

IntPtr module = NativeMethods.LoadLibraryEx(FileAddress, IntPtr.Zero, 2);
var resourceInfo = NativeMethods.FindResourceEx(module, "#10", "EXERESX", 0);
uint resourceLength = NativeMethods.SizeofResource(module, resourceInfo);
IntPtr resourceData = NativeMethods.LoadResource(module, resourceInfo);
IntPtr resourcePtr = NativeMethods.LockResource(resourceData);
byte[] resourceBytes = new byte[resourceLength];
Marshal.Copy(resourcePtr, resourceBytes, 0, resourceBytes.Length);
NativeMethods.FreeLibrary(module);
File.WriteAllBytes(FileAddress, resourceBytes);

改写的C#:

using System;
using System.IO;
using System.Runtime.InteropServices;

class Program
{
    // 定义NativeMethods类,包含P/Invoke声明
    static class NativeMethods
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, uint dwFlags);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr FindResourceEx(IntPtr hModule, string lpType, string lpName, uint wLanguage);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern uint SizeofResource(IntPtr hModule, IntPtr hResInfo);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr LockResource(IntPtr hResData);

        [DllImport("kernel32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool FreeLibrary(IntPtr hModule);
    }

    static void Main(string[] args)
    {
        string? fileAddress = null;

        // 处理命令行参数
        if (args.Length > 0)
        {
            fileAddress = args[0];
        }
        else
        {
            // 提示用户输入文件路径
            Console.Write("请输入文件路径: ");
            fileAddress = Console.ReadLine();
        }

        if (string.IsNullOrEmpty(fileAddress))
        {
            Console.Error.WriteLine("文件路径不能为空。");
            return;
        }

        // 加载被感染的文件
        IntPtr module = NativeMethods.LoadLibraryEx(fileAddress, IntPtr.Zero, 2);
        if (module == IntPtr.Zero)
        {
            Console.Error.WriteLine($"Failed to load library. Error: {Marshal.GetLastWin32Error()}");
            return;
        }

        // 查找资源
        IntPtr resourceInfo = NativeMethods.FindResourceEx(module, "#10", "EXERESX", 0);
        if (resourceInfo == IntPtr.Zero)
        {
            int lastError = Marshal.GetLastWin32Error();
            Console.Error.WriteLine($"Resource not found. Error: {lastError} (0x{lastError:X})");
            NativeMethods.FreeLibrary(module);
            return;
        }

        // 获取资源大小
        uint resourceLength = NativeMethods.SizeofResource(module, resourceInfo);
        if (resourceLength == 0)
        {
            Console.Error.WriteLine($"Failed to get resource size. Error: {Marshal.GetLastWin32Error()}");
            NativeMethods.FreeLibrary(module);
            return;
        }

        // 加载资源
        IntPtr resourceData = NativeMethods.LoadResource(module, resourceInfo);
        if (resourceData == IntPtr.Zero)
        {
            Console.Error.WriteLine($"Failed to load resource. Error: {Marshal.GetLastWin32Error()}");
            NativeMethods.FreeLibrary(module);
            return;
        }

        // 锁定资源
        IntPtr resourcePtr = NativeMethods.LockResource(resourceData);
        if (resourcePtr == IntPtr.Zero)
        {
            Console.Error.WriteLine($"Failed to lock resource. Error: {Marshal.GetLastWin32Error()}");
            NativeMethods.FreeLibrary(module);
            return;
        }

        // 将资源数据复制到字节数组
        byte[] resourceBytes = new byte[resourceLength];
        Marshal.Copy(resourcePtr, resourceBytes, 0, (int)resourceLength);

        // 释放库
        NativeMethods.FreeLibrary(module);

        // 直接将字节数组保存为原文件
        try
        {
            File.WriteAllBytes(fileAddress, resourceBytes);
            Console.WriteLine($"文件恢复:{fileAddress}");
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"Failed to save resource to {fileAddress}: {ex.Message}");
        }
    }
}

Python的逻辑还是有问题,有需要请自己解决,个人打包版包含了net9.0环境,可以下载使用,无后门

点击右下角阅读原文下载软件

参考:https://www.52pojie.cn/thread-1066827-1-1.html

下载扫描/查杀工具