iT邦幫忙

2021 iThome 鐵人賽

DAY 3
1
Security

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

【Day 03】- 打針!打針!從 R0 注入的那件事!

Agenda

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

資安宣言


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


測試環境與工具

學習目標

  • 1.能將 DLL 注入到 Process 中
  • 2.能將 DLL 注入到受保護的 Process 中

技術原理與程式碼

  • 今天要講的是一個公開的 Project:Blackbone
  • 在閱讀這篇文章前,你/妳應該先了解一下 DLL_injection

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

然後這篇文章不是從零開始講,所以有些東西需要某些基本知識才能完全掌握。

研究過或是寫過遊戲外掛的人,都應該會知道這個知名的「Blackbone」Library 吧?
Blackbone 是一個 Windows memory hacking library,
程式碼寫得挺漂亮的,可以直接拿來學習、使用(前提要先看懂,因為只是 library),
這個 Library 有非常多實用的功能,今天只有要講一個小功能:INJECT_DLL

首先,先來說一下從 Kernel mode(R0) 注入 DLL 的好處:

  1. 擁有系統級別的「操作權」
  2. 能任意、輕鬆的存取/修改 Memory(要有能力)
  3. 假設你/妳有「能力」的話,想做「任何事情」都不是問題

再來,根據我的經驗告訴我一件事,DLL Injection 不是一項 「等待被解決的問題」

What is 不是一項 「等待被解決的問題」 ???
意思是說 DLL Injection 這件事情本身就不是問題,
換句話說就是 DLL Injection 太容易了,手法非常非常多,
就技術面來看,基本上保護得再好都有方法能繞過,然後注入,
所以你/妳還在為 DLL Injection 這件事煩惱嗎?
/images/emoticon/emoticon47.gif

接著,講一下這個 Project 所採用的三種注入技術:
(今天只講第一種,在寫下去內容太多,而且小弟我時間不多 >,<)
(有時間再來寫一篇)

  1. LdrLoadDll + ZwCreateThreadEx
  2. LdrLoadDll + APC
  3. Manual map

首先,程式碼位於 Inject.c 的 BBInjectDll() 中,

一開始可以看到:
透過 PsLookupProcessByProcessId 拿 Process(注入目標) 的 EProcess

status = PsLookupProcessByProcessId( (HANDLE)pData->pid, &pProcess );

What is EProcess?

  • 在 Kernel 還有個東西叫做「KPROCESS」,這邊就不講,直接講「EProcess」
  • 用一句話做簡單、簡潔有力的說明就是:儲存著 Process 的各種資訊的一個 Structure
  • EProcess 的「E」代表 Execute 的意思

這個 EProcess Structure 長怎樣?要怎麼看?

  1. 可以使用 Administrator 權限打開 WinDbg
  2. 選 Kernel Debug
    • (可能需要打開測試模式,自己看跳出來的說明囉!)
  3. 選 Local 按下確定
    • (可能需要打開測試模式,自己看跳出來的說明囉!)
  4. 輸入 !process 0 0 查看目前打開的所有 Process
    • 內容大概長這樣:
    lkd> !process 0 0
    **** NT ACTIVE PROCESS DUMP ****
    PROCESS ffffc182be847040
        SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
        DirBase: 001ab000  ObjectTable: ffff848ddf014040  HandleCount: 2192.
        Image: System
    
    PROCESS ffffc182bff3a5c0
        SessionId: none  Cid: 0144    Peb: 775efe7000  ParentCid: 0004
        DirBase: 011b8000  ObjectTable: ffff848ddf5ab500  HandleCount:  52.
        Image: smss.exe
    
    --- --- --- --- --- ---
    --- --- --- --- --- ---
    --- --- --- --- --- ---
    
    PROCESS ffffc182be948080
        SessionId: 1  Cid: 0888    Peb: 9a982f9000  ParentCid: 11c0
        DirBase: 10b2f000  ObjectTable: ffff848debb41600  HandleCount: 345.
        Image: windbg.exe
    
    PROCESS ffffc182c08835c0
        SessionId: 1  Cid: 1528    Peb: d2ccd46000  ParentCid: 11c0
        DirBase: 109a1000  ObjectTable: ffff848debc60800  HandleCount: 262.
        Image: notepad.exe
    
  5. 假設要看 notepad.exe 的 EProcess 輸入 dt _eprocess ffffc182c08835c0
    • ffffc182c08835c0 就是 notepad.exe 的 _EPROCESS 地址
    • 今天不講 EProcess 哦,只是帶過一下,後面幾天就會來詳細談談 EProcess ^^
    • 然後就會看到:
    lkd> dt _eprocess ffffc182c08835c0
    nt!_EPROCESS
       +0x000 Pcb              : _KPROCESS ? //剛剛說到的 KPROCESS
       +0x2d8 ProcessLock      : _EX_PUSH_LOCK
       +0x2e0 UniqueProcessId  : 0x00000000`00001528 Void
       +0x2e8 ActiveProcessLinks : _LIST_ENTRY [ xxx - xxx ]
       +0x2f8 RundownProtect   : _EX_RUNDOWN_REF
    
        --- --- --- --- --- ---
        --- --- --- --- --- ---
        --- --- --- --- --- ---
    
       +0x82c MitigationFlags2 : 0
       +0x82c MitigationFlags2Values : <unnamed-tag>
       +0x830 PartitionObject  : 0xffffc182`be8489f0 Void
    
       //end
    

