lang1yd 发表于 2024-9-26 14:35

重做论坛的Synaptics病毒修复工具

本帖最后由 lang1yd 于 2024-9-28 02:20 编辑

一、为什么要重做Synaptics修复工具


很久很久以前,个人电脑和多个U盘上就被 Synaptics病毒 攻占了,使用U盘时经常会传给其它电脑,或被其它电脑上的杀毒软件以病毒之名
把U盘上的专用工具给删除了,是直接删除而不是恢复文件.
发现中着后,个人以前的做法时, 将Synaptics 病毒的源目录中的exe文件,用个txt代替后修改权限,从而使病毒不能运行.


前些天,又被这个病毒唤醒了,就在网上搜罗. 从本论坛 下载到一个由 @cdj68765制作的Synaptics病毒专杀工具,
Synaptics蠕虫病毒感染解决方案
[开始还不是本站会员]由于权限所限,只能看到第一页.下载了一个1.1.1.1版本的, 也不晓得是不是最新的文件.
同一时间,搜索到一篇分析报告文章 ,偷梁换柱:谨防“Synaptics”蠕虫病毒


知道了病毒的运作过程.于是用下载的专杀工具,效果感觉很好, U盘空间顿时空了两三百兆出来,根据工具中扫描时提示的目录信息,我再到U盘的相关目录中去查看,
用exeScope工具来检查一些文件时,发现还是存在感染文件,但是专杀工具识别不了.正如作者所说:


目前已知问题,对某些exe文件会无法恢复,问题发生的原因不明,程序会自动跳过该类文件但是病毒还在要小心
对长路径名的文件无法处理,这个是Win系统的通病,我也不清楚病毒是怎么做到对长路径和长文件名的感染的
被感染的xlsm文件恢复还处于验证阶段,需要你们的测试结果

现在以 病毒样品文件 : Oem7F7.exe (感染文件大小:1.58M,原始文件大小:881K)来测试.
下面是exeScope载入后的结果,确认是感染文件,其中RC数据中DESCRIPTION, EXERESX, EXEVSNX 的内容为 :

DESCRIPTION:
000B139C:53 00 79 00 6E 00 61 00 70 00 74 00 69 00 63 00S.y.n.a.p.t.i.c.
000B13AC:73 00 20 00 50 00 6F 00 69 00 6E 00 74 00 69 00s. .P.o.i.n.t.i.
000B13BC:6E 00 67 00 20 00 44 00 65 00 76 00 69 00 63 00n.g. .D.e.v.i.c.
000B13CC:65 00 20 00 44 00 72 00 69 00 76 00 65 00 72 00e. .D.r.i.v.e.r.
000B13DC:00 00 00 00                                    ....         


EXERESX:
000B13F0:4D 5A 50 00 02 00 00 00 04 00 0F 00 FF FF 00 00MZP.............
000B1400:B8 00 00 00 00 00 00 00 40 00 1A 00 00 00 00 00?......@.......
000B1410:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00................
000B1420:00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00................
000B1430:BA 10 00 0E 1F B4 09 CD 21 B8 01 4C CD 21 90 90?...???L?悙
000B1440:54 68 69 73 20 70 72 6F 67 72 61 6D 20 6D 75 73This program mus
000B1450:74 20 62 65 20 72 75 6E 20 75 6E 64 65 72 20 57t be run under W
000B1460:69 6E 33 32 0D 0A 24 37 00 00 00 00 00 00 00 00in32..$7.........
.....
0018D9E0:00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00................


EXEVSNX:
0018D9F0:31 30 36                                       106            

在 exeScope将资源中的EXERESX内容导出为BIN文件后,直接就是原始文件,于是准备写一个脚本调用exeScope.exe 来批量导出原始文件, 成是成功了,但是要附带一个exeScope.exe程序在脚本中也不像个事儿.
于是准备根据前辈的分析报告自己来写一个专杀的工具. 顺便用exeinfo PE查看了下载的专杀工具的编译工具是 - MS Visual C# / Basic.NET]- EPToken :0600001E.
于是丢到 dnSpy中参考借鉴.经过分析源码,发现扫描不全的原因有:

