今天是: Monday, 8th September 2008
登录

Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy.

欢迎来到知识库,这里是收集整理的地方。希望能够给你点帮助

SEAL加密算法源代码(C)

/* seal.c - SEAL encryption algorithm */

#undef SEAL_DEBUG

#define ALG_OK 0
#define ALG_NOTOK 1

struct SealKey {
unsigned long t[520]; /* 512 rounded up to a multiple of 5 + 5*/
unsigned long s[265]; /* 256 rounded up to a multiple of 5 + 5*/
unsigned long r[20]; /* 16 rounded up to multiple of 5 */
};

#define ROT2(x) (((x) >> 2) | ((x) << 30))
#define ROT9(x) (((x) >> 9) | ((x) << 23))
#define ROT8(x) (((x) >> 8) | ((x) << 24))
#define ROT16(x) (((x) >> 16) | ((x) << 16))
#define ROT24(x) (((x) >> 24) | ((x) << 8))
#define ROT27(x) (((x) >> 27) | ((x) << 5))

#define WORD(cp) ((cp[0] << 24)|(cp[1] << 16)|(cp[2] << 8)|(cp[3]))

#define F1(x, y, z) (((x) & (y)) | ((~(x)) & (z)))
#define F2(x, y, z) ((x)^(y)^(z))
#define F3(x, y, z) (((x) & (y)) | ((x) & (z)) | ((y) & (z)))
#define F4(x, y, z) ((x)^(y)^(z))

int g(in, i, h)
unsigned char *in;
int i;
unsigned long *h;
{
unsigned long h0;
unsigned long h1;
unsigned long h2;
unsigned long h3;
unsigned long h4;
unsigned long a;
unsigned long b;
unsigned long c;
unsigned long d;
unsigned long e;
unsigned char *kp;
unsigned long w[80];
unsigned long temp;

kp = in;
h0 = WORD(kp); kp += 4;
h1 = WORD(kp); kp += 4;
h2 = WORD(kp); kp += 4;
h3 = WORD(kp); kp += 4;
h4 = WORD(kp); kp += 4;

w[0] = i;
for (i=1;i<16;i++)
w[i] = 0;
for (i=16;i<80;i++)
w[i] = w[i-3]^w[i-8]^w[i-14]^w[i-16];

a = h0;
b = h1;
c = h2;
d = h3;
e = h4;

for (i=0;i<20;i++)
{
temp = ROT27(a) + F1(b, c, d) + e + w[i] + 0×5a827999;
e = d;
d = c;
c = ROT2(b);
b = a;
a = temp;
}
for (i=20;i<40;i++)
{
temp = ROT27(a) + F2(b, c, d) + e + w[i] + 0×6ed9eba1;
e = d;
d = c;
c = ROT2(b);
b = a;
a = temp;
}
for (i=40;i<60;i++)
{
temp = ROT27(a) + F3(b, c, d) + e + w[i] + 0×8f1bbcdc;
e = d;
d = c;
c = ROT2(b);
b = a;
a = temp;
}
for (i=60;i<80;i++)
{
temp = ROT27(a) + F4(b, c, d) + e + w[i] + 0xca62c1d6;
e = d;
d = c;
c = ROT2(b);
b = a;
a = temp;
}
h[0] = h0+a;
h[1] = h1+b;
h[2] = h2+c;
h[3] = h3+d;
h[4] = h4+e;

return (ALG_OK);
}

unsigned long gamma(a, i)
unsigned char *a;
int i;
{
unsigned long h[5];

(void) g(a, i/5, h);
return h[i % 5];
}

int seal_init(key, ks)
unsigned char *key;
struct SealKey **ks;
{
struct SealKey *result;
int i;
unsigned long h[5];

result = (struct SealKey *) calloc(1, sizeof(*result));
if (result == (struct SealKey *) 0)
return (ALG_NOTOK);

for (i=0;i<510;i+=5)
g(key, i/5, &(result->t[i]));
/* horrible special case for the end */
g(key, 510/5, h);
for (i=510;i<512;i++)
result->t[i] = h[i-510];
/* 0×1000 mod 5 is +1, so have horrible special case for the start */
g(key, (-1+0×1000)/5, h);
for (i=0;i<4;i++)
result->s[i] = h[i+1];
for (i=4;i<254;i+=5)
g(key, (i+0×1000)/5, &(result->s[i]));
/* horrible special case for the end */
g(key, (254+0×1000)/5, h);
for (i=254;i<256;i++)
result->s[i] = h[i-254];
/* 0×2000 mod 5 is +2, so have horrible special case at the start */
g(key, (-2+0×2000)/5, h);
for (i=0;i<3;i++)
result->r[i] = h[i+2];
for (i=3;i<13;i+=5)
g(key, (i+0×2000)/5, &(result->r[i]));
/* horrible special case for the end */
g(key, (13+0×2000)/5, h);
for (i=13;i<16;i++)
result->r[i] = h[i-13];
#ifdef SEAL_DEBUG
printf(”T:\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->t[i]);
printf(”\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->t[i+8]);
printf(”\n”);
printf(”…\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->t[i+504]);
printf(”\n”);
printf(”S:\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->s[i]);
printf(”\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->s[i+8]);
printf(”\n”);
printf(”…\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->s[i+248]);
printf(”\n”);
printf(”R:\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->r[i]);
printf(”\n”);
for (i=0;i<8;i++)
printf(”%08x “, result->r[i+8]);
printf(”\n”);
#endif /* SEAL_DEBUG */
*ks = result;
return (ALG_OK);
}

