好久没写博客了,近期看了下DeviceIoControl  关于磁盘的应用,特记一文,以备久后查阅。

首先介绍下,文件在磁盘的存储结构(详细能够到网上查询NTFS文件系统相关的教程后者数据恢复方面教程的介绍)。以下介绍的仅与此文相关。

文件属性(头):

(Ps: 截图摘自[数据重现文件系统原理精解与数据恢复最佳实践].(马林))

然后我们须要认识两个结构:

typedef struct {
LARGE_INTEGER StartingVcn;
} STARTING_VCN_INPUT_BUFFER, *PSTARTING_VCN_INPUT_BUFFER;
typedef struct RETRIEVAL_POINTERS_BUFFER {
DWORD ExtentCount;
LARGE_INTEGER StartingVcn;
struct {
LARGE_INTEGER NextVcn;
LARGE_INTEGER Lcn;
} Extents[1];
} RETRIEVAL_POINTERS_BUFFER, *PRETRIEVAL_POINTERS_BUFFER;

通过使用參数

FSCTL_GET_RETRIEVAL_POINTERS  调用函数DeviceIoControl 我们就能够获得文件在磁盘中的定位信息。
方式例如以下:
DeviceIoControl(
(HANDLE) hDevice, // handle to volume
FSCTL_GET_RETRIEVAL_POINTERS, // dwIoControlCode
(LPVOID) lpInBuffer, // input buffer
(DWORD) nInBufferSize, // size of input buffer
(LPVOID) lpOutBuffer, // output buffer
(DWORD) nOutBufferSize, // size of output buffer
(LPDWORD) lpBytesReturned, // number of bytes returned
(LPOVERLAPPED) lpOverlapped ); // OVERLAPPED structure

函数第三个參数相应上述第一个结构,此结构比較简单,须要传入文件的事实上Vcn号,这里填入 0 就可以 (StartingVcn.QuadPart = 0)。

第二个结构相对复杂些:

由上述介绍能够知道,文件(相对较大的文件)在磁盘中是以簇流(连续的簇)的形式存放的。结构体中 ExtentCount 即表示簇流的个数
StartingVcn 第一个簇流的起始Vcn号, 而每一个Extents都包括一个NextVcn号和一个Lcn,Lcn即表示本簇流的起始Lcn,NextVcn是用来推断下一个簇流的位置(通过NextVcn也能够的到上一个簇流的大小)
以下是msdn的解释:
NextVcn
The VCN at which the next extent begins. This value minus either StartingVcn (for the first Extents array member) or the NextVcn of the previous member of the array (for all other Extents array members) is the length, in clusters, of the current extent. The length is an input to the FSCTL_MOVE_FILE operation.

对于第一个簇流,NextVcn 减去 StartingVcn 即得到第一个簇流的大小,而对于兴许的簇流,使用此NextVcn 减去上一个簇流的NextVcn即上一个簇流的大小。

所以依据此信息,我们可以得到文件在磁盘中簇流链的信息,从而定位文件,从磁盘中直接读取文件,详细代码例如以下(基本參考网上已有代码):