其一: 专杀工具先取源病毒文件/或感染文件的属性中的描述字段信息作为病毒的特征码,若不能获取到 versionInfo.FileDescription,就不能确定病毒特征码,原代码摘要:
string text ="C:\\ProgramData\\Synaptics\\Synaptics.exe";
....
FileVersionInfo versionInfo =FileVersionInfo.GetVersionInfo(text);
CS$<>8__locals1.SynapticsDescription =versionInfo.FileDescription;
....
FileVersionInfo versionInfo2 =FileVersionInfo.GetVersionInfo(openFileDialog.FileName);
CS$<>8__locals1.SynapticsDescription =versionInfo2.FileDescription;
...
if (string.IsNullOrEmpty(CS$<>8__locals1.SynapticsDescription))
{
    MessageBox.Show("获得病毒描述信息失败,退出程序");
    return;
}


用原专杀工具来测试上面的Oem7F7.exe时,会报错:获得病毒描述信息失败,退出程序,然后退出工具.


其二, 判断是否病毒的标准是以文件的描述字段是不是匹配病毒源/或感染文件 的描述字段, 另外 不能取到检测文件的描述信息,也会跳过该文件,原代码摘要:
FileVersionInfo versionInfo =FileVersionInfo.GetVersionInfo(text);
    if (versionInfo.FileDescription == null)
    {
      continue;
    }
    if(versionInfo.FileDescription.StartsWith(A_1.SynapticsDescription))
    {
       ....      
    }

用下面的 C# 来验证专杀工具中的代码检测样品病毒文件描述 的返回值:
private void button7_Click(object sender, EventArgs e)
      {
            string text = textBox8.Text;
            FileVersionInfo versionInfo = FileVersionInfo.GetVersionInfo(text);
            string desc = versionInfo.FileDescription;
            if ( desc is null)
               Console.WriteLine($"文件:\n{text}\n是否存在:{File.Exists(text)}\n描述信息为:\n{desc}\n描述为:null");
            else
               Console.WriteLine($"文件:\n{text}\n是否存在:{File.Exists(text)}\n描述信息为:\n{desc}\n描述信息长度为: {desc.Length}");
      }

返回值 :
文件:F:\下载\Download\病毒\Synaptics病毒\病毒样本\Oem7F7.exe    是否存在:True    描述信息为:   描述为:null

结果是取不到描述值, 然后随机试了下其它exe文件, 发现很多文件都取不到描述值,如大家熟悉的程序:IDA的卸载程序,exeScope.exe ,也有能取到描述的文件如ida64.exe,下面列举几个验证结果:


文件:D:\ProgramFiles\ollydbg\IDA\uninstall.exe    是否存在:True    描述信息为:   描述信息长度为:0
文件:D:\Program Files\eXeScope\eXeScope.exe    是否存在:True    描述信息为:   描述信息长度为:0
文件:D:\Program Files\ollydbg\IDA\ida64.exe    是否存在:True    描述信息为:    The Interactive Disassembler    描述信息长度为:28

所以这会漏检很大一部分文件.这里测试了几个不同的文件,发现病毒文件取的返回值是null,而不是病毒文件没取到值时,返回不是null,不知这个是不是个例.


其三,文件/文件夹遍历递归时,try语句位置不当, 会使遍历过程中遇到错误后,未遍历的文件会跳过,原代码:
internal static void <Main>g__AddDirFiles|4_2(stringdir, ref Program.<>c__DisplayClass4_1 A_1)
{
    try
    {
      DirectoryInfodirectoryInfo = new DirectoryInfo(dir);
      List<FileInfo>list = new List<FileInfo>();
      list.AddRange(directoryInfo.GetFiles("*.exe"));
      list.AddRange(directoryInfo.GetFiles("*.xlsm"));
      foreach (FileInfofileInfo in list)
      {
         A_1.files.Add(fileInfo.FullName);
      }
      DirectoryInfo[]directories = directoryInfo.GetDirectories();
      for (int i = 0; i <directories.Length; i++)
      {
         Program.<Main>g__AddDirFiles|4_2(directories.FullName, ref A_1);
      }
    }
    catch (UnauthorizedAccessException)
    {
    }
}


同样以c#例子来测试一个明显的try,for逻辑:

