【Day 02】- 消失在系统上的目录与文件(教你如何藏档案或目录)

Agenda

  • 资安宣言
  • 测试环境与工具
  • 学习目标
  • 技术与原理
  • 关键程序码与解释
  • 简单绕过隐藏的方法
  • 神奇的问题
  • References
  • 下期预告

资安宣言


撰写本系列文章目的在於提升资讯安全之实务能力,
并透过实作体悟到资讯安全领域的重要性,
本系列所有文章之内容皆有一定技术水平,
不得从事非法行为、恶意攻击等非法活动,
「一切不合法规之行为皆受法律所约束」,
为了避免造成公司、厂商或玩家之间困扰,
所有实作不会拿已上市产品、Online Game 等等来作范例学习,
且部分具有深度、价值之内容,将会提升一定阅读门槛(不对该技术做分析、解说),
请勿透过本系列文章所学,从事任何非法活动,请不要以身试法!!!


测试环境与工具

学习目标

  • 1.隐藏指定档案
  • 2.隐藏指定目录
  • 有图有影有真相,DEMO先看一下,先了解要学习的项目:

技术与原理

学会这个,就可以把邪恶的东西藏在硬碟里了XD

首先要先说一下,
小弟我目前还属於菜鸟阶段,正不断努力学习中,
若有发现错误或不妥之处还请不吝赐教。
欢迎大家多多留言,互相交流交流。

那麽进入今天主题~~

使用这个 Project 隐藏後的档案或目录,实际上还是存在的,
但这个 Project 不会让你/你用「正常」的方式浏览、打开(有绝对路径也一样),

关键技术:File System Mini Filter

Mini Filter 是向 Filter Manager 注册一些 callback operations,
通过这些 callback operations 来过滤一些「基於 IRP 的 I/O 操作」,

  • What is IRP?
    • 是 ... 这个吗?
    • 不对,不是这个喔 XD
    • 是下面这个,这边就不多做讲解了
    • IRP Major Function Codes
    • 学过驱动的肯定都会知道阿~

而每个选定的 IRP 都可以各自注册一个:

  1. 「前处理(preoperation callback routine)」
    • 收到 IRP 讯号,但还没执行操作。
  2. 「後处理(postoperation callback routine)」
    • 收到 IRP 讯号,且操作已经执行。

更多详细内容可看官方文件,这边就不多做说明:

再来就是你/你可能会问:

  • 一个系统上不可能只有一个 Driver 对吧?
  • 那如果同时很多 Driver 向 Filter Manager 注册一些 callback operations,
  • 执行的顺序如何决定?

这就要讲到 Mini Filter 的小规则,
执行的顺序会由 Altitude 来决定,
当 Altitude 越高,就会越先执行你/你所注册的 preoperation,
根据 Altitude 的大小依序由大到小执行 preoperation,
当这个 IRP 操作已经执行或应该说已经执行完成,
则会依照 Altitude 的大小依序由小到大执行 postoperation。

完整解释就是官方文件上的这张图:

图片来源:Filter Manager Concepts

Altitude 怎麽看?
可以用 YDark 这个ARK工具找到:

Altitude 怎麽改、又是如何决定?
/images/emoticon/emoticon39.gif

这个 Project 是过滤以下「IRP」的操作来达到隐藏效果,

  1. IRP_MJ_DIRECTORY_CONTROL

  2. IRP_MJ_CREATE

    • 当尝试读取、新增档案、目录等等 ... ...,系统就会发送这个 IRP,
    • 所以过滤这个 IRP 就可以达到档案(文件)或目录打不开的效果。
    • IRP_MJ_CREATE (IFS)

好,那简单说明一下这个 Project 的流程:

  1. (这里就不讲了,有能力写驱动的夥伴会知道要做什麽复杂的动作)
  2. 向 Filter Manager 注册一些 callback operations
  3. User mode 向 Kernel mode 传递要隐藏的档案或目录
  4. 将收到要隐藏的档案或目录资讯各自存放到一张表中(这边称A、B表)
  5. 当收到 IRP 请求,确认请求目标是否存在於A、B表中
  6. 若存在於A、B表中将其从请求目标中删除
  7. 最後就会看到达成的效果

