用 ntfs 流隐藏文件

本贴最后更新于 2579 天前,其中的信息可能已经时移俗易

大家把 NTFS 分区上的文件拷贝到非 NTFS 分区上时, 可能偶尔遇到过下面的情况, 系统提示会有数据丢失, 这是怎么回事呢?

ntfs.gif

实际上 NTFS 文件系统引入了 这个概念, 每个文件都可以有多个流, 而我们一般只使用了一个, 通过给文件分配更多的流, 可以实现某种意义上的"文件隐藏". 例如可以控制台中使用下面的命令建立一个文本文件:

dir d:>abc.txt

它列出 d: 根目录的所有文件, 然后将其重定向到文件 abc.txt, 现在你可以检查一下 abc.txt 的大小和内容, 并记录下来. 然后再执行下面这条命令:

dir c:>abc.txt:stream.txt

执行完毕后, 检查 abc.txt, 大小和内容都没有变化, 但其实 abc.txt 已经多了一个流 stream.txt, 而重定向的内容就输出到了它里面, 不信使用下面的命令看一下(注意流的名字也要以 .txt 结尾, 否则 notepad 就找不到了):

notepad abc.txt:stream.txt

这样我们就把一个文件隐藏了, dir 命令看不见, 文件属性看不到, 资源管理器也看不到, 如果不知道流的名字, notepad 也是无法访问的.

实际上, 流还可以不依赖于文件, 下面的命令也是合法的(先不要试, 否则可能会有点麻烦):

dir e:>:stream.txt

这是把流绑到了文件夹上, 这种流就更隐蔽了. 一般情况下要想删除流只有将其宿主删除, 如果你执行了刚才的命令, 并且是在根文件夹上执行的, 如果你想删除它, 那就恭喜你要格盘了:). 不过我们是程序员, 通过写程序还是不难删除流的, 只要调用 DeleteFile, 并提供流的名字就行了. 要想枚举一个文件中的所有流, 目前只能通过 BackupRead 来完成. 我写了一个小程序, 通过它可以枚举、删除、导入导出流中的数据, 下面的是它的代码(写的比较仓促, 可能还有一些 bug, 不过主要功能都实现了).