private void button6_Click(object sender, EventArgs e)
{
    int lastint = 0;
    try
    {
       for (int i = -7; i < 10; i++)
       {
         lastint = i;
         Console.WriteLine(55 / i);
       }
    }
    catch(System.DivideByZeroException)
    {
       Console.WriteLine("发生了除以零的异常,i 的值为: 0");
    }
    catch(Exception ex)
    {
       Console.WriteLine("发生了其他异常: " + ex.Message);
    }
   Console.WriteLine("i 最后的值为: " + lastint);
}


测试结果:
-7
-9
-11
-13
-18
-27
-55
引发的异常:“System.DivideByZeroException”(位于 Synaptics病毒修复工具.exe 中)发生了除以零的异常,i 的值为: 0
i 最后的值为: 0

用除0测试try,确定抛错后会跳出遍历,后面没有遍历的值会漏掉.所以原程序将try放在foreach外也可能会漏掉一些文件.

因为我看到的帖子时间是2019年12月份发表的,现在都2024了,[开始还不是本站会员]没有权限看后边的页,也不知道作者有没有修复上面的bug,也找不到更好的专杀工具,就参考这个专杀工具重制一个专杀工具.


二.程序设计思路


既然知道有以上三个bug,就先依次打补丁
1. 取病毒特征码   
分析病毒文件:根据分析报告中提到的思路,用exeScope检测多个感染文件和病毒原型文件,都有 RCDATA中 DESCRIPTION字段,且 值都为 "S y n a p t ic sP o i n t i n g   D e v i c e   D r i v er    "
其中 感染文件DESCRIPTION 字段的偏移地址是 0x000B139C-0x000B13DF,病毒原型DESCRIPTION字段的偏移地址是 0x000B135C - 0x000B139F,


感染的:
000B139C:53 00 79 00 6E 00 6100 70 00 74 00 69 00 63 00S.y.n.a.p.t.i.c.
000B13AC:73 00 20 00 50 00 6F00 69 00 6E 00 74 00 69 00s..P.o.i.n.t.i.
000B13BC:6E 00 67 00 20 00 4400 65 00 76 00 69 00 63 00n.g..D.e.v.i.c.
000B13CC:65 00 20 00 44 00 7200 69 00 76 00 65 00 72 00e..D.r.i.v.e.r.
000B13DC:00 00 00 00                                     ....   




原型:
000B135C:53 00 79 00 6E 00 6100 70 00 74 00 69 00 63 00S.y.n.a.p.t.i.c.
000B136C:73 00 20 00 50 00 6F00 69 00 6E 00 74 00 69 00s..P.o.i.n.t.i.
000B137C:6E 00 67 00 20 00 4400 65 00 76 00 69 00 63 00n.g..D.e.v.i.c.
000B138C:65 00 20 00 44 00 7200 69 00 76 00 65 00 72 00e..D.r.i.v.e.r.
000B139C:00 00 00 00                                     ....         

可以将里面的二进制数据做为病毒的特征码.

      
private static byte[] CreateVirDESCRIPTION()
{
    //string vir0 = "S y n a p t i c s   P o i n t i n g   D e v ic e   D r i v e r   ";
    //string vir1 ="53-00-79-00-6E-00-61-00-70-00-74-00-69-00-63-00-73-00-20-00-50-00-6F-00-69-00-6E-00-74-00-69-00-6E-00-67-00-20-00-44-00-65-00-76-00-69-00-63-00-65-00-20-00-44-00-72-00-69-00-76-00-65-00-72-00-00-00-00-00";
    string vir2 ="530079006E00610070007400690063007300200050006F0069006E00740069006E0067002000440065007600690063006500200044007200690076006500720000000000";

    string hexString = vir2;
    int byteCount = vir2.Length / 2;
    byte[] virDESCRIPTION = new byte;
    for (inti = 0; i < byteCount; i++)
    {
       virDESCRIPTION = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
    }
    Console.WriteLine("病毒特征码长度:" + virDESCRIPTION.Length);
    Console.WriteLine(BitConverter.ToString(virDESCRIPTION));
    return virDESCRIPTION;
}


