ADS
Microsoft于90年代初期引入了一种称为“数据流”的概念,从而使NTFS可以作为Macintosh客户端访问文件服务器的文件系统。因为Mac OS 是利用Mac的分层式文件系统(HFS)上所谓的资源分支数据流,用于存放图标等应用程序的元数据。NTFS和FAT的差别就是FAT由一个数据流构成,而NTFS可以在一个文件内存里面存储多个数据流。
在windows协议MS-FSA中ADS的定义:一个被命名的数据流是一个文件或者目录的一部分,它们可以独立于默认数据流被单独的打开。许多数据流的操作之影响数据流并不影响其他数据流,文件和目录
所有的文件在NTFS中至少包含一个主数据流,也就是用户可见的文件或是目录,一个文件在NTFS中真正的文件名称格式:
<文件名>:<流名>:<流种类>
文件ADS默认没有流名字,一个文件test.txt
在NTFS中的全名为test.txt::$DATA
文件夹没有默认的数据流(也就是没有主数据流),但是有一个默认的目录流为$INDEX_ALLOCATION
,默认的流名为$I30
。比如文件夹testDir
全名为testDir:$I30:$INDEX_ALLOCATION
假设查看文件1.txt
的数据流内容,可以输入如下命令,其内容和1.txt
是一样的,在数据流中添加的内容保存后会被写入对应的文件中。如下图,原来1.txt
中只有内容1.txt,在数据流中写入2,保存后重新打开原文件,发现2被写入
notepad.exe 1.txt::$DATA
简单提一下,用微软的streams.exe工具可以遍历出程序的备用数据流
自删除
自删除原理是,将文件重命名为数据流格式,然后删除该数据流。利用这种方法去删除可以无视当前文件句柄占用,如果程序处于运行中也不会破坏程序的运行。
代码如下,首先以DELETE
方式获取自身文件句柄,调用SetFileInformationByHandle()
将文件名修改为:anyany
,以调试编译运行的时候SetFileInformationByHandle()
函数会报错,忽略就行。
修改完成后重新打开一次文件句柄,再次调用SetFileInformationByHandle()
删除文件
程序执行,自删除文件,后续的代码依旧可以运行
上面的代码中我设置了两个暂停,第一个pause中,如果以代码调试的方式打开程序,CreateFileW
函数会获取不到文件句柄,解决方法也简单,x64gbd附加到进程就可以调试了。
第二个pause是在将文件修改为数据流之后,用winhex创建磁盘快照可以发现,原来的程序变成了0B,进入程序之后any
文件其实就是原来的程序
从上面可以看出,我们在第一次rename的时候是将程序移动到了数据流中(磁盘中会有一个0B的文件),第二次删除的时候删除的0B文件是我们的数据流文件。
这里还有一个点,在第一次rename文件之后如果不关闭程序的句柄(也就是一直暂停到第二个pause),火绒剑操作这个文件的时候会卡死
完整代码
CloseSelf.h
#pragma once
#pragma comment(lib, "Shlwapi.lib")
#include <Windows.h>
#include <shlwapi.h>
#include <stdio.h>
#include <stdlib.h>
#define DS_STREAM_RENAME L":anyany:$DATA"
#define DS_DEBUG_LOG(msg) wprintf(L"[LOG] - %s\n", msg)
BOOL ds_deposite_handle(HANDLE hHandle);
BOOL ds_rename_handle(HANDLE hHandle);
HANDLE ds_open_handle(PWCHAR pwPath);
CloseSelf.cpp
#include "CloseSelf.h"
HANDLE ds_open_handle(PWCHAR pwPath) {
return CreateFileW(pwPath, DELETE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
}
BOOL ds_rename_handle(HANDLE hHandle) {
FILE_RENAME_INFO fRename;
RtlSecureZeroMemory(&fRename, sizeof(fRename));
// set our FileNameLength and FileName to DS_STREAM_RENAME
LPWSTR lpwStream = (LPWSTR)DS_STREAM_RENAME;
fRename.FileNameLength = sizeof(lpwStream);
RtlCopyMemory(fRename.FileName, lpwStream, sizeof(lpwStream));
return SetFileInformationByHandle(hHandle, FileRenameInfo, &fRename, sizeof(fRename) + sizeof(lpwStream));
}
BOOL ds_deposite_handle(HANDLE hHandle) {
// set FILE_DISPOSITION_INFO::DeleteFile to TRUE
FILE_DISPOSITION_INFO fDelete;
RtlSecureZeroMemory(&fDelete, sizeof(fDelete));
fDelete.DeleteFile = TRUE;
return SetFileInformationByHandle(hHandle, FileDispositionInfo, &fDelete, sizeof(fDelete));
}
SelfDelete.cpp
// SelfDelete.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <Windows.h>
#include <time.h>
#include "CloseSelf.h"
BOOL CloseSelf() {
WCHAR wcPath[MAX_PATH + 1];
RtlSecureZeroMemory(wcPath, sizeof(wcPath));
// 获取当前文件名
if (GetModuleFileNameW(NULL, wcPath, MAX_PATH) == 0)
{
DS_DEBUG_LOG(L"failed to get the current module handle");
return FALSE;
}
// 获取当前文件句柄
HANDLE hCurrent = ds_open_handle(wcPath);
if (hCurrent == INVALID_HANDLE_VALUE)
{
DS_DEBUG_LOG(L"failed to acquire handle to current running process");
std::cout << "Error code: " << GetLastError() << std::endl;
return FALSE;
}
// rename the associated HANDLE's file name
system("pause");
DS_DEBUG_LOG(L"attempting to rename file name");
if (!ds_rename_handle(hCurrent))
{
DS_DEBUG_LOG(L"failed to rename to stream");
return FALSE;
}
DS_DEBUG_LOG(L"successfully renamed file primary :$DATA ADS to specified stream, closing initial handle");
system("pause");
// 不关闭句柄可造成其他程序读取该文件是假死现象
CloseHandle(hCurrent);
// open another handle, trigger deletion on close
hCurrent = ds_open_handle(wcPath);
if (hCurrent == INVALID_HANDLE_VALUE)
{
DS_DEBUG_LOG(L"failed to reopen current module");
return FALSE;
}
if (!ds_deposite_handle(hCurrent))
{
DS_DEBUG_LOG(L"failed to set delete deposition");
return FALSE;
}
// trigger the deletion deposition on hCurrent
DS_DEBUG_LOG(L"closing handle to trigger deletion deposition");
CloseHandle(hCurrent);
// verify we've been deleted
if (PathFileExistsW(wcPath))
{
DS_DEBUG_LOG(L"failed to delete copy, file still exists");
return FALSE;
}
DS_DEBUG_LOG(L"successfully deleted self from disk");
return TRUE;
}
void do_something() {
int i = 0;
while (true) {
std::cout << "test: " << i << std::endl;
i++;
Sleep(1000);
}
}
int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) {
HANDLE exeHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)do_something, NULL, NULL, NULL);
Sleep(3000);
CloseSelf();
WaitForSingleObject(exeHandle, INFINITE);
}