iT邦幫忙

2021 iThome 鐵人賽

DAY 11
0
Security

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

【Day 11】- 再次創造 Ghost Process,這次找不到了吧哈哈(基於修改 PspCidTable 隱藏的 Rookit)

Agenda

  • 資安宣言
  • 測試環境與工具
  • 學習目標
  • 前情提要
  • 技術原理與程式碼
  • References
  • 下期預告

資安宣言


撰寫本系列文章目的在於提升資訊安全之實務能力,
並透過實作體悟到資訊安全領域的重要性,
本系列所有文章之內容皆有一定技術水平,
不得從事非法行為、惡意攻擊等非法活動,
「一切不合法規之行為皆受法律所約束」,
為了避免造成公司、廠商或玩家之間困擾,
所有實作不會拿已上市產品、Online Game 等等來作範例學習,
且部分具有深度、價值之內容,將會提升一定閱讀門檻(不對該技術做分析、解說),
請勿透過本系列文章所學,從事任何非法活動,請不要以身試法!!!


測試環境與工具

學習目標

  • 1.了解 PspCidTable
  • 2.透過 PspCidTable 列舉 Process
  • 3.將 Process 從 PspCidTable 中刪除,完成隱藏

前情提要

在昨日的文章中:
【Day 10】- 藏起來的 Process 真的看不見摸不著?(講解找出斷鏈後的 Process 方法)

已經有說到如何找出修改 ActiveProcessLinks 隱藏的 Process,
到文末時,
有稍微提到 PsLookupProcessByProcessId 會透過 PspCidTable 拿對應 PID 的 Object,
透過這樣的方式拿到 EProcess Address,
那今天就要來說說 PspCidTable 是什麼東西。

上一篇與上上篇還沒看得可以先去看看哦~

技術原理與程式碼

首先開始前要先說一下,
小弟我目前還屬於菜鳥階段,正不斷努力學習中,
若有發現錯誤或不妥之處還請不吝賜教。
歡迎大家多多留言,互相交流交流。

那就開始今天的主題吧~~
/images/emoticon/emoticon24.gif

什麼是 PspCidTable ?
A Process's、A Thread's Kernel Object Handle Table

每個 Process 都有自己的 Handle Table,
Index 就是 Process 的 PID、TID,
要操作 Process 時(應該說要拿 EProcess Address 時),
通過 Index(PID、TID)存取 Handle Table,然後返回 object pointer。

那 PspCidTable 長什麼樣子?
在 WinDbg 中輸入 dt _HANDLE_TABLE

lkd> dt _HANDLE_TABLE
nt!_HANDLE_TABLE
   +0x000 NextHandleNeedingPool : Uint4B
   +0x004 ExtraInfoPages   : Int4B
   +0x008 TableCode        : Uint8B
   +0x010 QuotaProcess     : Ptr64 _EPROCESS
   +0x018 HandleTableList  : _LIST_ENTRY
   +0x028 UniqueProcessId  : Uint4B
   +0x02c Flags            : Uint4B
   +0x02c StrictFIFO       : Pos 0, 1 Bit
   +0x02c EnableHandleExceptions : Pos 1, 1 Bit
   +0x02c Rundown          : Pos 2, 1 Bit
   +0x02c Duplicated       : Pos 3, 1 Bit
   +0x02c RaiseUMExceptionOnInvalidHandleClose : Pos 4, 1 Bit
   +0x030 HandleContentionEvent : _EX_PUSH_LOCK
   +0x038 HandleTableLock  : _EX_PUSH_LOCK
   +0x040 FreeLists        : [1] _HANDLE_TABLE_FREE_LIST
   +0x040 ActualEntry      : [32] UChar
   +0x060 DebugInfo        : Ptr64 _HANDLE_TRACE_DEBUG_INFO

