发新话题
打印

[分享] [from Debugman]一个稳定完整的磁盘还原代码2k-win7的(加上分析)

[from Debugman]一个稳定完整的磁盘还原代码2k-win7的(加上分析)

经N次改动已经很稳定,给用的上的人,放硬盘里只会让他长毛
详细的介绍
http://www.dbgger.com/?t=project
> 支持2000 xp 2003 vista win7
> 不在磁盘上产生任何临时文件
> 超优化的算法,在保护的情况下,操作硬盘,跟没有保护的情况下,速度一样, 不伤硬盘
> 模拟硬件还原卡工作原理,稳定快速
> 密码保护,用户登录后可以任意配置还原选项
> 支持只保护系统盘,和全盘保护, 支持多硬盘
> 保护MBR,加入防机器狗模块,用户可以手头关闭拦截第三方驱动的功能,比如用来完一些带驱动保护的游戏(以后改成白名单)
> 所有功能,集成到一个驱动文件里,全绿色免安装,一共一个文件
> 界面简洁,操作方便,没任何副作用,呵呵, 我诚认界面是学习迅闪的,以前用习惯它了,不过它不防狗
> 更改了下算法,应付内存特别小的情况 V1.3
> 当然,以上介绍有部分夸大之词

应sysnap提义,下午没事,写了下原理
磁盘还原原理, 以后下拙见,可能有些地方高手有异义,请指出

两种方法
1:卷过滤
2:磁盘过滤
3:hook磁盘IRP分发函数

第一种方法,2k下不适用, 但写起来比较省事
第二种方法,通用,写起来不太省事
第三种方法,算了,我没见过哪个稳定商业的还原软件放着好好的上两种方法不用,用HOOK的, HOOK好用,不能乱用啊
衡量了一下,用第二种

准备工作 :

一个磁盘过滤框架,如diskperf, 或者其它的

开始加代码

磁盘读写,独立出来一个回调
用线程分发,保证读写在同一个线程里,为了稳定,毕竟,都知道多线程只对应用层好使
但低层硬件操作,大家都还得排队,啥时候发展到硬盘一个柱面一个磁头了,就能放开跑了

驱动加载后,读配置,我把配置写到自身了,省事,不想再写别的文件

获取要保护卷的位图,有两种方法
1: FSCTL_GET_VOLUME_BITMAP
2: 自己分析NTFS和FAT格式,NTFS的比较好获取,FAT的我查了点资料,源码放我blog上
  http://www.dbgger.com/?id=765

分析以上两种方法:
第一种的缺点:
  如果系统盘为NTFS,那么系统启动的时候,会挂载NTFS解析器,这时候,如果其它分区不是跟系统盘相同的
  分区格式FSCTL_GET_VOLUME_BITMAP获取其它分区会获取失败,不很完美
第二种的缺点:
    显而易见,FAT16 extfat这些格式,都要分析,加大了复杂度,还有一个很不爽的原因,动态磁盘,没办法很好操作
    因为系统动盘磁盘有一个专门的驱动,这玩意儿只会在需要的时候挂载,比如E为动态磁盘,系统启动完成后,你访问E
    盘他才会加载, 这时候我想起卷过滤的好了,不过已经到着了,索性继续写下去

考虑以上两种方法,第一种,对动态磁盘支持不太好,但有一个挽救的方法,就是等系统启动后,再访问一下动态磁盘分区,再获取位图
这样也算是亡羊补牢了,第二种,代码复杂度很大,缺点也不少,考虑用第一种

