编程实现根据NTFS文件系统定位文件在磁盘上的偏移地址

Pullarla

发布日期: 2018-12-13 12:53:58 浏览量: 1104
评分:
star star star star star star star star star_border star_border
*转载请注明来自write-bug.com

背景

之前在上一篇博文中 “NTFS文件系统介绍及文件定位” 介绍过 NTFS 的基础概念和常用格式介绍,同时还详细给出了使用 NTFS 定位磁盘文件的例子。现在,这篇文章讲解的就是,编程实现 NTFS 文件定位。也就是把之前手动定位全部改成编程实现,输入一个文件路径,就可以得到文件的大小和数据在磁盘上的偏移地址。

现在,就把实现的过程整理成文档,分享给大家。如果你之前没有了解过 NTFS 的相关概念,可以先阅读之前写的 “NTFS文件系统介绍及文件定位” 这篇文章。

实现原理

使用 NTFS 文件系统来定位文件,原理如下:

  • 首先打开磁盘,获取磁盘的分区引导扇区 DBR 中的数据。根据 DBR 的数据含义,从中获取扇区大小、簇大小以及\$MFT住文件记录的起始簇号

  • 根据根目录文件记录,一般处于 5 号文件,而且每个文件记录大小为 2 个扇区大小的知识前提。我们可以计算出,根目录文件记录的偏移地址。得到根目录文件记录偏移地址,就可以来到了磁盘文件根目录下了

  • 然后,我们根据文件记录头的数据格式,获取第一个属性的偏移位置,然后在获取属性的大小,以此计算出下一个属性的偏移位置。这样就可以对文件记录中的属性进行遍历了。我们按照 90H属性、A0H属性的处理顺序进行处理,获取文件或者目录的的$MFT参考号,然后继续跳转到下一个文件记录,重复处理 90H属性、A0H属性,获取文件或者目录的的$MFT参考号,直到获取到最终的文件

  • 这时,我们就可以根据 80H 属性获取文件数据的偏移位置

这样文件定位就结束了。其中,我们需要继续介绍 90H 属性的处理过程、A0H属性的处理过程以及使用 80H 属性 定位文件数据偏移的过程。

90H 属性的处理就是,直接扫描 90H 属性的数据,判断有没有我们要定位的文件或者目录的名称,若有,则获取该文件或者目录的\$MTF文件参考号;若没有,则不处理。

A0H 属性的处理过程是:

  • 我们首先获取 Data Run 在属性中的偏移,然后从偏移中获取 Data Run 数据,并跳转到 Data Run 指向的偏移地址

  • 跳转到 Data Run 指向的偏移地址,便到了 INDX 索引。然后,我们就扫描 INDX 的数据,判断有没有我们要定位的文件或者目录的名称。若没有,则继续退出。若有,则获取该文件或者目录的\$MTF文件参考号

  • 如果有多个Data Run,则重复上面操作,若没有,执行完毕后,就退出

根据 80H 属性定位文件数据:

  • 首先,我们先根据$MTF文件参考号来到文件记录处,并获取 80H 属性的偏移

  • 然后,判断 80H 属性是常驻属性还是非常驻属性。若是常驻属性,则直接在 80H 属性中获取文件数据。若为非常驻属性,则需要获取 80H 属性的 Data Run 数据,那么,Data Run 中执行的地址就是数据内容的存储地址

编码实现