首先,先來逆向 PsLookupProcessByProcessId:

  1. 使用 Administrator 權限打開 WinDbg
  2. 選 Kernel Debug...
    • (需要打開測試模式,依照提示的指示進行操作)
  3. 選 Local 按確定
    • (需要打開測試模式,依照提示的指示進行操作)
  4. 輸入:uf PsLookupProcessByProcessId
    • 反彙編 PsLookupProcessByProcessId
    lkd> uf PsLookupProcessByProcessId
    nt!PsLookupProcessByProcessId:
    fffff800`09de0ad0 48895c2408      mov     qword ptr [rsp+8],rbx
    fffff800`09de0ad5 4889742410      mov     qword ptr [rsp+10h],rsi
    fffff800`09de0ada 48897c2418      mov     qword ptr [rsp+18h],rdi
    fffff800`09de0adf 4156            push    r14
    fffff800`09de0ae1 4883ec20        sub     rsp,20h
    fffff800`09de0ae5 65488b342588010000 mov   rsi,qword ptr gs:[188h]
    fffff800`09de0aee 4c8bf2          mov     r14,rdx
    fffff800`09de0af1 66ff8ee6010000  dec     word ptr [rsi+1E6h]
    fffff800`09de0af8 b203            mov     dl,3
    fffff800`09de0afa e8412afcff      call    nt!PspReferenceCidTableEntry (fffff800`09da3540)
    

因為時間、文章長度的關係,我就直接講重點,
這邊看到 call 了一個 PspReferenceCidTableEntry,直接跟進去看,

  1. 輸入:uf fffff800`09da3540
    lkd> uf fffff800`09da3540
    nt!PspReferenceCidTableEntry:
    fffff800`09da3540 48896c2420      mov     qword ptr [rsp+20h],rbp
    fffff800`09da3545 56              push    rsi
    fffff800`09da3546 4883ec20        sub     rsp,20h
    fffff800`09da354a 488b05afbcedff  mov     rax,qword ptr [nt!PspCidTable (fffff800`09c7f200)]
    

不遠處就可以看到拿了 PspCidTable Address,
所以可以直接透過這個 API 來定位 PspCidTable,
PspCidTable 是沒有導出的,無法直接呼叫使用,
需要手動透過特徵定位。

定位方式:
一、取得 PsLookupProcessByProcessId Address
二、找到 call PspReferenceCidTableEntry
(特徵直接抓 0xe8 即可)
三、進入 PspReferenceCidTableEntry 這個 call
四、找到 rax,qword ptr [nt!PspCidTable (xxx)]
(特徵直接抓 0x48、0x8b 0x05 即可)
五、取得 PspCidTable Address

  1. 我這裡跳過一些東西,直接講重點囉,為了節省一點時間
    • 繼續往下看會發現
    • 它會把指向 Level1 的 Pointer
    • 先 sar 10h,然後 and FFFFFFFFFFFFFFF0h
    • 就是 (Address >> 0x10) & 0xfffffffffffffff0
    • 看的出來這是在做解密的動作,
    • 所以稍後列舉到 Level2 時,
    • 要把指向 Level1 的 Pointer Address 解密
  2. 輸入 dp PspCidTable
    • 其實可以直接拿 PspCidTable 地址
    • 看看底下的結果和剛剛是一樣的 fffff800`09c7f200
    lkd> dp PspCidTable
    fffff800`09c7f200  ffffcf01`82216cc0 fffff800`09c7f208
    fffff800`09c7f210  fffff800`09c7f208 ffffe307`15ec0480
    fffff800`09c7f220  00000000`00000000 00000000`00000000
    fffff800`09c7f230  00001000`00010000 ffffe307`15ec7800
    fffff800`09c7f240  00000000`00000000 00000002`00005e03
    fffff800`09c7f250  00000000`00000000 00000000`00000000
    fffff800`09c7f260  00000000`00000000 fffff800`0a158000
    fffff800`09c7f270  fffff800`09886000 00000000`00000000
    
  3. 輸入:dt _HANDLE_TABLE ffffcf01`82216cc0
    • 拿 TableCode,這是指向 HANDLE TABLE 的 Pointer
    lkd> dt _HANDLE_TABLE ffffcf01`82216cc0
    nt!_HANDLE_TABLE
       +0x000 NextHandleNeedingPool : 0x1800
       +0x004 ExtraInfoPages   : 0n0
       +0x008 TableCode        : 0xffffcf01`87f59001
       +0x010 QuotaProcess     : (null) 
       +0x018 HandleTableList  : _LIST_ENTRY [ 0x?? - 0x?? ]
       +0x028 UniqueProcessId  : 0
       +0x02c Flags            : 1
       +0x02c StrictFIFO       : 0y1
       +0x02c EnableHandleExceptions : 0y0
       +0x02c Rundown          : 0y0
       +0x02c Duplicated       : 0y0
       +0x02c RaiseUMExceptionOnInvalidHandleClose : 0y0
       +0x030 HandleContentionEvent : _EX_PUSH_LOCK
       +0x038 HandleTableLock  : _EX_PUSH_LOCK
       +0x040 FreeLists        : [1] _HANDLE_TABLE_FREE_LIST
       +0x040 ActualEntry      : [32]  ""
       +0x060 DebugInfo        : (null) 
    

