_ _ (_) | | __ ____ __ _ _ _ _ __ ___ _ __ _ __ ___ | |_ \ \ / /\ \/ /| || | | || '_ ` _ \ | '_ \ | '_ \ / _ \| __| \ V / > < | || |_| || | | | | || |_) |_ | | | || __/| |_ \_/ /_/\_\| | \__,_||_| |_| |_|| .__/(_)|_| |_| \___| \__| _/ | | | |__/ |_| /---------------------------------------------------------------------------------------\ |>...................[ 文件保险柜的简单实现--CryptFileDisk ]...................<| |>......................[ by coltor/vxjump.net ]......................<| |>......................[ 2010-04-12 ]......................<| \>...................... [ coltor@qq.com ] ...................... 32 MB size UCHAR bsDriveNumber; // Drive Number - not used UCHAR bsReserved1; // Reserved UCHAR bsBootSignature; // New Format Boot Signature - 0x29 ULONG bsVolumeID; // VolumeID - set to 0x12345678 CCHAR bsLabel[11]; // Label - set to RamDisk CCHAR bsFileSystemType[8];// File System Type - FAT12 or FAT16 CCHAR bsReserved2[448]; // Reserved UCHAR bsSig2[2]; // Originial Boot Signature - 0x55, 0xAA } BOOT_SECTOR, *PBOOT_SECTOR; [3.1] -- 创建file_container format的实现就是往新创建的一个文件中,写入bootSector信息和加密信息KEY值. 在这里我将KEY值写入保留扇区,其中 KEY = Encrypt(VolumePassword.text,Volume.Length); CryptFormatVolume()函数在实现的时候,本来也想引用上述bootsector结构进行 填写的,但是在实际测试的过程中,发现编译器会对变量进行补0对齐,这样就无法精确 定位到每个byte了,所以最终还是决定每个byte自己填入. //往file中写入DBR和加密信息 BOOL CryptFormatVolume(char* KEY,long VolumeSize,HANDLE hfile) { DWORD nBytes; BOOL bRet; USHORT fatEntries; USHORT fatSectorCnt; ULONG TotalSectors; char* bootSector = (char*)malloc( 512 ); memset(bootSector,0,512); //jmp XX nop 3 bytes bootSector[0]=0xEB; bootSector[1]=0x3c; bootSector[2]=0x90; //OEM name string 8 bytes bootSector[3]=0x4d; bootSector[4]=0x53; bootSector[5]=0x44; bootSector[6]=0x4f; bootSector[7]=0x53; bootSector[8]=0x35; bootSector[9]=0x2e; bootSector[10]=0x30; // bytesPerSector 512 = 0x0200 bootSector[11]=0x00; bootSector[12]=0x02; // sectorPerCluster // 需根据VolumeSize进行判断. // FAT16 为 0x01--32MB // FAT16 为 0x02--64MB // FAT16 为 0x04--128MB // FAT16 为 0x08--256MB // FAT16 为 0x10--400MB // FAT16 为 0x10--512MB // FAT16 为 0x20--1024MB bootSector[13]=0x04; // ReservedSectorCount // FAT16 as 0x0004 // FAT12 as 0x0008?? // default as 0x08 bootSector[14]=0x04;// 64MB此处为0x02 128MB此处为0x08 bootSector[15]=0x00; // NumberOfFats bootSector[16]=0x02; //RootEntryCount 512 = 0x0200 bootSector[17]=0x00; bootSector[18]=0x02; // TotalSector16 2 bytes need to discuss bootSector[19]=0x00; bootSector[20]=0x00; // MediaType 0xF8 bootSector[21]=0xF8; // FAT 所占的扇区数 // 32MB -- 0x00FE // 64MB -- 0x00FF // 128MB --0x0100 // fatEntries需要计算的... fatEntries = (VolumeSize/512) - 0x04 - 0x0200/0x10/0x02 + 0x02 ; fatSectorCnt = (fatEntries * 2 + 511) / 512; fatEntries -= fatSectorCnt; fatSectorCnt = (fatEntries * 2 + 511) / 512; fatSectorCnt = fatSectorCnt+1; //bootSector[22]=0xFE; //bootSector[23]=0x00; bootSector[22] = (char)fatSectorCnt; bootSector[23] = (char)(fatSectorCnt>>8); // SectorPerTrack bootSector[24]=0x20; bootSector[25]=0x00; // Number of Heads bootSector[26]=0x02; bootSector[27]=0x00; // Hidden Sectors bootSector[28]=0x01; bootSector[29]=0x00; bootSector[30]=0x00; bootSector[31]=0x00; // TotalSector32 4 bytes // 该卷的总扇区数(32-bit) // 对于FAT12/FAT16,如果总扇区数目>=0x10000的话 // 此域就是总扇区数,同事TotalSector16 为 0; TotalSectors = (ULONG)(VolumeSize/(ULONG)512); bootSector[32]=(char)TotalSectors; bootSector[33]=(char)(TotalSectors>>8); bootSector[34]=(char)(TotalSectors>>16);// 64MB时 此处为0x02 128MB的 为0x04 bootSector[35]=(char)(TotalSectors>>24); // DriverNumber // FloopyDisk : 0x00 // HardDisk : 0x80 bootSector[36]=0x80; // Reserved1 bootSector[37]=0x00; // BootSig // 扩展引导标记(0x29),用于指明此后的三个域可用 bootSector[38]=0x29; // Volume ID // 此域和Volume Lab 一起可以用来检测磁盘是否正确 // FAT文件系统可以用此判断可移动是否正确,此域往往 // 是由时间和日期组成的一个32位值 bootSector[39]=0xD9; bootSector[40]=0x6F; bootSector[41]=0x0B; bootSector[42]=0xB4; // Volume Lable bootSector[43]=0x4E; bootSector[44]=0x4F; bootSector[45]=0x20; bootSector[46]=0x4E; bootSector[47]=0x41; bootSector[48]=0x4D; bootSector[49]=0x45; bootSector[50]=0x20; bootSector[51]=0x20; bootSector[52]=0x20; bootSector[53]=0x20; // File System Type FAT16 bootSector[54]=0x46; bootSector[55]=0x41; bootSector[56]=0x54; bootSector[57]=0x31; bootSector[58]=0x36; bootSector[59]=0x20; bootSector[60]=0x20; bootSector[61]=0x20; // 448 bytes Reserved // 写入KEY值在保留扇区中. // 0x55 0xAA bootSector[510] = 0x55; bootSector[511] = 0xAA; //write the Information into file bRet=WriteFile( hfile, bootSector, 512, &nBytes, NULL); if(bRet != TRUE) { printf("WriteFile Error!"); } return bRet; } file_container实现函数: BOOL CryptFileContainer(long file_size,char file_name,char* KEY) { // 根据file_name创建一个file_size大小的空白文件 BOOL bRet = FALSE; HANDLE hfile = CreateFile( file_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_FLAG_NO_BUFFERING, NULL ); if (hfile != INVALID_HANDLE_VALUE) { SetFilePointer(hfile,file_size,NULL,FILE_BEGIN); SetEndOfFile(hfile); file_size = file_size | 0x80000000 ; SetFilePointer( hfile, file_size, NULL, FILE_CURRENT ); bRet=CryptFormatVolume(KEY,long file_size,hfile); if(bRet != TRUE) { CloseHandle(hfile); printf("FormatFile Error!"); } return bRet; } else { printf("CreateFile Error!"); return FALSE; } } [0x04] .杂谈磁盘加密 上述代码和思路实现的磁盘加密是很简单的,因为是应用层谈不上磁盘数据加密,也 就只是给磁盘加把"锁"吧.要真正做到磁盘数据加密是要处理驱动层的IRP_MJ_WRITE和 IRP_MJ_READ.下面看看两种实现方案: (一).实现磁盘数据加密---Format操作 by Windows 实现磁盘的数据加密,关键就是要处理好驱动层的IRP_MJ_WRITE和IRP_MJ_READ. 这个时候文件mount成磁盘,(但是Format操作是由Windows来完成的~), 往磁盘Write数据的时候,实现数据的加密,往磁盘Read数据的时候,实现数据的解密.具体实现如下: 1.用户输入UserPassword,可以初始化这个UserPassword为一个KeyNum,然后应用层将 这个KeyNum传入到驱动层. 2.加密操作的实现:处理IRP_MJ_WRITE,具体如下: 写入操作,在驱动层调用的是 NTSTATUS ZwWriteFile( IN HANDLE FileHandle, IN HANDLE Event OPTIONAL, IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, IN PVOID ApcContext OPTIONAL, OUT PIO_STATUS_BLOCK IoStatusBlock, IN PVOID Buffer, IN ULONG Length, IN PLARGE_INTEGER ByteOffset OPTIONAL, IN PULONG Key OPTIONAL ); 需要处理的加密信息就是Buffer这块. 先获得BUffer,使用MmGetSystemAddressForMdlSafe()函数 然后可以调用Encrypt()函数加密Buffer,然后调用ZwWriteFile()再写入磁盘中. 3.解密操作的实现,处理IRP_MJ_READ.. 读取数据的时候,在驱动层调用ZwReadFile(). NTSTATUS ZwReadFile( IN HANDLE FileHandle, IN HANDLE Event OPTIONAL, IN PIO_APC_ROUTINE ApcRoutine OPTIONAL, IN PVOID ApcContext OPTIONAL, OUT PIO_STATUS_BLOCK IoStatusBlock, OUT PVOID Buffer, IN ULONG Length, IN PLARGE_INTEGER ByteOffset OPTIONAL, IN PULONG Key OPTIONAL ); 读取出Buffer,不过这个时候的Buffer是已经加密了的信息. 调用Decrypt()函数,将Buffer解密之. 然后将Buffer写入SystemBuffer中,IRP返回. 我这个文件保险柜的Demo就是基于这个思想的,参考了CrossCrypt这个开源的磁盘加 密src.(这个src的详细分析,过两天贴上). 使用AES加/解密数据,但是为了仿写QQ文件保险柜,追求较好的用户体验,Format 操作由我自己来完成了,这个实现可以参考Mark大叔的,<<调用未公开的函数实现自己的磁盘格式化>>, 由于是未公开的,所以兼容性比较差~.Demo的大概介绍如下: 1. 处理Button"新建文件保险柜".调用Mount函数,将文件Mount成磁盘,然后再调用Format 函数,将磁盘格式化.这样就能打开磁盘和写入/读取数据了. 2. 处理Button"打开保险柜",也是调用Mount函数,如果用户输入的密码是错误的话, 那么bootsector那块信息还是加密了的数据, 还是不能正确被Windows识别,仍然需 要进行格式化操作,这个时候调用GetDiskFreeSpaceEx(),来获取这个磁盘的相关信息, 如果密码正确,那么不需要Windows进行格式化操作,GetDiskFreeSpaceEx()获取成功, 那么打开磁盘即可.否则GetDiskFreeSpaceEx()获取失败,那么就是密码错误,这个时 候提示用户再次输入密码,然后调用Umount()函数. (QQ文件保险柜也是这么做的,不过效率比我的更高~ 呵呵,上面说的有点乱,大家可以参考下源码.) 3. 处理Button"关闭保险柜",选择你要关闭的盘符,然后调用Umount函数即可了. (二).实现磁盘数据加密---Format操作 by Windows 这个思路,其实就是综合了上面思路和思路(一).Truecrypt的file_container也是这么实现的. QQ文件保险柜是从Truecrypt抠出来的,当然也是这个思想,具体如下: 1.用户输入UserPassword,可以初始化这个UserPassword为一个KeyNum, 2.根据文件的大小,初始化bootsector信息.这个时候的bootsector信息还保存在bootsectorBuffer中.并没有写入文件中. 3.1 应用层: KeyNum和bootsectorBuffer根据Encrypt()函数,生成EncryptBootsector,然后将EncryptBootsector写入file中. 3.2 驱动层: 和思路(二)一致,将这个KeyNum传入到驱动层.然后处理IRP_MJ_WRITE和IRP_MJ_READ. 这里必须保证.驱动层和应用层的加/解密函数是同一套函数! [0x05].参考资料 FileDisk源码:http://www.acc.umu.se/~bosse/ Truecrypt :http://www.truecrypt.org/ [0x06].附录 下面对FAT12/16适用. +--------------+---------+----------+---------------------------------------------+ + 名称 + offset + 大小 + 描述 + +--------------+---------+----------+---------------------------------------------+ + jmpBoot + 0 + 3 + 跳转指令,指向启动代码,通常为0xEB,0x3C,0x90 + +--------------+---------+----------+---------------------------------------------+ + OEMName + 3 + 8 + 在FAT16/12下,为"MSDOS5.0",这个可以自行更改 + +--------------+---------+----------+---------------------------------------------+ + BytePerSec + 11 + 2 + 每扇区的字节数,一般设为512,即0x0200 + +--------------+---------+----------+---------------------------------------------+ + + + + 每簇(cluster)的扇区数,其值必须为2^n (n>=0) + + BytePerClu + 13 + 1 + 而簇的大小能影响系统对磁盘容量的识别,所以不+ + + + + 同容量的磁盘,它们cluster的大小也是不一样的.+ +--------------+---------+----------+---------------------------------------------+ + RsvdSecCount + 14 + 2 + 保留区中保留区的数目,FAT12/16的保留扇区为 1,实际测试情况defaut as 0x08+ +--------------+---------+----------+---------------------------------------------+ + NumFATs + 16 + 1 + 此卷中FAT表的份数,任何FAT格式此域都建议为 2+ +--------------+---------+----------+---------------------------------------------+ + + + + 对于FAT12/16此域包含根目录中的项目数,每个项+ +RootEntryCount+ 17 + 2 + FAT12/16应该取值为512. + + + + + + +--------------+---------+----------+---------------------------------------------+ + TotalSec16 + 19 + 2 + 早期16bit的总扇区数,在这里设置为0就行了 + +--------------+---------+----------+---------------------------------------------+ + Media + 21 + 1 + 因为是"固定"的存储介质,在这里填写0xF8 + +--------------+---------+----------+---------------------------------------------+ + + + + FAT12/16中FAT表所占的扇区数,根据VolumeSize,+ + FATsz16 + 22 + 2 + BytePerSec,BytePerClu等值进行计算,可以参见下+ + + + + 面公式(1). + +--------------+---------+----------+---------------------------------------------+ +SectorPerTrack+ 24 + 2 +每磁道的扇区数,用于BIOS中断int 13h,这写0x0020+ +--------------+---------+----------+---------------------------------------------+ + NumHeads + 26 + 2 +磁头数,同样用于BIOS中断int 13h,这里写0x0002 + +--------------+---------+----------+---------------------------------------------+ + + + +在此FAT分区之前,所隐藏的扇区数,调用BIOS 0x13 + + HiddenSector + 28 + 4 +中断得到此数值,而具体使用什么由操作系统决定, + + + + +在这里我们填写0x00000001. + +--------------+---------+----------+---------------------------------------------+ + TotalSec32 + 32 + 4 +该卷的总扇区数目,=VolumeSize/BytePerSector + +--------------+---------+----------+---------------------------------------------+ + DriverNumber + 36 + 1 +0x00为软盘,0x80为硬盘.用于0x13中断获得参数 + +--------------+---------+----------+---------------------------------------------+ + Reserved1 + 37 + 1 + 保留(供NT使用) + +--------------+---------+----------+---------------------------------------------+ + BootSig + 38 + 1 + 扩展引导标记(0x29),用于指明后面三个域可用 + +--------------+---------+----------+---------------------------------------------+ + VolID + 39 + 4 + 卷序列号,与下面的VolLab一起用来检测磁盘 + +--------------+---------+----------+---------------------------------------------+ + VolLab + 43 + 11 +磁盘卷标, 此域必须与根目录中11字节长卷标一致 + +--------------+---------+----------+---------------------------------------------+ + FileSysType + 54 + 8 + 在这里我们填写"FAT16" + +--------------+---------+----------+---------------------------------------------+ + Reserved2 + 62 + 48 + 保留部分,可以留白,这里我们用来写入加密信息.+ +--------------+---------+----------+---------------------------------------------+ + EndSig + 510 + 2 + 磁盘结束标志,0x55,0xAA. + +--------------+---------+----------+---------------------------------------------+ 公式(1):引自Ramdisk.c,对于FAT16,如下: fatEntries取512. fatType = 16; fatSectorCnt = (fatEntries * 2 + 511) / 512; fatEntries -= fatSectorCnt; fatSectorCnt = (fatEntries * 2 + 511) / 512; 注 :源码及bin下载见 http://www.vxjump.net/files/a_page/4/code1.htm