最後你/你可能会问:

  • 那如何向 Filter Manager 注册 callback operations?
  • 这个部分就是门槛了,其实在MSDN上都有教学,也可以直接看 Project
  • 加上小弟时间有限,不可能从零开始写,还请见谅。 >,<

关键程序码与解释

  • Project 连结:hidden
  • 这个是网路上公开的 Project 鸭~~
  • 虾?你/你问怎麽编译、怎麽用?
  • 我的时间不多了,这部分就交给你自己罗!(README有写)
  • Tip1:编译的时候要下一点 Command,因为有符号解析问题。
  • Tip2:Add -> #define _NO_CRT_STDIO_INLINE
  • 後面某几天还会讲这个 Project 的隐藏 Process 手法,敬请期待哦!

程序码

  • HiddenFile
    • FsFilter.c
    NTSTATUS AddHiddenFile(PUNICODE_STRING FilePath, PULONGLONG ObjId)
    {
    
        --- --- --- ---
        --- --- --- ---
    
        --- --- --- ---
        --- --- --- ---
    
        //将要隐藏的档案资讯放进 g_excludeFileContext
        status = AddExcludeListFile(g_excludeFileContext, &normalized, ObjId, 0);
        if (NT_SUCCESS(status))
            LogTrace("Added hidden file:%wZ", &normalized);
        else
            LogTrace("Adding hidden file failed with code:%08x, path:%wZ", status, &normalized);
    
        --- --- --- ---
        --- --- --- ---
    
    }
    
  • HiddenDir
    • FsFilter.c
    NTSTATUS AddHiddenDir(PUNICODE_STRING DirPath, PULONGLONG ObjId)
    {
    
        --- --- --- ---
        --- --- --- ---
    
        --- --- --- ---
        --- --- --- ---
    
        //将要隐藏的目录资讯放进 g_excludeDirectoryContext
        status = AddExcludeListDirectory(g_excludeDirectoryContext, &normalized, ObjId, 0);
        if (NT_SUCCESS(status))
            LogTrace("Added hidden dir:%wZ", &normalized);
        else
            LogTrace("Adding hidden dir failed with code:%08x, path:%wZ", status, &normalized);
    
        --- --- --- ---
        --- --- --- ---
    }
    
  • IRP_MJ_DIRECTORY_CONTROL
    • FsFilter.c
    • (我猜应该没人看得懂注解?)
    • (我尽力了>.<)
    //每当有 IRP_MJ_DIRECTORY_CONTROL 讯号都走进这个 Func
    //这个 Project 把事件写在 PostOperation 中,PerOperation 没写。
    FltDirCtrlPostOperation(PFLT_CALLBACK_DATA Data, PCFLT_RELATED_OBJECTS FltObjects, PVOID CompletionContext, FLT_POST_OPERATION_FLAGS Flags)
    {
        PFLT_PARAMETERS params = &Data->Iopb->Parameters;
    
        --- --- --- ---
        --- --- --- ---
    
        __try
        {
            status = STATUS_SUCCESS;
    
            // _FILE_INFORMATION_CLASS
    
            switch (params->DirectoryControl.QueryDirectory.FileInformationClass)
            {
    
            case FileBothDirectoryInformation:
                //XP && XP 以下的系统走这里。
                status = CleanFileBothDirectoryInformation(xxx);
                break;
    
            --- --- --- ---
            --- --- --- ---
    
            case FileIdBothDirectoryInformation:
                //Vista && Vista 以上的系统走这里。
                status = CleanFileIdBothDirectoryInformation((PFILE_ID_BOTH_DIR_INFORMATION)params->DirectoryControl.QueryDirectory.DirectoryBuffer, fltName);
                break;
            }
    
            Data->IoStatus.Status = status;
        }
    
        --- --- --- ---
        --- --- --- ---
    
    }
    
    • CleanFileIdBothDirectoryInformation();
    CleanFileIdBothDirectoryInformation(PFILE_ID_BOTH_DIR_INFORMATION info, PFLT_FILE_NAME_INFORMATION fltName)
    {
        //这个FUNC传进来的第一个参数 info 是一个缓冲区
        //这个缓冲区里保存着 User 尝试存取的目录的各种资讯
        //要做的事情很简单:
        //1.列举这个缓冲区
        //2.过滤掉要隐藏的目录或文件就能达到隐藏的效果
        //以下 code 很难懂,请在头脑清晰时在阅读
    
        PFILE_ID_BOTH_DIR_INFORMATION nextInfo, prevInfo = NULL;
        UNICODE_STRING fileName;
        UINT32 offset, moveLength;
        BOOLEAN matched, search;
        NTSTATUS status = STATUS_SUCCESS;
    
        offset = 0;
        search = TRUE;
    
        do
        {
            fileName.Buffer = info->FileName;
            fileName.Length = (USHORT)info->FileNameLength;
            fileName.MaximumLength = (USHORT)info->FileNameLength;
    
            //检查当前请求的目录或档案是否存在於要隐藏的列表中。
            //因为要隐藏的目录或档案分别放在不同的表
            //所以这边写了 if else
            if (info->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                matched = CheckExcludeListDirFile(xxx);
            else
                matched = CheckExcludeListDirFile(xxxxxxx);
    
            //发现了当前请求的目录或档案是要被隐藏的。
            if (matched)
            {
                BOOLEAN retn = FALSE;
    
                //如果在之前的 do 循环中没发现要隐藏的目录或档案才会走这里。
                if (prevInfo != NULL)
                {
                    //如果不是最後一个位置。
                    if (info->NextEntryOffset != 0)
                    {
                        //纪录目前位置,并计算下一个位置的 offset。
                        //这里可以直接看做是往前了一个位置(略过了目前这个位置)。
                        prevInfo->NextEntryOffset += info->NextEntryOffset;
                        offset = info->NextEntryOffset;
                    }
                    else
                    {
                        //已经走到最後没东西了。
                        prevInfo->NextEntryOffset = 0;
                        status = STATUS_SUCCESS;
                        retn = TRUE;
                    }
    
                    //不管现在 info 是什麽,通通清空,
                    //因为会走到这有两个原因:
                    //1.已经走到最後没东西了,清空不影响,稍後要 return 了。
                    //2.目前这个位置是要略过的,清空不影响(直接略过了目前位置,前往下一个位置了)。
                    RtlFillMemory(info, sizeof(FILE_ID_BOTH_DIR_INFORMATION), 0);
                }
                else //第一次发现要隐藏档案或目录时走这个else。
                {
                    //判断是不是最後一个位置。
                    if (info->NextEntryOffset != 0)
                    {
                        //略过现在这个位置(当前的位置就是要被隐藏的),所以往前移动一个。
                        nextInfo = (PFILE_ID_BOTH_DIR_INFORMATION)((PUCHAR)info + info->NextEntryOffset);
                        moveLength = 0;
    
                        //把这之後的所有位置记录起来。
                        while (nextInfo->NextEntryOffset != 0)
                        {
                            moveLength += nextInfo->NextEntryOffset;
                            nextInfo = (PFILE_ID_BOTH_DIR_INFORMATION)((PUCHAR)nextInfo + nextInfo->NextEntryOffset);
                        }
    
                        moveLength += FIELD_OFFSET(FILE_ID_BOTH_DIR_INFORMATION, FileName) + nextInfo->FileNameLength;
    
                        //覆盖原本 info,这样就略过了要隐藏的档案或目录。
                        RtlMoveMemory(info, (PUCHAR)info + info->NextEntryOffset, moveLength);//continue
    
                        //这里不确定後面是否还有要被隐藏的目录或档案
                        //所以让它继续往下走(continue)。
                    }
                    else
                    {
                        //已经走到最後没东西了。
                        status = STATUS_NO_MORE_ENTRIES;
                        retn = TRUE;
                    }
                }
    
                LogTrace("Removed from query: %wZ\\%wZ", &fltName->Name, &fileName);
    
                if (retn)
                    return status;
    
                info = (PFILE_ID_BOTH_DIR_INFORMATION)((PCHAR)info + offset);
                continue;
            }
    
            //往下一个位置移动
            offset = info->NextEntryOffset;
            prevInfo = info;
            info = (PFILE_ID_BOTH_DIR_INFORMATION)((PCHAR)info + offset);
    
            if (offset == 0)
                search = FALSE;
        } while (search);
    
        return status;
    }
    
  • IRP_MJ_CREATE
FLT_PREOP_CALLBACK_STATUS FltCreatePreOperation(
	_Inout_ PFLT_CALLBACK_DATA Data,
	_In_ PCFLT_RELATED_OBJECTS FltObjects,
	_Flt_CompletionContext_Outptr_ PVOID *CompletionContext)
{

            --- --- --- ---
            --- --- --- ---

            --- --- --- ---
            --- --- --- ---

            --- --- --- ---
            --- --- --- ---

	if (!(options & FILE_DIRECTORY_FILE))
	{
		// If it is create file event
		if (CheckExcludeListDirectory(g_excludeFileContext, &fltName->Name))
			neededPrevent = TRUE;
	}

	// If it is create directory/file event
	if (!neededPrevent && CheckExcludeListDirectory(g_excludeDirectoryContext, &fltName->Name))
		neededPrevent = TRUE;

	FltReleaseFileNameInformation(fltName);

	if (neededPrevent)
	{
        //如果有 create directory/file event
        //而且操作对象在隐藏的列表中
        //就回送STATUS_NO_SUCH_FILE
        //(底下有放效果图)
		LogTrace("Operation has been cancelled for: %wZ", &Data->Iopb->TargetFileObject->FileName);
		Data->IoStatus.Status = STATUS_NO_SUCH_FILE;
		return FLT_PREOP_COMPLETE;
	}

	return FLT_PREOP_SUCCESS_NO_CALLBACK;
}
  • STATUS_NO_SUCH_FILE 对照图

  • 不知道那个 Project 什麽时候会不见?放张图片在这好了。(20210915)

简单绕过隐藏的方法

  • 1.打开 ARK 工具:Windows-Kernel-Explorer
    • ARK 工具有很多,这边拿 Windows-Kernel-Explorer 作范例
    • 例如:PChunter、YDArk... ... ...
  • 2.点选 Mini Filter
  • 3.找到这两个
  • 4.通通 Disable
  • 5.你/你就会发现隐藏的目录或档案出现并且可以开启罗!
    • ^^
    • /images/emoticon/emoticon07.gif
  • 6.假设只 Disable IRP_MJ_DIRECTORY_CONTROL
    • 因为上方有讲到有 IRP_MJ_CREATE 的关系
    • Data->IoStatus.Status = STATUS_NO_SUCH_FILE;
  • 小问题
    • 1.有什麽能自动化找出隐藏的档案吗?
      • 有,写 Driver 和它对抗
    • 2.我能写一个 Driver 来对抗这个 Driver 吗?
      • 有时间的话我也会想写来玩玩 XD
      • 期待留言区有人实作出来~~

神奇的问题

大家看到这边有没有想到什麽问题?
如果没有我来提几个SB问题 XD

  1. 如果我隐藏目录的参数是下这样会发生什麽事?
    • xxx.exe /hide dir C:\
    • xxx.exe /hide dir D:\
    • 1... ...
    • 2... ... ...
    • 3... ... ... ...
    • 好~ 公布解答:
  2. 这种隐藏的方式能避开防毒软件的侦测吗?
    • 听说防毒软件也都具备Driver,所以 maybe?..

大家可以想一想这些问题和发生的原因,都留个言讨论一下吧 XD
有空再来给大家回复、解答罗~~~
我们下期见 o( ̄▽ ̄)ブ

References

下期预告


<<:  DAY2 - 找寻生活中的问题

>>:  Day02 何谓Django?

Alpine Linux Porting (1.99) ES LEBT !!!

惯例先上成果图XD https://asciinema.org/a/439607 终於开机开到she...

番外篇(1)一起来做计算机!

距离完赛已经过了一阵子,前天想自己刻刻看计算机,拆解任务、实际执行後才发现知识量不足,导致无法顺利完...

【第10天】训练模型-预训练模型

摘要 Keras Application 预训练模型种类 模型选用考量 选用结果 内容 Keras ...

Day09: 【TypeScript 学起来】物件型别 Object Types : Arrays / Function

Q: 如何反驳「工程师离开电脑之後就是个废物」的这个说法? A: 不不不,很多工程师在电脑前面也是...

追求JS小姊姊系列 Day7 -- 郑列展现的工具力(中)

前情提要 郑列展现了自己的工具力(快速找杂物),但似乎还有别的? 郑列:我看你是完全不懂啊,我还有别...