中易网

如何防止驱动被恶意利用

答案:1  悬赏:10  
解决时间 2021-04-09 11:27
如何防止驱动被恶意利用
最佳答案
对于一个经常写程序的人来说,写驱动不是一件困难的事情。因为网络上有很多现成的
  代码,要实现某个功能,直接 Ctrl+C和Ctrl+V 就能解决问题。但是写出来了驱动能不能加
  载进入内核就是另外一回事了,准确的说是能不能存在于别人的硬盘上就是另外一回事了。
  因为很多杀毒软件(特别像360这种没技术含量的)见到后缀名为sys的文件就直接删除,
  甚至连调用NtLoadDriver的机会都没有。对于一般的软件来说,给出一个声明说明一下解
  决方法就算了。但是对于恶意程序,是不能给出声明的。于是很多恶意软件的作者另辟蹊径,
  利用大公司写好的而且有数字签名的驱动来做坏事。
  有人说,大公司做好的驱动怎么可能被用来做坏事呢?其实,这是很容易理解的事情。
  很多安全类或者系统优化类的软件,甚至系统毫不相关的软件(比如:迅雷)都附带有驱动。
  这些驱动都带有一定的通用性。q_lai_a_qu网友在其博客里说:“ComputerZ.sys……没事
  逆了逆是鲁大师的驱动,发现这个驱动功能齐全,而且没有调用者验证!既可以读、写Msr
  寄存器,也可以用in、out指令读写端口,而且char/short/long数据长度齐全!”。这个是
  个人之言,可信度请自行揣度。下面说个可信度比较高的例子:曾经有病毒利用了360的
  AntiRK.dll来删除杀毒软件的文件(请自行用谷歌搜索“360 antirk.dll”,会有惊喜发现。
  AntiRK.dll虽然不是驱动,但也是被非法利用了)。破坏杀毒软件的病毒已经算是小儿科了,
  其实利用某些驱动还能破坏硬件!我最近在笔记本上折腾硬件,“本友会”上的网友给我推
  荐了几款软件:SetFSB、ThrottleStop、NvFlash、WinFlash。它们分别是修改CPU外频、设
  置CPU倍频(可以调节CPU电压)、读写显卡BIOS和读写主板BIOS的软件。一言概括他们的特性,
  就是它们都支持NT x86/x64,它们的驱动都有正规数字签名(特别是最后两个,分别带的是 NVIDIA和ASUS的数字签名)。
  最为重要的是,他们的驱动没有加花加壳,没有校验调用者,
  如果利用这几个驱动,加上一丁点的逆向知识,就能做出破坏性的病毒(以下摘自我在紫水
  晶编程论坛的帖子):
  1.SetFSB能调节处理器的外频,如果直接把外频调到600MHz,电脑会瞬间黑屏,可能
  会损坏 CPU或主板;
  2.ThrottleStop能调节 CPU的倍频(如果CPU没有锁倍频),如果直接把倍频调到 31,
  电脑会瞬间黑屏,可能会损坏CPU 或主板;ThrottleStop还能调节CPU的核心电压,如果
  把CPU的核心电压调到3V,能直接烧毁CPU 甚至主板;
  3.NvFlash、WinFlash等软件能直接读写BIOS(显卡BIOS 和主板BIOS),我们可以把
  BIOS全部写零;
  4.如果做病毒的话,先写坏显卡BIOS 和主板BIOS,然后通过调节电压烧掉显卡和CPU
  (有可能会连同主板一起损坏);
  解决方案
  由此可见,没有验证调用者的驱动实在是有着巨大的危害。我最近受学院委托,做一个
  需要驱动的软件(那个驱动会被加上数字签名)。为了防止上述悲剧发生,我决定在正式写
  驱动之前,先解决如何防止自己的驱动被恶意利用。以前我曾经在紫水晶编程论坛上问过这
  个问题,网友的回答五花八门,不过大概是可以分成三类:第一类是信息验证,比如应用程
  序发个信息给驱动来验证一下是“自己人”;第二类是加壳保护,比如给驱动和应用程序加
  上极强难脱的壳,利用VMP加密通信部分(类似XueTr 的做法);还有人提出混合应用,综
  合第一类和第二类的做法。
  这三种想法看似都不错,但是我认为不妥。第一种:别人只要把驱动全部逆向完毕就行
  了;第二种:虽然VMP保护和加保护壳使得破解不容易,但是不是使破解变得不可能。而且
  VMP 和保护壳能使程序执行的效率降低,我不太喜欢。最可恶的是,杀毒软件对加了壳(甚
  至包括 UPX)和 VMP的程序一律报毒,得不偿失。于是我想出了第三种思路:校验调用者的
  特征。如果符合,就执行功能语句,否则不予执行。如何校验调用者的特征码呢?不少人想
  到的是使用CRC32 或者 MD5。使用它们不是不可以,不过我还有自己的想法。我的想法是自
  己设计一套验证算法,它的规则如下:
  1.获得调用者的EPROCESS
  2.通过调用者的EPROCESS获得调用者的文件路径
  3.获取调用者的文件全部内容,放到字节数组buff里
  4.把 buff里所有的元素依次相加减(fb1 + fb2 - fb3...),得到y1
  5.把 buff里所有的元素依次异或(0 XOR fb1 XOR fb2 XOR fb3...),得到y2
  把 y1和 y2与已经计算出来的数值对比,如果都相同则执行功能代码,如果不相同则不
  执行功能代码
  获得调用者的EPROCESS直接用 PsGetCurrentProcess()就行了,获得调用者的文件路
  径比较麻烦,大家可以使用我以前向高手购买的代码(已经封装为函数,方便调用):
  //依据 EPROCESS得到进程全路径
  VOID GetFullPathByEprocess( ULONG eprocess, PCHAR ProcessImageName )
  {
  ULONG object;
  PFILE_OBJECT FileObject;
  UNICODE_STRING FilePath;
  UNICODE_STRING DosName;
  STRING AnsiString;
  FileObject = NULL;
  FilePath.Buffer = NULL;
  FilePath.Length = 0;
  *ProcessImageName = 0;
  //Eprocess->sectionobject(offset_SectionObject)
  if(MmIsAddressValid((PULONG)(eprocess+offset_SectionObject)))
  {
  object=(*(PULONG)(eprocess+offset_SectionObject));
  //KdPrint(("[GetProcessFileName] sectionobject :0x%x\n",object));
  if(MmIsAddressValid((PULONG)((ULONG)object+0x014)))
  {
  object=*(PULONG)((ULONG)object+0x014);
  //KdPrint(("[GetProcessFileName] Segment :0x%x\n",object));
  if(MmIsAddressValid((PULONG)((ULONG)object+0x0)))
  {
  object=*(PULONG)((ULONG_PTR)object+0x0);
  //KdPrint(("[GetProcessFileName]
  ControlAera :0x%x\n",object));
  if(MmIsAddressValid((PULONG)((ULONG)object+0x024)))
  {
  object=*(PULONG)((ULONG)object+0x024);
  if (NtBuildNumber >= 6000) object=((ULONG)object &
  0xfffffff8);
  //KdPrint(("[GetProcessFileName]
  FilePointer :0x%x\n",object));
  }
  else
  return ;
  }
  else
  return ;
  }
  else
  return ;
  }
  else
  return ;
  FileObject=(PFILE_OBJECT)object;
  FilePath.Buffer = ExAllocatePool(PagedPool,0x200);
  FilePath.MaximumLength = 0x200;
  //KdPrint(("[GetProcessFileName]
  FilePointer :%wZ\n",&FilePointer->FileName));
  ObReferenceObjectByPointer((PVOID)FileObject,0,NULL,KernelMode);
  RtlVolumeDeviceToDosName(FileObject-> DeviceObject, &DosName);
  RtlCopyUnicodeString(&FilePath, &DosName);
  RtlAppendUnicodeStringToString(&FilePath, &FileObject->FileName);
  ObDereferenceObject(FileObject);
  RtlUnicodeStringToAnsiString(&AnsiString, &FilePath, TRUE);
  if ( AnsiString.Length >= 216 )
  {
  memcpy(ProcessImageName, AnsiString.Buffer, 0x100u);
  *(ProcessImageName + 215) = 0;
  }
  else
  {
  memcpy(ProcessImageName, AnsiString.Buffer, AnsiString.Length);
  ProcessImageName[AnsiString.Length] = 0;
  }
  RtlFreeAnsiString(&AnsiString);
  ExFreePool(DosName.Buffer);
  ExFreePool(FilePath.Buffer);
  }
  以上代码需要三个硬编码,分别是NtBuildNumber(系统版本号)、EPROCESS中的
  SectionObject项和UniqueProcessId项的偏移。我测试的操作系统是Windows 2003。所以
  我在代码里如下定义:
  #define offset_SectionObject 0x124
  #define offset_UniqueProcessId 0x94
  ULONG NtBuildNumber=3790;
  获得进程路径后就校验特征码。由于流程已经说清楚了,所以直接给出代码:
  VOID CalcChar(PUNICODE_STRING logFileUnicodeString, LONG *XorChar, LONG
  *AnSChar)
  {
  OBJECT_ATTRIBUTES objectAttributes;
  IO_STATUS_BLOCK iostatus;
  HANDLE hfile;
  NTSTATUS ntStatus;
  FILE_STANDARD_INFORMATION fsi;
  PUCHAR pBuffer;
  ULONG i=0,y1=0,y2=0;
  //初始化 objectAttributes
  InitializeObjectAttributes(&objectAttributes,
  logFileUnicodeString,
  OBJ_CASE_INSENSITIVE,//对大小写敏感
  NULL,
  NULL);
  //创建文件
  ntStatus = ZwCreateFile(&hfile,
  GENERIC_READ,
  &objectAttributes,
  &iostatus,
  NULL,
  FILE_ATTRIBUTE_NORMAL,
  FILE_SHARE_READ,
  FILE_OPEN,//即使存在该文件,也创建
  FILE_SYNCHRONOUS_IO_NONALERT,
  NULL,
  0 );
  if (!NT_SUCCESS(ntStatus))
  {
  dprintf("The file is not exist!\n");
  return;
  }
  //读取文件长度
  ntStatus = ZwQueryInformationFile(hfile,
  &iostatus,
  &fsi,
  sizeof(FILE_STANDARD_INFORMATION),
  FileStandardInformation);
  dprintf("The program want to read %d bytes\n",fsi.EndOfFile.QuadPart);
  //为读取的文件分配缓冲区
  pBuffer = (PUCHAR)ExAllocatePool(PagedPool,
  (LONG)fsi.EndOfFile.QuadPart);
  //读取文件
  ZwReadFile(hfile,NULL,
  NULL,NULL,
  &iostatus,
  pBuffer,
  (LONG)fsi.EndOfFile.QuadPart,
  NULL,NULL);
  dprintf("The program really read %d bytes\n",iostatus.Information);
  //异或计算
  for(i=0;i<iostatus.Information;i++)
  y1=y1^(LONG)(*(pBuffer+i));
  *XorChar=y1;
  //加减计算
  for(i=0;i<iostatus.Information;i++)
  {
  if(i%2==0)
  y2=y2+(LONG)(*(pBuffer+i));
  else
  y2=y2-(LONG)(*(pBuffer+i));
  }
  *AnSChar=y2;
  //关闭文件句柄
  ZwClose(hfile);
  //释放缓冲区
  ExFreePool(pBuffer);
  }
  接下来就要调用了。我们需要编写一个函数VerifyCaller,在此函数里有两个值需要
  固化在驱动里,就是合法调用者的两个特征值。为了方便计算这两个特征值,我特地写了个
  应用程序,核心代码如下:
  Option Explicit
  Private Function ReadFile(ByVal strFileName As String, Optional ByVal
  lngStartPos As Long = 1, Optional ByVallngFileSize As Long = -1) As Byte()
  Dim FilNum As Long
  FilNum = FreeFile
  Open strFileName For Binary As #FilNum
  If lngFileSize = -1 Then
  ReDim ReadFile(LOF(FilNum) - lngStartPos)
  Else
  ReDim ReadFile(lngFileSize - 1)
  End If
  Get #FilNum, lngStartPos, ReadFile
  Close #FilNum
  End Function
  Private Function WriteFile(ByVal strFileName As String, bytData() As Byte,
  Optional ByVal lngStartPos As Long = -1,Optional ByVal OverWrite As Boolean =
  True)
  On Error GoTo erx
  Dim FilNum As Long
  FilNum = FreeFile
  If OverWrite = True And Dir(strFileName) <> "" Then
  Kill strFileName
  End If
  Open strFileName For Binary As #FilNum
  If lngStartPos = -1 Then
  Put #FilNum, LOF(FilNum) + 1, bytData
  Else
  Put #FilNum, lngStartPos, bytData
  End If
  Close #FilNum
  erx:
  End Function
  Private Sub Command1_Click()
  Dim buff() As Byte, i As Long, y As Long, ub As Long
  'text1.text is the file name
  buff = ReadFile(Text1.Text, 1, -1)
  ub = UBound(buff)
  'calc xor char
  y = 0
  For i = 0 To ub
  y = y Xor buff(i)
  Next
  Text2.Text = CLng(y)
  DoEvents
  'calc add/sub char
  y = 0
  For i = 0 To ub
  If i Mod 2 = 0 Then
  y = y + CLng(buff(i))
  Else
  y = y - CLng(buff(i))
  End If
  Next
  Text3.Text = CLng(y)
  End Sub
  Private Sub Form_Load()
  Me.Icon = LoadPicture("")
  End Sub
  驱动里的 VerifyCaller代码如下:
  LONG VerifyCaller(void)
  {
  PEPROCESS cur_ep;
  char cur_pp[260];
  char *nt_cur_pp;
  ANSI_STRING asCur_pp;
  UNICODE_STRING usCur_pp;
  LONG xorc, ansc;
  cur_ep=PsGetCurrentProcess();
  GetFullPathByEprocess((ULONG)cur_ep, cur_pp);
  //在文件名前面加上\??\
  nt_cur_pp=cs("\\??\\",cur_pp);
  DbgPrint("%s",nt_cur_pp);
  RtlInitAnsiString(&asCur_pp, nt_cur_pp);
  RtlAnsiStringToUnicodeString(&usCur_pp, &asCur_pp, TRUE);
  DbgPrint("%wZ",&usCur_pp);
  CalcChar(&usCur_pp, &xorc, &ansc);
  DbgPrint("XorChar: %ld; AnSChar: %ld",xorc,ansc);
  //这个就是事先算好的合法程序的特征码,【必须】固化在驱动里!
  if(xorc==186 && ansc==136176)
  return 1;

  else
  return 0;
  }
  在 DispatchIoctl函数的每个功能执行之前,都调用VerifyCaller()校验一下调用者:
  switch(uIoControlCode)
  {
  case IOCTL_VERIFY:
  {
  DbgPrint("[MyDriver] DispatchIoctl - IOCTL_VERIFY");
  if(VerifyCaller()==1)
  DbgPrint("[MyDriver] {IOCTL_VERIFY} Function code run now!");
  else
  DbgPrint("[MyDriver] {IOCTL_VERIFY} You're illegal caller!");
  status = STATUS_SUCCESS;
  break;
  }
  //下面省略
  }
  运行测试

  3.首先把合法的调用者,非法的调用者(用eXeScope随便把合法的调用者Patch一下,
  比如删掉程序的版本信息)和驱动复制到虚拟机
  4.用合法的调用者来加载驱动并执行
  5.用非法的调用者来加载驱动并执行
  6.对比以上两者在DbgView的输出
  调用者合法时:

  调用者非法时:

  写在最后

  写完这篇文章,我必须再次重申:只有当驱动程序携带正式数字签名时,验证调用者的
  代码才有使用价值。为什么这么说呢?因为别人无法patch 带有正式数字签名的驱动(一旦
  驱动被 patch,签名就失效了,就像被破处的女人,不值钱了。这个比喻虽然粗俗,但是很
  恰当)。而没有加上签名的驱动,本来就没有使用价值。即使别人要使用,直接把驱动扔到
  IDA 里,什么代码都出来了。
