如何防止驱动被恶意利用

 我来答
zxh4567
推荐于2016-01-31 · TA获得超过6.1万个赞
知道大有可为答主
回答量:2.2万
采纳率:83%
帮助的人:1.2亿
展开全部
  对于一个经常写程序的人来说,写驱动不是一件困难的事情。因为网络上有很多现成的
  代码,要实现某个功能,直接 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 里,什么代码都出来了。
推荐律师服务: 若未解决您的问题,请您详细描述您的问题,通过百度律临进行免费专业咨询

为你推荐:

下载百度知道APP,抢鲜体验
使用百度知道APP,立即抢鲜体验。你的手机镜头里或许有别人想知道的答案。
扫描二维码下载
×

类别

我们会通过消息、邮箱等方式尽快将举报结果通知您。

说明

0/200

提交
取消

辅 助

模 式