再來就是 Table 是有分 Level 的:

  • Level1:Process's、Thread's Object
  • Level2:指向 Level1 的 Pointer
  • Level3:指向 Level2 的 Pointer

判斷 Table 的 Level:

  • (看指向 HANDLE TABLE 的 Pointer 最後兩位是多少)
  • 00 -> Level1(_Handle_Table_Entry)
  • 01 -> Level2(存放指針)
  • 10 -> Level3(存放指針)

這裡的 TableCode:0xffffcf01`87f59001 結尾是 01
代表這張表是 Level2

  1. 輸入:dp 0xffffcf01`87f59000
    • 結果:
    lkd> dp 0xffffcf01`87f59000
    ffffcf01`87f59000  ffffcf01`8221a000 ffffcf01`87f5a000
    ffffcf01`87f59010  ffffcf01`88585000 ffffcf01`88b50000
    ffffcf01`87f59020  ffffcf01`88fe9000 ffffcf01`89d39000
    ffffcf01`87f59030  00000000`00000000 00000000`00000000
    ffffcf01`87f59040  00000000`00000000 00000000`00000000
    ffffcf01`87f59050  00000000`00000000 00000000`00000000
    ffffcf01`87f59060  00000000`00000000 00000000`00000000
    ffffcf01`87f59070  00000000`00000000 00000000`00000000
    
  2. 輸入:dp ffffcf01`8221a000
    • 現在來到 Level1
    • 可以看到底下的 Address 是很奇怪的
    • 需要解密,解密方法就是上面說到的
    lkd> dp ffffcf01`8221a000
    ffffcf01`8221a000  00000000`00000000 00000000`00000000
    ffffcf01`8221a010  e30715ed`8040ff57 00000000`00000000
    ffffcf01`8221a020  e30718f8`5080fe9b 00000000`00000000
    ffffcf01`8221a030  e30715ec`8700fff7 00000000`00000000
    ffffcf01`8221a040  e30715ec`e040fff7 00000000`00000000
    ffffcf01`8221a050  e30715ed`3700fff7 00000000`00000000
    ffffcf01`8221a060  e30715eb`9040fff7 00000000`00000000
    ffffcf01`8221a070  e30715e4`e040fff7 00000000`00000000
    
  3. 解密
    • e30715ed`8040ff57
    • 這個地址需要做解密
    • (Address >> 0x10) & 0xfffffffffffffff0
    • (0xe30715ed8040ff57 >> 0x10) & 0xfffffffffffffff0
    • =
    • 0xFFFFE30715ED8040
  4. 輸入:dt _eprocess 0xFFFFE30715ED8040
    • 結果:
    lkd> dt _eprocess 0xFFFFE30715ED8040
    nt!_EPROCESS
       +0x000 Pcb              : _KPROCESS
       +0x2d8 ProcessLock      : _EX_PUSH_LOCK
    
       --- --- ---
       --- --- ---
       --- --- ---
    
       +0x450 ImageFileName    : [15]  "System"
    
       --- --- ---
       --- --- ---
       --- --- ---
    

