0x00 前言

在渗透测试中,文件的恢复和删除好比矛与盾。

文件恢复是指恢复目标系统已删除的文件,而文件删除是指删除在目标系统上使用过的工具,避免被恢复。

0x01 简介

本文将要介绍以下内容:

  • 文件删除的原理
  • 文件恢复的原理
  • 利用PowerForensics恢复文件
  • 利用SDelete删除文件
  • 利用SDelete删除文件后,能否使用PowerForensics恢复
  • 通过文件覆盖,防止文件被恢复
  • 枚举所有进程,搜索指定文件的句柄,释放该句柄,解除文件占用,实现文件删除
  • 程序实现细节和开源代码

0x02 文件删除与恢复的原理

参考资料:

http://www.ntfs.com/ntfs_basics.htm

基本概念

Windows文件系统大都使用NTFS(New Technology File System)技术

NTFS中每个文件对应一个主文件表(Master File Table,MFT)

MFT作为文件索引,存储文件的属性

文件删除的直观理解:

只修改了MFT(即文件属性),没有修改删除文件的内容

文件恢复的直观理解:

恢复文件的MFT即可

简单测试

新建文件test.txt,写入内容0123456789

使用工具:WinHex

下载地址:

http://www.x-ways.net/winhex/

选择Tools -> Open Disk,选择盘符

找到文件test.txt,右键 -> Navigation -> Seek FILE Record

查看test.txt的MFT信息,如下图

2-1.png

MFT的结构如下图

2-2.png

注:

图片截取自http://www.blogfshare.com/detail-ntfs-filesys.html

接下来删除文件test.txt,并且清空回收站的文件

使用WinHex再次查看硬盘内容,关闭当前盘符,重新选择Tools -> Open Disk,选择盘符

弹框提示,选择更新快照,如下图

2-3.png

再次查看MFT结构,如下图

2-4.png

经对比发现,区别如下:

  • 偏移0x08
  • 偏移0x10,数值加1
  • 偏移0x16,由1变为0

在WinHex界面中选择恢复文件,成功恢复文件

注:

恢复成功的前提是该文件尚未被覆盖

综上,文件恢复的原理可以简单理解如下:

文件删除操作仅修改了文件的MFT,如果文件内容尚未被覆盖,就能恢复文件

0x03 利用PowerForensics恢复文件

文件恢复软件有多种,这里给出一个通过powershell实现文件恢复的工具:PowerForensics

工程地址:

https://github.com/Invoke-IR/PowerForensics/

下载地址:

https://github.com/Invoke-IR/PowerForensics/releases

注:

PowerForensicsv2.zip对应Powershell v2,为Win7和Sever2008默认版本

工具使用方法可直接参考xpn的博客:

https://blog.xpnsec.com/offensive-forensics/

获得所有可供恢复的文件列表,powershell命令如下:

powershell -executionpolicy bypass
import-module .\PowerForensicsv2.psd1
Get-ForensicFileRecord | Where {$_.Deleted -eq $true} | Select FullName

恢复指定的文件C:\test.txt,保存为recovered.txt:

$file = Get-ForensicFileRecord | Where {$_.FullName -eq “C:\test.txt”}
$file.CopyFile(“recovered.txt”)

0x04 防止文件被恢复

1、使用工具SDelete

下载地址:

https://docs.microsoft.com/en-us/sysinternals/downloads/sdelete

删除命令如下:

sdelete64.exe -accepteula C:\test.txt

2、通过c++实现

经过0x01的原理分析,我们知道,只要覆盖原文件即可避免文件被恢复

最简单的实现思路:

修改原文件的内容,填充随机字符串,然后再删除文件

我写了一个简单的测试代码,将待删除的文件内容先填充为0,再删除文件,即使文件被恢复,内容也全是0,可供参考的c代码如下

如下:

#include <windows.h>
int main(int argc, char *argv[])
{
   if (argc != 2)
   {
       printf(“\nOverwrite the file,avoid being restored\n\n”);
       printf(“Usage:\n”);
       printf(“%s <File Path>\n”,argv[0]);
       return 0;
   }
   printf(“[*]Try to overwrite file <%s>   “, argv[1]);
   FILE* fp;
   int err = fopen_s(&fp, argv[1], “rb+”);
   if (err != 0)
   {
       printf(“\n[!]Openfile error!”);
       return 0;
   }
   fseek(fp, 0, SEEK_END);
   int len = ftell(fp);
   char *buf = new char[len];
   memset(buf, 0, len);
   fclose(fp);
   err = fopen_s(&fp, argv[1], “wb+”);
   if (err != 0)
   {
       printf(“\n[!]Openfile error!”);
       return 0;
   }
   fwrite(buf, len, 1, fp);
   fclose(fp);
   printf(“done\n”);
   printf(“[*]Try to delete file   <%s>   “, argv[1]);
   if(DeleteFile(argv[1])!=0)
       printf(“done\n”);
   else
       printf(“error\n”);
   return 0;
}

0x05 解除文件占用

在实际删除文件的时候,常常会碰到文件被占用导致无法删除的情况

这里需要找到占用文件的进程,获得文件句柄,释放句柄后才能删除文件

实现思路如下:

  • 程序提升至debug权限
  • 枚举所有进程
  • 获得指定文件的句柄
  • 释放该句柄

具体对应到程序实现上,需要注意以下问题:

1、提升至debug权限

BOOL EnableDebugPrivilege(BOOL fEnable)
{
   BOOL fOk = FALSE;
   HANDLE hToken;
   if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
   {
       TOKEN_PRIVILEGES tp;
       tp.PrivilegeCount = 1;
       LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid);
       tp.Privileges[0].Attributes = fEnable ? SE_PRIVILEGE_ENABLED : 0;
       AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
       fOk = (GetLastError() == ERROR_SUCCESS);
       CloseHandle(hToken);
   }
   return(fOk);
}

2、枚举所有进程,获得指定文件的句柄

使用内核API NtQuerySystemInformation查询SystemHandleInformation,获得所有进程的句柄

筛选出类型为文件的句柄: ObjectTypeNumber = 0x1e

如果无法打开句柄对应的进程,留下标志位,不再重复打开该进程

过滤出有可能导致挂起的句柄,利用API WaitForSingleObject进行判断

3、释放句柄

使用内核API NtQuerySystemInformation查询SystemHandleInformation得到的句柄是“伪句柄”,无法直接释放

需要使用API DuplicateHandle将“伪句柄”转为实句柄

函数模型如下:

BOOL WINAPI DuplicateHandle(
 _In_  HANDLE   hSourceProcessHandle,
 _In_  HANDLE   hSourceHandle,
 _In_  HANDLE   hTargetProcessHandle,
 _Out_ LPHANDLE lpTargetHandle,
 _In_  DWORD    dwDesiredAccess,
 _In_  BOOL     bInheritHandle,
 _In_  DWORD    dwOptions
);

第7个参数设置为DUPLICATE_CLOSE_SOURCE,表示会释放源进程中的句柄

具体参数如下:

DuplicateHandle(processHandle, (HANDLE)handle.Handle, GetCurrentProcess(), &dupHandle, 0, 0, DUPLICATE_CLOSE_SOURCE)

完整代码已开源,地址如下:

https://github.com/3gstudent/Catch-specified-file-s-handle

代码实现了枚举当前系统的所有进程,找到指定文件的句柄,对其释放

不仅可以用来解除文件占用,而且可以用来禁用日志的某些功能

例如如果释放了system.evtx的句柄,那么日志服务无法向system.evtx写入日志,导致system.evtx下的日志失效

0x06 小结

本文简单介绍了文件删除与恢复的原理,测试工具,编写程序利用文件覆盖防止文件被恢复,并且解决了文件占用的问题,开源代码。

站在渗透的角度,一是想办法恢复目标系统的文件,二是安全删除自己的工具,避免被恢复。

站在防御的角度,清除重要的文件可使用工具SDelete进行安全删