//////////////////////////////////////////////////////////////////////////
/// ReadFileFromSectors.cpp #include <windows.h>
#include <WinIoCtl.h>
#include <stdio.h> ULONGLONG *GetFileClusters(PCHAR lpFilename, ULONG *ClusterSize, ULONG *ClusterCount, ULONG *FileSize)
{
HANDLE hFile = NULL;
//磁盘基本信息变量定义
ULONG SectorsPerCluster;
ULONG BytesPerSector; STARTING_VCN_INPUT_BUFFER InVcvBuffer; //输入的開始vcn号
PRETRIEVAL_POINTERS_BUFFER pOutFileBuffer; //输出的结果缓冲区
ULONG OutFileSize; LARGE_INTEGER PreVcn,Lcn; ULONGLONG *Clusters = NULL;
BOOLEAN bDeviceIoResult = FALSE; //逻辑路径(卷号)
char DriverPath[8];
memset(DriverPath, 0, sizeof(DriverPath));
DriverPath[0] = lpFilename[0];
DriverPath[1] = ':';
DriverPath[2] = 0;
GetDiskFreeSpace(DriverPath, &SectorsPerCluster, &BytesPerSector, NULL, NULL);
*ClusterSize = SectorsPerCluster * BytesPerSector; //定位文件
hFile = CreateFile(lpFilename,
//GENERIC_READ | GENERIC_WRITE,
FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
0,
0);
if(hFile == INVALID_HANDLE_VALUE)
{
printf("GetFileClusters(): Failed to open file %s ...\n",lpFilename);
return 0;
}
*FileSize = GetFileSize(hFile, NULL);
//初始化IO相关參数
DWORD dwRead, Cls, CnCount, r;
OutFileSize = sizeof(RETRIEVAL_POINTERS_BUFFER) + (*FileSize / *ClusterSize) * sizeof(pOutFileBuffer->Extents); //个人觉得这个结果应该比实际所需的缓冲区大
pOutFileBuffer = (PRETRIEVAL_POINTERS_BUFFER)malloc(OutFileSize);
InVcvBuffer.StartingVcn.QuadPart = 0;
//调用函数后去信息
bDeviceIoResult = DeviceIoControl(hFile,
FSCTL_GET_RETRIEVAL_POINTERS,
&InVcvBuffer,
sizeof(InVcvBuffer),
pOutFileBuffer,
OutFileSize,
&dwRead,
NULL);
if(!bDeviceIoResult)
{
printf("GetFileClusters(): Failed to call DeviceIocontrol with paramter FSCTL_GET_RETRIEVAL_POINTERS...\n|---errorcode = %d\n",GetLastError());
CloseHandle(hFile);
return 0;
} *ClusterCount = (*FileSize + *ClusterSize -1) / *ClusterSize; //Cluster 数组的大小,一个簇占一个元素
Clusters = (ULONGLONG *)malloc(*ClusterCount * sizeof(ULONGLONG)); //分配簇数组空间 //開始遍历返回结果
PreVcn = pOutFileBuffer->StartingVcn;
for(r=0,Cls=0; r<pOutFileBuffer->ExtentCount; r++) //ExtentCount 簇流的个数(每一个簇流中有几个连续的簇)
{
Lcn = pOutFileBuffer->Extents[r].Lcn;
//簇流中连续簇的个数等于 下一个簇流的起始 Vcn 号 减去 上一个 簇流的 起始 Vcn号
for(CnCount = (ULONG)(pOutFileBuffer->Extents[r].NextVcn.QuadPart - PreVcn.QuadPart); CnCount; CnCount--,Cls++,Lcn.QuadPart++)
{
Clusters[Cls] = Lcn.QuadPart; //保存每一个簇流中簇的 Lcn 号
} PreVcn = pOutFileBuffer->Extents[r].NextVcn;
} free(pOutFileBuffer);
CloseHandle(hFile);
return Clusters;
} int ReadFileFromSectors(PCHAR lpFileName, PCHAR pDstFileName)
{
ULONG ClusterSize, BlockSize, ClusterCount, FileSize;
ULONGLONG *Clusters = NULL;
DWORD dwReads,dwWrites;
HANDLE hDriver, hFile;
ULONG SectorsPerCluster, BytesPerSector, r;
PVOID FileBuff; //存放从扇区中读取的数据
LARGE_INTEGER offset;
char DrivePath[10];
Clusters = GetFileClusters(lpFileName, &ClusterSize, &ClusterCount, &FileSize);
if(Clusters == NULL)
{
printf("ReadFileFromSectors(): Failed to GetFileClusters ...\n|---errrorcode = %d\n",GetLastError());
return 0;
}
DrivePath[0] = '\\';
DrivePath[1] = '\\';
DrivePath[2] = '.';
DrivePath[3] = '\\';
DrivePath[4] = lpFileName[0];
DrivePath[5] = ':';
DrivePath[6] = 0;
//打开磁盘卷
hDriver = CreateFile(DrivePath,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
0,
NULL);
if(hDriver == INVALID_HANDLE_VALUE)
{
printf("ReadFileFromSectors(): Failed to CreateFile %s ...\n|---errrorcode = %d\n",DrivePath,GetLastError());
return 0;
}
//存放读出的文件
hFile = CreateFile(pDstFileName, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, 0);
if(hFile == INVALID_HANDLE_VALUE)
{
printf("ReadFileFromSectors(): Failed to CreateFile %s ...\n|---errrorcode = %d\n",pDstFileName,GetLastError());
return 0;
} FileBuff = malloc(ClusterSize);
//開始读扇区文件内容
for (r=0; r<ClusterCount; r++, FileSize -= BlockSize)
{
offset.QuadPart = ClusterSize * Clusters[r]; //确定每一个簇的偏移
SetFilePointer(hDriver, offset.LowPart, &offset.HighPart, FILE_BEGIN);
ReadFile(hDriver, FileBuff, ClusterSize, &dwReads, NULL); //每次读一个簇的大小
BlockSize = FileSize < ClusterSize ? FileSize : ClusterSize;
WriteFile(hFile, FileBuff, BlockSize, &dwWrites, NULL); //将读取的文件保存起来
}
free(FileBuff);
free(Clusters);
CloseHandle(hFile);
CloseHandle(hDriver);
} //--------------------------------------------------------------------
//
// Usage
//
// Tell user how to use the program.
//
//--------------------------------------------------------------------
int Usage( CHAR *ProgramName )
{
printf("\nusage: %s -f srcfile dstfile ...\n", ProgramName );
return -1;
} int main(int argc, char *argv[])
{
if(argc != 4)
{
Usage(argv[0]);
return 0;
} //读文件
if(strcmp(argv[1], "-f") == 0)
{
ReadFileFromSectors(argv[2], argv[3]);
}
else
{
Usage(argv[0]);
} system("pause");
return 1;
}

编译程序,以管理员权限执行,測试结果例如以下:


Ps: 此方法还有些弊端,文件不能使加密、压缩的文件,并且文件必须是很驻的(相对大些的文件即要有自己的簇),对于常驻的(小文件),文件内容直接存放到文件的MFT中,此方法是读不到的。

