主にマルウェアの関連で耳にする代替データストリーム (Alternate Data Stream / ADS) を、C#で単純に読み書きする。
なお、マルウェア云々は以降で特に触れないので参考文献を挙げておく。
参考:代替データストリームの危険性と、閲覧/削除機能
C#における代替データストリームについては、検索すると P/Invoke を使った方法が出てくる。
ただ、実は単純に読み書きするだけなら標準のAPIで行うことができる。
試しに、以下のようなパスとストリーム名(と作法としてキャンセレーショントークン)を渡すメソッドを用意してみる。
public class ADS { public static async Task<byte[]> OpenReadAsync(string fileName, string streamName, CancellationToken cancellationToken = default) { var fullName = $"{fileName}:{streamName}"; using var handle = File.OpenHandle(fullName); if (handle.IsInvalid) return Array.Empty<byte>(); var size = RandomAccess.GetLength(handle); var buf = new byte[size]; await RandomAccess.ReadAsync(handle, buf.AsMemory(), 0, cancellationToken); return buf; } }
パスを$"{fileName}:{streamName}"
にしているほかは、特に変わった点はなし。
試しに、代替データストリーム代表格であるZone.Identifier
(インターネットでダウンロードしたファイルの実行を警告したりするために使用されている)を読んでみる。
var path = @"適当にインターネットで拾ったファイル.png"; var data = await ADS.OpenReadAsync(path, "Zone.Identifier"); Console.WriteLine(Encoding.ASCII.GetString(data)); // [ZoneTransfer] // ZoneId=3
このように、単純に読み書きするだけであれば標準だけでOK。
ストリーム名を指定せず代替データストリームを一覧する、みたいなことをやりたい場合は(おそらく)P/Invoke が必要になる。
書き込みもやってみる。
public static async Task OpenWriteAsync(string fileName, string streamName, ReadOnlyMemory<byte> data, CancellationToken cancellation = default) { var fullName = $"{fileName}:{streamName}"; using var handle = File.OpenHandle(fullName, FileMode.OpenOrCreate, FileAccess.Write); if (handle.IsInvalid) return; await RandomAccess.WriteAsync(handle, data, 0, cancellation); }
byte[] bytes = Encoding.ASCII.GetBytes("Hello, world!"); await ADS.OpenWriteAsync(path, "testStream", bytes.AsMemory()); data = await ADS.OpenReadAsync(path, "testStream"); Console.WriteLine(Encoding.ASCII.GetString(data)); // Hello, world!
難しいこともなく書込み可能。
存在確認や削除なども。
Console.WriteLine(File.Exists($"{path}:testStream")); File.Delete($"{path}:testStream"); Console.WriteLine(File.Exists($"{path}:testStream")); // True // False