如何在Windows下自删除的艺术?

通常来说,在windows程序不可能在运行的时候实现删除自己,微软设计之初为了保证程序的安全性,当一个可执行程序运行的时候会处于一种被占用的状态,如果尝试删除程序,会显示程序被占用,一般需要结束掉程序后才能删掉,而自删除利用了NTFS文件特性达到的程序运行时解除文件锁定,最终删除自身的效果,本篇文章是对此项技术的总结,这项技术已经出现很多年了,互联网上最早的消息来自2021年,于jonasLy在推特公开了这项技术

NTFS 的 Alternate Data Stream

NTFS(New Technology File System)文件系统包括对备用数据流的支持。这是一个相对不太为人熟知的功能,主要是为了与Macintosh文件系统中的文件兼容性而被引入的。备用数据流允许文件包含多个数据流,而每个文件至少包含一个数据流。在Windows操作系统中,每个文件的默认数据流称为:$DATA

尽管Windows资源管理器(Windows Explorer)没有提供一种直观的方式来查看文件中的备用数据流,也没有提供一种在不删除文件的情况下删除这些数据流的方法,但实际上可以相对容易地创建和访问它们。备用数据流中的可执行文件可以从命令行中运行,但不会在Windows资源管理器(或控制台)中显示

创建方法

notepad hello.txt:test

文件格式:

<filename>:<stream name>:<stream type>

查看方法:

dir /r

这是我创建的内容:

图片

核心原理

  1. 微软虽然阻止直接删除自己,但是并不阻止重命名自己,我们可以轻松的按下F2修改我们运行的程序名称;

  2. 文件锁和重命名挂钩,文件锁和着你重命名的文件走,假设我们重命名为Alternate Data Stream类型的文件,文件锁便会锁定我们重命名的文件;

  3. 要知道所有的Alternate Data Stream都有主的文件,主文件现在没有被使用意味着主文件是可以被删除的;虽然无法直接在系统上进行重命名带有:的文件名,但是微软提供的Windows API却不影响我们对本身进行重命名;

  4. 通过删除自己的主文件,根据微软的系统特性,备选的数据流也会被系统自动删除,即便是有文件锁也会被强制释放;

  5. 程序走到Main方法,PE文件已经加载到内存里面了,因此删除自己不影响程序的正常运行。

编程实现

核心Windows API

SetFileInformationByHandle是用来重新设置文件名,同时也可以用来设置删除位:

BOOL SetFileInformationByHandle(
  [in] HANDLE                    hFile,     //要更改信息的文件的句柄。
  [in] FILE_INFO_BY_HANDLE_CLASS FileInformationClass,  //指定要更改的信息类型
  [in] LPVOID                    lpFileInformation,  //指向包含要更改的指定文件信息类的信息的缓冲区的指针
  [in] DWORD                     dwBufferSize  //lpFileInformation 的大小,以字节为单位
);

实现:

if(SetFileInformationByHandle(hFile, FileInformationClass, &FileInformation, sizeof(FileInformation)))
{
   std::cout << "delete self success" << std::endl;
}

查看FileInformationClass的枚举类型:

typedef enum _FILE_INFO_BY_HANDLE_CLASS {
    FileBasicInfo,                     // 文件的基本信息
    FileStandardInfo,                  // 文件的标准信息
    FileNameInfo,                      // 文件的名称信息
    FileRenameInfo,                    // 文件的重命名信息
    FileDispositionInfo,               // 文件的处置信息
    FileAllocationInfo,                // 文件的分配信息
    FileEndOfFileInfo,                 // 文件的结束信息
    FileStreamInfo,                    // 文件流的信息
    FileCompressionInfo,               // 文件的压缩信息
    FileAttributeTagInfo,              // 文件属性标签信息
    FileIdBothDirectoryInfo,           // 文件和目录信息(同时包括文件和目录的标识信息)
    FileIdBothDirectoryRestartInfo,    // 文件和目录信息(同时包括文件和目录的标识信息)的重启信息
    FileIoPriorityHintInfo,            // 文件的IO优先级提示信息
    FileRemoteProtocolInfo,            // 文件的远程协议信息
    FileFullDirectoryInfo,             // 完整的目录信息
    FileFullDirectoryRestartInfo,      // 完整的目录信息的重启信息
    FileStorageInfo,                   // 文件的存储信息
    FileAlignmentInfo,                 // 文件的对齐信息
    FileIdInfo,                        // 文件的标识信息
    FileIdExtdDirectoryInfo,           // 文件的扩展目录信息
    FileIdExtdDirectoryRestartInfo,    // 文件的扩展目录信息的重启信息
    FileDispositionInfoEx,             // 文件的扩展处置信息
    FileRenameInfoEx,                  // 文件的扩展重命名信息
    FileCaseSensitiveInfo,             // 文件的大小写敏感信息
    FileNormalizedNameInfo,            // 文件的规范化名称信息
    MaximumFileInfoByHandleClass       // 用于计数的枚举最大值
} FILE_INFO_BY_HANDLE_CLASS, *PFILE_INFO_BY_HANDLE_CLASS;

