該樣本於去年6月份絕地求生wg氾濫時蒐集到,根據pdb路徑(ReadCF\x64\Win7Release\ReadCF.pdb)推測該樣本至少2016年PUBG沒火時就已經出現而且當時是針對CF寫的,直到2018年5月居然還有人把該樣本用在自己的絕地求生wg上,曾經用過該樣本的wg多達幾十款(真的)。涉及各種原因這裡不細說了,我們只談技術:
先從DriverEntry開始
先從ntos匯出了一系列API,存放到g_Context結構中,這裡g_Context被優化成了一個個獨立的全域性變數,實際上作者寫的時候是用類似g_Context.PsGetCurrentProcess = MmGetSystemRoutineAddress( 這樣的形式
然後是InitAll,先搜尋mouseservicecallback回撥函式
先找到mouhid / i8042和mouclass的驅動物件然後遍歷 mouhid / i8042的裝置棧
在其裝置擴充套件中找到 servicecallback(特徵是地址在DriverStart到DriverStart+DriverSize指定的驅動Image區域內並且可以訪問)
存放進g_Context.MouseServiceCallback中,將來用於模擬滑鼠操作
↓ pContext = MakeContext(RtlCopyMemory, 0i64, 1);的作用是複製當前驅動的g_Context結構到一片pool記憶體中,g_Context的大小是0x1E8。
因為該讀寫驅動載入完即釋放,所以需要把本驅動Image空間中的g_Context結構放到不會被釋放的pool記憶體中。
WHAT = AllocCode(&unk_140005140, 0x400u);
↑這個WHAT變數也是分配到一塊大小0x400的pool記憶體中,看起來是一個類似key或者key table的東西,後面在給程式加上OB程式回撥保護的時候校驗呼叫者會用到,這裡先跳過
qword_140006148 = FixCode(ReadWriteProcessMemoryTemplate, 0x250u, pContext);
qword_140006150 = FixCode(FindModuleByNameTemplate, 0x350u, pContext);
qword_140006138 = FixCode(sub_14000432C, 0x100u, pContext);
qword_140006140 = FixCode(sub_1400041F0, 0x100u, pContext);
qword_140006188 = FixCode(NewWPONTemplate, 0x50u, pContext);
qword_140006180 = FixCode(NewWPOFFTemplate, 0x50u, pContext);
qword_140006190 = FixCode(BypassTesvboxReadWriteProcessMemoryTemplate, 0x280u, pContext);
qword_140006198 = FixCode(BackupPageTableTemplate, 0xB0u, pContext);
qword_1400061A0 = FixCode(sub_1400039CC, 0x350u, pContext);
routine = FixCode(ProcessObCallbackTemplate, 0x90u, pContext);
↑這裡把一些“ 模板函式 ”的程式碼複製到pool記憶體中,“模板函式”的意思是先用正常程式碼寫對應的功能,再把裡面可能出現的引用全域性變數、呼叫函式替換為引用g_Context中的函式,比如PsGetCurrentProcess()替換為 g_pContext->PsGetCurrentProcess()。令 g_pContext = 0x3737373737373737i64,在FixCode中講模板中出現的所有 0x3737373737373737i64 替換為之前 MakeContext中分配的pool記憶體的 g_Context。
這麼做的理由在於: g_pContext = 0x3737373737373737i64之後訪問 g_pContext->中的成員變數,會被編譯為
mov r?x, 3737373737373737h
call [r?x+變數偏移]
mov qword ptr [r?x+變數偏移], yyyyy
的形式,其中r?x代表rax rbx rcx之類的64位通用暫存器,這樣編譯出來的程式碼只需要無腦替換掉裡面的 0x3737373737373737即可正常使用,而不會出現64位彙編跨4GB地址訪問用4位元組偏移訪問不了的尷尬情況(64位下 E8 call, E9 jmp都只能跳轉4GB以內地址)。
具體的模板函式這裡只貼兩個關鍵的:
跨程式讀寫記憶體
和在WOW64PEB->LDR中查詢模組
↓然後用ZwQuerySystemInformation功能號11 SystemModuleInformation列舉驅動找到Fs_Rec.sys驅動,並返回他的ImageBase
1
FsRecImageBase = LookupDriver("Fs_Rec.sys");
找到後設定其LdrEntry的Flags欄位 為了將來安裝程式OB回撥時繞過MmVerifyCallbackFunction限制
1
FixLdrEntry(MainDriverObject, FsRecImageBase);
再取到Fs_Rec的驅動物件,將其IRP_MJ_CREATE、IRP_MJ_CLOSE和IRP_MJ_DEVICE_CONTROL三個IRP派遣函式儲存進g_Context並替換為自己往fs_rec.sys驅動Image空間中寫入的程式碼MyCode,這個MyCode怎麼來的下面會講。
RtlInitUnicodeString(&DestinationString, L"\\filesystem\\Fs_Rec");
v14 = ObReferenceObjectByName(
&DestinationString,
0x240u,
0i64,
0,
IoDriverObjectType,
0,
0i64,
&FsRecDriverObject);
RtlInitUnicodeString(&DeviceName, L"\\Device\\iubesks");
RtlInitUnicodeString(&SymbolicLinkName, L"\\DosDevices\\iubesks");
v14 = IoCreateDevice(FsRecDriverObject, 0, &DeviceName, 0x22u, 0, 0, &DeviceObject);
FsRecCreate = FsRecDriverObject->MajorFunction[IRP_MJ_CREATE];
FsRecClose = FsRecDriverObject->MajorFunction[IRP_MJ_CLOSE];
FsRecIoctl = FsRecDriverObject->MajorFunction[14];
FsRecDriverObject->MajorFunction[IRP_MJ_CREATE] = MyCode;
FsRecDriverObject->MajorFunction[IRP_MJ_CLOSE] = MyCode;
FsRecDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyCode;
現在講MyCode是怎麼來的:
↑先找到fs_rec.sys的驅動Image的.text段起始地址
↓再往.text段起始地址上用mdl方式寫入位元組"0x48,0xB8,8位元組的函式地址,0xFF,0xE0"
翻譯成彙編就是
mov rax, 函式地址
jmp rax
第一個mov rax的函式地址是從ProcessObCallbackTemplate模板複製出來的 ProcessObCallback回撥
在
RegisterProcessObCallback(1, routine);
中將其安裝為程式Ob回撥
同理mov rax, CreateProcessNotifyRoutine和 mov rax, LoadImageNotifyRoutine 也被安裝
這兩個回撥的功能分別是:
根據遊戲程式名 儲存遊戲程式的EPROCESS,程式名由某個IOCTL指定。
根據遊戲程式名 備份遊戲程式的頁表(為後面讀寫CF記憶體做準備)
同理,DispatchIrp派遣函式的模板也被複制到pool並用mov rax的形式設定為Fs_Rec驅動物件的真實派遣函式
此時fs_rec.sys的.text段起始地址的程式碼如下:
mov rax, pool記憶體中的程式OB回撥
jmp rax
jmp rax, pool記憶體中的程式建立回撥
jmp rax
jmp rax, pool記憶體中的執行緒建立回撥
jmp rax
jmp rax, pool記憶體中的DispatchIrp派遣函式
jmp rax
至此該驅動樣本初始化完成,固定返回錯誤碼0xC0000001強行載入失敗,以讓其被系統自動解除安裝。
下面簡要分析該樣本DispatchIrp派遣函式中的內容:
首先判斷訪問該派遣函式的裝置物件是否是我們剛才建立的\Device\iubesks裝置物件,如果不是,根據MajorFunction呼叫g_pContext->中對應的原始函式,如FsRecDisptchCreate 、 FsRecDisptchClose、 FsRecDisptchIOCTL
並且如果MajorFunction不是IRP_MJ_DEVICE_CONTROL則直接返回成功並完成該IRP
再根據不同的Parameters.DeviceIoControl.IoControlCode執行不同的操作
如: IoControlCode=0x223BD8,執行g_pContext->MouseServiceCallback模擬滑鼠操作
如:IoControlCode= 0x223BE4 ,儲存使用者傳來的程式名到自己分配的pool記憶體中並存到g_pContext->process_name中
如: IoControlCode= 0x223BE0 0x223BF4,則遍歷程式的Wow64Peb->Ldr查詢指定模組的ImageBase和ImageSize,具體程式碼如下
IoControlCode= 0x223BE8,使用KeStackAttachProcess+MDL方式跨程式讀寫記憶體(這裡其實不用MDL也可以,直接memcpy就行了)
IoControlCode= 0x223BFC 繞過CF/DNF PageFault記憶體保護使用自己備份的頁表讀寫記憶體
這裡和上面正常讀寫方法唯一的區別就是使用了自己在LoadImage回撥中備份的頁表
IoControlCode= 0x223BF8,設定保護程式,開啟被保護的程式時在我們剛才安裝的程式OB回撥中會被摘掉VM_READ VM_WRITE等許可權,使其無法被(兩年前的)CF遊戲程式讀取記憶體,以避開記憶體特徵掃描
樣本總結:
1、利用DriverEntry返回失敗的STATUS碼來讓系統自動解除安裝,免去了sc stop / ZwUnloadDriver的麻煩。
2、把程式碼放在pool記憶體中以達到(遊戲執行期間)無驅動(也就是記憶體載入)的效果
3、把各個回撥入口放到了fs_rec.sys的.text可執行段中,使其在ARK工具中不易被發現,還可以躲過ntos的guardcall檢查
4、自建裝置物件讓其可以被應用層的程式正常開啟其符號連結並進行互動
5、備份了CF遊戲程式的頁表使其可以正常讀寫CF的遊戲記憶體
6、程式OB回撥保護自己的wg程式不被記憶體特徵掃描
至此樣本全部分析完畢,具體的樣本利用程式碼這裡就不放了,樣本我已經放出來了,有興趣的讀者可以自己逆一下。
來源:[原創]某被外掛用爛了的讀寫驅動樣本全逆向+功能分析-『軟體逆向』-看雪安全論壇
本文由看雪論壇 hzqst 原創
轉載請註明來自看雪社群