2.病毒文件匹配方式
这里就有两种方式来检测:
A 用文件流方式,直接读取 0x000B139C -0x000B13DF 这一段地址,来与 特征码匹配.
B 用win32api读取文件的Rcdata中的DESCRIPTION字段,就是原专杀工具中导出exe的方法.
方式A:

private static byte[]ReadFileBinMark(string filePath)
{   //检测模式0: BIN   以二进制读取文件的特征值
    int startAddress = 0x000B139C;
    int endAddress = 0x000B13DF;
    byte[] buffer = ReadFileBin(filePath, startAddress, endAddress);
    return buffer;
}
private static byte[]ReadFileBin(string filePath, int startAddress, int endAddress)
{//以二进制读取文件
    int length = endAddress - startAddress + 1;
    // 创建一个足够大的缓冲区来存储数据
    byte[] buffer = new byte;
    // 定位到文件的起始地址并读取数据
    try
    {
       using (FileStream fs = new FileStream(filePath, FileMode.Open,FileAccess.Read,FileShare.ReadWrite))//添加FileShare.ReadWrite参数来读取已被其它程序打开的文件
       {
         fs.Position = startAddress;
         fs.Read(buffer, 0, length);
       }
    }
    catch(IOException ex)
    {
       Console.WriteLine("无法读取文件: " + filePath + "\n\n" +ex.Message);
       MessageBox.Show(ex.Message);
    }
    return buffer;
}



方式B:
private static byte[] ResGetRCDATA(string file, string[]rcLabels,bool limited = false)
{   //获取 RCDATA 中 多个字段的二进制 的值 并返回连接到一起的整串,   "#10" 是指 RCDATA,
    if(rcLabels.Length > 0)
    {
       List<byte> rltByte = new List<byte>();      
       IntPtr module = IntPtr.Zero;
       module = WinApi.LoadLibraryEx(file, IntPtr.Zero, flags);
       string copyFile = "";
       for (int iti = 0;iti <5;iti++)//尝试5次
       {   
         Console.WriteLine($"ResGetRCDATALoadLibraryEx--flag: {flags}--IntPtr module : {module}");
         if (module == IntPtr.Zero)
         {
               // 加载失败,获取错误代码
               int errorCode =Marshal.GetLastWin32Error();
               Console.WriteLine("ResGetRCDATALoadLibraryEx --IntPtrmodule -- errorCode :" + errorCode);
               DialogResult dRlt =MessageBox.Show("文件加载失败 Win32ErrorCode: " + errorCode+ "\n\n" + file+"\n\n是否 尝试其它方式\n\n" +
                   "是----复制源文件并读取镜像文件,文件大小:["+ GetFileSize(file) + "]\n\n" +
                   "否----调用 BIN 方式--待完善\n\n" +
                   "取消--忽略该文件", $"文件加载失败,剩余尝试次数:{5-iti}", MessageBoxButtons.YesNoCancel);         
               switch (dRlt)
               {
                   case DialogResult.Yes:
                      Random random = new Random();
                      StringBuilder randomStr = new StringBuilder();
                      for(int i = 0; i < 5; i++)
                      {
                        int randomIndex = random.Next(chars.Length);
                        randomStr.Append(chars);
                      }
                      copyFile = file+$".{randomStr}";
                      File.Copy(file, copyFile,true);
                      module = WinApi.LoadLibraryEx(copyFile, IntPtr.Zero,flags); //flags = 0x60
                      if (module == IntPtr.Zero)//如果还是加载失败,就立即删除复制的文件,不然就在读取完后再删除.
                      {
                        WinApi.FreeLibrary(module);
                        File.Delete(copyFile);
                        Console.WriteLine("文件已被删除: " + copyFile);
                     }                                 
                      break;
                   default:
                      return new byte[] { };                              
               }
         }
         else
         {
               break;
         }
       }

       foreach (string rcLabel in rcLabels)
       {
         IntPtr resourceInfo =WinApi.FindResourceEx(module, "#10", rcLabel, 0);
         uint size = WinApi.SizeofResource(module,resourceInfo);
         IntPtr source =WinApi.LockResource(WinApi.LoadResource(module, resourceInfo));
         if (size != 0U)
         {
               if(limited && size >200) { size = 16; }//有限制时,且超长时,限制为16字节,针对"EXERESX" 的摘要信息
               byte[] array = new byte;
               Marshal.Copy(source, array, 0,array.Length);
               rltByte.AddRange(array);
         }
       }
       WinApi.FreeLibrary(module);
       if (File.Exists(copyFile))
       {
         File.Delete(copyFile);//如果存在复制的临时文件,就删除
         Console.WriteLine("文件已被删除: " + copyFile);
       }
       return rltByte.ToArray();
    }
    return new byte[] { };
}