微软搜索找到FileRenameInfo的具体属性:

typedef struct _FILE_RENAME_INFO {
  union {
    BOOLEAN ReplaceIfExists;
    DWORD   Flags;
  } DUMMYUNIONNAME;
  BOOLEAN ReplaceIfExists;
  HANDLE  RootDirectory;
  DWORD   FileNameLength;
  WCHAR   FileName[1];
} FILE_RENAME_INFO, *PFILE_RENAME_INFO;

图片

进一步查询文档翻阅:

一个以NUL结尾的宽字符字符串,包含文件的新路径。该值可以是以下之一:

  • 绝对路径(驱动器、目录和文件名)。

  • 相对于进程当前目录的路径。

  • NTFS文件流的新名称,以冒号:开头。

新的文件流NTFS应该要用 : 开始

HeapAlloc堆上分配,HeapAlloc可以帮助我们动态的分配内存空间

/**
 * 分配指定大小的内存块,并返回指向分配内存的指针。
 *
 * @param hHeap (输入): 用于分配内存的堆句柄。
 * @param dwFlags (输入): 分配标志,如 HEAP_ZERO_MEMORY(分配后初始化为零)等。
 * @param dwBytes (输入): 要分配的内存块的大小(以字节为单位)。
 *
 * @return 返回指向分配内存的指针。如果分配失败,返回 NULL。
 *
 * @remarks 通过调用HeapFree函数释放分配的内存。
 */
DECLSPEC_ALLOCATOR LPVOID HeapAlloc(
  [in] HANDLE hHeap,
  [in] DWORD  dwFlags,
  [in] SIZE_T dwBytes
);

部分代码实现:

const wchar_t* NewStream = L":endlessparadox";
PFILE_RENAME_INFO pRename = nullptr; //空指针指向结构体

hFile = CreateFileW(szPath, DELETE | SYNCHRONIZE, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
pRename = (PFILE_RENAME_INFO)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwBytes);

GetModuleFileNameW:检索包含指定模块的文件的完全限定路径。该模块必须已被当前进程加载。

/**
 * 获取指定模块的文件名或当前进程的可执行文件路径。
 *
 * @param hModule (输入,可选): 要获取文件名的模块的句柄,传入 NULL 表示当前进程。
 * @param lpFilename (输出): 存储获取到的文件名的缓冲区,应为一个 WCHAR 字符数组。
 * @param nSize (输入): lpFilename 缓冲区的大小(以字符数为单位)。
 *
 * @return 返回文件名的长度(以字符数表示,不包括 null 终止符)。
 *         如果函数执行失败,返回 0,可以通过调用 GetLastError() 获取更多错误信息。
 */
DWORD GetModuleFileNameW(
  [in, optional] HMODULE hModule,
  [out]          LPWSTR  lpFilename,
  [in]           DWORD   nSize
);

CreateFileW : 创建或打开一个文件或 I/O 设备,然后返回一个文件句柄以供后续操作

