驱动的安装与逆向

驱动的安装与逆向,第1张

对于一个经常写程序的人来说,写驱动不是一件困难的事情。因为网络上有很多现成的

代码,要实现某个功能,直接 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=0i<iostatus.Informationi++)

y1=y1^(LONG)(*(pBuffer+i))

*XorChar=y1

//加减计算

for(i=0i<iostatus.Informationi++)

{

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: %ldAnSChar: %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 里,什么代码都出来了。

出品|开源中国

译|罗奇奇

RedHat 桌面、图形、信息 娱乐 和 i18n 总监 Christian F.K. Schaller 在博客中分享了他对 NVIDIA 开源 Linux GPU 内核模块的一些看法,并分析了 NVidia 的开源驱动对 Linux 社区的重要性。

首先需要明确的是:NVIDIA 开源了 out of tree 源代码内核驱动程序。这些驱动程序已经过测试,它在作为数据中心的 GPU 上支持 CUDA 用例,但尚未支持图形显示。尽管也有支持图形显示的代码,但这些代码还没有写完或完全测试。

此外,开源的只是内核部分,然而现代图形驱动的很大一部分是在固件和用户空间组件中,而这些仍然是闭源的。因此,对于普通的 Linux 桌面用户来说,目前 NVIDIA 的开源驱动程序并不能带来一些显而易见的好处。

但无论如何,现在有了一个 NVidia 内核驱动,它能够使用 linux 内核中仅支持 gpl 的 API ,这就为后面的以切打下了基础。

开源驱动能取代二进制驱动程序吗?

目前还不能,该开源驱动程序只支持 NVidia 图灵芯片及更新版本的图形处理器,这意味着它对 2018 年之前的图形处理器都不起作用。另外,在 开源内核模块经过全面测试和扩展,以用于图形显示用例之前,即使你使用的是图灵或更新版本的 GPU,仍需要在系统中使用二进制驱动。

此外,正如上面所说的,现代图形驱动的很大一部分是在固件和用户空间组件中,因此,即使开源内核驱动程序有图形显示能力,二进制驱动程序仍将继续存在。

Nouveau 是一个旨在为 NVIDIA GPU 建立高质量的、自由的开源驱动项目,它最初是作为逆向工程驱动程序开发的,但近年来实得到了 NVIDIA 的积极支持。Nouveau 功能齐全,但由于无法重新计算 NVidia 显卡的时钟等功能而受到严重阻碍,这意味着它不能像二进制驱动那样提供完整的性能,此次 NVIDIA 开源的 GPU 内核模块对 Nouveau 社区和项目都大有帮助。

需要注意的是: Linux 内核不允许同一个硬件有多个驱动程序,所以要想让新的 NVidia 内核驱动程序在当前的 Linux 版本中运行,就必须退出现在的 Nouveau,或者至少被限制在一套不同的硬件上。就像二进制驱动一样,Nouveau 的很大一部分不在内核中,而是在 Mesa 和 NVidia 目前提供的 Nouveau 特定固件中发现的用户空间部分。

目前 NVIDIA 和 RadHat 等发行版讨论的计划是让 NVidia 的二进制驱动和 Mesa 共享一个内核驱动,这可能是一个全新的驱动程序,旨在同时满足 NVidia 用户空间和 Mesa 用户空间的需求。但这可能需要几年的时间来实现。这对开源社区和 NVidia 都有明显的优势。对于开源社区来说,这意味着现在将拥有一个内核驱动程序和固件,它允许更改 GPU 的时钟,以提供人们期望从 NVidia 显卡获得的显示性能,这意味着 Linux 将拥有一个可以在第一时间访问新一代 NVidia 硬件的固件和内核更新的驱动程序。

就近期而言,它不会产生重大影响。但随着时间的推移,它提供了一种从根本上简化对 NVidia 硬件支持的途径。从长远来看,用户有机会能在 NVidia 硬件上获得与 Linux 为 Intel 和 AMD 的硬件提供的的体验,即一些开箱即用的功能

开源还意味着 Linux 发行版可以第一时间支持新的 NVIDIA 芯片,一个高性能的 NVidia 开源 Mesa 驱动程序将允许 Linux 发行版签署 NVidia 驱动程序和内核的其他部分,以实现安全重启等功能。

由于第一个开源驱动版本 R515 是针对计算 GPU 的,所以可以预期这些选项也将首先为计算用户提供,然后再为显卡用户提供。

NVidia 需要继续努力完成这个新的驱动程序功能,无论是计算还是图形显示用例。而 Linux 社区和 NVIDIA 需要共同为未来的统一内核驱动程序制定一个计划,并围绕它制定一个适用于它的模型。无论是 Linux 社区还是 NVidia,都需要添加 Mesa Vulkan 驱动程序之类的东西,类似于为 AMD 提供 RADV 的方式。

我对驱动程序的了解水平有限,如有一些理解错误,欢迎大家指正批评。

原文:https://blogs.gnome.org/uraeus/2022/05/11/why-is-the-open-source-driver-release-from-nvidia-so-important-for-linux/

数据逆向可以分为不同的级别,其中包括低级别、中级别和高级别。

低级别的数据逆向主要是通过简单的技术手段对数据进行解密、解码或解压缩,并破解软件的加密机制,例如逆向工程和破解软件注册码等。这种级别的数据逆向对技术要求较低,只需要基本的编程和计算机知识即可实现。

中级别的数据逆向需要具备更高级的技术要求,需要对程序代码进行深入分析,并掌握复杂的加密算法和协议。这种级别的数据逆向常常用于安全评估和漏洞挖掘领域,例如反作弊系统的破解和网络安全的攻防对抗等。

高级别的数据逆向则需要具备极高的技术水平,需要针对硬件设备进行逆向分析,并对 *** 作系统、内核和驱动程序进行深入研究。这种级别的数据逆向通常应用于安全研究、数字取证和恶意代码分析等复杂领域,并对技术人员的能力和经验提出了更高的要求。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/yw/8100457.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-13
下一篇 2023-04-13

发表评论

登录后才能评论

评论列表(0条)

保存