正文:
由于是檔案映射,所以必然少不了這幾位主角:FILE_OBJECT、SEGMENT、SECTION、CONTROL_AREA,外加用戶層訪問記憶體的入口VAD(BITMAP就不提了它只是VAD的一種“快取”方式),由于上述文章中沒有過多的提及關于檔案映射的這塊內容,所以我必須自己在nt!NtCreateUserProcess函式流程的海洋中尋找與這些物件相關的函式影子,
尋找相關流程思路:
研究系統:由于頭鐵,所以直接拿了一個Win10 x64 1909或20HX的版本研究(搭建虛擬機太麻煩),所以我就以此版本為研究物件,
尋找相關函式的思路有兩種:
第一種,對特定物件中的特定欄位進行下斷點(這種需要知道相關物件的創建時機),然后進行堆疊回溯查看周圍的堆疊情況,
第二種,直接從頭開始從下找,人肉篩選其重點函式(參考之前一些前輩的文章),
我選擇的是第二種,但是也參考了一些技巧,首先我對nt!NtCreateUserProcess函式進行下斷點,斷下后,使用 uf /c /D 地址,查看此函式的的下一層的函式呼叫關系,然后進行人肉篩選一些不重要的函式,
uf /c /D 地址
結果如下圖:

篩選規則很簡單,就是對一些解參考、記憶體操作、引數檢查的函式一律PASS掉,因為我們的核心是檔案映射相關(這里參考《64位Windows創建64位行程逆向分析》系列),
經過篩選最終如下圖(此時還沒有動態除錯哦~):

開始除錯驗證流程和探究其細節:
提出問題和猜想:
由于在《XX之NTDLL隨機化“逆向”(XP系統)》文章中我提到了,在Win7乃至Win10中,有3種情況:
將一個exe程式重復啟動,查看其基址
第一次啟動:

在桌面移動一下坐標再次啟動:

將一個exe移動一下再回到原來位置啟動,查看其基址,
第一次啟動:

此時,我將這個exe移動(剪切的方式)到某個盤符下,再移動回來,

很明顯,此時,對于同一個軟體這個基址就發生了變化,
通過修改exe一些位元組,進行重新運行
沒改之前運行:

修改一些位元組:


很明顯,此時,exe在一個位置,但是其內容發生了變化,也會導致其基址改變,
通過這三個實驗我想提出的觀點是:對于檔案加載是存在“快取”機制的,而問題是如何進行快取呢?這是下文開始探討的問題,
除錯相關流程分析:
首先我給出各個物件的框架關系圖:

接下來按照所過濾的函式流程開始逐層分析,驗證上面圖片中物件的關系和尋找如何進行“快取”?
第一個分析的便是:IoCreateFileEx函式,該函式會生成一個FILE_OBJECT物件,通過分析可以得出IopCreateFile的第一個引數是file_object的Handle,所以只需跟蹤即可,

經過分析會得出以下結果:


此時已經產生FILE_OBEJCT、SEGMENT、CONTROL_AREA物件,此時圖形更新為:

由于此時還沒有創建EPROCESS和SECTION物件,所以用戶層是無法看到映射的內容的,所以重點就在于nt!MmCreateSpecialImageSection函式的身上,
我先簡單的劃分一下,可以看的更清楚一些,最終圖如下:

