重做论坛的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
lang1yd 发表于 2024-9-28 16:46
杀软只会找出来,将整个感染文件删除或隔离,,不能得到原来的文件
会恢复啊 hr 360都能修 雀食某些杀软会整个干掉。。。当时中奖后,插入win10最新版本,提示有病毒然后把整个U盘的资料给干掉了。。。:rggrg
还好后来发现在隔离区,赶紧停了win10自带的杀软,然后恢复。。后面换台电脑用火绒杀了一次。。。只能说恢复了大部分文件吧。。。
那些没有恢复的,现在看来,应该是楼主说分析的,长路径不友好的缘故吧。。。 大神,成品呢? 谢谢分享 楼主辛苦了 赞赞赞赞赞 感谢大佬分享原创作品,期待分享成品。 大佬太强了 先收藏了 可以分享个成品吗?大神 使用工具,感谢大佬分享 收藏了 感谢大佬分享 大佬厉害