int seal(key, in, out)
struct SealKey *key;
unsigned long in;
unsigned long *out;
{
int i;
int j;
int l;
unsigned long a;
unsigned long b;
unsigned long c;
unsigned long d;
unsigned short p;
unsigned short q;
unsigned long n1;
unsigned long n2;
unsigned long n3;
unsigned long n4;
unsigned long *wp;

wp = out;

for (l=0;l<4;l++)
{
a = in ^ key->r[4*l];
b = ROT8(in) ^ key->r[4*l+1];
c = ROT16(in) ^ key->r[4*l+2];
d = ROT24(in) ^ key->r[4*l+3];

for (j=0;j<2;j++)
{
p = a & 0×7fc;
b += key->t[p/4];
a = ROT9(a);

p = b & 0×7fc;
c += key->t[p/4];
b = ROT9(b);

p = c & 0×7fc;
d += key->t[p/4];
c = ROT9(c);

p = d & 0×7fc;
a += key->t[p/4];
d = ROT9(d);

}
n1 = d;
n2 = b;
n3 = a;
n4 = c;

p = a & 0×7fc;
b += key->t[p/4];
a = ROT9(a);

p = b & 0×7fc;
c += key->t[p/4];
b = ROT9(b);

p = c & 0×7fc;
d += key->t[p/4];
c = ROT9(c);

p = d & 0×7fc;
a += key->t[p/4];
d = ROT9(d);

for (i=0;i<64;i++)
{
p = a & 0×7fc;
b += key->t[p/4];
a = ROT9(a);
b ^= a;

q = b & 0×7fc;
c ^= key->t[q/4];
b = ROT9(b);
c += b;

p = (p+c) & 0×7fc;
d += key->t[p/4];
c = ROT9(c);
d ^= c;

q = (q+d) & 0×7fc;
a ^= key->t[q/4];
d = ROT9(d);
a += d;

p = (p+a) & 0×7fc;
b ^= key->t[p/4];
a = ROT9(a);

q = (q+b) & 0×7fc;
c += key->t[q/4];
b = ROT9(b);

p = (p+c) & 0×7fc;
d ^= key->t[p/4];
c = ROT9(c);

q = (q+d) & 0×7fc;
a += key->t[q/4];
d = ROT9(d);

*wp = b + key->s[4*i];
wp++;
*wp = c ^ key->s[4*i+1];
wp++;
*wp = d + key->s[4*i+2];
wp++;
*wp = a ^ key->s[4*i+3];
wp++;

if (i & 1)
{
a += n3;
c += n4;
}
else
{
a += n1;
c += n2;
}
}
}
return (ALG_OK);
}
/* end of file */

Tags:

列出本机安装的所有硬件设备

以下代码可以把你机器上已安装的硬件设备全部列出来。

#include 
#include 
#include 
#include 
#include 
#pragma comment(lib, “Setupapi.lib”)

int main( int argc, char *argv[ ], char *envp[ ] )
{
    HDEVINFO hDevInfo;
    SP_DEVINFO_DATA DeviceInfoData;
    DWORD i;

    // Create a HDEVINFO with all present devices.
    hDevInfo = SetupDiGetClassDevs(NULL,
        0, // Enumerator
        0,
        DIGCF_PRESENT | DIGCF_ALLCLASSES );

    if (hDevInfo == INVALID_HANDLE_VALUE)
    {
        // Insert error handling here.
        return 1;
    }

    // Enumerate through all devices in Set.

    DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
    for (i=0;SetupDiEnumDeviceInfo(hDevInfo,i,
        &DeviceInfoData);i++)
    {
        DWORD DataT;
        LPTSTR buffer = NULL;
        DWORD buffersize = 0;

        //
        // Call function with null to begin with,
        // then use the returned buffer size
        // to Alloc the buffer. Keep calling until
        // success or an unknown failure.
        //
        while (!SetupDiGetDeviceRegistryProperty(
            hDevInfo,
            &DeviceInfoData,
            SPDRP_DEVICEDESC,
            &DataT,
            (PBYTE)buffer,
            buffersize,
            &buffersize))
        {
            if (GetLastError() ==
                ERROR_INSUFFICIENT_BUFFER)
            {
                // Change the buffer size.
                if (buffer)
     LocalFree(buffer);
                buffer = (char *)LocalAlloc(LPTR,buffersize);
            }
            else
            {
                // Insert error handling here.
                break;
            }
        }

        printf(”Result:[%s]\n”,buffer);

        if (buffer) LocalFree(buffer);
    }

    if ( GetLastError()!=NO_ERROR &&
         GetLastError()!=ERROR_NO_MORE_ITEMS )
    {
        // Insert error handling here.
        return 1;
    }

    //  Cleanup
    SetupDiDestroyDeviceInfoList(hDevInfo);

    return 0;
}

彻底弄明白IRP

微软的东东,多数是把聪明人搞晕,把晕的人搞残,把残的人搞死!
    一直以来,想弄明白驱动和IRP到底是怎么回事,在驱网上也查了无数的资料,到头来,手脚抽筋也没弄明白。

    从网上查到一段说明,可以说是迄今最后的解释,在家里看了N天,似同天书。

引用:

1.    子系统调用NT的IO系统服务打开命名文件。

2.    NT的IO管理器调用对象管理器,查询命名文件,并且帮助解决文件对象的符号连接。同时调用安全参照监视器,检查子系统是否具备打开文件句柄的正确权限。

3.    如果NT文件系统不认识文件对象,IO管理器挂起请求。调用多个文件系统直到识别出文件对象才继续请求。

4.    IO管理器负责为打开的请求分配内存和初始化IRP。对于NT驱动,打开请求等同于创建请求。

5.    IO管理器调用文件系统驱动,将IRP传递给它们。文件系统存取它们的IRP中本地IO栈,决定必须进行哪一种操作。检查参数,确定请求文件是否在缓存中。如果不是,设置下一个IRP中驱动的IO栈。

6.    无论是驱动处理IRP还是完成IO请求操作。都调用IO管理器和其它NT元素提供的核心态例程。

7.    驱动设置返回给IO管理器的IRP中的IO状态块表示请求操作是成功还是失败。

8.    IO管理器通过获取IRP中的IO状态,将信息同过保护子系统返回给原始调用者。

9.    IO管理器释放已完成的IRP。

10.    如果打开操作成功,IO管理器返回文件句柄给子系统。反之返回错误状态。

晕晕乎乎中,似乎有所顿悟,何不用一个具体的类比,也解释一下IRP到底是怎么回事,所以喝了二两二锅头,斗胆把上面的10段话作如下的翻译,有翻译不妥当之处,望大大们指正:

引用:

1.    客人(IRP)来到大厦(驱动程序)外,该大厦有一个按人名造册的接待系统(IO管理器)。

2.    客人(IRP)先到大厅,查花名册,按人名(设备对象DeviceObject,符号连接SymbolicLink)查。同时查安全薄,看这个人是不是恐怖分子、台独分子(STATUS_INVALID_DEVICE_REQUEST, SECURITY_CLIENT_CONTEXT),不是则被允许进入。如是,则不允许则禁入。

3.    如果查不到客人名,则拒绝进入。调用多本花名册(文件系统,FSD),直到识别出来人,才继续。

4.    接待系统(IO管理器)为客人(IRP)分配房间、会议室,餐饮,一应俱全(RtlZeroMemory ,RtlCopyMemory),并打扫一遍(初始化, KeSetEvent, KeWaitForSingleObject, IoMarkIrpPending, InitializeObjectAttributes)。

5.    接待系统(IO管理器)调用管理人员,将客人的信息传递给下面各部门。各部门管理人员根据他们的子系统情况,分配资源队列(本地IO栈),并决定必须进行哪一种操作(IoGetCurrentIrpStackLocation, Irp->AssociatedIrp.SystemBuffer)。客人是否在不同级别的客人清单中。如果没有这个级别,则新建一个级别 (DeviceObject->DeviceExtension),并进行登记。比如是国宾级,则一定要准备一个总统套间。

6.    不论是对于客人(Irp),还是客人的级别档次要求(IO栈),都使用模块化的规范流程。

7.    向大厦总经理报告人员进入、级别招待的请求是成功还是失败。

8.    获取人员、招待的状态,将是不是超过招待能力的信息(过保护状态)返回给新来的客人。如果超出接待能力,则挂牌“客满,恕不接待”。

9.    人员离开,则释放客人占有的所有资源(分配房间、会议室等)。IoCompleteRequest, DriverObject->DriverUnload,IoDeleteDevice, ZwClose

10.    招待成功,返回OK或返回错误(Irp->IoStatus.Status = STATUS_SUCCESS,return ntStatus)

Tags:

深入了解文件系统Cache管理器

译者序

本文仅用于学习交流,不负版权责任。翻译者楚狂人,有问题可与我联系,qq16191935。MSN walled_river@hotmail.com.

我以前不大明白Windows的文件系统和缓冲管理器之间的关系。仅仅知道Cc开头的系列调用是缓冲管理器提供的,文件系统中可以调用。也知道缓冲对于文件系统的意义,在于缓冲读写操作,使磁盘得到高效的利用。

为了翻译了这些资料,我也大致明白了文件系统和缓冲管理器之间的互动。windows的文件系统和缓冲管理器之间是相互调用的关系,双向沟通。总的来说,是缓冲管理器提供一些调用给文件系统,文件系统注册一些回调函数给缓冲管理器,而且缓冲管理器会发IRP给文件系统来处理。

首先文件系统如果想要实现缓冲读写,那么肯定要调用缓冲管理器提供的调用。(如对于缓冲读,不直接自己读磁盘,而是调用CcCopyRead),但是缓冲管理器自然还是要通过文件系统来实际读磁盘。因此缓冲管理器会发出IRP来给文件系统进行处理。这样,文件系统就要分别缓冲管理器发来的请求和用户进程发来的请求分别处理了。

把不同的请求放在同一个例程中处理,这正是设计上最令人恼火的地方。不过理解了这一点之后,你也就大致明白缓冲管理器和文件系统是如何交互的了。

NT 文件系统缓冲管理器

本文中,我们的同缓冲管理器的运行例程的一个基础描述.此外,给出了一些例程的使用例子.也参考了MS IFS 中能找到的代码.

缓冲管理器概述

缓冲管理器是一个纯软件组件.和Windows NT的内存管理器紧密的结合为一个整体.同时使文件系统数据的缓冲和虚拟内存管理系统也结合在一起.一些操作系统单独实现他们的文件系统,所以他们有不同的数据缓冲方式.但由于物理内存的有限性,这样的缓冲一样必须合理的管理.而且内存被这样的缓冲用了,就不能在系统中再做其他的用途了.

所以Window NT 缓冲管理器的一个关键优点,就是允许我们在文件系统缓冲和程序运行使用物理内存之间保持一个平衡.当一个应用正在消耗内存,用来进行文件数据缓冲的内存可能被缩减到接近0.结果系统让物理内存得到了更好的使用,最终提供了更好的性能.

文件系统还有一个使用Cache管理器的关键原因.一个文件可能被标准的文件系统接口访问.比如读和写,也有可能通过内存管理器被做为内存映射文件访问.有时两种访问方式被用在同一个文件上.这是Cache管理器提供一个机制,建立这两种访问方式之间的桥梁,以确保数据的可靠性.

Cache管理器数据结构

文件系统和Cache管理器之间的接口是一种过程上的接口.所有的Cache管理器用到的数据结构,有必要通过一个文件联系起来.但是实际上这些内部接口对文件系统是透明的.在这里我们描述这些文件系统和Cahce管理器共享的关键的数据结构.

缓冲控制块(BCB,Buffer Control Block)

如果一个文件的一部分被映射到一个系统内存地址,Cahce管理器内部使用缓冲控制块来记录.有时文件系统进行某些临界操作的时候,需要在内存中锁定一些数据.所以这个结构必须暴露给文件系统使用:

缓冲控制块的大部分是部可见的.开头的一部分暴露给文件系统:

typedef struct _PUBLIC_BCB {
 CSHORT NodeTypeCode;
 CSHORT NodeByteSize;
 ULONG MappedLength;
 LARGE_INTEGER MappedFileOffset;
} PUBLIC_BCB,*PPUBLIC_BCB;

开始的两个域是Windows数据结构的标准域,说明数据结构的类型和长度.后边的两个域是文件系统感兴趣的,说明了这个文件被这个特殊的缓冲控制块所管理的内容范围.

文件大小信息

文件系统和内存管理器都维护文件大小的信息.当文件系统建立了一个文件的映射,它记录了文件的当前大小.以后的任何改动同样被提交给Cache管理器.

