〇.背景
在build 2016大會上,微軟宣布windows10 中將原生支持bash,并且號稱在windows中加入了一個Linux子系統(tǒng)(Windows Subsystem for Linux),而不是一個虛擬機。而后在發(fā)布的win10 14316(x64)更新上就開啟了bash功能,但32位版本沒有bash。
筆者隨即下載了14316想體驗一下windows上執(zhí)行bash命令,并且想了解這個Linux子系統(tǒng)機制的實現(xiàn)。
一.介紹
首先打開system32\bash.exe后,隨便輸入一個ls命令,通過procmon發(fā)現(xiàn)有進(jìn)程訪問了C:\Users\xxxxx\AppData\Local\lxss\rootfs\bin\ls文件。
隨即發(fā)現(xiàn)了linux根目錄掛載在C:\Users\xxxxx\AppData\Local\lxss\rootfs,可以看到目錄結(jié)構(gòu)和ubuntu基本一致。
反過來看到訪問ls這個elf文件的進(jìn)程比較奇怪,procmon無法顯示進(jìn)程名字,而且掛上調(diào)試器還會發(fā)現(xiàn)這個進(jìn)程對象內(nèi)不僅沒有名字,也沒有section,peb等信息。
二.Pico Process
這種沒有名字的進(jìn)程稱為pico process,也就是ELF的宿主進(jìn)程。關(guān)于picoprocess介紹: http://research.microsoft.com/en-us/projects/drawbridge/#Picoprocess。簡單來說Pico Process是一種Container,實現(xiàn)drawbridge沙盒技術(shù)的一種機制,并且之前被微軟斃掉的“Project Astoria”項目也用到了lxcore+picoprocess這種技術(shù)?,F(xiàn)在微軟把這種技術(shù)應(yīng)用到了win10 linux子系統(tǒng)當(dāng)中。
Picoprocess統(tǒng)一由PspCreatePicoProcess創(chuàng)建,而PspCreatePicoProcess里面主要的工作是由PsCreateMinimalProcess實現(xiàn)的,PsCreateMinimalProcess這個函數(shù)的名字像是用于創(chuàng)建一個精簡進(jìn)程,實際上確實也是這樣。PsCreateMinimalProcess里面會調(diào)用PspAllocateProcess,PspAllocateProcess函數(shù)本身的功能是用來創(chuàng)建EPROCESS對象,進(jìn)程地址空間,PEB等信息。
PspAllocateProcess函數(shù)的原型大致如下:
NTSTATUS
PspAllocateProcess(
void *ParentProcess,
int PreviousMode,
void *ObjectAttributes,
char Protection,
char SignatureLevel,
char SectionSignatureLevel,
void *SectionObject,
void *TokenObject,
int Flags,
void *UserProcessParameters,
int bSystemToken,
OUT int a12,
OUT void *Process)
而PsCreateMinimalProcess中調(diào)用PspAllocateProcess的參數(shù)如下圖所示,可以發(fā)現(xiàn)ObjectAttributes,SectionObject,等都為NULL,這也論證了之前我們看到picoprocess沒有進(jìn)程名,沒有用戶空間信息等現(xiàn)象。
再回來看PspCreatePicoProcess這個函數(shù)的調(diào)用棧,可以看到ring3的調(diào)用是由PsPicoSystemCallDispatch這個函數(shù)分發(fā)到lxcore驅(qū)動中的,這里需要引出另一個東西:PicoProvider
三.PicoProvider
Nt導(dǎo)出了一個函數(shù)PsRegisterPicoProvider提供注冊一個PicoProvider,在目前只有l(wèi)xss.sys會調(diào)用這個接口,lxss.sys和lxcore.sys這兩個驅(qū)動負(fù)責(zé)實現(xiàn)linux子系統(tǒng)的大部分功能。Lxss.sys作為一個boot start driver會在初始化時調(diào)用lxcore.sys->PsRegisterPicoProvider,而且PsRegisterPicoProvider在系統(tǒng)啟動后只允許調(diào)用一次,在所有boot driver初始化完畢后,nt會把PspPicoRegistrationDisabled設(shè)置為TRUE,從而禁止以后所有的PsRegisterPicoProvider調(diào)用,也就是說當(dāng)前系統(tǒng)只允許存在一個Provider。
注意因為目前最新的符號只是14295,14316的符號微軟還沒有放出,但lxss.sys相關(guān)的文件在14295就存在了并且大部分功能nt中已經(jīng)使用了,只是上層機制并沒有實現(xiàn)。所以筆者使用14295的符號配合14316文件來分析lxss內(nèi)核相關(guān)機制,如有不正確歡迎指出。
PsRegisterPicoProvider的原型大致如下:
NTSTATUS PsRegisterPicoProvider(
IN PICO_INTERFACE *PicoInterface,
OUT PSP_INTERFACE *PspInterface
);
struct PICO_INTERFACE
{
__int64 cbSize; //目前只支持0x48,不排除以后會有EX
PVOID PicoSystemCallDispatch;
PVOID PicoThreadExit;
PVOID PicoProcessExit;
PVOID PicoDispatchException;
PVOID PicoProcessTerminate;
PVOID PicoWalkUserStack;
PVOID LxpProtectedRanges;
PVOID PicoGetAllocatedProcessImageName;
}
struct PSP_INTERFACE
{
__int64 cbSize; //目前只支持0x60
PVOID PspCreatePicoProcess;
PVOID PspCreatePicoThread;
PVOID PspGetPicoProcessContext;
PVOID PspGetPicoThreadContext;
PVOID PspGetContextThreadInternal;
PVOID PspSetContextThreadInternal;
PVOID PspTerminateThreadByPointer;
PVOID PsResumeThread;
PVOID PspSetPicoThreadDescriptorBase;
PVOID PsSuspendThread;
PVOID PspTerminatePicoProcess;
}
Lxss通過PsRegisterPicoProvider向NT提供一組PICO操作接口,以獲取系統(tǒng)調(diào)用分發(fā)、進(jìn)線程退出、異常訪問等消息、自己進(jìn)行處理。并且會獲取NT的一組進(jìn)線程操作接口,用于對nt進(jìn)線程進(jìn)行操作。其中有一個PICO接口PicoGetAllocatedProcessImageName,比較感興趣,因為之前提到了我們用procmon觀察pico process是沒有名字的,并且內(nèi)核調(diào)試器也無法看到名字,如果能知道pico process對應(yīng)哪個ELF會對分析有很大幫助。
通過分析,Pico Provider把這個接口提供給NT,在NtQuerySystemInformation獲取進(jìn)程名相關(guān)信息的時候,正常情況下會從EPROCESS->SeAuditProcessCreationInfo中取進(jìn)程名信息,但如果發(fā)現(xiàn)這是個pico process則調(diào)用PicoGetAllocatedProcessImageName獲取。所以邏輯上說NT已經(jīng)做了對pico process的兼容,但為什么procmon還是無法獲取進(jìn)程名?果然筆者自己試了下用最簡單的CreateToolhelp32Snapshot是可以輕松枚舉到elf進(jìn)程相關(guān)的名字。
所以procmon可能并不是用標(biāo)準(zhǔn)接口來獲取進(jìn)程名字的。
但pico process的進(jìn)程名保存在哪里呢?通過分析這個函數(shù),發(fā)現(xiàn)pico process對應(yīng)的ELF路徑信息保存在EPROCESS->PicoContext +0x15b0處,而PicoContext是在創(chuàng)建pico process的時候,由PspCreatePicoProcess設(shè)置的,保存了pico process的各種信息。用一個簡單的腳本,用來遍歷當(dāng)前系統(tǒng)的pico process的名字信息,這是當(dāng)我使用bash執(zhí)行一個wget下載任務(wù)的時候的pico進(jìn)程信息:
繼續(xù)說回PsRegisterPicoProvider,PICO Interface中的PicoSystemCallDispatch也比較重要。負(fù)責(zé)分發(fā)pico process傳來的系統(tǒng)調(diào)用,用于picoprocess和provider進(jìn)行通信。在pico process的一次系統(tǒng)調(diào)用中,當(dāng)ring3代碼sysenter后,內(nèi)核入口為KiSystemCall64,如果當(dāng)前thread->_DISPATCHER_HEADER中設(shè)置了Minimal標(biāo)志位,則KiSystemCall64會調(diào)用nt!PsPicoSystemCallDispatch進(jìn)行pico相關(guān)分發(fā),不走原始的sdt分發(fā)表。Lxcore!LxpSyscalls保存這個分發(fā)表的地址。
這是打印的部分分發(fā)函數(shù),類似SDT表一樣,仍然是通過rax作為id分發(fā),但參數(shù)的傳遞和走sdt的有一些不一樣。使用rdi,rsi,rdx,r10傳遞參數(shù)。
(分析過程中還發(fā)現(xiàn)14316后多了一張新的SDT表KeServiceDescriptorTableFilter,當(dāng)一個線程flag被設(shè)置為RestrictedGuiThread時,則使用KeServiceDescriptorTableFilter這張表,這張表會限制很多native api的調(diào)用,主要限制edge等進(jìn)程調(diào)用win32k等函數(shù),做更嚴(yán)格的安全性隔離,不過目前KeServiceDescriptorTableFilter的內(nèi)容還是和普通的SDT一樣,應(yīng)該還未使用)
我們來看下lxcore提供給Linxu子系統(tǒng)一些功能的實現(xiàn)。比如當(dāng)我們使用bash創(chuàng)建一個ELF進(jìn)程的時候,lxcore的調(diào)用流程大概是:LxpSyscall_FORK->LxpThreadGroupFork ->LxpThreadGroupCreate –>PspCreatePicoProcess。PspCreatePicoProcess在之前PsRegisterPicoProvider的時候已經(jīng)從NT中獲取了,所以lxsscore可以操作nt的進(jìn)線程,但對于沒有向NT獲取響應(yīng)的接口的其他功能呢?
對于文件系統(tǒng)的操作則直接調(diào)用相應(yīng)的nt api,而并非直接和文件系統(tǒng)打交道,比如當(dāng)使用rm命令刪除一個文件的時候,調(diào)用流程:LxpSyscall_UNLINK->LxpUnlinkHelper->VfsPerformUnlink->VfsUnlinkChild->LxDrvFsRemoveChild->LxDrvFsDeleteFile->ZwSetInformationFile,其中會通過LxDrvFsDeleteFile 會使用IoCreateFile打開文件,再進(jìn)行刪除。 lxcore.sys中實現(xiàn)了一套VfsXXXX接口向上虛擬了VFS,向下使用LxDrvFsXXXX的一組API提供文件訪問能力。
對于網(wǎng)絡(luò)的操作,分為UNIX協(xié)議簇和INET協(xié)議簇,兩個有不同的分發(fā)表,比如當(dāng)上層發(fā)送一個UDP包的時候,lxcore的調(diào)用流程: LxpSyscall_SENDMMSG->LxpSocketSendMultipleMessages->LxpSocketInetSendMessage->LxpSocketInetSend->LxpSocketInetDatagramSend->afd!WskProAPISendTo。最終會進(jìn)入到afd執(zhí)行相應(yīng)的功能。這是打印出來的lxcore使用的Afd相關(guān)函數(shù):
再其它的一些,比如獲取當(dāng)前系統(tǒng)信息,lxcore也是直接調(diào)用Nt相應(yīng)的函數(shù)獲取,例如date命令,lxcore通過LxpSyscall_CLOCK_GETTIME->KeQuerySystemTimePrecise獲取。
四.注入Pico Process
最后試了一下注入到pico process,打算注入進(jìn)去調(diào)用一下pico process的分發(fā)表,由于pico process沒有section對象所以無法用遠(yuǎn)程線程注入。最后使用SetThreadContext方法注入到了pico process進(jìn)程。雖然可以注入成功,但windbg是無法調(diào)試pic process的,因為lxcore接管了pico process的異常分發(fā),nt!KiDispatchException中如果發(fā)現(xiàn)異常在pico thread中,則使用PicoDispatchException處理異常,lxcore中使用APC模擬signal機制處理進(jìn)程通信與異常分發(fā)。
五.總結(jié)
目前來說LxpSyscalls目前包含0×138個調(diào)用,而且有些調(diào)用目前內(nèi)部沒有邏輯實現(xiàn),所以微軟在未來會逐漸完善各種命令的支持。上面簡單分析了一下lxcore對于linux子系統(tǒng)操作的支持,當(dāng)然只是部分操作,還有一些比如ELF加載,內(nèi)存管理,設(shè)備管理等都還沒有分析。但從目前已經(jīng)分析的結(jié)果來看,微軟確實是自己實現(xiàn)一套linux子系統(tǒng)支持,并不是一個Linux虛擬機,所以執(zhí)行效率會好很多,比如pico process的進(jìn)程的時間片分配和原生的process基本一致。但是缺點也是有,增加了一套lxss機制后,同時也增加了復(fù)雜性,也就是說win10以后可能會面臨win和linux二進(jìn)制安全的雙重考驗,這可能對windows的安全性保障又增加了新的難題。