从DBR中获取扇区大小、簇大小、\$MFT起始簇号

  1. // 从DBR中获取数据:每个扇区字节数、每个簇的扇区数、原文件$MFT的起始簇号
  2. BOOL GetDataFromDBR(HANDLE hFile, WORD &wSizeOfSector, BYTE &bSizeOfCluster, LARGE_INTEGER &liClusterNumberOfMFT)
  3. {
  4. // 获取扇区大小(2)、簇大小(1)、$MFT起始簇号(8)
  5. BYTE bBuffer[512] = { 0 };
  6. DWORD dwRead = 0;
  7. // 注意:数据读取的大小最小单位是扇区!!!
  8. ::SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
  9. ::ReadFile(hFile, bBuffer, 512, &dwRead, NULL);
  10. wSizeOfSector = MAKEWORD(bBuffer[0x0B], bBuffer[0x0C]);
  11. bSizeOfCluster = bBuffer[0x0D];
  12. liClusterNumberOfMFT.LowPart = MAKELONG(MAKEWORD(bBuffer[0x30], bBuffer[0x31]), MAKEWORD(bBuffer[0x32], bBuffer[0x33]));
  13. liClusterNumberOfMFT.HighPart = MAKELONG(MAKEWORD(bBuffer[0x34], bBuffer[0x35]), MAKEWORD(bBuffer[0x36], bBuffer[0x37]));
  14. return TRUE;
  15. }

文件定位

  1. // 定位文件
  2. BOOL LocationFile(HANDLE hFile, char *lpszFilePath, WORD wSizeOfSector, BYTE bSizeOfCluster, LARGE_INTEGER liMFTOffset, LARGE_INTEGER &liRootOffset)
  3. {
  4. BYTE bBuffer[1024] = { 0 };
  5. DWORD dwRead = 0;
  6. // 分割文件路径
  7. char szNewFile[MAX_PATH] = { 0 };
  8. ::lstrcpy(szNewFile, (lpszFilePath + 3));
  9. char szDelim[] = "\\";
  10. char *lpResult = strtok(szNewFile, szDelim);
  11. BYTE bUnicode[MAX_PATH] = { 0 };
  12. while (NULL != lpResult)
  13. {
  14. BOOL bFlag = FALSE;
  15. DWORD dwNameOffset = 0;
  16. // 将分割的目录转换成2字节表示的Unicode数据
  17. DWORD dwLen = ::lstrlen(lpResult);
  18. ::RtlZeroMemory(bUnicode, MAX_PATH);
  19. for (DWORD i = 0, j = 0; i < dwLen; i++)
  20. {
  21. bUnicode[j++] = lpResult[i];
  22. bUnicode[j++] = 0;
  23. }
  24. // 读取目录的数据,大小为1KB
  25. ::SetFilePointer(hFile, liRootOffset.LowPart, &liRootOffset.HighPart, FILE_BEGIN);
  26. ::ReadFile(hFile, bBuffer, 1024, &dwRead, NULL);
  27. // 获取第一个属性的偏移
  28. WORD wAttributeOffset MAKEWORD(bBuffer[0x14], bBuffer[0x15]);
  29. // 遍历文件目录的属性
  30. DWORD dwAttribute = 0;
  31. DWORD dwSizeOfAttribute = 0;
  32. while (TRUE)
  33. {
  34. if (bFlag)
  35. {
  36. break;
  37. }
  38. // 获取当前属性
  39. dwAttribute = MAKELONG(MAKEWORD(bBuffer[wAttributeOffset], bBuffer[wAttributeOffset + 1]),
  40. MAKEWORD(bBuffer[wAttributeOffset + 2], bBuffer[wAttributeOffset + 3]));
  41. // 判断属性
  42. if (0x90 == dwAttribute)
  43. {
  44. bFlag = HandleAttribute_90(bBuffer, wAttributeOffset, bUnicode, dwLen, liMFTOffset, liRootOffset);
  45. }
  46. else if (0xA0 == dwAttribute)
  47. {
  48. bFlag = HandleAttribute_A0(hFile, bBuffer, wSizeOfSector, bSizeOfCluster, wAttributeOffset, bUnicode, dwLen, liMFTOffset, liRootOffset);
  49. }
  50. else if (0xFFFFFFFF == dwAttribute)
  51. {
  52. bFlag = TRUE;
  53. break;
  54. }
  55. // 获取当前属性的大小
  56. dwSizeOfAttribute = MAKELONG(MAKEWORD(bBuffer[wAttributeOffset + 4], bBuffer[wAttributeOffset + 5]),
  57. MAKEWORD(bBuffer[wAttributeOffset + 6], bBuffer[wAttributeOffset + 7]));
  58. // 计算下一属性的偏移
  59. wAttributeOffset = wAttributeOffset + dwSizeOfAttribute;
  60. }
  61. // 继续分割目录
  62. lpResult = strtok(NULL, szDelim);
  63. }
  64. return TRUE;
  65. }