#include <windows.h> #include <stdio.h> #include <locale.h> #include <wchar.h> #include <malloc.h> #include <stddef.h> enum RUN_MODE { SHOW_USAGE = 0, SHOW_STREAMS, DELETE_STREAMS, IMPORT_STREAM, EXPORT_STREAM, }; LPCWSTR g_szObj = NULL; LPCWSTR g_szStrm = NULL; LPCWSTR g_szFile = NULL; int ParseArgs( int argc, LPWSTR* argv ) { if( argc == 1 || argc == 3 ) return SHOW_USAGE; g_szObj = *(argv + 1); if( argc == 2 ) return SHOW_STREAMS; LPCWSTR act = *(argv + 2); if( act[0] != L'-' && act[0] != L'/' ) return SHOW_USAGE; if( act[1] == L'd' ) return DELETE_STREAMS; if( argc == 4 || argc > 5 ) return SHOW_USAGE; g_szStrm = *(argv + 3); g_szFile = *(argv + 4); if( act[1] == L'i' ) return IMPORT_STREAM; if( act[1] == L'e' ) return EXPORT_STREAM; return SHOW_USAGE; } int ShowUsage() { wprintf( L"USAGE:/n" L"nsvw file.a : view streams in file.a/n" L"nsvw file.a -d s1 s2 ... : delete stream s1, s2 and ... from file.a/n" L"nsvw file.a -i s1 file.b : copy the content of file.b to stream s1 in file.a/n" L"nsvw file.a -e s1 file.c : copy the content of stream s1 in file.a to file.c/n" ); return 0; } int ShowStreams() { HANDLE hFile = CreateFile( g_szObj, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if( hFile == INVALID_HANDLE_VALUE ) { wprintf( L"Unable to open object /"%s/"/n", g_szObj ); return static_cast<int>( GetLastError() ); } WIN32_STREAM_ID wsi = {0}; WCHAR szStrmName[MAX_PATH]; LPVOID pContext = NULL; BOOL bOk = TRUE; int nCount = 0; while( bOk ) { DWORD dwBytes = 0; LPBYTE buf = reinterpret_cast<LPBYTE>( &wsi ); DWORD dwSize = static_cast<DWORD>(offsetof(WIN32_STREAM_ID, cStreamName)); bOk = BackupRead( hFile, buf, dwSize, &dwBytes, FALSE, FALSE, &pContext ); if( !bOk || dwBytes == 0 ) break; if( wsi.dwStreamNameSize > 0 ) { buf = reinterpret_cast<LPBYTE>( szStrmName ); dwSize = wsi.dwStreamNameSize; BackupRead( hFile, buf, dwSize, &dwBytes, FALSE, FALSE, &pContext ); szStrmName[dwSize / sizeof(WCHAR)] = 0; wprintf( L"NAME: /"%s/"/t/tSIZE: %I64d/n", szStrmName, wsi.Size.QuadPart ); ++nCount; } DWORD dw1, dw2; BackupSeek( hFile, wsi.Size.LowPart, wsi.Size.HighPart, &dw1, &dw2, &pContext ); } DWORD dwError = GetLastError(); ::BackupRead( hFile, NULL, 0, NULL, TRUE, FALSE, &pContext ); ::CloseHandle( hFile ); wprintf( L"Total %d stream(s)./n", nCount ); return static_cast<int>( dwError ); } void BuildStreamName( LPCWSTR szStrm, LPWSTR buf, size_t size ) { _snwprintf( buf, size, L"%s:%s", g_szObj, szStrm ); buf[size - 1] = 0; } int DeleteStreams( int count, LPWSTR* streams ) { const int nSize = MAX_PATH * 2; WCHAR szStrmName[nSize]; size_t size = sizeof(szStrmName) / sizeof(WCHAR); for( int i = 0; i < count; ++i ) { BuildStreamName( *(streams + i), szStrmName, nSize ); if( ::DeleteFileW( szStrmName ) ) wprintf( L"stream %s was deleted./n", *(streams + i) ); else wprintf( L"unable to delete stream %s./n", *(streams + i) ); } return 0; } int CopyStream( LPCWSTR szSrc, LPCWSTR szDst ) { int nRet = 0; HANDLE hSrc = INVALID_HANDLE_VALUE, hDst = INVALID_HANDLE_VALUE; HANDLE hSrcFm = NULL, hDstFm = NULL; PVOID pSrc = NULL, pDst = NULL; __try { hSrc = ::CreateFile( szSrc, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if( hSrc == INVALID_HANDLE_VALUE ) __leave; DWORD dwSize = ::GetFileSize( hSrc, NULL ); if( dwSize > 0 ) { hSrcFm = ::CreateFileMapping( hSrc, NULL, PAGE_READONLY, 0, 0, NULL ); if( hSrcFm == NULL ) __leave; pSrc = ::MapViewOfFile( hSrcFm, FILE_MAP_READ, 0, 0, dwSize ); if( pSrc == NULL ) __leave; } hDst = ::CreateFile( szDst, FILE_ALL_ACCESS, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); if( hDst == INVALID_HANDLE_VALUE ) __leave; if( dwSize > 0 ) { hDstFm = ::CreateFileMapping( hDst, NULL, PAGE_READWRITE, 0, dwSize, NULL ); if( hDstFm == NULL ) __leave; pDst = ::MapViewOfFile( hDstFm, FILE_MAP_WRITE, 0, 0, dwSize ); if( pDst == NULL ) __leave; memcpy( pDst, pSrc, dwSize ); } else { ::SetFilePointer( hDst, 0, 0, FILE_BEGIN ); ::SetEndOfFile( hDst ); } } __finally { nRet = static_cast<int>( ::GetLastError() ); } if( pDst != NULL ) ::UnmapViewOfFile( pDst ); if( pSrc != NULL ) ::UnmapViewOfFile( pSrc ); if( hDstFm != NULL ) ::CloseHandle( hDstFm ); if( hSrcFm != NULL ) ::CloseHandle( hSrcFm ); if( hDst != INVALID_HANDLE_VALUE ) ::CloseHandle( hDst ); if( hSrc != INVALID_HANDLE_VALUE ) ::CloseHandle( hSrc ); return nRet; } int ImportStream() { const int nSize = MAX_PATH * 2; WCHAR szStrmName[nSize]; size_t size = sizeof(szStrmName) / sizeof(WCHAR); BuildStreamName( g_szStrm, szStrmName, nSize ); int nRes = CopyStream( g_szFile, szStrmName ); if( nRes != 0 ) wprintf( L"Import failed./n" ); else wprintf( L"Import completed./n" ); return nRes; } int ExportStream() { const int nSize = MAX_PATH * 2; WCHAR szStrmName[nSize]; size_t size = sizeof(szStrmName) / sizeof(WCHAR); BuildStreamName( g_szStrm, szStrmName, nSize ); int nRes = CopyStream( szStrmName, g_szFile ); if( nRes != 0 ) wprintf( L"Export failed./n" ); else wprintf( L"Export completed./n" ); return nRes; } int __cdecl wmain( int argc, LPWSTR* argv ) { int nRetCode = 0; _wsetlocale( LC_ALL, L".OCP" ); wprintf( L"NTFS Stream Viewer VERSION 1.0/n" ); switch( ParseArgs( argc, argv ) ) { case SHOW_USAGE: nRetCode = ShowUsage(); break; case SHOW_STREAMS: nRetCode = ShowStreams(); break; case DELETE_STREAMS: nRetCode = DeleteStreams( argc - 3, argv + 3 ); break; case IMPORT_STREAM: nRetCode = ImportStream(); break; case EXPORT_STREAM: nRetCode = ExportStream(); break; default: wprintf( L"internel error!/n" ); nRetCode = -1; break; } return nRetCode; }

