iT邦幫忙

2021 iThome 鐵人賽

DAY 2
1
Security

打通任督二脈奇幻之旅 - 用 30 天探索 Windows 底層運作原理系列 第 2

【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

下期預告


上一篇
【Day 01】- 孤燈蓑冠衣,獨究程式碼:前言與大綱
下一篇
【Day 03】- 打針!打針!從 R0 注入的那件事!
系列文
打通任督二脈奇幻之旅 - 用 30 天探索 Windows 底層運作原理15

尚未有邦友留言

立即登入留言