/**
 * 创建或打开一个文件或 I/O 设备,然后返回一个文件句柄以供后续操作。
 *
 * @param lpFileName (输入): 要创建或打开的文件名或 I/O 设备的路径。
 * @param dwDesiredAccess (输入): 打开文件的访问权限,如读取、写入等。
 * @param dwShareMode (输入): 其他进程可以与文件共享的方式,如共享读取、共享写入等。
 * @param lpSecurityAttributes (输入,可选): 安全描述符,用于控制文件或目录的安全性。通常传入NULL。
 * @param dwCreationDisposition (输入): 文件的创建或打开方式,如创建新文件、打开已有文件等。
 * @param dwFlagsAndAttributes (输入): 文件或目录的属性标志,如普通文件、目录等,以及其他标志位。
 * @param hTemplateFile (输入,可选): 用于复制文件属性的文件句柄。通常传入NULL。
 *
 * @return 返回一个文件句柄,用于后续文件操作。如果函数执行失败,返回INVALID_HANDLE_VALUE (-1),
 *         可以通过调用 GetLastError() 获取更多错误信息。
 */
HANDLE CreateFileW(
  [in]           LPCWSTR               lpFileName,
  [in]           DWORD                 dwDesiredAccess,
  [in]           DWORD                 dwShareMode,
  [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  [in]           DWORD                 dwCreationDisposition,
  [in]           DWORD                 dwFlagsAndAttributes,
  [in, optional] HANDLE                hTemplateFile
);

RtlCopyMemory例程将源内存块的内容复制到目标内存块

/**
 * @brief 复制内存区域
 * 
 * @param Destination 目标内存区域的指针,数据将被复制到这里
 * @param Source 源内存区域的指针,数据将从这里被复制
 * @param Length 要复制的字节数
 * 
 * @note 这个函数用于将源内存区域中的数据复制到目标内存区域中,以字节为单位进行复制。
 * @note 这是一个通用的内存复制函数,允许你复制数据到任何类型的内存区域。
 * @note 源内存区域是只读的,不会被修改。
 */
void RtlCopyMemory(
   void*       Destination,
   const void* Source,
   size_t      Length
);

完整C++代码实现

 

执行效果:

#include <Windows.h>  
#include <iostream>  

BOOL Self_Delete() {  
    const wchar_t* NewStream = L":endlessparadox";  
    WCHAR szPath[MAX_PATH * 2] = { 0 };  

    // 获取当前可执行文件的路径  
    if (GetModuleFileNameW(NULL, szPath, MAX_PATH * 2) == 0) {  
        std::wcerr << L"[!] GetModuleFileNameW fail , code is  " << GetLastError() << std::endl;  
        return FALSE;  
    }  

    // 打开文件
    HANDLE hFile = CreateFileW(szPath,  
                               DELETE | SYNCHRONIZE,  
                               FILE_SHARE_READ,  
                               NULL,  
                               OPEN_EXISTING,  
                               NULL, NULL);  
    if (hFile == INVALID_HANDLE_VALUE) {  
        std::wcerr << L"[!] CreateFileW fail , code is " << GetLastError() << std::endl;  
        return FALSE;  
    }  

    // 准备重命名信息  
    SIZE_T sRename = sizeof(FILE_RENAME_INFO) + sizeof(wchar_t) * wcslen(NewStream);  
    PFILE_RENAME_INFO pRename = (PFILE_RENAME_INFO)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sRename);  
    if (!pRename) {  
        CloseHandle(hFile);  
        std::wcerr << L"[!] HeapAlloc fail , code is " << GetLastError() << std::endl;  
        return FALSE;  
    }  

    pRename->FileNameLength = wcslen(NewStream) * sizeof(wchar_t);  
    RtlCopyMemory(pRename->FileName, NewStream, pRename->FileNameLength);  
    std::wcout << L"[i] Renaming :$DATA to file data as " << NewStream << std::endl;  

    if (!SetFileInformationByHandle(hFile, FileRenameInfo, pRename, sRename)) {  
        std::wcerr << L"[!] SetFileInformationByHandle fail, code is" << GetLastError() << std::endl;  
        CloseHandle(hFile);  
        HeapFree(GetProcessHeap(), 0, pRename);  
        return FALSE;  
    }  

    std::wcout << L"[+] Completed" << std::endl;  
    CloseHandle(hFile);  

    // 打开文件以删除  
    hFile = CreateFileW(szPath,  
                        DELETE | SYNCHRONIZE,  
                        FILE_SHARE_READ,  
                        NULL,  
                        OPEN_EXISTING,  
                        NULL, NULL);  

    if (hFile == INVALID_HANDLE_VALUE && GetLastError() == 0) {  
        std::wcout << "free memory" << std::endl;  
        HeapFree(GetProcessHeap(), 0, pRename);  
        return TRUE;  
    }  

    FILE_DISPOSITION_INFO Delete = { 0 };  
    Delete.DeleteFile = TRUE;  
    std::wcout << L"[+] Deleting ....." << std::endl;  

    if (!SetFileInformationByHandle(hFile, FileDispositionInfo, &Delete, sizeof(Delete))) {  
        std::wcerr << L"[!] SetFileInformationByHandle fail, code is  " << GetLastError() << std::endl;  
        CloseHandle(hFile);  
        HeapFree(GetProcessHeap(), 0, pRename);  
        return FALSE;  
    }  

    CloseHandle(hFile);  
    HeapFree(GetProcessHeap(), 0, pRename);  
    wprintf(L"[+] Done\n");  
    return TRUE;  
}  