继续写:
  磁盘位图以簇为单位,比如要保护C盘吧,需要准备一些工作
  1: 获取C盘位图
  2: 建立一个新位图,做为保护一些需要放过的文件用,但要用扇区为单位, 原因有二
    A: 磁盘读写最小单位为扇区
    B: 为了减少代码复杂度,因为比如说,上层要写512字节,如果你以簇为单位,在重定位就需要做一些不必要的工作,接着向下看就知道了
    bootstat.dat pagefile.sys hiberfil.sys 这三个需要放过,后两个也许大家都知道是干啥的,我只说下第一个
    第一个是系统在记动后写入启动是否成功的标志,要是不放过他,重启后,xp及以后的系统会提示上次非正常关机之类的, 详细说明见http://www.dbgger.com/?id=758
  3: 建立一个重定向位图,同样以扇区为单位, 标记着哪些扇区让重定向了,这个不用说了,充当着稀疏文件的句柄
  4: 建立一个重定向表,用NTDLL的GenericTable, 查找扇区被重定向到哪里
  以前准备工作做好了以后,基本上就是操作位图了,这里有个跟代码效率有关的话题
  位图是一次性申请完,还是一段段申请,如果一个分区有200G, 重定向位图,跟要放过指定文件的位图,就要占,来算一下,(200 * 1024 * 1024 * 1024) / 512 / 8 / 1024 / 1024 = 50M, 50M内存,还是连续的,算了吧,太大了,申请不成功,就完了,还是多写几  行,分块来存 位图吧,分块就是,把位图表始的范围,分成几个区域,当然越多越好,这个有点像简单的hash map, 比如,你需要访问指定扇区  的 时候,查找下  这个扇区所在的位图是否已经建立,如果已经建立,就使用,没建立,就申请,初始化

下面放上系统的核心代码,一些尽在代码中。看注释应该很明白:
复制内容到剪贴板
代码:
NTSTATUS
flt_initVolumeLogicBitMap(PVOLUME_INFO volumeInfo)
{
    NTSTATUS    status;
    PVOLUME_BITMAP_BUFFER    bitMap = NULL;   

    // 逻辑位图大小
    ULONGLONG    logicBitMapMaxSize = 0;
   
    ULONG        sectorsPerCluster = 0;

    ULONGLONG    index = 0;
    ULONGLONG    i = 0;

    status = flt_getVolumeBitmapInfo(volumeInfo->volume, &bitMap);
   
    if (!NT_SUCCESS(status))
    {
        return status;
    }

    sectorsPerCluster = volumeInfo->bytesPerCluster / volumeInfo->bytesPerSector;

    // 获取此卷上有多少个扇区, 用bytesTotal这个比较准确,如果用其它的比如fsinfo,会少几个扇区发现
    volumeInfo->sectorCount = volumeInfo->bytesTotal / volumeInfo->bytesPerSector;
   
    // 得到逻辑位图的大小bytes
    logicBitMapMaxSize = (volumeInfo->sectorCount / 8) + 1;

    ........
   
    // 这几个需要放过写入的文件,获取所在的扇区,专门弄一张位图放进去
    setBitmapDirectRWFile(volumeInfo->volume,
        (*NtBuildNumber >= 2600) ? L"\\Windows\\bootstat.dat" : L"\\WINNT\\bootstat.dat",
        volumeInfo->bitMap_Protect);

    // 页面文件
    setBitmapDirectRWFile(volumeInfo->volume, L"\\pagefile.sys", volumeInfo->bitMap_Protect);

    // 休眠文件
    setBitmapDirectRWFile(volumeInfo->volume, L"\\hiberfil.sys", volumeInfo->bitMap_Protect);
}
下面这个函数很关键,写的不好,直接影响还原状态时的磁盘读写速度
复制内容到剪贴板
代码:
ULONGLONG
DPBitMap_FindNext(DP_BITMAP * bitMap, ULONGLONG startIndex, BOOL set)
{
    LONG    jmpValue = set ? 0 : 0xFFFFFFFF;
    ULONG    slot = 0;
   
    // 遍历slot
    for (slot = startIndex / bitMap->regionSize; slot < bitMap->regionNumber; slot++)
    {
        ULONGLONG    max = 0;
        
        // 还没有分配
        if (!bitMap->buffer[slot])
        {
            if (set)
            {
                startIndex = (slot + 1) * bitMap->regionSize;
                continue;
            }
            else
            {
                return startIndex;
            }
        }
        // 便历块
        for (max = min((slot + 1) * bitMap->regionSize, bitMap->bitMapSize);
        startIndex < max; )
        {
            ULONG    sIndex = startIndex % bitMap->regionSize;

            // 查找下一个置位的索引

            if (jmpValue == ((PULONG)bitMap->buffer[slot])[sIndex / 32])
            {
                // 快速跳越
                startIndex += 32 - (sIndex % 32);
                continue;
            }
            
            if (set == ((((PULONG)bitMap->buffer[slot])[sIndex / 32] & (1 << (sIndex % 32))) > 0))
            {
                // 找到
                return startIndex;
            }   
            startIndex++;
        }
    }
   
    return -1;
}

