钩子透明加密技术和过滤驱动加密技术

超时代视频加密软件系列产品均采用过滤驱动透明加密技术对大文件的视频流进行加密,完美地解决了加密强度与效率之间的问题,成为视频加密产品的行业标杆。

钩子透明加密技术

早期的透明加密的技术手段大多是通过API钩子技术进行,其工作方式为静态加密和重定向,基本思想为钩子程序拦截到受保护文件的打开操作,先将已加密的文件拷贝到一个临时目录中,然后告诉驱动程序把这个文件隐藏起来,然后解密这个文件,并将这个临时文件返回给打开文件的进程,这样打开的就是磁盘上的一个明文文件,用户程序可以进行正常的处理,这里文件是进行了整体的拷贝并解密;在文件关闭时,钩子拦截到以后,将那个明文的临时文件加密,然后再拷贝回来覆盖掉原文件。
简单的讲,就是打开文件时,将加密文件的副本拷贝到隐蔽位置,然后把这个文件解密、打开,保存时再把临时文件加密后覆盖原文件。这种加密实质上是通过临时文件来实现的,为了确保明文和密文之间的一致性,必须在每次用户存盘或者系统自动存盘的时候对整个文件进行一次整体加密和并复制到用户文件原来存储的位置,因此效率较低,且由于在计算机上保存了完整的明文文件,只要跟踪到临时文件所在位置即可能泄密,此外,文件在打开和存储过程中需要多次复制,效率损失很大,HookAPI的方式对于一些程序不能够完美的支持,兼容性有待考证。
由于文件映射的存在,在应用层进行动态加密比较困难,虽然原理上有一定的可行性,但是其加密效率、兼容性、可移植性、稳定性较之于内核层的过滤驱动加密要差很多,好处在于开发难度较小,网络操作能力强。

过滤驱动透明加密技术

1 驱动技术简介

过滤驱动加密技术是基于windows的文件系统过滤驱动(IFN)技术,工作在Windows的内核层。它是目前炙手可热的一门技术,其特点是技术门槛较高,需要深入理解windows系统内核,开发过程中稍有不慎就会破坏系统内核,因此核心技术仅被少数几家实力雄厚的公司所掌握。
文件过滤驱动是把文件作为一种设备来处理的一种虚拟驱动,WindowsNF系统内核采用堆栈式可扩展驱动模型J,原则上可以在任何一个层次上加载自己的过滤驱动,但是加载的层次不同,开发的难度和应用价值也不同,现在的技术主要还是加载在文件系统驱动的上层,这样就可以充分利用并扩充文件系统的现有功能],该技术在病毒实时监控与防护、数据备份与还原以及文件访问控制等领域都有着非常广泛的应用。
过滤驱动中几种非常重要的概念包括驱动对象、设备对象、设备堆栈、I/O堆栈,驱动对象标示驱动程序,设备对象记录加载的设备,一个驱动对象可以有多个设备对象构成设备对象链表,设备堆栈用于记录所有驱动程序的设备,I/O栈用来记录穿越设备堆栈时的操作属性,操作的对象为IRP(I/O Request Package),IRP包由系统的某个组件创建,类似于Windows应用程序的“消息”概念,要处理某种数据只需要把IRP包传送到相应驱动的相应派遣函数中。

2 加密的基本思想

在Windows NT内核操作系统中,应用程序的一次数据请求的过程如图2所示:只有在无缓存或者FAST I/O不可用的情况下才在硬盘上读取数据,因此只用拦截IRP->F1agS为IRP_NOCACHE(表示I/O请求从存储的媒介而不是高速缓存中读取数据)、IRP—PAGING—IO(表示此时执行内存页的I/O操作)和IRP—SYNCHRONOUS—PAGING—1O(表示内存页需要同步更新,此标志也是由内存管理器使用)的数据包,对于写数据的IRP包,在其分发例程中嵌入加密算法完成加密动作,而对于读数据的IRP包,在IRP包的完成例程中嵌入解密算法,对得到的数据进行解密处理。

3 写数据加密的实现