int main() {  

    Self_Delete();  
    std::wcout << "stop in memory" << std::endl;  
    std::string userInput; // 声明一个字符串变量用于存储用户输入  
    std::cout << "请输入一个字符串: ";  
    std::cin >> userInput ;
    std::cout << "你输入的字符串是: " << userInput << std::endl;  
    return 0;  
}

图片

一些现实场景下的利用方法

配合自删除反调试

简单的判断反调试的代码,可以写一个定期判断的逻辑,当有人尝试分析调试的时候就自我删除

#include <windows.h>  
#include <iostream>  

int main() {  

    while(TRUE){  
        if (IsDebuggerPresent()) {  
            std::cout << "Debugger is attached." << std::endl;  
            std::getchar();
            self_deletio();
            exit(0);  
        } else {  
            std::cout << "Debugger is not attached." << std::endl;  
            std::getchar();  
            Sleep(500);  
        }  
    }  
}

高级的执行完任务自销毁

我们可以把自删除功能编入工具,实现执行完任务后就自我销毁,达到一种非常隐蔽的实战效果,进一步延长我们自己开发的工具的存活时间,这类方法更加优雅,对比调用cmd和使用MoveFileEx方式是需要重启电脑等更加隐蔽安全

钓鱼活动中的无样本攻击

设想一下这样的场景,实际的恶意程序托管在攻击者控制的服务器下。钓鱼邮件诱导攻击者访问此恶意程序,普通用户一般对此类程序不会进行备份上传,如果钓鱼成功,攻击者立刻销毁本地和云端上的样本,这可能会大大增加溯源和分析的难度,尽管我们可以通过网络流量还原样本,但是攻击者也可以在流量层面进一步做手脚,获取最初样本的难度就会有一定难度提升。

兼容性和稳定性测试

在系统win11、win10、win7、ws2012均通过测试

总结

jonasLy利用文件特性巧妙的转移了文件锁,使得文件锁移动在可选备份流,得以在Ring3层面下达到自删除,这项技术将会存在很久很久,直到微软把NTFS技术废弃掉或者修改掉文件的API底层,2年已经过去,目前来说微软没有打算修复这个缺陷。

参考资料:

https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/c54dec26-1551-4d3a-a0ea-4fa40f848eb3
https://learn.microsoft.com/en-us/windows/win32/api/
https://www.youtube.com/watch?v=lcJdlzKS_5o&ab_channel=crow
https://chat.openai.com/
https://twitter.com/jonasLyk/status/1350401461985955840
https://github.com/secur30nly/go-self-delete
https://github.com/LloydLabs/delete-self-poc/tree/main
https://owasp.org/www-community/attacks/Windows_alternate_data_stream

来源: https://xz.aliyun.com/t/13045

声明:⽂中所涉及的技术、思路和⼯具仅供以安全为⽬的的学习交流使⽤,任何⼈不得将其⽤于⾮法⽤途以及盈利等⽬的,否则后果⾃⾏承担。所有渗透都需获取授权

@学习更多渗透技能!体验靶场实战练习

免费领取安全学习资料包!

渗透工具

技术文档、书籍

 

面试题

帮助你在面试中脱颖而出

视频

基础到进阶