由于我是直接運行了一個exe程式,所以必然流程走的是MiCreateImageOrDataSection函式,
struct CREATE_SECTION_PACKET
{
ULONG Flags;
DWORD Unknown04;
POBJECT_ATTRIBUTES InputObjectAttributes;
ULONG AllocateAttributes;
ULONG InputAllocationAttributes;
UCHAR InputSectionSignatureLevel;
BYTE Unknown19;
WORD Unknown1A;
ULONG InputSectionPageProtection;
ULONG PageProtectionMask;
DWORD Unknown24;
HANDLE InputFileHandle;
PFILE_OBJECT InputFileObject;
PFILE_OBJECT FileObject;
CONTROL_AREA* SectionControlArea;
KPROCESSOR_MODE InputPreviousMode;
BYTE Unknown49[67];
DWORD Unknown8C;
SECTION* SectionObject;
PLARGE_INTEGER MaximumSize;
PACCESS_TOKEN InputToken;
DWORD InputSessionId;
DWORD UnknownAC;
MI_PARTITION* Partition;
PIRP TopLevelIrp;
BYTE UnknownC0;
BYTE UnknownC1[3];
DWORD UnknownC4;
};
NTSTATUS __fastcall MiReferenceControlArea(
CREATE_SECTION_PACKET* CreateSectionPacket,
CONTROL_AREA* ControlArea,
CONTROL_AREA** ControlAreaOut)
{
CONTROL_AREA* controlArea;
//...
fileObject = CreateSectionPacket->FileObject;
/*
檢索Section Object指標, 如果 SEC_IMAGE 使用 ImageSectionObject 否則使用 DataSectionObject
*/
controlArea = fileObject->SectionObjectPointer->DataSectionObject;
if ((CreateSectionPacket->AllocateAttributes & SEC_IMAGE) != 0)
{
controlArea = fileObject->SectionObjectPointer->ImageSectionObject;
}
//...
//
// 一些非常丑陋的鎖回圈和驗證,
//
//...
*ControlAreaOut = controlArea;
return STATUS_SUCCESS;
//...
}
NTSTATUS __fastcall MiCreateImageOrDataSection(
CREATE_SECTION_PACKET* CreateSectionPacket)
{
NTSTATUS status;
PFILE_OBJECT fileObject;
CONTROL_AREA controlArea;
CONTROL_AREA* newControlArea;
//...
fileObject = CreateSectionPacket->InputFileObject;
if (fileObject)
{
//
// 已經提供了檔案物件,請使用它,
//
goto HaveFileObject;
}
if ((allocationAttributes & SEC_LARGE_PAGES) == 0)
{
//
// 從輸入檔案句柄獲取檔案物件,
//
status = ObReferenceObjectByHandle(
CreateSectionPacket->InputFileHandle,
MmMakeFileAccess[CreateSectionPacket->PageProtectionMask & 7],
IoFileObjectType,
CreateSectionPacket->InputPreviousMode,
&fileObject,
NULL);
if (!NT_SUCCESS(status))
{
goto Exit;
}
if (!fileObject->SectionObjectPointer)
{
//
// 如果使用了檔案句柄并且沒有為它創建的節,這是一個失敗條件,
//
status = STATUS_INVALID_FILE_FOR_SECTION;
goto Exit;
}
:HaveFileObject
//...
//
// 在資料包和本地 CONTROL_AREA 中存盤一些資訊以維護狀態以供進一步呼叫,
//
ObfReferenceObject(fileObject);
CreateSectionPacket->FileObject = fileObject;
controlArea.u.LongFlags = 2;
controlArea.FilePointer.Value = fileObject;
newControlArea = NULL;
//...
while (1)
{
//...
//
// Go reference the correct control area.
// 去參考正確的控制區域,
//
status = MiReferenceControlArea(CreateSectionPacket,
&controlArea,
§ionControlArea);
if (NT_SUCCESS(status))
{
break;
}
if ((status == 0xC000060B) || (status == 0xC0000476))
{
//
// The control area is not charged or is invalid.
// 這個控制區域已經無效
//
goto Exit;
}
}
CreateSectionPacket->SectionControlArea = sectionControlArea;
if ((sectionControlArea->u.LongFlags & 2) != 0)
{
//
// 我們有section控制區域,其中將包含參考部分, 現在,去創建一個新的,
//
status = MiCreateNewSection(CreateSectionPacket,
&newControlArea);
if (NT_SUCCESS(status)))
{
//...
CreateSectionPacket->SectionControlArea = newControlArea;
goto Exit;
//...
Exit:
//...
return status;
}
偽代碼參考:https[:]//github.com/jxy-s/herpaderping/blob/main/res/DivingDeeper.md

首先會判斷是否存在FILE_OBEJCT物件,如果有的話直接跳轉,不需要再通過輸入的句柄獲取其FILE_OBJECT物件(沒有的話,需要走這一步),

其次默認設定一些標志已經欄位,但是最重要的是BeingCreated標志位,

后續會根據此標志位(BeingCreated = 1)來判斷是否創建新的CONTROL_AREA,此時也會修改掉_SEGMENT.BaseAddress欄位,所以這里就不使用快取,

此時圖更新為:

實驗驗證:
先運行一個exe一次,然后記錄其基址:

然后在nt!NtCreateUserProcess中下斷,再次啟動該程式,跟蹤上述流程,并尋找_Segment.BaseAddress,

不難發現,第二次啟動程式使用了第一次啟動后快取的_SEGMENT,所以基址保持不變,并且查看BeingCreated標志,也會發現此時該標志為0,

接下來再次啟動該程式,并將其BeingCreated標志置為0,查看其基址又是如何的情景呢?(此時我是將提出的問題1,2合并來進行測驗),將exe剪切移動到一個位置后,再移動回來,查看其BeingCreated標志,


為了更加了解_SEGMENT.BaseAddress如何來的,所以繼續順著流程往下跟蹤,(注解:ImageBase來源于_SEGMENT.BaseAddress,所以我一般會混合稱呼,

對于新創建的SEGMENT流程來說:
nt!MiCreateNewSection->nt!MiRelocateImage->nt!MiSelectImageBase最終生成_SEGMENT.BaseAddress,
其中生成的演算法為:

充滿好奇心的同學,就如我一樣,肯定會心中產生一個疑問,那么便是為什么,這里會有兩種產生隨機地址的流程呢?那么讓我們繼續順藤摸瓜,向上查找
接著,就會尋找到如下圖的關系:
在nt! MiSelectImageBase會判斷_CONTROL_AREA.u2.e2.ImageBaseOkToReuse欄位是否為1,說明當這個欄位為1的時候,那么也會進行重定位,這個情況是發生在_CONTROL_AREA“快取的情況”下(為什么是這個情況呢?因為另一種情況會重新創建Segment),即發生下:


這就得出了一個結論,如果ImageBaseOkToReuse標志在置位的情況下,即時存在CONTROL_AREA的“快取”,那么也會進行重新計算BaseAddress,所以可以看到在這個分支中會出現一個獨特的函式,即:nt!MiSwitchBaseAddress,
當nt!MiSelectImageBase回傳后,會回傳到nt!MiRelocateImageAgain函式中,
nt!MiRelocateImageAgain函式會判斷回傳的BaseAddress值與原來的_SEGMENT.BaseAddress中的值是否一致,如果不一致的話,那么就會呼叫nt!MiSwitchBaseAddress 函式更新_SEGMENT,

但是這里還有一個小細節,就是它會判斷ImageActive標志位,這個位顧名思義(我猜的),代表的是當前行程是否存活,為了突出重點,所以我標出了流程中關鍵的部分,具體圖如下:

細分析如下:
在nt!MiRelocateImageAgain函式中,首先判斷 _control_area .u2.e2.ImageActive,是否為1(即)當前行程是否存活,如果不是退出狀態,則會呼叫nt!MiSelectImageBase函式來獲取基址,

如果獲取到新的BaseAddress后,則將其和老的進行比較,不相同,則呼叫 nt!MiSwitchBaseAddress來更新_SEGMENT,

對于其他的流程,后續總結的時候簡要說明一下,nt!PspAllocateProcess函式流程可以參考《64位Windows創建64位行程逆向分析》,

總結:
對于復用CONTROL_AREA“快取”的情況圖:

對于重新創建SEGMENT的圖:


簡單說一句,如果想讓記憶體在用戶層可見,都需要呼叫nt!MiMapViewOfImageSection函式,這個函式主要作用就是創建_SECTION物件,并將_SEGMENT.BaseAddress函式映射到用戶層可見部分,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/297088.html
標籤:其他