PS: 真正注意到"流"的存在是卸载"卡巴斯基"时, 它提示我要删除 NTFS 分区上所有文件的附加数据流. 说实话, 卸载了它之后, 机器快多了, 因此怀疑卡巴会导致每次文件操作都去访问附加的数据流, 也就是导致文件访问变慢.

  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1063 引用 • 3455 回帖 • 167 关注
  • Windows

    Microsoft Windows 是美国微软公司研发的一套操作系统,它问世于 1985 年,起初仅仅是 Microsoft-DOS 模拟环境,后续的系统版本由于微软不断的更新升级,不但易用,也慢慢的成为家家户户人们最喜爱的操作系统。

    226 引用 • 476 回帖
  • 技术

    到底什么才是技术呢?

    88 引用 • 179 回帖 • 4 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 612 关注
  • ngrok

    ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。

    7 引用 • 63 回帖 • 647 关注
  • CSS

    CSS(Cascading Style Sheet)“层叠样式表”是用于控制网页样式并允许将样式信息与网页内容分离的一种标记性语言。

    199 引用 • 541 回帖 • 1 关注
  • Caddy

    Caddy 是一款默认自动启用 HTTPS 的 HTTP/2 Web 服务器。

    12 引用 • 54 回帖 • 166 关注
  • Windows

    Microsoft Windows 是美国微软公司研发的一套操作系统,它问世于 1985 年,起初仅仅是 Microsoft-DOS 模拟环境,后续的系统版本由于微软不断的更新升级,不但易用,也慢慢的成为家家户户人们最喜爱的操作系统。

    226 引用 • 476 回帖
  • 音乐

    你听到信仰的声音了么?

    62 引用 • 512 回帖 • 1 关注
  • C

    C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

    85 引用 • 165 回帖 • 1 关注
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    172 引用 • 516 回帖
  • Node.js

    Node.js 是一个基于 Chrome JavaScript 运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞 I/O 模型而得以轻量和高效。

    139 引用 • 269 回帖 • 1 关注
  • Outlook
    1 引用 • 5 回帖
  • SOHO

    为成为自由职业者在家办公而努力吧!

    7 引用 • 55 回帖
  • 持续集成

    持续集成(Continuous Integration)是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

    15 引用 • 7 回帖
  • CAP

    CAP 指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。

    12 引用 • 5 回帖 • 638 关注
  • Openfire

    Openfire 是开源的、基于可拓展通讯和表示协议 (XMPP)、采用 Java 编程语言开发的实时协作服务器。Openfire 的效率很高,单台服务器可支持上万并发用户。

    6 引用 • 7 回帖 • 100 关注
  • 百度

    百度(Nasdaq:BIDU)是全球最大的中文搜索引擎、最大的中文网站。2000 年 1 月由李彦宏创立于北京中关村,致力于向人们提供“简单,可依赖”的信息获取方式。“百度”二字源于中国宋朝词人辛弃疾的《青玉案·元夕》词句“众里寻他千百度”,象征着百度对中文信息检索技术的执著追求。

    63 引用 • 785 回帖 • 108 关注
  • jsDelivr

    jsDelivr 是一个开源的 CDN 服务,可为 npm 包、GitHub 仓库提供免费、快速并且可靠的全球 CDN 加速服务。

    5 引用 • 31 回帖 • 100 关注
  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    952 引用 • 944 回帖
  • 智能合约

    智能合约(Smart contract)是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易,这些交易可追踪且不可逆转。智能合约概念于 1994 年由 Nick Szabo 首次提出。

    1 引用 • 11 回帖 • 2 关注
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 19 关注
  • NGINX

    NGINX 是一个高性能的 HTTP 和反向代理服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 NGINX 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本 0.1.0 发布于 2004 年 10 月 4 日。

    315 引用 • 547 回帖
  • Python

    Python 是一种面向对象、直译式电脑编程语言,具有近二十年的发展历史,成熟且稳定。它包含了一组完善而且容易理解的标准库,能够轻松完成很多常见的任务。它的语法简捷和清晰,尽量使用无异义的英语单词,与其它大多数程序设计语言使用大括号不一样,它使用缩进来定义语句块。

    556 引用 • 675 回帖
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    6 引用 • 26 回帖 • 548 关注
  • RIP

    愿逝者安息!

    8 引用 • 92 回帖 • 394 关注
  • Flume

    Flume 是一套分布式的、可靠的,可用于有效地收集、聚合和搬运大量日志数据的服务架构。

    9 引用 • 6 回帖 • 652 关注
  • FFmpeg

    FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。

    23 引用 • 32 回帖 • 3 关注
  • Excel
    31 引用 • 28 回帖 • 1 关注
  • Sphinx

    Sphinx 是一个基于 SQL 的全文检索引擎,可以结合 MySQL、PostgreSQL 做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。

    1 引用 • 220 关注