3.修改 检测文件及文件夹遍历递归方式,将try放在for/foreach 内部
private void ScanDirectoryRecursively(string directory, boolisTest, object sender, ref int scanCount, ref int virsCount)
{
    if(reqStop)
    {
       //(sender as BackgroundWorker).ReportProgress(3);//退出
       Console.WriteLine("ScanDirectoryRecursively--return");
       return;
    }

    foreach(string file in Directory.GetFiles(directory,"*.exe"))//后续要添加xlsm,不要添加SearchOption.AllDirectories 参数
    {
       try
       {
         scanCount++;
         (sender as BackgroundWorker).ReportProgress(1,file + '|' + scanCount);
         if (!file.ToLower().EndsWith(".exe"))
               continue;//过滤以 ".exe_bak" 结尾的文件
         if (skipCache &&file.Contains("\\._cache_"))
               continue;//过滤含"\\._cache_" 的文件
         //判断文件大小是不是< 740K               
         long.TryParse(GetFileSize(file, false),out longfileSizeB);
         if (fileSizeB < VIR_FILE_MIN_SIZE)
               continue;//过滤小于740K 的文件
         if (isPause)
         {//暂停
               if(PauseWork(sender)) return;
         }
         Console.WriteLine(file);
         bool isVirus = false; //是否感染
         if (scanMode == 0)
         {   //快速匹配
               if(ReadFileBinMark(file).SequenceEqual(virDESCRIPTION))
               {
                   isVirus = true;
               }
         }
         else if (ResGetRCDATA(file,resDesLabel).SequenceEqual(virDESCRIPTION))
         {   //精确匹配
               isVirus = true;
         }
         if (isVirus)
         {
               virsCount++;
               (sender as BackgroundWorker).ReportProgress(2,file + '|' + virsCount);
               //int tableIndex =0; //用添加的方式时,新项依次追加在后面,要scollView查看最后项,用insert直接插入时,最新的项在最前面
               // 将病毒信息添加到DataGridView中// 使用Invoke来在UI线程上执行添加行的操作               
               dataGridView1.Invoke(new Action(()=>
               {
                  //tableIndex=dataGridView1.Rows.Add((dataGridView1.Rows.Count).ToString(),file, Rlt);
                  dataGridView1.Rows.Insert(0, (dataGridView1.Rows.Count).ToString(), file,Rlt);
               }));
               // 尝试修复文件
               string repairResult =RepairFile(file, isTest);

               // 更新DataGridView中的修复结果// 使用Invoke来在UI线程上执行更新行的操作
               dataGridView1.Invoke(new Action(()=>
               {
                  //dataGridView1.Rows.Cells.Value = repairResult;
                  dataGridView1.Rows.Cells.Value = repairResult;

               }));
         }
       }

       catch (UnauthorizedAccessException)
       {
         Console.WriteLine("file无法访问某些文件夹,因为没有足够的权限。" + file);
         if (checkBox_showErr.Checked)
         {
               MessageBox.Show("file无法访问某些文件夹,因为没有足够的权限。" + file);
         }
       }
       catch (Exception ex)
       {
         Console.WriteLine("file发生了其他错误: " + ex.Message + file);
         if (checkBox_showErr.Checked)
         {
               MessageBox.Show("file发生了其他错误: " + ex.Message + file);
         }
       }
    }
   foreach(string dir in Directory.GetDirectories(directory))//不添加SearchOption.AllDirectories 参数
    {
       if (reqStop)
       {
         //(sender as BackgroundWorker).ReportProgress(3);//退出
         Console.WriteLine("ScanDirectoryRecursively--dir--return");
         return;
       }

       try
       { // 检查是否是junction文件夹
         if ((WinApi.GetFileAttributes(dir) & FILE_ATTRIBUTE_REPARSE_POINT)== FILE_ATTRIBUTE_REPARSE_POINT)
         {
               Console.WriteLine($"Skippingjunction: {dir}");
               continue;
         }
         ScanDirectoryRecursively(dir, isTest, sender,ref scanCount, ref virsCount);

       }
       catch (UnauthorizedAccessException)
       {
         Console.WriteLine("dir无法访问某些文件夹,因为没有足够的权限。" + dir);
         if (checkBox_showErr.Checked)
         {
               MessageBox.Show("dir无法访问某些文件夹,因为没有足够的权限。" + dir);
         }
       }
       catch (Exception ex)
       {
         Console.WriteLine("dir发生了其他错误: " + ex.Message + dir);
         if (checkBox_showErr.Checked)
         {
               MessageBox.Show("dir发生了其他错误: " + ex.Message + dir);
         }
       }
    }
}