我要举报
如以上问答内容为低俗、色情、不良、暴力、侵权、涉及违法等信息,可以点下面链接进行举报!
大家都在看
求推荐有关机器人的电影,比如像超能查派,还
英雄联盟高校联赛闽江学院为什么被禁赛
农村的分家两兄弟都结婚了,有父母,有两亩地
八年级下册 数学题 分式的混合运算 搞不懂是
一个电动车装两个48V,20A的电池能跑多远
国策跆拳道教育在哪里啊,我有事要去这个地方
华远轻钢龙骨在哪里啊,我有事要去这个地方
一部高达的动漫。只记得男主和他朋友是偷出来
457,2961,145,221,1371,147,296,1456
求一块400左右的显卡
淘宝和支付宝登录密码可不可以不同?我说的是
5.12护士节 该用什么节目?新鲜一点的
苹果三代i7和五代i5哪个速度更快
黑龙江省鹤岗市南山逸夫小学2012年,六年组那
忘了考号怎么查四级英语考试的成绩
推荐资讯
为什么从小到大都不能过自己想要的生活?
梦见天上飞来一双白鞋是怎么会事
在温度为27℃时,一直径为0.3米的薄塑料球内
佛山三中、荣山中学、惠景中学、张槎中学、佛
帮忙看下这家淘宝店卖的飞利浦剃须刀是正品吗
老师发火了 作文
电力系统职业技能鉴定题库中得初级,中级,高
请问租租车闪租的押金返回快不快?
西数1t的那个移动硬盘为什么插在苹果电脑上显
人体在36V以下的电压下触电会不会对人体造成
九云墩地址在什么地方,想过去办事
TFT和LTPS屏幕有什么本质区别
手机登qq时,显示手机磁盘不足,清理后重新登
刺客的套装怎么选啊?