处理90H属性

  1. // 0x90属性的处理
  2. BOOL HandleAttribute_90(BYTE *lpBuffer, WORD wAttributeOffset, BYTE *lpUnicode, DWORD dwLen, LARGE_INTEGER liMFTOffset, LARGE_INTEGER &liRootOffset)
  3. {
  4. // 先遍历判断0x90属性里是否有此目录或文件(UNICODE)
  5. // 获取当前属性的大小
  6. DWORD dwSizeOfAttribute = MAKELONG(MAKEWORD(lpBuffer[wAttributeOffset + 4], lpBuffer[wAttributeOffset + 5]),
  7. MAKEWORD(lpBuffer[wAttributeOffset + 6], lpBuffer[wAttributeOffset + 7]));
  8. for (DWORD i = 0; i < dwSizeOfAttribute; i++)
  9. {
  10. if (CompareMemory(lpUnicode, (lpBuffer + wAttributeOffset + i), 2 * dwLen))
  11. {
  12. DWORD dwNameOffset = wAttributeOffset + i;
  13. // 计算文件号
  14. dwNameOffset = dwNameOffset / 8;
  15. dwNameOffset = 8 * dwNameOffset;
  16. dwNameOffset = dwNameOffset - 0x50;
  17. // 获取文件号(6)
  18. LARGE_INTEGER liNumberOfFile;
  19. liNumberOfFile.LowPart = MAKELONG(MAKEWORD(lpBuffer[dwNameOffset], lpBuffer[dwNameOffset + 1]),
  20. MAKEWORD(lpBuffer[dwNameOffset + 2], lpBuffer[dwNameOffset + 3]));
  21. liNumberOfFile.HighPart = MAKELONG(MAKEWORD(lpBuffer[dwNameOffset + 4], lpBuffer[dwNameOffset + 5]),
  22. 0);
  23. // 计算文件号的偏移,文件号是相对$MFT为偏移说的
  24. liRootOffset = liNumberOfFile;
  25. liRootOffset.QuadPart = liMFTOffset.QuadPart + liRootOffset.QuadPart * 0x400;
  26. return TRUE;
  27. }
  28. }
  29. // 读取Data Run List,去到索引处INDX遍历UNICODE,获取文件号
  30. return FALSE;
  31. }