// 获取真实需要处理的簇
ULONGLONG
getRealSectorForRead(PVOLUME_INFO volumeInfo, ULONGLONG orgIndex)
{
    ULONGLONG    mapIndex = orgIndex;

    // 此簇是否允许直接操作
    if (isSectorProtect(volumeInfo, orgIndex))
    {
        return orgIndex;
    }

    // 此簇是否已经被重定向
    if (DPBitMap_Test(volumeInfo->bitMap_Redirect, orgIndex))
    {
        // 找到重定向到哪里, 并返回   
        PAIR *    result;
        PAIR    pair;
        pair.orgIndex = orgIndex;

        result = (PAIR *)RtlLookupElementGenericTable(&volumeInfo->redirectMap, &pair);

        if (result)
        {
            mapIndex = result->mapIndex;
        }
    }
   
    return mapIndex;
}


// 获取真实需要处理的簇
ULONGLONG
getRealSectorForWrite(PVOLUME_INFO volumeInfo, ULONGLONG orgIndex)
{
    ULONGLONG    mapIndex = -1;

    // 此扇区是否允许直接写
    if (isSectorProtect(volumeInfo, orgIndex))
    {
        return orgIndex;
    }

    // 此簇是否已经被重定向
    if (DPBitMap_Test(volumeInfo->bitMap_Redirect, orgIndex))
    {
        // 找到重定向到哪里, 并返回   
        PAIR *    result;
        PAIR    pair;
        pair.orgIndex = orgIndex;
        
        result = (PAIR *)RtlLookupElementGenericTable(&volumeInfo->redirectMap, &pair);
        
        if (result)
        {
            mapIndex = result->mapIndex;
        }
    }
    else
    {
        // 查找下一个可用的空闲扇区
        mapIndex = DPBitMap_FindNext(volumeInfo->bitMap_Free, volumeInfo->last_scan_index, FALSE);

        if (mapIndex != -1)
        {
            // lastScan = 当前用到的 + 1
            volumeInfo->last_scan_index = mapIndex + 1;

            // 标记为非空闲
            DPBitMap_Set(volumeInfo->bitMap_Free, mapIndex, TRUE);
            
            // 标记此扇区已被重定向(orgIndex)
            DPBitMap_Set(volumeInfo->bitMap_Redirect, orgIndex, TRUE);
            
            // 加入重定向列表
            {
                PAIR    pair;
                pair.orgIndex = orgIndex;
                pair.mapIndex = mapIndex;               
                RtlInsertElementGenericTable(&volumeInfo->redirectMap, &pair, sizeof(PAIR), NULL);
            }
        }
    }

    return mapIndex;
}
下面这个函数,整个磁盘还原的核心代码, 细看。
复制内容到剪贴板
代码:
// 模拟直接读写
NTSTATUS
handle_disk_request(
    PVOLUME_INFO volumeInfo,
    ULONG majorFunction,
    ULONGLONG logicOffset,  
    void * buff,
    ULONG length)
{
    NTSTATUS    status;
   
    // 当前操作的物理偏移
    ULONGLONG    physicalOffset = 0;
    ULONGLONG    sectorIndex;
    ULONGLONG    realIndex;
    ULONG        bytesPerSector = volumeInfo->bytesPerSector;
   
    // 以下几个参数为判断为处理的簇是连续的簇而设
    BOOLEAN        isFirstBlock = TRUE;
    ULONGLONG    prevIndex = -1;
    ULONGLONG    prevOffset = -1;
    PVOID        prevBuffer = NULL;
    ULONG        totalProcessBytes = 0;

    // 判断上次要处理的簇跟这次要处理的簇是否连续,连续了就一起处理,否则单独处理, 加快速度
    while (length)
    {
        sectorIndex = logicOffset / bytesPerSector;   
        
        if (IRP_MJ_READ == majorFunction)
        {
            realIndex = getRealSectorForRead(volumeInfo, sectorIndex);
        }
        else
        {
            // 传入后两个参数判断是否复制原簇数据
            realIndex = getRealSectorForWrite(volumeInfo, sectorIndex);
        }
        
        // 哥不是吧,硬盘太几巴小了, 老子不干了
        if (-1 == realIndex)
        {
            dprintf("no enough disk space\n");
            return STATUS_DISK_FULL;
        }
        
        physicalOffset = realIndex * bytesPerSector;

__reInit:        
        // 初始prevIndex
        if (isFirstBlock)
        {
            prevIndex = realIndex;
            prevOffset = physicalOffset;
            prevBuffer = buff;
            totalProcessBytes = bytesPerSector;
            
            isFirstBlock = FALSE;
            
            goto __next;
        }
        
        // 测试是否连继,  如果连续,跳到下个判断
        if (prevIndex == (realIndex - 1))
        {
            prevIndex = realIndex;
            totalProcessBytes += bytesPerSector;
            goto __next;
        }
        // 处理上次连续需要处理的簇, 重置isFirstBlock
        else
        {
            isFirstBlock = TRUE;
            status = fastFsdRequest(volumeInfo->LowerDevObj, majorFunction, volumeInfo->physicalStartingOffset + prevOffset,
                prevBuffer, totalProcessBytes, TRUE);

            // 重新初始化
            goto __reInit;
        }
__next:        
        // 最后一簇。。也就是完成了已经
        if (bytesPerSector >= length)
        {
            status = fastFsdRequest(volumeInfo->LowerDevObj, majorFunction, volumeInfo->physicalStartingOffset + prevOffset,
                prevBuffer, totalProcessBytes, TRUE);

            // 中断退出
            break;
        }
        
        // 跳到下一簇, 处理剩余的数据
        logicOffset += (ULONGLONG)bytesPerSector;
        buff = (char *)buff + bytesPerSector;
        length -= bytesPerSector;
    }
   
    return status;
}
磁盘读写的,注意vista以上的系统SL_FORCE_DIRECT_WRITE 的标志就行了
复制内容到剪贴板
代码:
NTSTATUS
fastFsdRequest(
    IN PDEVICE_OBJECT DeviceObject,
    ULONG majorFunction,
    IN LONGLONG ByteOffset,
    OUT PVOID Buffer,
    IN ULONG Length,               
    IN BOOLEAN Wait
    )
{
    PIRP                irp;
    IO_STATUS_BLOCK        iosb;
    KEVENT                event;
    NTSTATUS            status;
   
    //
    irp = IoBuildAsynchronousFsdRequest(majorFunction, DeviceObject,
        Buffer, Length, (PLARGE_INTEGER) &ByteOffset, &iosb);
    if (!irp) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    // vista 对直接磁盘写入进行了保护, 驱动操作需要在IRP的FLAGS加上SL_FORCE_DIRECT_WRITE标志
    /*
    If the SL_FORCE_DIRECT_WRITE flag is set, kernel-mode drivers can write to volume areas that they
    normally cannot write to because of direct write blocking. Direct write blocking was implemented for
    security reasons in Windows Vista and later operating systems. This flag is checked both at the file
    system layer and storage stack layer. For more
    information about direct write blocking, see Blocking Direct Write Operations to Volumes and Disks.
    The SL_FORCE_DIRECT_WRITE flag is available in Windows Vista and later versions of Windows.
    [url]http://msdn.microsoft.com/en-us/library/ms795960.aspx[/url]
    */
    if (IRP_MJ_WRITE == majorFunction)
    {
        IoGetNextIrpStackLocation(irp)->Flags |= SL_FORCE_DIRECT_WRITE;
    }
   
    if (Wait) {
        KeInitializeEvent(&event, NotificationEvent, FALSE);
        IoSetCompletionRoutine(irp, FltReadWriteSectorsCompletion,
            &event, TRUE, TRUE, TRUE);
   
        status = IoCallDriver(DeviceObject, irp);
        if (STATUS_PENDING == status) {
            KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
            status = iosb.Status;
        }
    } else {
        IoSetCompletionRoutine(irp, FltReadWriteSectorsCompletion,
            NULL, TRUE, TRUE, TRUE);
        irp->UserIosb = NULL;
        status = IoCallDriver(DeviceObject, irp);
    }
   
    if (!NT_SUCCESS(status))
    {
        dprintf("IoCallDriver 0x%x fail 0x%x\n", majorFunction, status);
    }
    return status;
}
其实这个代码还可以优化,比如回收线程,我没加上,想让驱动尽量减低与系统驱动的耦合度
如果想加上,可以Hook 下文件删除操作,分析下删除的文件,把这个文件占用的簇标记为空闲,就行了
复制内容到剪贴板
代码:
// 回收线程
VOID
flt_thread_reclaim (
    IN PVOID Context
    )
{
    ULONG    i = 0;
    ULONG    timeout = getTickCount();
    PFILTER_DEVICE_EXTENSION    device_extension = (PFILTER_DEVICE_EXTENSION)Context;

    while (!device_extension->terminate_thread)
    {
        if ((getTickCount() - timeout) > (1000 * 60))
        {
            for (i = 0; i < _countof(_volumeList); i++)
            {
                if (_volumeList[i].isProtect && _volumeList[i].isProtect && _volumeList[i].isDiskFull)
                {
                    // 回收
                    reclaimDiskSpace(&_volumeList[i]);
                }
               
            }
            
            timeout = getTickCount();
        }
    }

    PsTerminateSystemThread(STATUS_SUCCESS);
}
到此为止,基本上没啥说的了,其实上单点的磁盘还原都是这原理,不管你是硬的还原卡,还是软的磁盘过滤,最终都要用到以上这些东西。
相信会写驱动的人,只要知道原理,加上有充分的时间来看资料,跟写代码,都能写出来优秀的磁盘还原程序。程序不是最终目的,关键
写的过程中学到的东西,能认识到自己的不足,总结经验,所以程序写出来了,如果不是商业的,我支持开源。
复制内容到剪贴板
代码:
H:\diskRestore\diskfilter>dir /s
驱动器 H 中的卷没有标签。
卷的序列号是 949B-BAA3

