商城首页欢迎来到中国正版软件门户

您的位置:首页 >C#读写NTFS备用数据流方法详解

C#读写NTFS备用数据流方法详解

  发布于2026-02-27 阅读(0)

扫一扫,手机访问

C#中ADS需通过P/Invoke调用Win32 API操作,System.IO类库完全忽略ADS;读写必须用CreateFile(带FILE_FLAG_BACKUP_SEMANTICS)、ReadFile、WriteFile等,并注意流创建、SetEndOfFile、FlushFileBuffers及句柄释放。

C# NTFS备用数据流 C#如何读写文件的Alternate Data Streams (ADS)

ADS在C#中没有原生API支持,必须调用Windows API

Windows .NET运行时(包括.NET Framework、.NET Core 3.1+ 和 .NET 5+)的System.IO类库完全忽略备用数据流——File.ReadAllTextFileStreamFileInfo.Length等所有标准API均无法感知ADS存在。想读写ADS,唯一可靠方式是P/Invoke调用CreateFileSetFilePointerExReadFileWriteFile等Win32函数。

常见错误现象:File.Exists("test.txt:secret")返回falsenew FileInfo("test.txt:secret").Length抛出IOException;直接用File.WriteAllText("test.txt:secret", "data")会静默创建主文件test.txt:secret(即冒号被当作文件名一部分),而非向test.txt写入流。

  • 必须使用CreateFile打开带流名的路径,如"test.txt:secret:$DATA"$DATA可省略,但显式写出更清晰)
  • 打开模式需设为GENERIC_READ | GENERIC_WRITE,访问方式必须含FILE_FLAG_BACKUP_SEMANTICS(否则AccessDenied
  • 句柄必须用CloseHandle显式释放,.NET GC不管理Win32句柄

读取ADS内容的最小可行代码

以下代码片段能安全读取指定ADS(假设流名为secret):

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern IntPtr CreateFile(
    string lpFileName,
    uint dwDesiredAccess,
    uint dwShareMode,
    IntPtr lpSecurityAttributes,
    uint dwCreationDisposition,
    uint dwFlagsAndAttributes,
    IntPtr hTemplateFile);

[DllImport("kernel32.dll", SetLastError = true)] static extern bool CloseHandle(IntPtr hObject);

const uint GENERIC_READ = 0x80000000; const uint GENERIC_WRITE = 0x40000000; const uint OPEN_EXISTING = 3; const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;

string filePath = @"C:\temp\test.txt:secret"; IntPtr handle = CreateFile( filePath, GENERIC_READ, 0, IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero);

if (handle == new IntPtr(-1)) { int error = Marshal.GetLastWin32Error(); throw new IOException($"Failed to open ADS: {error}"); }

try { var buffer = new byte[4096]; uint bytesRead; if (!ReadFile(handle, buffer, (uint)buffer.Length, out bytesRead, IntPtr.Zero)) { throw new IOException("ReadFile failed"); } string content = Encoding.UTF8.GetString(buffer, 0, (int)bytesRead); } finally { CloseHandle(handle); }

注意:ReadFileWriteFile需自行P/Invoke声明;缓冲区大小应按需调整,ADS通常很小,但无上限限制;若流不存在,CreateFile返回INVALID_HANDLE_VALUE(-1),不是null

写入ADS时容易踩的三个坑

写入比读取更易出错,尤其在流不存在时需正确处理创建逻辑:

  • CreateFiledwCreationDisposition必须用CREATE_ALWAYSOPEN_ALWAYS,不能用OPEN_EXISTING(否则流不存在时失败)
  • 写入前未调用SetEndOfFile会导致旧内容残留——ADS不像主文件有自动截断,必须显式收缩长度
  • 写入后未调用FlushFileBuffers,数据可能滞留在内核缓存,其他进程(如资源管理器)立即读不到最新内容

例如覆盖写入test.txt:hidden

IntPtr handle = CreateFile(@"test.txt:hidden:$DATA", 
    GENERIC_WRITE, 0, IntPtr.Zero, CREATE_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero);
// ... WriteFile ...
SetEndOfFile(handle); // 关键:清空原有内容
FlushFileBuffers(handle);
CloseHandle(handle);

ADS路径解析与权限注意事项

ADS路径格式为主文件名:流名[:流类型],其中流类型默认是$DATA,可省略。但以下情况必须注意:

  • 流名不能含\ / : * ? " < > |,且不能以空格结尾(Windows会截断)
  • NTFS权限独立于主文件:即使用户对test.txt有读写权,也可能因ADS ACL被拒绝——调试时用icacls test.txt /stream:secret检查
  • .NET程序需以管理员权限运行?不一定。只要当前用户对文件有READ_PROPERTIES/WRITE_PROPERTIES权限且启用了备份权限(通常普通用户也有),即可操作ADS;但某些域策略可能禁用SeBackupPrivilege

最常被忽略的是流名编码:Windows内部用UTF-16,但C#字符串已是UTF-16,所以无需额外编码转换;但若从外部输入(如HTTP参数)获取流名,需先验证是否含非法字符,否则CreateFile直接失败且错误码不直观。

本文转载于:互联网 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注