Cache管理器用三个值用来表示文件的大小:

typedef struct _CC_FILE_SIZES {
 LARGE_INTEGER AllocationSize;
 LARGE_INTEGER FileSize;
 LARGE_INTEGER ValidDataLength;
} CC_FILE_SIZES,*PCC_FILE_SIZES;

这些域的名字容易混淆.比如AllocationSize不是说给这个文件分配实际的物理空间,而是当前已经分配的空间中能容纳的数据总量.对一般的文件系统来说,上边两个数据当然应该是一样的.但是,对一个支持数据压缩或者扩展的文件系统而言,这个值表示当前控件中所能容纳的数据量(而不是空间大小).

AllocationSize被内存管理器用来得到section object.section object被用来觉得一个文件是如何映射到内存的,所以AllocationSize总是至少和文件一样大.缓冲管理器和内存管理起没有考虑当文件系统把AllocationSize设置得比file size小的情况.系统会因数据结构的混乱而崩溃.

FileSize表示了文件数据中最后一个有效的字节的位置,一般是文件结束标记.

缓冲管理器回调

文件系统和内存管理器之间的互动是通过一系列的回调实现的.这些回调函数被注册给Cache管理器之后被Cahce管理器用来确保数据结构在一个文件系统操作之前被\”锁定\”.

Windows NT假设资源如何被文件系统,Cahce管理器和内存管理器使用有一个严格的顺序.这个顺序确保死缩不会发生.反之则可能死锁.一般来说,文件系统首先得到资源.然后是Cache管理器,最后是内存管理器.

因此,这些回调被内存管理器用来维护它的层级:这些回调有:

typedef BOOLEAN (*PACQUIRE_FOR_LAZY_WRITE) ( IN PVOID Context, IN BOOLEAN Wait );
typedef VOID (*PRELEASE_FROM_LAZY_WRITE) ( IN PVOID Context );
typedef BOOLEAN (*PACQUIRE_FOR_READ_AHEAD) ( IN PVOID Context, IN BOOLEAN Wait, );
typedef VOID (*PRELEASE_FROM_READ_AHEAD ( IN PVOID Context ); 

typedef struct _CAHHE_MANAGER_CLALLBACKES {
 PACQUIRE_FOR_LAZY_WRITE AcquireForLazyWrite;
 PRELEASE_FROM_LAZY_WRITE ReleaseFromLazyWrite;
 PACQUIRE_FOR_READ_AHEAD AcquireForReadAhead;
 PRELEASE_FROM_READ_AHEAD ReleaseFromReadAhead; 

} CACHE_MANAGER_CALLBACKS,*PCACHE_MANAGER_CLALLBACKS;

注意这些回调分别被缓冲管理器的两个部分使用.首先是延迟写,负责把脏的(修改过的)缓冲数据写回文件系统.另一种是预读,预先的读取文件,给用户调用的读返回信息.

首先,在设计上很重要的是要理解你的文件系统应该避免那些情况,而哪些是不需要避免的.比如说,没有理由把用户应用的缓冲读操作和缓冲管理器的延迟写序列化.它们互无影响.但是,你需要避免用户调用的非缓冲写操作改变文件的长度和缓冲管理器延迟写冲突.

NT的文件系统使用两个资源结构来实现这个.所有这些资源可以被不同的操作系统组件来使用,通过使用共同的头,铁别是头中的Resource域和PageingIoResource域.缓冲管理器并不直接去获取这些资源,它通过调用文件系统中的回调函数来获取这些必要的资源.

请注意这些回调例程必须由你的文件系统提供,他们不是可选的.如果你不能提供,那么系统会崩溃.

微软的IFSKit中每个文件系统的例子都含有这些例程.比如延迟写的位置入下:

文件系统 文件名 例程
FAT resrcsup.c FatAcquireFcbForLazyWrite
CDFS resrcsup.c CdAcquireForCache
RDR2 rxce\\resrcsup.c RxAcquireFcbForLazyWrite

其他的例程基本上可以在同一个文件中找到.

下面的代码是一个回调例程的例子:

static BOOLEAN OwAcquireForLazyWrite(PVOID Conetext,BOOLEAN Wait)
{
POW_FCB fcb = (POW_FCB)Context;
BOOLEAN result;

// 打开文件上的锁
result = OwAcquireResourceExclusiveExp(&fcb->Resource,Wait);
if(!result) {
// 我们没有能获得资源
return result;
}

// 我们得到了资源,我们必须:
// (1)保存当前线程的线程id(用于释放)
// (2)设置顶级irp一个假的值.
// 无论如何,当前线程id在设置之前应该为0(必须是没有使用的)

OwAssert(!fcb->ResourceThread);
fcb->ResourceThread = OwGetCurrentResourceThread();
return (TRUE);
}

CcCanIWrite

因为一个应用程序可能修改内存中的数据的速度超过写磁盘到数据上的能力,导致虚拟内存系统可能被数据所\”饱和\”.这可能导致虚拟系统发生内存枯竭的情况.为了避免这点,文件系统必须和虚拟系统合作探测这种条件.缓冲管理器提供的一个关键操作之一就是CcCanIWrite,原型如下:

NTKERNELAPI BOOLEAN CcCanIWrite (
IN PFILE_OBJECT FileObject,
IN ULONG BytesToWrite,
IN BOOLEAN Wait,
IN BOOLEAN Retrying
);

如果返回了FALSE,那么文件系统必须延迟脏页面的写入来避免内存枯竭.典型的内存枯竭症状是一些调用返回NO_PAGES_AVAILABLE.

文件系统必须排列这些写操作的重试并发送.文件系统可以通过内部的发送机制来发送,也可以使用这个例程.

CcDeferWrite

你的文件系统可以调用FsRtlCopyWirte,这样就不必直接调缓冲管理器了.这样内部的延迟写实际上是用这个例程实现延迟写的.

CcCopyRead

只要一个文件系统建立了一个缓冲(通过CcInitializeCacheMap调用),它就可以使用FsRtl例程(比如FsRtlCopyRead)或者这个例程.一般FsRtlCopyRead用来实现Fast IO的读,而这个例程一般用来实现IRP_MJ_READ,原型如下:

NTKERNELAPI BOOLEAN CcCopyRead (
IN PFILE_OBJECT FileObject,
IN PLARGE_INTERGER FileOffset,
IN ULONG Length,
IN BOOLEAN Wait,
OUT PVOID Buffer,
OUT PIO_STATUS_BLOCK IoStatus
);

FileObject含有一个指向SectionObjectPointer的指针.当数据从缓冲拷贝到用户缓冲区间(也就是这里的Buffer所指的区域),SectionObjectPointer是被缓冲管理器所使用.当然这里假设缓冲已经实现初始化过了.

Wait说明调用这是否希望阻塞一段不定长的时间.比如可能要等待获取一个锁.这个参数应该被看成一个\”提示\”而不是一个\”确保\”.比如说,如果一个磁盘io有必要结束这个操作,这个操作可能继续运行.即使Wait的值是FALSE.

Buffer是调用者提供的缓冲.它不一定是一个有效的缓冲.失败的情况下,这个调用会抛出一个异常.一个文件系统驱动应该捕获这个异常并返回一个错误给应用程序.

IoStatus用来收集操作的完成状态,以及有多少个字节被成功的读取了.

请注意缓冲管理器的这个调用可能导致分页交换.这可能导致文件系统驱动处理实际的分页交换读写的操作的例程重入.

(译者注:假设文件系统中的读处理例程中调用了CcCopyRead从缓冲中读,这可能导致缓冲进行页面交换来读取硬盘.而读取硬盘的操作又是文件系统的读处理例程进行的,因此这个读处理例程是可能重入的.).

CcCopyWrite

当一个文件系统已经初始化了一个缓冲(使用CcInitializeCachedMap调用),它可以使用FsRtl例程(FsRtlCopyWrite)或者这个例程.一般的,FsRtlCopyWrite用来实现FastIo写.而这个例程用来实现普通的IRP_MJ_WRITE,原型如下:

NTKERNELAPI BOOLEAN CcCopyWrite (
IN PFILE_OBJECT FileObject,
IN PLARGE_INTEGER FileOffset,
IN ULONG Length,
IN BOOLEAN Wait,
IN PVOID Buffer
);

参数同上一函数.

请注意这个操作可能导致一个缓冲页面的部分内容被改写.这种情况下,这个页面的内容首先从磁盘上读出,然后被修改.因此,文件系统用来处理文件被修改导致的页面失败的例程有可能被重入.

CcDeferWrite

你的文件系统必须限制数据写入的最大流量.为了简化这个实现,缓冲管理器提供一个简单的机制来把这些写操作排队,直到虚拟内存系统能容纳它们.当CcCanIWrite调用返回失败的时候,这些由你的文件系统所注册的回调函数来完成.

这个回调函数的原型为:

Typedef VOID (*PCC_POST_DEFERRED_WRITE) (
IN PVOID Context1,
IN PVOID Context2
);

这些上下文指针由你的文件系统使用,座位建立延迟写过程的一部分.CcDeferWrite的原型如下:

NTKERNELAPI VOID CcDeferWrite (
IN PFILE_OBJECT FileObject,
IN PCC_POST_DEFERRED_WRITE PostRoutine,
IN PVOID Context1,
IN PVOID Context2,
IN ULONG BytesToWrite,
IN BOOLEAN Retrying);

FileObject标明要写的文件.

PostRoutine是文件系统驱动提供的回调函数,当虚拟内存系统已经发生了改变,附加的写操作可以被允许执行了,缓冲管理器会调用这个回调.

Context1和Context2指正是文件系统驱动所定义的.当写文件被允许的时候传给上面的回调函数.

BytesToWrite参数说明要写的字节数.虚拟内存系统用这个信息判断这个操作是否安全的(基于可用的页).

Retrying参数表示这是第一次尝试(Retrying是FALSE)还是一次延后的尝试(Retrying是TRUE).

CcGetDirtyPages

这个例程列出来仅仅为了完整性.这个用于文件系统利用windows nt的内部日志机制.文件系统中一般不使用.原型如下:

NTKERNELAPI LARGE_INTEGER CcGetDirtyPages (
IN PVOID LogHandle,
IN PDIRTY_PAGE_ROUTINE DirtyPageRoutine,
IN PVOID Context1,
IN PVOID Context2
);

CcGetFileObjectFromBcb

一个私有的缓存控制块包含一个FileObject的指针.虚拟内存系统用此来跟踪文件缓冲的信息.FileObject因此可以从一个BCB中获得.这是一个必要的调用.原型如下:

NTKERNELAPI PFILE_OBJECT CcGetFileObjectFromBcb (
IN PVOID Bcb
);

CcGetFileObjectFromSectionPtrs

一个文件建立缓冲时,缓冲管理器使用FileObject作为参数调用CcInitializeCacheMap来生成一个新的section object.这个对象用于缓冲文件数据.所以当缓冲管理器得到了这个文件的缓冲数据,原来的FileObject被虚拟内存系统用于各种不同的IO操作.

给定的任意一个FileObject的SectionObjectPointer,这个例程可以告诉文件系统实际被虚拟内存系统所使用的实际file object.原型如下:

NTKERNELAPI PFILE_OBJECT CcGetFileObjectFromSectionPtrs (
IN PSECTION_OBJECT_POINTERS SectionObjectPointer
);

文件过滤系统驱动开发Filemon学习笔记

WINDOWS文件过滤系统驱动开发,可用于硬盘还原,防病毒,文件安全防护,文件加密等诸多领域。而掌握核心层的理论及实践,对于成为一名优秀的开发人员不可或缺。

WINDOWS文件过滤系统驱动开发的两个经典例子,Filemon与SFilter,初学者在经过一定的理论积累后,对此两个例子代码的研究分析,会是步入驱动开发殿堂的重要一步,相信一定的理论积累以及贯穿剖析理解此两个例程后,就有能力开始进行文件过滤系统驱动开发的实际工作了。
对于SFilter例子的讲解,楚狂人的教程已经比较流行,而Filemon例子也许因框架结构相对明晰,易于剖析理解,无人贴出教程,本人在剖析Filemon的过程中积累的一些笔记资料,陆续贴出希望对初学者有所帮助,并通过和大家的交流而互相提高。

Filemon学习笔记 第一篇:

Filemon的大致架构为,在此驱动程序中,创建了两类设备对象。
一类设备对象用于和Filemon对应的exe程序通信,以接收用户输入信息,比如挂接或监控哪个分区,是否要挂接,是否要监控,监控何种操作等。此设备对象只创建了一个,在驱动程序的入口函数DriverEntry中。此类设备对象一般称为控制设备对象,并有名字,以方便应用层与其通信操作。
第二类设备对象用于挂接到所须监控的分区,比如c:,d:或e:,f:,以便拦截到引应用层对该分区所执行的读,写等操作。此类设备对象为安全起见,一般不予命名,可根据须监控多少分区而创建一个或多个。

驱动入口函数大致如下:

NTSTATUS
DriverEntry(
    IN PDRIVER_OBJECT DriverObject,
    IN PUNICODE_STRING RegistryPath
    )
{
    NTSTATUS                ntStatus;
    PDEVICE_OBJECT          guiDevice;
    WCHAR                   deviceNameBuffer[]  = L”\\Device\\Filemon”;
    UNICODE_STRING          deviceNameUnicodeString;
    WCHAR                   deviceLinkBuffer[]  = L”\\DosDevices\\Filemon”;
    UNICODE_STRING          deviceLinkUnicodeString;
    ULONG                   i;

    DbgPrint ((”Filemon.SYS: entering DriverEntry\n”));
    FilemonDriver = DriverObject;

    //
    // Setup the device name
    //
    RtlInitUnicodeString (&deviceNameUnicodeString,
                          deviceNameBuffer );

    //
    // Create the device used for GUI communications
    //此设备对象用来和用户交互信息
    ntStatus = IoCreateDevice ( DriverObject,
                                sizeof(HOOK_EXTENSION),
                                &deviceNameUnicodeString,
                                FILE_DEVICE_FILEMON,
                                0,
                                TRUE,
                                &guiDevice );

    //
    // If successful, make a symbolic link that allows for the device
    // object’s access from Win32 programs
    //
    if(NT_SUCCESS(ntStatus)) {

        //
        // Mark this as our GUI device
        //
        ((PHOOK_EXTENSION) guiDevice->DeviceExtension)->Type = GUIINTERFACE;

        //
        // Create a symbolic link that the GUI can specify to gain access
        // to this driver/device
        //
        RtlInitUnicodeString (&deviceLinkUnicodeString,
                              deviceLinkBuffer );
        ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString,
                                         &deviceNameUnicodeString );
        if(!NT_SUCCESS(ntStatus)) {

            DbgPrint ((”Filemon.SYS: IoCreateSymbolicLink failed\n”));
            IoDeleteDevice( guiDevice );
            return ntStatus;
        }

        //
        // Create dispatch points for all routines that must be handled.
        // All entry points are registered since we might filter a
        // file system that processes all of them.
        //
        for( i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++ ) {

            DriverObject->MajorFunction[i] = FilemonDispatch;
        }
#if DBG
        //
        // Driver unload is only set if we are debugging Filemon. This is
        // because unloading a filter is not really safe - threads could
        // be in our fastio routines (or about to enter them), for example,
        // and there is no way to tell. When debugging, we can risk the
        // occasional unload crash as a trade-off for not having to
        // reboot as often.
        //
        // DriverObject->DriverUnload = FilemonUnload;
#endif // DBG

        //
        // Set up the Fast I/O dispatch table
        //
        DriverObject->FastIoDispatch = &FastIOHook;

    } else {

        //
        // If something went wrong, cleanup the device object and don’t load
        //
        DbgPrint((”Filemon: Failed to create our device!\n”));
        return ntStatus;
    }

    //
    // Initialize the name hash table
    //
    for(i = 0; i < NUMHASH; i++ ) HashTable[i] = NULL;

    //
    // Find the process name offset
    //
    ProcessNameOffset = FilemonGetProcessNameOffset();//为了得到当前进程名字

    //
    // Initialize the synchronization objects
    //
#if DBG
    KeInitializeSpinLock( &CountMutex );
#endif
    ExInitializeFastMutex( &LogMutex );
    ExInitializeResourceLite( &FilterResource );
    ExInitializeResourceLite( &HashResource );

    //
    // Initialize a lookaside for file names
    //
    ExInitializeNPagedLookasideList( &FullPathLookaside, NULL, NULL,
				     0, MAXPATHLEN, ‘mliF’, 256 );

    //
    // Allocate the first output buffer
    //
    CurrentLog = ExAllocatePool( NonPagedPool, sizeof(*CurrentLog) );
    if( !CurrentLog ) {

        //
        // Oops - we can’t do anything without at least one buffer
        //
        IoDeleteSymbolicLink( &deviceLinkUnicodeString );
        IoDeleteDevice( guiDevice );
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Set the buffer pointer to the start of the buffer just allocated
    //
    CurrentLog->Len  = 0;
    CurrentLog->Next = NULL;
    NumLog = 1;

    return STATUS_SUCCESS;
}

在此驱动入口点函数中,主要做了生成新的设备对象,此设备对象用来和应用层信息交互,比如应用层向驱动传递需要挂接或者监控的分区盘符,或者是否挂接盘符,是否监控操作等。
上面创建设备对象的代码为:

ntStatus = IoCreateDevice ( DriverObject,
                                sizeof(HOOK_EXTENSION),
                                &deviceNameUnicodeString,
                                FILE_DEVICE_FILEMON,
                                0,
                                TRUE,
                                &guiDevice );

    //
    // If successful, make a symbolic link that allows for the device
    // object's access from Win32 programs
    //
    if(NT_SUCCESS(ntStatus)) {

        //
        // Mark this as our GUI device
        //
        ((PHOOK_EXTENSION) guiDevice->DeviceExtension)->Type = GUIINTERFACE;

        //
        // Create a symbolic link that the GUI can specify to gain access
        // to this driver/device
        //
        RtlInitUnicodeString (&deviceLinkUnicodeString,
                              deviceLinkBuffer );
        ntStatus = IoCreateSymbolicLink (&deviceLinkUnicodeString,
                                         &deviceNameUnicodeString );
        if(!NT_SUCCESS(ntStatus)) {

            DbgPrint (("Filemon.SYS: IoCreateSymbolicLink failed\n"));
            IoDeleteDevice( guiDevice );
            return ntStatus;
        }

上面代码完成的功能为创建了用于与应用层交互的控制设备对象,名字在参数&deviceNameUnicodeString,中。设备对象创建成功后又调用IoCreateSymbolicLink创建了一个符号连接,以便于应用层交互。 在入口点函数DriverEntry代码中,还有一处代码: ProcessNameOffset = FilemonGetProcessNameOffset();//为了得到当前进程名字。此函数体如下:

ULONG
FilemonGetProcessNameOffset(
    VOID
    )
{
    PEPROCESS       curproc;
    int             i;

    curproc = PsGetCurrentProcess();//调用PsGetCurrentProcess取得KPEB基址

    //然后搜索KPEB,得到ProcessName相对KPEB的偏移量
    // Scan for 12KB, hoping the KPEB never grows that big!
    //
    for( i = 0; i < 3*PAGE_SIZE; i++ ) {

        if( !strncmp( SYSNAME, (PCHAR) curproc + i, strlen(SYSNAME) )) {

            return i;
        }
    }

    //
    // Name not found - oh, well
    //
    return 0;

这个函数通过查找KPEB (Kernel Process Environment Block),取得进程名,GetProcessNameOffset主要是调用PsGetCurrentProcess取得KPEB基址,然后搜索KPEB,得到ProcessName相对KPEB的偏移量,存放在全局变量ProcessNameOffset中,得到此偏移量的作用是:无论当前进程为哪个,其名字在KPEB中的偏移量不变,所以都可以通过此偏移量得到。而在入口点函数DriverEntry执行时,当前进程必为系统进程,所以在此函数中方便地根据系统进程名SYSNAME(#define SYSNAME “System”)得到此偏移量。

分发函数剖析:
在入口点函数中,通过代码:

for( i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++ ) {

            DriverObject->MajorFunction[i] = FilemonDispatch;
}

简单地把各个分发例程设置成了FilemonDispatch; 然后我们追踪其函数体:

NTSTATUS
FilemonDispatch(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
{
    //
    // Determine if its a request from the GUI to us, or one that is
    // directed at a file system driver that we've hooked
    //
    if( ((PHOOK_EXTENSION) DeviceObject->DeviceExtension)->Type == GUIINTERFACE ) {

        return FilemonDeviceRoutine( DeviceObject, Irp );

    } else {

        return FilemonHookRoutine( DeviceObject, Irp );
    }
}

函数体先判断需要处理IRP包的设备对象的类型,看是属于控制设备对象,还是属于用于挂接并监控文件读写操作的过滤设备对象。如果是属于后者 则进入:FilemonHookRoutine( DeviceObject, Irp )
此函数是拦截文件操作的中心,在其中获得了被操作的文件名字,并且根据操作类型,在
switch( currentIrpStack->MajorFunction ) {
}
中针对不同的MajorFunction,打印出相关操作信息。
因此函数体太长 不再全部列出。
其函数体总体框架为:得到被操作的文件名字,打印相关操作信息,然后下发IRP到底层驱动。
在下发IRP到底层驱动处理前,本层驱动必须负责设置下层IO堆栈的内容。这样下一层驱动调用IoGetCurrentIrpStackLocation()时能得到相应的数据。
设置下层IO堆栈的内容,一般用两个函数来实现:
IoCopyCurrentIrpStackLocationToNext( Irp )
此函数一般用在本驱动设置了完成例程时调用,把本层IO _STACK_LOCATION 中的参数copy到下层,但与完成例程相关的参数信息例外。因为本驱动设置的完成例程只对本层驱动有效。
IoSkipCurrentIrpStackLocationToNext(Irp)
此函数的作用是:直接把本层驱动IO堆栈的内容设置为下层驱动IO堆栈指针的指向。因两层驱动IO堆栈的内容完全一致,省却copy过程。

而在Filemon的处理中,它用了一个特别的办法,没有调用此两个函数,FilemonHookRoutine函数体里面有三句代码:

 

PIO_STACK_LOCATION  currentIrpStack = IoGetCurrentIrpStackLocation(Irp);
PIO_STACK_LOCATION  nextIrpStack    = IoGetNextIrpStackLocation(Irp);

*nextIrpStack = *currentIrpStack;//此步设置了下层驱动的IO_STACK_LOCATION
直接设置了下层驱动IO堆栈的值。

在FilemonHookRoutine函数里,用一个宏实现了复杂的获得拦截到的被操作文件的名字:

if( FilterOn && hookExt->Hooked ) {

        GETPATHNAME( createPath );
}

GETPATHNAME( createPath )宏展开为:

#define GETPATHNAME(_IsCreate)                                                  \
        fullPathName = ExAllocateFromNPagedLookasideList( &FullPathLookaside ); \
        if( fullPathName ) {                                                    \
            FilemonGetFullPath( _IsCreate, FileObject, hookExt, fullPathName ); \
        } else {                                                                \
            fullPathName = InsufficientResources;                               \
        }

在函数:FilemonGetFullPath( _IsCreate, FileObject, hookExt, fullPathName )中实现了获得被操作的文件名字,此函数代码较多,判断条件复杂,理解起来比较麻烦,下面重点讲解。
对函数FilemonGetFullPath的理解关键在于理顺结构,
此函数的功能就是获得文件名字,获得文件名字一般在三种状态下:
一:在打开文件请求中,但在打开文件前。
二:在打开文件请求中,但在打开文件后,通过在本层驱动中设置完成例程。在完成例程中获得。
三:在过滤到读写等操作时。
而在此函数中,它包含了第一种和第三种方法,通过一些烦琐的条件判断,先判断出目前是处于什么状态中,然后根据不同状态采取不同方法。

先分析当在第一种条件下,此函数的处理方法,可以精炼为如下:

VOID
FilemonGetFullPath(
    BOOLEAN createPath,
    PFILE_OBJECT fileObject,
    PHOOK_EXTENSION hookExt,
    PCHAR fullPathName
    )
{
	ULONG               pathLen, prefixLen, slashes;
    PCHAR               pathOffset, ptr;
    BOOLEAN             gotPath;
    PFILE_OBJECT        relatedFileObject;

    ANSI_STRING         fileName;
    ANSI_STRING         relatedName;

    UNICODE_STRING      fullUniName;

	prefixLen = 2; // "C:"

	if( !fileObject ) { 

        sprintf( fullPathName, "%C:", hookExt->LogicalDrive );
        return;
    }

    //
    // Initialize variables
    //
    fileName.Buffer = NULL;
    relatedName.Buffer = NULL;
    gotPath = FALSE;

	if( !fileObject->FileName.Buffer)
	{
		sprintf( fullPathName, "%C:", hookExt->LogicalDrive);
		return;
	}else
		DbgPrint("fileOjec->FileName:%s",fileObject->FileName);

	 if( !NT_SUCCESS( RtlUnicodeStringToAnsiString( &fileName, &fileObject->FileName, TRUE ))) {

            sprintf( fullPathName, "%C: <Out of Memory>", hookExt->LogicalDrive );
            return;
        }

        pathLen = fileName.Length + prefixLen;
        relatedFileObject = fileObject->RelatedFileObject;

		  //
        // Only look at related file object if this is a relative name
        //
        if( fileObject->FileName.Buffer[0] != L’\\’ &&
            relatedFileObject && relatedFileObject->FileName.Length ) {
			DbgPrint(”relatedFileObject filename : %s”,relatedFileObject->FileName);

			if( !NT_SUCCESS( RtlUnicodeStringToAnsiString( &relatedName, &relatedFileObject->FileName, TRUE ))) {

                sprintf( fullPathName, “%C: <Out of Memory>”, hookExt->LogicalDrive );
                RtlFreeAnsiString( &fileName );
                return;
            }
            pathLen += relatedName.Length+1;
        }

		if( fileObject->DeviceObject->DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM ) {

            sprintf( fullPathName, “%C:”, hookExt->LogicalDrive );
        }

		 if( pathLen >= MAXPATHLEN ) {

            strcat( fullPathName, ” <Name Too Long>” );

        } else {

            //
            // Now we can build the path name
            //
            fullPathName[ pathLen ] = 0;

            pathOffset = fullPathName + pathLen - fileName.Length;
            memcpy( pathOffset, fileName.Buffer, fileName.Length + 1 );

            if( fileObject->FileName.Buffer[0] != L’\\’ &&
                relatedFileObject && relatedFileObject->FileName.Length ) {

                //
                // Copy the component, adding a slash separator
                //
                *(pathOffset - 1) = ‘\\’;
                pathOffset -= relatedName.Length + 1;

                memcpy( pathOffset, relatedName.Buffer, relatedName.Length );

                //
                // If we’ve got to slashes at the front zap one
                //
                if( pathLen > 3 && fullPathName[2] == ‘\\’ && fullPathName[3] == ‘\\’ )  {

                    strcpy( fullPathName + 2, fullPathName + 3 );
                }
            }
        }  

}

上面的精简后的函数代码为只考虑目前处于第一种情况,即打开文件请求中,但文件尚未打开时。
在此时,文件的名字信息存储在文件对象fileObject->FileName,与fileObject->RelatedFileObject->FileName, FileObject->FileName RelatedObject 的相对路径,通过对两者的解析组合出文件名字。

而在FilemonGetFullPath 函数体中的另一段代码:

FilemonGetFullPath
{
…………………..
…………………..
…………………..
if( !gotPath && !createPath ) {

        fileNameInfo = (PFILE_NAME_INFORMATION) ExAllocatePool( NonPagedPool,
                                                                MAXPATHLEN*sizeof(WCHAR) );

        if( fileNameInfo &&
            FilemonQueryFile(hookExt->FileSystem, fileObject, FileNameInformation,
                             fileNameInfo, (MAXPATHLEN - prefixLen - 1)*sizeof(WCHAR) )) {

            fullUniName.Length = (SHORT) fileNameInfo->FileNameLength;
            fullUniName.Buffer = fileNameInfo->FileName;
            if( NT_SUCCESS( RtlUnicodeStringToAnsiString( &fileName, &fullUniName, TRUE ))) { 

                fullPathName[ fileName.Length + prefixLen ] = 0;

                if( hookExt->Type == NPFS ) {

                    strcpy( fullPathName, NAMED_PIPE_PREFIX );

                } else if( hookExt->Type == MSFS ) {

                    strcpy( fullPathName, MAIL_SLOT_PREFIX );

                } else if( fileObject->DeviceObject->DeviceType != FILE_DEVICE_NETWORK_FILE_SYSTEM ) {

                    sprintf( fullPathName, “%C:”, hookExt->LogicalDrive );

                } else {

                    //
                    // No prefix for network devices
                    //
                }

                memcpy( &fullPathName[prefixLen], fileName.Buffer, fileName.Length );
                gotPath = TRUE;
                RtlFreeAnsiString( &fileName );
                fileName.Buffer = NULL;
            }
        }
        if( fileNameInfo ) ExFreePool( fileNameInfo );
}
…………………
…………………
…………………
}

上面这段代码是处理另外一种情况,即是在其他读写请求中,自己根据拦截到的fileObject构建IRP,下发到底层,以此来查询文件名信息。整个过程还是易于理解的。

在理清这两种脉络后,再剖析此整个函数,就很容易理解整个函数代码了。
代码中对 MajorFunction == IRP_MJ_CREATE_NAMED_PIPE
MajorFunction == IRP_MJ_CREATE_MAILSLOT 的判断是为了辨别对拦截到的进程间的两种通信方式:命名管道与邮槽的处理。

周末来了,祝大家周末愉快。余下的整理后再帖,希望和大家多交流。

连城碧 QQ 276265852 MSN:haochao0000@hotmail.com