4.其它个性功能的定制就不在这里赘述了.只发2个完成的截图,都是原专杀漏检的病毒.

https://s21.ax1x.com/2024/09/26/pAlVew4.png
https://s21.ax1x.com/2024/09/24/pAQgpeP.png

5.编译的程序待完善的:
1)没有感染的xlsm文件, 不能做样本分析, 将病毒 丢到vmware后,也没有得到感染的xlsm病毒文件, xlsm文件的检测还不可用.找到样品了再完善

2)用res方式检测时,若文件已被其它程序写入占用,如被exeScope打开,会检测失败,造成漏检,然后会进行弹窗选择是否复制文件来检测处理

3)用BIN方式检测时.会漏synaptics原型文件, 因为DESCRIPTION字段的偏移不一样,没有做扩展检测,可用清理环境功能清理病毒原型 "C:\\ProgramData\\Synaptics\\Synaptics.exe"

4)鉴于手中只有两个病毒原型文件,可能对其它版本的synaptics病毒会失效.

5)制作匆忙,其它不足之处,请不吝赐教,后面有时间再做一个完善的版本给需要的人.


6 再次感谢 @cdj68765 前辈提供的源程序,及 pianshen 里的分析报告.


7.其它说明:
   本人不是专业程序员,也不从事软件开发的工作,只是对各种事物喜欢研究,但终归是杂而不专,以上内容也是个人兴趣所至,有感而发. 谨以此为媒,加入本论坛.有时间就会上来观摩大神,继续研究.


附一个甩不掉的小尾巴: S y n a p t i c s   P o i n t i n g   D e v i c eD r i v e r

tjsh 发表于 2024-9-28 21:28

lang1yd 发表于 2024-9-28 16:46
杀软只会找出来,将整个感染文件删除或隔离,,不能得到原来的文件

会恢复啊 hr 360都能修

mavericklaw 发表于 2024-10-3 17:23

雀食某些杀软会整个干掉。。。当时中奖后,插入win10最新版本,提示有病毒然后把整个U盘的资料给干掉了。。。:rggrg
还好后来发现在隔离区,赶紧停了win10自带的杀软,然后恢复。。后面换台电脑用火绒杀了一次。。。只能说恢复了大部分文件吧。。。
那些没有恢复的,现在看来,应该是楼主说分析的,长路径不友好的缘故吧。。。

byh3025 发表于 2024-9-26 15:26

大神,成品呢?

WXJYXLWMH 发表于 2024-9-26 19:17

谢谢分享 楼主辛苦了

gusgusice 发表于 2024-9-26 20:09

赞赞赞赞赞

Haoyua 发表于 2024-9-26 21:46

感谢大佬分享原创作品,期待分享成品。

justwz 发表于 2024-9-26 22:34

大佬太强了 先收藏了

5iAnn2020 发表于 2024-9-26 23:13

可以分享个成品吗?大神

xiaoaivcm 发表于 2024-9-27 01:54

使用工具,感谢大佬分享

幺零幺幺 发表于 2024-9-27 06:43

收藏了 感谢大佬分享

mengxz2023 发表于 2024-9-27 07:49

大佬厉害
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 重做论坛的Synaptics病毒修复工具