Alternative Data Streams (ADS)是Windows文件系统中引入的一项功能,可以为文件关联额外的数据流。这项功能的诞生原因是为了提高文件系统的兼容性和功能性,特别是与Macintosh和Unix系统创建的文件进行交互时。
在Macintosh和Unix系统中,文件可以具有多个数据流,而Windows文件系统中最初只支持一个数据流。为了使Windows文件系统与其他操作系统更兼容,Microsoft引入了ADS功能,使Windows文件系统能够处理多个数据流。这项功能可以用于存储文件的元数据,如作者、创建日期、修改日期等,还可以用于存储图像和音频文件的缩略图、元数据等。
ADS功能可以提供重要的功能和灵活性,但也带来了一些安全风险。因为ADS可以隐藏在文件中,不容易被普通的文件系统API或一些防病毒软件所发现,因此,它也可能被恶意软件利用来隐藏其存在,使得系统的安全受到威胁。
尽管如此,ADS仍然被保留在Windows文件系统中,因为它在某些应用程序和使用情况下提供了重要的功能。但是,用户和组织需要意识到ADS所带来的安全风险,并采取措施来减轻这些风险,例如使用专门设计用于检测ADS中隐藏的恶意软件的防病毒软件等。本文将介绍ADS的创建和检测,以及如何用Win32 API读写ADS。
创建ADS
NTFS交换数据流(Alternate Data Streams,简称ADS)是NTFS磁盘格式的一个特性。在NTFS文件系统下,每个文件都可以存在多个数据流。通俗的理解,就是其它文件可以“寄宿”在某个文件身上。
首先来看个例子,打开cmd.exe,在C:\Test\ADS下创建一个hello.txt文件C:\Test\ADS>ECHO HelloWorld > hello.txt
用DIR可以看到该目录下已经有一个hello.txt文件,可以用notepad.exe hello.txt查看其内容,会发现”HelloWorld”已经被写入。
接着继续指向如下命令:C:\Test\ADS>echo ThisIsADS1 > hello.txt:ads1.txt
这条命令在cmd.exe下可以执行成功,看起来是向hello.txt:ads1.txt这个文件写入了”ThisIsADS1”的内容,但是此时用DIR列出该目录下所有文件,会发现依然只有hello.txt:
| C:\Test\ADS>DIR | 
如果用notepad.exe hello.txt打开hello.txt会发现内容依然是”HelloWorld”,而用notepad.exe hello.txt:ads1.txt打开hello.txt:ads1.txt文件则会发现”ThisIsADS1”这个内容已经被成功写入了,如果删除hello.txt则hello.txt:ads.txt也无法打开。如果直接echo ThisIsADS1 > hello.txt:ads1.txt创建hello.txt:ads1.txt则实际上会创建一个空的hello.txt文件。以上这些现象说明hello.txt:ads1.txt在当前文件系统是真实存在的而又不可见,看似就是依附于hello.txt的子文件。
hello.txt:ads.txt这类文件就是指定了宿主的ADS文件,hello.txt就是它的宿主。宿主的删除会导致依附的ADS文件也被删除
要查看目录下的ADS文件可以用DIR /RC:\Test\ADS>DIR /R
 驱动器 C 中的卷是 Windows
 卷的序列号是 CC7A-4265
 C:\Test\ADS 的目录
2023/03/28  21:55    <DIR>          .
2023/03/07  22:47    <DIR>          ..
2023/03/28  21:55                 0 hello.txt
                                 26 hello.txt:ads1.txt:$DATA
               1 个文件              0 字节
               2 个目录 67,761,876,992 可用字节
可以看见hello.txt有时间信息,而hello.txt:ads.txt并没有。这是因为ADS文件和宿主共享相同的文件元数据。ADS文件的大小也不会反应在宿主文件的大小上,宿主文件的大小属性只描述其自身数据的大小。
ADS文件不仅可以依附于文件,也可以依附于目录。创建依附目录的ADS文件同理:C:\Test\ADS>mkdir dir1
C:\Test\ADS>echo ThisIsDirADS1 > dir1:dirADS1.txt
C:\Test\ADS>DIR /R
 驱动器 C 中的卷是 Windows
 卷的序列号是 CC7A-4265
 C:\Test\ADS 的目录
2023/03/28  22:09    <DIR>          .
2023/03/07  22:47    <DIR>          ..
2023/03/28  22:09    <DIR>          dir1
                                 16 dir1:dirADS1.txt:$DATA
2023/03/28  21:55                 0 hello.txt
                                 26 hello.txt:ads1.txt:$DATA
               1 个文件              0 字节
               3 个目录 67,760,422,912 可用字节
也可以创建不指定宿主的ADS文件:C:\Test\ADS>echo ThisIsDirADS2 > :dirADS2.txt
C:\Test\ADS>DIR /R
 驱动器 C 中的卷是 Windows
 卷的序列号是 CC7A-4265
 C:\Test\ADS 的目录
2023/03/28  22:13    <DIR>          .
                                 16 .:dirADS2.txt:$DATA
2023/03/07  22:47    <DIR>          ..
2023/03/28  22:09    <DIR>          dir1
                                 16 dir1:dirADS1.txt:$DATA
2023/03/28  21:55                 0 hello.txt
                                 26 hello.txt:ads1.txt:$DATA
               1 个文件              0 字节
               3 个目录 67,745,931,264 可用字节