处理A0H属性

  1. // 0xA0属性的处理
  2. BOOL HandleAttribute_A0(HANDLE hFile, BYTE *lpBuffer, WORD wSizeOfSector, BYTE bSizeOfCluster, WORD wAttributeOffset, BYTE *lpUnicode, DWORD dwLen, LARGE_INTEGER liMFTOffset, LARGE_INTEGER &liRootOffset)
  3. {
  4. // 读取Data Run List,去到索引处INDX遍历UNICODE,获取文件号
  5. DWORD dwCount = 0;
  6. LONGLONG llClusterOffet = 0;
  7. // 获取索引号的偏移
  8. WORD wIndxOffset = MAKEWORD(lpBuffer[wAttributeOffset + 0x20], lpBuffer[wAttributeOffset + 0x21]);
  9. // 读取Data Run List
  10. while (TRUE)
  11. {
  12. BYTE bTemp = lpBuffer[wAttributeOffset + wIndxOffset + dwCount];
  13. // 读取Data Run List,分解并计算Data Run中的信息
  14. BYTE bHi = bTemp >> 4;
  15. BYTE bLo = bTemp & 0x0F;
  16. if (0x0F == bHi || 0x0F == bLo || 0 == bHi || 0 == bLo)
  17. {
  18. break;
  19. }
  20. LARGE_INTEGER liDataRunSize, liDataRunOffset;
  21. liDataRunSize.QuadPart = 0;
  22. liDataRunOffset.QuadPart = 0;
  23. for (DWORD i = bLo; i > 0; i--)
  24. {
  25. liDataRunSize.QuadPart = liDataRunSize.QuadPart << 8;
  26. liDataRunSize.QuadPart = liDataRunSize.QuadPart | lpBuffer[wAttributeOffset + wIndxOffset + dwCount + i];
  27. }
  28. if (0 == llClusterOffet)
  29. {
  30. // 第一个Data Run
  31. for (DWORD i = bHi; i > 0; i--)
  32. {
  33. liDataRunOffset.QuadPart = liDataRunOffset.QuadPart << 8;
  34. liDataRunOffset.QuadPart = liDataRunOffset.QuadPart | lpBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + i];
  35. }
  36. }
  37. else
  38. {
  39. // 第二个及多个Data Run
  40. // 判断正负
  41. if (0 != (0x80 & lpBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + bHi]))
  42. {
  43. // 负整数
  44. for (DWORD i = bHi; i > 0; i--)
  45. {
  46. // 补码的原码=反码+1
  47. liDataRunOffset.QuadPart = liDataRunOffset.QuadPart << 8;
  48. liDataRunOffset.QuadPart = liDataRunOffset.QuadPart | (BYTE)(~lpBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + i]);
  49. }
  50. liDataRunOffset.QuadPart = liDataRunOffset.QuadPart + 1;
  51. liDataRunOffset.QuadPart = 0 - liDataRunOffset.QuadPart;
  52. }
  53. else
  54. {
  55. // 正整数
  56. for (DWORD i = bHi; i > 0; i--)
  57. {
  58. liDataRunOffset.QuadPart = liDataRunOffset.QuadPart << 8;
  59. liDataRunOffset.QuadPart = liDataRunOffset.QuadPart | lpBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + i];
  60. }
  61. }
  62. }
  63. // 注意加上上一个Data Run的逻辑簇号(第二个Data Run可能是正整数、也可能是负整数(补码表示), 可以根据最高位是否为1来判断, 若为1, 则是负整数, 否则是正整数)
  64. liDataRunOffset.QuadPart = llClusterOffet + liDataRunOffset.QuadPart;
  65. llClusterOffet = liDataRunOffset.QuadPart;
  66. // 去到索引处INDX遍历UNICODE,获取文件号
  67. LARGE_INTEGER liIndxOffset, liIndxSize;
  68. liIndxOffset.QuadPart = liDataRunOffset.QuadPart * bSizeOfCluster * wSizeOfSector;
  69. liIndxSize.QuadPart = liDataRunSize.QuadPart * bSizeOfCluster * wSizeOfSector;
  70. // 读取索引的数据,大小为1KB
  71. BYTE *lpBuf = new BYTE[liIndxSize.QuadPart];
  72. DWORD dwRead = 0;
  73. ::SetFilePointer(hFile, liIndxOffset.LowPart, &liIndxOffset.HighPart, FILE_BEGIN);
  74. ::ReadFile(hFile, lpBuf, liIndxSize.LowPart, &dwRead, NULL);
  75. // 遍历Unicode数据
  76. for (DWORD i = 0; i < liIndxSize.LowPart; i++)
  77. {
  78. if (CompareMemory(lpUnicode, (lpBuf + i), 2 * dwLen))
  79. {
  80. DWORD dwNameOffset = i;
  81. // 计算文件号
  82. dwNameOffset = dwNameOffset / 8;
  83. dwNameOffset = 8 * dwNameOffset;
  84. dwNameOffset = dwNameOffset - 0x50;
  85. // 获取文件号(6)
  86. LARGE_INTEGER liNumberOfFile;
  87. liNumberOfFile.LowPart = MAKELONG(MAKEWORD(lpBuf[dwNameOffset], lpBuf[dwNameOffset + 1]),
  88. MAKEWORD(lpBuf[dwNameOffset + 2], lpBuf[dwNameOffset + 3]));
  89. liNumberOfFile.HighPart = MAKELONG(MAKEWORD(lpBuf[dwNameOffset + 4], lpBuf[dwNameOffset + 5]),
  90. 0);
  91. // 计算文件号的偏移,文件号是相对$MFT为偏移说的
  92. liRootOffset = liNumberOfFile;
  93. liRootOffset.QuadPart = liMFTOffset.QuadPart + liRootOffset.QuadPart * 0x400;
  94. return TRUE;
  95. }
  96. }
  97. delete[]lpBuf;
  98. lpBuf = NULL;
  99. // 计算下一个Data Run List偏移
  100. dwCount = dwCount + bLo + bHi + 1;
  101. }
  102. return FALSE;
  103. }