好的,現在講回來程式碼,還記得講到哪裡?... 剛拿到注入目標的 EProcess 而已。

繼續往下看會看到正在判斷 pData->type 是什麼類型,
第一個可以看到是 pData->type == IT_MMap(不是這次要講的)

繼續往下看會看到正在拿 Ntdll.dll 的 Base

pNtdll = BBGetUserModule( pProcess, &ustrNtdll, isWow64 );

這邊有判斷目標是不是 Wow64,什麼?你/妳要問什麼是 Wow64
這個不是本章重點,就略過去囉~

繼續往下看會看到正在拿 LdrLoadDll address(實作方法就不說惹)

LdrLoadDll = BBGetModuleExport( pNtdll, "LdrLoadDll", pProcess, NULL );

繼續往下看會看到厲害的東西:

if (PsIsProtectedProcess( pProcess ))
{
    prot.pid         = pData->pid;
    prot.protection  = Policy_Disable;
    prot.dynamicCode = Policy_Disable;
    prot.signature   = Policy_Disable;
    BBSetProtection( &prot );
}

這段 CODE 的用意就是要把 Process 保護拿掉,就是要把以下的值清空,
(這邊不帶大家看 CODE 了,有興趣的可以自己看、自己找)
在 Win10 16299 的 EProcess 中,
有個位置叫做:

+0x6ca Protection       : _PS_PROTECTION

Protection 裡面有三樣東西:

[+0x000] Level                    : 0x0 [Type: unsigned char]
[+0x000 ( 2: 0)] Type             : 0x0 [Type: unsigned char]
[+0x000 ( 3: 3)] Audit            : 0x0 [Type: unsigned char]
[+0x000 ( 7: 4)] Signer           : 0x0 [Type: unsigned char]

所以這些代表什麼? o(≧∀≦)o
好了,我都提到這邊了,請自行谷歌~(>_<。)\,
這個部分涉及部分技術,而且也不是本章重點就不深入討論囉~

繼續往下看會看到正在判斷 if (pData->type == IT_Thread)
耶!終於到了本章重點,LdrLoadDll + ZwCreateThreadEx

xxx pUserBuf = isWow64 ? xxxFunc( xxx ) : BBGetNativeCode( LdrLoadDll, &ustrPath );