C:\Test\ADS>cd ..
C:\Test>DIR /R
 驱动器 C 中的卷是 Windows
 卷的序列号是 CC7A-4265
 C:\Test 的目录
2023/03/07  22:47    <DIR>          .
2023/03/28  22:13    <DIR>          ADS
                                 16 ADS:dirADS2.txt:$DATA
这时可以观察到实际上创建的没有宿主的ADS会把当前目录作为自己的宿主
除了上述用echo命令向ADS文件写入数据外,cmd.exe还提供了一个type命令用于将指定文件写入ADS文件。例如:C:\Test\ADS>type "C:\Program Files\Bandizip\Bandizip.exe" > hello.txt:Bandzip.exe
C:\Test\ADS>dir /R 
 驱动器 C 中的卷是 Windows
 卷的序列号是 CC7A-4265
 C:\Test\ADS 的目录
2023/03/28  22:13    <DIR>          .
                                 16 .:dirADS2.txt:$DATA
2023/03/07  22:47    <DIR>          ..
2023/03/28  22:09    <DIR>          dir1
                                 16 dir1:dirADS1.txt:$DATA
2023/03/28  22:32                 0 hello.txt
                                 26 hello.txt:ads1.txt:$DATA
                          3,211,240 hello.txt:Bandzip.exe:$DATA
               1 个文件              0 字节
               3 个目录 67,742,556,160 可用字节
将一个可执行程序Bandizip.exe附加在了hello.txt:Bandzip.exe上,该过程是拷贝的,即hello.txt:Bandzip.exe占用独立的磁盘空间。由于ADS不可见的特性,ADS很容易被用于将木马文件隐藏在常规文件中以躲避操查杀。要删除ADS可以用特殊的工具,也可以将文件拷入不支持ADS特性的FAT32分区。
ADS Win32 API
Windows提供FindFirstStreamW和FindNextStreamW来获取文件/目录拥有的所有数据流。
- 首先用 - FindFirstStreamW查找文件对应的第一个- hStream句柄。传入文件路径,第二参数固定为- _STREAM_INFO_LEVELS::FindStreamInfoStandard,第四个参数固定为0。如果成功将把查找到的流的信息读入- WIN32_FIND_STREAM_DATA结构,如果失败会返回- INVALID_HANDLE_VALUE。- std::wstring wPath = LR"(C:\Test\ADS\hello.txt)"; 
 WIN32_FIND_STREAM_DATA findStreamData{};
 HANDLE hStream = ::FindFirstStreamW(
 wPath.c_str(),
 _STREAM_INFO_LEVELS::FindStreamInfoStandard,
 &findStreamData,
 0
 );
 if (hStream == INVALID_HANDLE_VALUE) {
 /* get stream handle failed */
 return;
 }
- 用 - FindNextStreamW结合之前获取的- hStream继续查找下一个流的信息。这个过程和遍历文件用到的- FindNextFileW很类似。当查找完成- FindNextStreamW会失败,- GetLastError()将会返回- ERROR_HANDLE_EOF。查找成功下一个流的信息将会覆写入- WIN32_FIND_STREAM_DATA findStreamData中。- while (true) { 
 std::cout << findStreamData.cStreamName << std::endl;
 if (!::FindNextStreamW(hStream, &findStreamData)) {
 if (::GetLastError() != ERROR_HANDLE_EOF) {
 /* error occured */
 std::cerr << "error happened" << std::endl;
 }
 break;
 }
 }
- 最后用 - FindClose关闭句柄,释放资源。- ::FindClose(hStream); 
 hStream = INVALID_HANDLE_VALUE;
对于之前的hello.txt文件,会打印出这样的结果:::$DATA
:ads1.txt:$DATA
:ads2.txt:$DATA
形式可以看成是:<stream name>:$DATA。其中::$DATA表示streamName为空,这表示一个主数据流,所有文件(非目录),无论是否包含ADS,都必定有一个主数据流。:ads1.txt:$DATA和:ads2.txt:$DATA表明hello.txt还包含两个ADS,分别为ads1.txt和ads2.txt。所有目录,都没有主数据流,但可能包含ADS。例如上文中C:\Test\ADS\dir1可以拿到如下打印结果::dirADS1.txt:$DATA
:dirADS2.txt:$DATA
无论目录还是文件的ADS都可以看作是文件。对于ADS的读取、创建、写入都可以直接用之前几章节提到的文件操作API CreateFileW、ReadFile、WriteFile等。传入的路径可以用<filePath>:<streamName>表示。打开一个ADS的句柄可以表示为:std::wstring wPath = LR"(C:\Test\ADS\hello.txt:ads1.txt)";
HANDLE hFile = ::CreateFileW(
    wPath.c_str(),
    GENERIC_READ,
    FILE_SHARE_READ,
    nullptr,
    OPEN_EXISTING,
    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
    0);
根据该句柄执行ReadFile和WriteFile就可以和读写常规文件一样读写ADS了。
需要注意的是,对ADS的读写不光会改变ADS的内容,也会改变宿主文件的部分属性(例如AccessTime、ModifyTime),且ADS只和宿主在数据流内容和大小上相互区分,在元数据上他们和宿主是共享的。用获得的宿主文件句柄和ADS句柄可以获得一样的文件元数据信息(Index、设备号,时间,Attribute)。