读取文件数据内容偏移

  1. BOOL FileContentOffset(HANDLE hFile, WORD wSizeOfSector, BYTE bSizeOfCluster, LARGE_INTEGER liMFTOffset, LARGE_INTEGER liRootOffset)
  2. {
  3. BYTE bBuffer[1024] = { 0 };
  4. DWORD dwRead = 0;
  5. LARGE_INTEGER liContenOffset = liRootOffset;
  6. // 读取目录的数据,大小为1KB
  7. ::SetFilePointer(hFile, liRootOffset.LowPart, &liRootOffset.HighPart, FILE_BEGIN);
  8. ::ReadFile(hFile, bBuffer, 1024, &dwRead, NULL);
  9. // 获取第一个属性的偏移
  10. WORD wAttributeOffset MAKEWORD(bBuffer[0x14], bBuffer[0x15]);
  11. // 遍历文件目录的属性
  12. DWORD dwAttribute = 0;
  13. DWORD dwSizeOfAttribute = 0;
  14. while (TRUE)
  15. {
  16. // 获取当前属性
  17. dwAttribute = MAKELONG(MAKEWORD(bBuffer[wAttributeOffset], bBuffer[wAttributeOffset + 1]),
  18. MAKEWORD(bBuffer[wAttributeOffset + 2], bBuffer[wAttributeOffset + 3]));
  19. // 判断属性
  20. if (0x80 == dwAttribute)
  21. {
  22. // 读取偏移0x8出1字节,判断是否是常驻属性
  23. BYTE bFlag = bBuffer[wAttributeOffset + 0x8];
  24. if (0 == bFlag) // 常驻
  25. {
  26. // 读取偏移0x14出2字节,即是内容的偏移
  27. WORD wContenOffset = MAKEWORD(bBuffer[wAttributeOffset + 0x14], bBuffer[wAttributeOffset + 0x15]);
  28. liContenOffset.QuadPart = liContenOffset.QuadPart + wAttributeOffset + wContenOffset;
  29. printf("File Content Offset:0x%llx\n\n", liContenOffset.QuadPart);
  30. }
  31. else // 非常驻
  32. {
  33. // 读取偏移0x20出2字节,即是数据运行列表偏移
  34. DWORD dwCount = 0;
  35. LONGLONG llClusterOffet = 0;
  36. // 获取索引号的偏移
  37. WORD wIndxOffset = MAKEWORD(bBuffer[wAttributeOffset + 0x20], bBuffer[wAttributeOffset + 0x21]);
  38. // 读取Data Run List
  39. while (TRUE)
  40. {
  41. BYTE bTemp = bBuffer[wAttributeOffset + wIndxOffset + dwCount];
  42. // 读取Data Run List,分解并计算Data Run中的信息
  43. BYTE bHi = bTemp >> 4;
  44. BYTE bLo = bTemp & 0x0F;
  45. if (0x0F == bHi || 0x0F == bLo || 0 == bHi || 0 == bLo)
  46. {
  47. break;
  48. }
  49. LARGE_INTEGER liDataRunSize, liDataRunOffset;
  50. liDataRunSize.QuadPart = 0;
  51. liDataRunOffset.QuadPart = 0;
  52. for (DWORD i = bLo; i > 0; i--)
  53. {
  54. liDataRunSize.QuadPart = liDataRunSize.QuadPart << 8;
  55. liDataRunSize.QuadPart = liDataRunSize.QuadPart | bBuffer[wAttributeOffset + wIndxOffset + dwCount + i];
  56. }
  57. if (0 == llClusterOffet)
  58. {
  59. // 第一个Data Run
  60. for (DWORD i = bHi; i > 0; i--)
  61. {
  62. liDataRunOffset.QuadPart = liDataRunOffset.QuadPart << 8;
  63. liDataRunOffset.QuadPart = liDataRunOffset.QuadPart | bBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + i];
  64. }
  65. }
  66. else
  67. {
  68. // 第二个及多个Data Run
  69. // 判断正负
  70. if (0 != (0x80 & bBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + bHi]))
  71. {
  72. // 负整数
  73. for (DWORD i = bHi; i > 0; i--)
  74. {
  75. // 补码的原码=反码+1
  76. liDataRunOffset.QuadPart = liDataRunOffset.QuadPart << 8;
  77. liDataRunOffset.QuadPart = liDataRunOffset.QuadPart | (BYTE)(~bBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + i]);
  78. }
  79. liDataRunOffset.QuadPart = liDataRunOffset.QuadPart + 1;
  80. liDataRunOffset.QuadPart = 0 - liDataRunOffset.QuadPart;
  81. }
  82. else
  83. {
  84. // 正整数
  85. for (DWORD i = bHi; i > 0; i--)
  86. {
  87. liDataRunOffset.QuadPart = liDataRunOffset.QuadPart << 8;
  88. liDataRunOffset.QuadPart = liDataRunOffset.QuadPart | bBuffer[wAttributeOffset + wIndxOffset + dwCount + bLo + i];
  89. }
  90. }
  91. }
  92. // 注意加上上一个Data Run的逻辑簇号(第二个Data Run可能是正整数、也可能是负整数(补码表示), 可以根据最高位是否为1来判断, 若为1, 则是负整数, 否则是正整数)
  93. liDataRunOffset.QuadPart = llClusterOffet + liDataRunOffset.QuadPart;
  94. llClusterOffet = liDataRunOffset.QuadPart;
  95. // 显示逻辑簇号和大小
  96. liContenOffset.QuadPart = liDataRunOffset.QuadPart*wSizeOfSector*bSizeOfCluster;
  97. printf("File Content Offset:0x%llx\nFile Content Size:0x%llx\n", liContenOffset.QuadPart, (liDataRunSize.QuadPart*wSizeOfSector*bSizeOfCluster));
  98. // 计算下一个Data Run List偏移
  99. dwCount = dwCount + bLo + bHi + 1;
  100. }
  101. }
  102. }
  103. else if (0xFFFFFFFF == dwAttribute)
  104. {
  105. break;
  106. }
  107. // 获取当前属性的大小
  108. dwSizeOfAttribute = MAKELONG(MAKEWORD(bBuffer[wAttributeOffset + 4], bBuffer[wAttributeOffset + 5]),
  109. MAKEWORD(bBuffer[wAttributeOffset + 6], bBuffer[wAttributeOffset + 7]));
  110. // 计算下一属性的偏移
  111. wAttributeOffset = wAttributeOffset + dwSizeOfAttribute;
  112. }
  113. return TRUE;
  114. }

程序测试

我们在 main 函数中调用上述封装好的函数,定位文件:H:\NtfsTest\520.exe,成功定位文件:

总结

这个程序需要管理员权限运行,因为我们使用 CreateFile 函数打开磁盘,这一步操作,需要权限才可操作成功。

这个程序实现起来不是很难,关键是理解起来并不是那么容易。需要大家对 NTFS 要有足够的了解,熟练地使用掌握 80H属性、90H属性、A0H属性的数据含义,同时,还需要了解 Data Run 的分析。还是建议大家先阅读之前写的 “NTFS文件系统介绍及文件定位” 这篇文章,这里提到的知识点,都在这个程序中体现了。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download NTFS_FileLocation_Test.7z ( 155.14kb, 11次下载 )

发送私信

你在背后说我,因为我走在你前面

14
文章数
8
评论数
最近文章
eject