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

image-20230610204240146.png

简单提一下,用微软的streams.exe工具可以遍历出程序的备用数据流

自删除

自删除原理是,将文件重命名为数据流格式,然后删除该数据流。利用这种方法去删除可以无视当前文件句柄占用,如果程序处于运行中也不会破坏程序的运行。

代码如下,首先以DELETE方式获取自身文件句柄,调用SetFileInformationByHandle()将文件名修改为:anyany,以调试编译运行的时候SetFileInformationByHandle()函数会报错,忽略就行。

image-20230611073536926.png

修改完成后重新打开一次文件句柄,再次调用SetFileInformationByHandle()删除文件

6677dc2ec879bb349924f65dd52b4c11.png

程序执行,自删除文件,后续的代码依旧可以运行

image-20230611074025955.png

上面的代码中我设置了两个暂停,第一个pause中,如果以代码调试的方式打开程序,CreateFileW函数会获取不到文件句柄,解决方法也简单,x64gbd附加到进程就可以调试了。

第二个pause是在将文件修改为数据流之后,用winhex创建磁盘快照可以发现,原来的程序变成了0B,进入程序之后any文件其实就是原来的程序

image-20230610205747913.png

image-20230610205636515.png

从上面可以看出,我们在第一次rename的时候是将程序移动到了数据流中(磁盘中会有一个0B的文件),第二次删除的时候删除的0B文件是我们的数据流文件。

这里还有一个点,在第一次rename文件之后如果不关闭程序的句柄(也就是一直暂停到第二个pause),火绒剑操作这个文件的时候会卡死

image-20230610210315963.png

完整代码

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);
}