H:\diskRestore\diskfilter 的目录

2010-03-19  09:56    <DIR>          .
2010-03-19  09:56    <DIR>          ..
2010-03-19  09:51    <DIR>          app
2010-03-19  09:51    <DIR>          Bin
2010-03-02  16:02              423 build.bat
2010-03-19  09:56          713,756 diskfilter.zip
2010-03-19  09:51    <DIR>          sys
              2 个文件        714,179 字节

H:\diskRestore\diskfilter\app 的目录

2010-03-19  09:51    <DIR>          .
2010-03-19  09:51    <DIR>          ..
2010-02-01  21:10            79,159 CoolControlsManager.cpp
2010-02-01  21:04            14,588 CoolControlsManager.h
2010-01-28  15:19            3,195 DiskfltInst.cpp
2010-03-02  09:25            5,019 DiskfltInst.dsp
2010-01-27  12:08              545 DiskfltInst.dsw
2010-01-27  12:08                18 DiskfltInst.h
2010-03-02  12:04            6,475 DiskfltInst.rc
2010-03-02  12:19            6,483 DiskfltInst.rc.ljp
2010-01-28  03:18            2,075 GetPassWordDlg.h
2010-02-01  21:31            1,727 LoginDlg.h
2010-03-02  16:06            25,852 maindlg.h
2009-11-01  10:53            11,779 md5.c
2009-11-01  00:46            3,607 md5.h
2009-12-04  00:17          106,301 ntdll.h
2003-06-19  18:23          301,770 ntdll.lib
2010-01-27  19:47    <DIR>          res
2010-01-28  06:06            1,237 resource.h
2010-01-28  01:46              290 stdafx.cpp
2010-02-01  21:28              943 stdafx.h
              18 个文件        571,063 字节