DeviceIoControl 直接从磁盘扇区读文件的更多相关文章

  1. 【linux相识相知】磁盘分区及文件系统管理详解

    磁盘,提供持久的数据存储,它不像我们的内存,如果突然断电了,在内存中的数据一般都会被丢掉了,内存中的数据在保存的时候,会被写到硬盘里面,磁盘也是一种I/O设备. 我们都知道磁盘分区完成之后,还要进行格 ...

  2. VC++信息安全编程(13)Windows2000/xp/vista/7磁盘扇区读写技术

    有些时候,我们读取磁盘文件,会被hook.我们读到的可能并非实际的文件. 我们直接读取磁盘扇区获取数据. 实现磁盘数据的读写,不依赖WindowsAPI. [cpp] view plaincopy v ...

  3. Linux:Day7(下) 磁盘管理、文件系统管理

    Linux入门 Linux系统管理: 磁盘管理.文件系统管理 RAID基本原理.LVM2 网络管理:TCP/IP协议.Linux网络属性配置 程序包管理:rpm,yum 进程管理:htop,glanc ...

  4. Linux中硬盘物理扇区与文件系统文件对应关系(转)

    1               概述 系统读写文件过程中,如下面内核打印信息,报告读写某个扇区错误.那么我们如何能够通过sector找到读写哪个文件错误? kernel: end_request: I ...

  5. Python: 读文件,写文件

    读写文件是最常见的IO操作.Python内置了读写文件的函数. 读写文件前,我们先了解一下,在磁盘上读写文件的功能都是有操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求 ...

  6. C++写和读文件

    1.写: /*C++写文件和读文件*/ #include <stdio.h> #include <stdlib.h> int main() { FILE * fp; fp = ...

  7. GoLang几种读文件方式的比较

    GoLang提供了很多读文件的方式,一般来说常用的有三种.使用Read加上buffer,使用bufio库和ioutil 库. 那他们的效率如何呢?用一个简单的程序来评测一下: package main ...

  8. Python之路 day2 按行读文件

    #1. 最基本的读文件方法: # File: readline-example-1.py file = open("sample.txt") while 1: line = fil ...

  9. java的读文件操作

    java读取文件内容,可以作如下理解: 首先获得一个文件句柄,File file = new File():file即为文件句柄.两人之间联通电话网络了,就可以开始打电话了. 通过这条线路读取甲方的信 ...

随机推荐

  1. HDU 1142 A Walk Through the Forest(dijkstra+记忆化DFS)

    题意: 给你一个图,找最短路.但是有个非一般的的条件:如果a,b之间有路,且你选择要走这条路,那么必须保证a到终点的所有路都小于b到终点的一条路.问满足这样的路径条数 有多少,噶呜~~题意是搜了解题报 ...

  2. 此三层非彼三层——MVC&amp;UBD

    学习了三年编程了,到如今这个阶段,開始接触架构,開始认识架构,怎样设计一个程序的结构,学名称"架构模式"(architectural pattern).个人经历告诉我这在编程中是一 ...

  3. ThinkPHP多应用/项目配置技巧(使用同一配置文件)--(十六)

    原文:ThinkPHP多应用/项目配置技巧(使用同一配置文件)--(十六) ThinkPHP多应用配置技巧(没有使用分组,这是通过入口文件产生的Home.Admin)----很实用! 比如:现在有Ho ...

  4. SilkTest高级进阶系列10 – bitmap工具bitview

    SilkTest 提供了一个bitmap的工具,它可以帮助我们捕捉,比较bitmap文件.该bitmap工具的位置是~/SilkTest/bitview.exe 运行该工具后,你会发现它提供了捕捉控件 ...

  5. HashTable的数组和连接两种实现方法(Java版本号)

    1.散列表的接口类 package cn.usst.hashtable; /** * 散列表的接口类 * @author G-Xia * */ public interface HashTable { ...

  6. object-c编程tips-timer

    object-c定时器 object-c定时器会自己主动retain当前的使用者,假设不注意调用invalidate,则非常easy引起循环引用导致内存泄露.以下的思路提供了一套还算可行的解决方式. ...

  7. 黑马程序员:Java基础总结----类加载器

    黑马程序员:Java基础总结 类加载器   ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! 类加载器 Java虚拟机中可以安装多个类加载器,系统默认三个主要类加载器,每个 ...

  8. 如果在线显示php源代码

    原文:如果在线显示php源代码 通过php提供的函数highlight_file和highlight_string实现

  9. BroadcastReceiver总结

    一.工程整体图 二.activity_main.xml <LinearLayout xmlns:android="http://schemas.android.com/apk/res/ ...

  10. Deep Learning(深度学习) 学习笔记(四)

    神经概率语言模型,内容分为三块:问题,模型与准则,实验结果.[此节内容未完待续...] 1,语言模型问题 语言模型问题就是给定一个语言词典包括v个单词,对一个字串做出二元推断,推断其是否符合该语言表达 ...