if (pData->type == IT_Thread)
{
    status = BBExecuteInNewThread( 
                                pUserBuf, 
                                NULL, 
                                THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER, 
                                pData->wait, 
                                &threadStatus 
                                );

先來看一下這一行:

xxx pUserBuf = isWow64 ? xxxFunc( xxx ) : BBGetNativeCode( LdrLoadDll, &ustrPath );

為了節省時間與文章長度,
直接假設程式走的是 BBGetNativeCode( LdrLoadDll, &ustrPath )
其中 ustrPath 是被注入的 DLL Full Path

跟進去可以看到有一段 shellcode,
這段 shellcode 的用意可以在註解中找到:

// <summary>
// Build injection code for native x64 process
// Must be running in target process context
// </summary>
UCHAR code[] =
{
    0x48, 0x83, 0xEC, 0x28,
    // sub rsp, 0x28
    
    0x48, 0x31, 0xC9,
    // xor rcx, rcx
    
    0x48, 0x31, 0xD2,
    // xor rdx, rdx
    
    0x49, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0,
    // mov r8, ModuleFileName   offset +12
    
    0x49, 0xB9, 0, 0, 0, 0, 0, 0, 0, 0,
    // mov r9, ModuleHandle     offset +22
    
    0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0,
    // mov rax, LdrLoadDll      offset +32
    
    0xFF, 0xD0,
    // call rax
    
    0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0,
    // mov rdx, COMPLETE_OFFSET offset +44
    
    0xC7, 0x02, 0x7E, 0x1E, 0x37, 0xC0,
    // mov [rdx], CALL_COMPLETE 
    
    0x48, 0xBA, 0, 0, 0, 0, 0, 0, 0, 0,
    // mov rdx, STATUS_OFFSET   offset +60
    
    0x89, 0x02,
    // mov [rdx], eax
    
    0x48, 0x83, 0xC4, 0x28,
    // add rsp, 0x28
    
    0xC3
    // ret
};

繼續往下看會看到正在申請記憶體空間:

status = ZwAllocateVirtualMemory( 
                                ZwCurrentProcess(), 
                                &pBuffer, 
                                0, 
                                &size, 
                                MEM_COMMIT, 
                                PAGE_EXECUTE_READWRITE 
                                );

繼續往下看會看到:

  1. 正在將 shellcode 寫入剛申請到的記憶體空間中
  2. 正在將 DLL 資訊寫進記憶體空間中
  3. (也可以想成是把 DLL 寫進 shellcode,再把 shellcode 寫進記憶體)
  4. (不過看 CDOE 的話,是先把 shellcode 複製進記憶體,再將 DLL 資訊寫進記憶體)
// Copy path
PUNICODE_STRING pUserPath = &pBuffer->path;
pUserPath->Length = 0;
pUserPath->MaximumLength = sizeof(pBuffer->buffer);
pUserPath->Buffer = pBuffer->buffer;

RtlUnicodeStringCopy( pUserPath, pPath );

// Copy code
memcpy( pBuffer, code, sizeof( code ) );

// Fill stubs
*(ULONGLONG*)((PUCHAR)pBuffer + 12) = (ULONGLONG)pUserPath;
*(ULONGLONG*)((PUCHAR)pBuffer + 22) = (ULONGLONG)&pBuffer->module;
*(ULONGLONG*)((PUCHAR)pBuffer + 32) = (ULONGLONG)LdrLoadDll;
*(ULONGLONG*)((PUCHAR)pBuffer + 44) = (ULONGLONG)&pBuffer->complete;
*(ULONGLONG*)((PUCHAR)pBuffer + 60) = (ULONGLONG)&pBuffer->status;

繼續往下看就是在 Init routine 了,最後恢復 Process 保護。

實際上到這邊 DLL Injection 就結束了,
不過再往下看的話會看到一些有趣的東西,
例如:

// Unlink module
if (pData->unlink)

// Erase header
if (pData->erasePE)

這是之後要講到的隱藏 Module 方法
隱藏的方法非常多,之後會舉幾個有趣的例子。

最後整理了一下大致流程:

  1. User mode 向 Driver 傳送要注入的相關資訊
  2. Get EProcess
  3. Get ntdll base
  4. Get LdrLoadDll address
  5. 檢查是否有保護,如果有移除,注入完成後再恢復
  6. 申請一塊記憶體空間,然後傳入 DLL(Build injection code for native x64 process)
  7. 啟動剛申請好的那塊空間
  8. 最後執行(Init routine)

編譯與執行:

  • 蝦?你/妳問怎麼編譯、怎麼用?
  • 編譯 Driver,然後寫個 User mode 程式,將 ioctl 訊號發過去給 Driver。
  • 有學過 Driver 的都會知道怎麼做的!
  • 點我學習

額外補充:

  • 這個 Library 已經出現在網路上有一段時間了
  • 所以基本上有滿多東西被擋掉了
  • 所以呢~想拿來開掛應該要碰碰運氣囉~

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

References

下期預告

  • 【Day 04】- 今天來把 Module 藏起來
  • 如果你/妳喜歡我的文章,請記得訂閱按讚分享並且打開小鈴鐺哦
  • 這樣就能在第一時間收到通知,也不會錯過任何文章啦~~

上一篇
【Day 02】- 消失在系統上的目錄與文件(教你如何藏檔案或目錄)
下一篇
【Day 04】- 今天來把 Module 藏起來(基於 PEB 斷鏈,隱藏 DLL 的方法)
系列文
打通任督二脈奇幻之旅 - 用 30 天探索 Windows 底層運作原理15

尚未有邦友留言

立即登入留言