在用户写受保护的文件时,文件加解密客户端能透明地对文件写入的内容进行加密。加密文件判别模块判别出此文件为受保护文件后,将密钥传递给加解密过滤驱动,并由过滤驱动完成写操作。为了使过滤驱动能够加密对文件的写操作,需要拦截IRP—MJ—WRITE和IRP—MJ—DEVICE—CONTROL两种IRP。当应用层进程调用API:DeviceIOControl时,向系统发出IRP—MJ—DEVICE—CONTROL,过滤驱动可以通过处理此IRP获得传递给驱动的加密密钥,进而在拦截IRP—MJ—WRITE的例程中进行加密。
下面将分别介绍这两个例程的实现。
(1)处理IRP—MJ—DEVICE—CONTROL例程。
首先获得应用层程序在IRP中携带的输人数据其操作代码,如果携带了传递密钥的操作代码,例程将存储密钥,并直接完成此IRP,否则将IRP交给底层驱动处理。
伪代码如下:
NTSrATUS EneryptDeviceControl(INPDEVICE—OBJECT DeviceObiect,IN PIRP Irp)
{
PVOID inputBuffer;
ULONG inputBufferLength;
Irp->loStatus.Smtus=STATUS—SUCCESS;
Irp->loStams.Information=O;
ioControlCode=irpStack->Parameters.DeviceIoContro1.IoControlCode;
inputBuffer=Irp->AssociaredIrp.SystemBuffer;
inputBufferLength=irpStack->Parameters.DeviceIoContro1.InputBufferLength;
if(ioControlCode==INPUT—KEY)
{
SetKey(inputBuffer);//将输入的加密密钥保存起来
IoCompleteRequest(Irp,IO—NO—INCREMENT);//完成此IRP
Return STATUS-SUCCESS;
}
//接下来将IRP继续下发传递给下层的驱动处理
……
……
}
(2)处理IRP—MJ—WRITE例程。
需要区分IRI携带数据的两种方式,由MDL结构指定或直接包含数据的指针。
其实现的伪代码如下:
NTSTATUS EncryptWrite(IN PDEVICE-OBJECT DeviceObject,IN PIRP Irp)
{
……//完成初始化的工作
PIO—STACK—LOCATION irpsp=loGetCurrentIrpStackLocation(Irp);
switch(irpsp->MinorFunction)
{
case IRP—MN—N0RMAI;
{
if(Irp->MdlAddmss!=NULL)//判断写的数据是否是在MDL结构中
{
产生一个新的MDL结构,将其指向一个加密后的数据库,并将Irp->MdlAddress指针指向新的MDL结构
}
else
{
buffer=Irp->UserBuffer;
Encrypt(buffer,GetKey());//加密所写的数据
}
……//其它的必要操作
}
3.4 读数据解密的实现
在用户读受保护的文件时,文件加解密客户端能透明地对读出的原始内容进行解密。加密文件判别器判别出此文件为受保护文件后,将密钥传递给加解密过滤驱动,并由过滤驱动完成读操作,和写操作类似,需要拦截IRP—MJ—WRITE和IRP一MJ一DEVICE—CONTROL两种IRP。IRP—MJ—DEVICE-CONTROl例程的用途和实现方式和加密时相同。过滤驱动拦截IRP—MJ—READ的例程中进行加密。IRP—MJ—READ例程的实现需要利用完成例程,在完成例程中完成加解密过程。
下面将介绍这个例程的实现。
NTSrATUS EncryptWrite(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
……初始化操作
KeInitializeEvent(&waitEvent,NotificationEvent,FALSE);
IoCopyCurrentIrpStackLocationToNext(Irp);
loSetCompletionRoutine(IrP,EncryptReadCompletion,&waitEvent,TRUE,TRUE,TRUE);//设置完成例程
status=IoCallDriver(devExt->AttachedToDeviceObject,Irp);
if(STATUS—PENDING==status)
{
status=KeWaitForSingleObject(&waitEvent,Executive,KernelMode,FALSE,NULL);//等待完成例程发出的解密完成消息
}
IoCompleteRequest(Irp,IO-NO—INCREMENT);
ReturnSTATUS—SUCCESS
}
代码中首先初始化一个消息事件,将消息事件作为参数传递给完成例程,接着调用底层驱动获得读出的数据,IRP返回后系统将启动一个新的线程来执行完成例程。
主线程等待消息事件,同时完成例程执行数据的解密操作,解密完成后完成例程发送消息事件,这样主线程被唤醒并正常返回。
完成例程的伪代码如下:
NTSTATUS SfReadCompletion(IN PDEVICE—OBJECT DeviceObject,IN PIRP Irp,IN PVOID Context)
{
PKEVENT event=Context;
ASSERT(IS—MY—DEVICE—OBJECT(DeviceObject));
……//完成初始化的工作
PIO—TSACK—LOCATION irpsp=IoGetCurrentIrpStackLocation(Irp);
switch(irpsp->MinorFunction)
{
case IRP—MN—NORMAL;
{
if(Irp->MdlAddress!=NULL)//判断读的数据是否是在MDL结构中
}
产生一个新的MDL结构,将其指向一个解密后的数据块,并将Irp->MdlAddress指针指向新的MDL结构,从而返回被解密的数据
}
else
{
buffer=Irp->UserBuffer;
Decrypt(buffre,GetKey());//解密文件
}
……//其它的必要操作
KeSetEvnet(event,IO—NO—INCREMENT,FALSE);//向主例程发送完成解密的消息
return STATUS_MORE_PROCESSING_ERQUIERD;
}