环境搭建、HTML,PHP,MySQL基础学习,信息收集,SQL注入,XSS,CSRF,暴力破解等等

 

应急响应笔记

学习路线

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2022年11月4日-2022年11月14日购买当前课程赠送课程学习地址如下:https://edu.csdn.net/course/detail/32434https://edu.csdn.net/course/detail/35658https://edu.csdn.net/course/detail/30223https://edu.csdn.net/course/detail/32408https://edu.csdn.net/course/detail/32429注:因赠送课程不会出现在已订阅课程列表中,以下课程学习地址一定要收藏保存。#课程服务 在线答疑:本课程设有专门的讨论留言区,学习中遇到任何问题,直接给老师留言即可,老师都会及时进行回复。远程协助:如果遇到复杂问题,老师还可进行远程协助,这个一般可不是一两百元的课程就能享受到的。源码分享:为了让大家更好的进行项目实战,老师还将课程中涉及到的所有源码分享给学员,按照视频中的提示进行下载即可。在CSDN分享C++ Qt开发知识已经有6年了,感谢众多博友对我的支持,了解到很多人对Qt的使用还是有些困扰,例如Qt环境搭建,Qt布局的使用,如何使用Qt编写复杂的界面,如何自定义非标控件,Qt如何和Web交互,Qt和后台接口如何交互等;经过这几年的整理,我决定出这套《Qt高级开发视频教程》,带领大家学习Qt高级开发知识,学习如何使用Qt开发企业级别的项目;通过本课程的学习,大家将会达到企业招聘的中高级要求。为了照顾零基础学员,本课程第一章会介绍Qt环境搭建、QtCreator / VS2019的基本使用方法,Qt整体架构、Qt信号机制,Qt内存管理等知识。即使没有Qt开发的学习经验,也能跟着课程顺利学习。课程核心知识点地图如下: 课程每章核心知识点介绍如下: 第一章:介绍Qt环境搭建、QtCreator / VS2019的基本使用方法,Qt整体架构、Qt信号机制,Qt内存管理等知识。第二章:了解到很多学员对于Qt界面布局很不熟悉,将会详细介绍Qt设计器布局,以及如何C++代码手写布局,从常见的企业级项目入手,带领大家学会各种布局的实现,例如WPS、腾讯会议、优酷、迅雷等界面的实现;界面布局会了,这是企业项目开发的第一步,还有更重要的无边框窗口,如何设计一个合理的无边框窗口很重要,第三/四章:详细介绍如何实现一个无边框窗口,如何自定义标题栏,如何实现拖拽拉伸;第四章将会介绍如何自定义非标控件,优化Qt界面。第五章:介绍Qt web混合编程,一个商用项目,必然会涉及到web交互,这也是很多Qt开发者的弱项,这一章讲详细介绍C++ Qt web混合开发。第六章:既然是做企业级项目,必然需要和后台交互,http编程也是必要的,将详细介绍http编程,用户注册,登录,后台接口请求等知识;通过第五、六章的学习,将会是你的Qt开发技术更上一层楼。第七章:介绍Qt并发编程,耗时任务处理,进程调用等知识。第八、九章:讲解 Qt 比较重要的知识,图形视图结构,以及MVD模式;通过这两章的学习,大家会对图形视图有更好的了解。第十章:本章是独立章节,主要介绍Qt中一些特殊技巧,项目编译,dpi适配、多语言等知识。第十一章:是我们的企业级项目实战:实现一个视频会议客户端,本项目可以进行多人视频通话,直播,桌面分享等功能,本项目我会从零开始,进行项目搭建,功能调试,bug fixed, 带领大家做一个企业级项目。希望通过本课程的学习,大家的C++ Qt开发技术能有质的飞越,能找到自己心仪的工作。课程中如果讲的不对的地方,请大家指出,我及时修正,我也只是一个普通开发者,也不是所有的技术都会,尽我所能,把我所会的教给大家,让我们一起为Qt的发展,尽一份绵薄之力。 下面是本课程一些项目的截图: 1 可以滑动的设置界面         2 所有图形的绘制       3 视频播放器          4 高仿youku界面         5 视频会议         相信通过本课程的学习,大家有能力实现绝大部分客户端项目,从此用C++ Qt再也不会有难写的界面。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值