驱动的安装与逆向

驱动的安装与逆向,第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 里,什么代码都出来了。

pdm做好后,最终是要将其转化为数据库实体的。这里pdm想必大家都知道是什么文件,对的,英文是physiyc data model 也就是物理数据模型。

1.确认当前Powerdesigner设置的dbms是否正确,即是否是我们要生成的数据库类型,我在这里使用的是sqlserver2000:

Powerdesigner->数据库->更改当前dbms,选择您的数据库类型。

2.配置Powerdesigner与数据源的连接

Powerdesigner->数据库->配置连接->用户dsn(或系统dsn)->选择、添加您的数据源

3.连接

Powerdesigner->数据库->连接->设置好您刚才建立的dsn确定。

OK,设置好连接后我们就可以将pdm生成sql语句了。

4.数据库生成

Powerdesigner->数据库->生成数据库->配置好(默认就可以)后选择确定就好了。

拿到sql脚本语句,大家都知道应当如何做了吧,放到sql查询分析器里执行吧。

Powerdesigner逆向工程从现有数据库生成PDM

在数据建模过程中,我们建立概念数据模型,通过正向工程生成物理数据模型,生成数据库建库脚本,最后将物理数据模型生成关系数据库,现在反过来,通过逆向工程将关系数据库,生成物理数据模型。

优点:

在丢失数据模型或者数据库模型同现有的数据库不一致,可以通过该方法生成使用中数据库的模型

缺点:

还原回的模型中,可能会没有中文注释,没有表外键对应关系(字段还有,索引关系没了)

前提:

1,确认要生成模型的数据库是最新的,并且可以使用

2,安装Powerdesigner软件

具体 *** 作步骤:

一,建立ODBC数据源

1, 打开系统ODBC数据源,位置“控制面板--管理工具--数据源(ODBC)

2,选择系统DSN,点击添加按钮,会d出如下界面,选择与数据库相匹配的驱动程序

3,点击完成逗码,d出数据源名称(自定),和选择你要连瞎姿接的数据库,如下图所示

4,选择确定后,该数据源建立成功,可以双击该数据源名称进行连接测试,如下图所示

5,至此数据源建立完成,当然这些也可以不用在这里建立,在powerdesigner里也可以建立,建立方法为

选择Database->configure connections,转到system dsn标签,点击Add按钮,选数据库类型DB2,点击完成。显 示如下:输入DataSource Name“PDMTest”;输入ServerName“Database”, 配置完成。如下所示:

二,在Powerdesigner中逆向生成

1,打开Powerdesigner工具,创建一个PDM文件,选择与之匹配的数据库类型“ibm db2 udb 8.x common server”。创建方法为:右键点击左侧面板中的WorkSpace---->new------>physical data nodel ,在DBMS中选择你要的数据类型,选择好后点击确定,则新建立了一个工作空间。

2,选择Database菜单下的Reverse Engineer Database,d出Database Reverse Engineering对话框,选Using an ODBC data source选ODBC数据源“s2ms”,如下图所示:

3,点击确定后,显示此数据库中所有表、视图、用户(这个选择一下该数据库的用户)。根据需要选择后,转换成pdm。图示如下

4,选择好后,点击OK,则生成模型。

三,利用脚本生成模型

如果你不光有个正在使有的数据库,你还有一下建立库的脚本,那你发财了!因为用.sql的脚本生成的模型,就不存在用数据库生成的缺点了,具体 *** 作如下:

二、 通过SQL脚磨指绝本逆向工程生成PDM

1、 数据库SQL脚本文件crebas.sql。下为脚本实例:

/*==============================================================*/

/* Database name: PhysicalDataModel_1 */

/* DBMS name: ORACLE Version 9i */

/* Created on: 2003-07-13 10:49:08 */

/*==============================================================*/

/*==============================================================*/

/* Table: "class" */

/*==============================================================*/

create table "class" (

"classID" NUMBER(2) not null,

"className" VARCHAR2(24),

constraint PK_CLASS primary key ("classID")

)

/

/*==============================================================*/

/* Table: "student" */

/*==============================================================*/

create table "student" (

"studentID" NUMBER(10) not null,

"studentName" VARCHAR2(4),

"classID" NUMBER(2),

constraint PK_STUDENT primary key ("studentID")

)

/

alter table "student"

add constraint FK_STUDENT_REFERENCE_CLASS foreign key ("classID")

references "class" ("classID")

/

2、 还是创建一个pdm,选择Database--->Reverse Engineer Database,选择Using files。

3、 看到由脚本自动生成相关的PDM如下所示:

四,生成模型后要导出数据库建库脚本

导好的模型,当然是用来修改和导出建库脚本的, *** 作方法如下

1,选择database--->generate databased出如下窗口

在用户的none中选择数据库用户,同时可选要导出的表,最后点击确定,如果不能正常生成脚本提示模型错误,则在上图中的options中将check modle 的选项去掉,点击确定,生成sql脚本,

这时生成的脚本不要着急去用,因为可能是有问题,用记事本或其它工具打开这个脚本,你会发现在所属名上也就是s2ms上都会加上""号,用Ctrl+H,将所有"替换为空,就大功告成了


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

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

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

发表评论

登录后才能评论

评论列表(0条)

保存