看到了吧,列舉到了系統 Process:System

補充:
每張 Table 在 x64 的大小是 0x1000(4096),
Level2 和 Level3 放的是 Pointer,大小是 8,
所以 Level2 和 Level3 都要列舉 4096 / 8 = 516 次,
Level1 是 _handle_table_entry 結構,所以大小是 16,
所以 Level1 要列舉 4096 / 16 = 256 次。

列舉結果:

所以說了這麼多要怎麼隱藏ㄋ?
請看 hidden Project 的 PsMonitor.c
我放一份在這裡:

VOID UnlinkProcessFromCidTable(PProcessTableEntry Entry)
{
	PVOID PspCidTable = GetPspCidTablePointer();

	if (!PspCidTable)
	{
		LogWarning("Can't unlink process %Iu from PspCidTable(NULL)", Entry->processId);
		return;
	}

	CidTableContext context;
	context.ProcessId = Entry->processId;
	context.Found = FALSE;

	EX_ENUMERATE_HANDLE_ROUTINE routine = (IsWin8OrAbove() ? (EX_ENUMERATE_HANDLE_ROUTINE)&RemoveHandleCallbackWin8 : &RemoveHandleCallback);
	if (!ExEnumHandleTable(PspCidTable, routine, &context, NULL))
	{
		LogWarning("Can't unlink process %Iu from PspCidTable", Entry->processId);
		return;
	}

	if (!context.Found)
	{
		LogWarning("Can't find process %Iu in PspCidTable", Entry->processId);
		return;
	}

	// Hack for Windows Vista, 7, to avoid lock bit leak
	if (!IsWin8OrAbove())
	{
		context.Entry->u1.Object = NULL;
		context.Entry->u2.GrantedAccess = 0;
	}

	Entry->cidEntryBackup = context.EntryBackup;
	Entry->cidEntry = context.Entry;
}

ExEnumHandleTable

BOOLEAN RemoveHandleCallback(PHANDLE_TABLE_ENTRY HandleTableEntry, HANDLE Handle, PVOID EnumParameter)
{
	PCidTableContext context = (PCidTableContext)EnumParameter;

	if (context->ProcessId != Handle)
		return FALSE;

	context->Found = TRUE;
	context->Entry = HandleTableEntry;
	context->EntryBackup = *HandleTableEntry;

	LogInfo(
		"PID %Iu has been removed from PspCidTable, entry:%p, object:%p, access:%08x",
		Handle,
		HandleTableEntry,
		context->EntryBackup.u1.Object,
		context->EntryBackup.u2.GrantedAccess
	);

	return TRUE;
}

notepad.exe 隱藏前:

在 R0 下,暴力列舉 PID,然後 CALL PsLookupProcessByProcessId:

在 R3 下,暴力列舉 PID,然後 CALL OpenProcess:

隱藏 notepad.exe:

隱藏後在 R0 下,暴力列舉 PID,然後 CALL PsLookupProcessByProcessId:

隱藏後在 R3 下,暴力列舉 PID,然後 CALL OpenProcess:

隱藏後在 R0 下,列舉 PspCidTable:

沒人找的到我了吧!哈哈哈哈哈~
/images/emoticon/emoticon01.gif

好啦,明天開大絕,
教你/妳把 Windows 裡所有藏起來的 Process(Rookit)找出來 :D

好了,今天就講到這結束了,
大家若有發現哪裡寫得不好或錯誤的地方,都留個言討論一下吧 XD
那我們下期見 o( ̄▽ ̄)ブ

References

下期預告


上一篇
【Day 10】- 藏起來的 Process 真的看不見摸不著?(講解找出斷鏈後的 Process 方法)
下一篇
【Day 12】- 找出看不見也摸不著的 Process,終極辦法!
系列文
打通任督二脈奇幻之旅 - 用 30 天探索 Windows 底層運作原理15

尚未有邦友留言

立即登入留言