H:\diskRestore\diskfilter\app\res 的目录

2010-01-27  19:47    <DIR>          .
2010-01-27  19:47    <DIR>          ..
2010-01-28  03:23              830 DiskfltInst.exe.manifest
2010-01-26  11:41            29,926 DiskfltInst.ico
              2 个文件        30,756 字节

H:\diskRestore\diskfilter\Bin 的目录

2010-03-19  09:51    <DIR>          .
2010-03-19  09:51    <DIR>          ..
2010-03-19  09:51          108,032 Aod.exe
2009-09-27  17:04          283,648 upx.exe
              2 个文件        391,680 字节

H:\diskRestore\diskfilter\sys 的目录

2010-03-19  09:51    <DIR>          .
2010-03-19  09:51    <DIR>          ..
2010-02-23  11:49              869 breakin.c
2010-03-02  15:52            7,559 diskfilter.dsp
2010-01-15  11:20              315 diskfilter.dsw
2010-01-26  11:48            3,212 diskfilter.rc
2010-03-19  09:51            68,142 diskflt.c
2010-03-02  09:04            1,300 diskflt.h
2010-01-28  00:28            66,869 diskfltlib.c
2010-01-28  00:57              925 diskfltlib.h
2010-01-15  22:54            3,376 fatlbr.h
2009-12-03  15:02            7,993 GenericTable.h
2010-01-18  22:15            1,246 log.c
2010-01-18  21:56              235 log.h
2009-11-01  10:53            11,779 md5.c
2009-11-01  00:46            3,607 md5.h
2010-03-01  10:51    <DIR>          mempool
2009-12-03  15:17          283,238 ntifs.h
2009-01-24  16:23            3,881 ntimage.h
2010-01-13  19:02            1,044 readme.txt
2010-01-26  11:52    <DIR>          res
2010-01-26  11:48              547 resource.h
              18 个文件        466,137 字节

