驱动的安装与逆向
的有关信息介绍如下:对于一个经常写程序的人来说,写驱动不是一件困难的事情。因为网络上有很多现成的 代码,要实现某个功能,直接 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