H:\diskRestore\diskfilter\sys\mempool 的目录

2010-03-01  10:51    <DIR>          .
2010-03-01  10:51    <DIR>          ..
2005-09-26  10:22            2,950 debug.c
2009-04-27  02:49            2,515 debug.h
2010-03-01  10:51          184,692 malloc.c
2009-05-14  11:48            19,834 malloc.h
2010-01-25  19:57            4,101 mempool.c
2009-11-20  12:43            1,139 mempool.h
              6 个文件        215,231 字节

H:\diskRestore\diskfilter\sys\res 的目录

2010-01-26  11:52    <DIR>          .
2010-01-26  11:52    <DIR>          ..
2010-01-26  11:41            29,926 disk.ico
2010-01-26  11:48            28,902 disk1.ico
2010-01-22  12:40            29,926 disk_5.ico
2010-01-26  11:18            26,694 disk_6.ico
2010-01-26  11:29            25,214 shell32 239.ico
              5 个文件        140,662 字节

    所列文件总数:
              53 个文件      2,529,708 字节
              20 个目录 13,904,281,600 可用字节
chinacrackinggroup@qq.com
update ccg_members set groupid = 28 where groupid = 8

TOP

diskfilter.zip (697.03 KB)
chinacrackinggroup@qq.com
update ccg_members set groupid = 28 where groupid = 8

TOP

这个是好东西....

TOP

我不是太清楚,是不是系统还原软件?像一键还原那种?
时不待我?光阴如梭!

TOP

好长呀,慢慢看

TOP

我怎么下载不了附件?
speedboy

TOP

http://www.dbgger.com
好久都打不开了。

TOP

发新话题
版块跳转 ...