Windows Shim Engine,即Windows 兼容性模式實現引擎,在exe文件的屬性對話框中有一個兼容性選項卡,用戶可設置此exe程序完美工做的系統版本,Windows會嘗試模擬老的系統環境運行此程序。
那Windows是如何模擬的呢?Windows認爲老程序出問題的緣由在於它們調用的API上,因新版本的Windows更新API,或者加入新的flag,或者取消老的API功能等等因素,若是老的程序在新版本的Win上不正確的使用了老API(如ChangeDisplayConfig等),則會出現錯誤或沒法達到預期的效果,致使後續的一連串錯誤發生。因此兼容性模式引擎的核心原理就十分簡單了——修復那些有問題的API調用。
如何修復?不外乎Hook。
如何Hook?不少應用程序可不會有HotPatch這種預留的東西,因此兼容性模式引擎使用的是比較安全通用的IAT Hook。
而出問題的API多數在用戶模式,因此兼容性引擎核心也運行在R3層。
本文的研究基本上徹底在Win8進行,對Win7有必定相通性,不過據我所知,Shim Engine從XP到Win8一直在變更,因此這篇文章僅僅只能是參考而已。
ReactOS有XP的Shim Engine實現源代碼,Google搜索LdrpLoadShimEngine便可看到。
1)兼容性模式引擎Dll的載入
兼容性模式引擎的核心Dll有2個,分別爲ntdll.dll和apphelp.dll,其中ntdll扮演着統轄全局的工做,apphelp則負責Sdb解包及邏輯判斷和爲功能實現核心作跳板,其餘的諸如AcLayers.dll則爲功能實現引擎。
隨便讓一個程序開啓兼容性,OD載入,讓其停在LdrInitializeThunk,我把啓動過程Dump出來,就像下面:緩存
1安全
2app
3函數
4測試
5ui
6編碼
7.net
83d
9指針
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
LdrInitializeThunk
LdrpInitialize
_LdrpInitialize
_LdrpInitializeProcess
_LdrpInitializeNlsInfo(RtlInitNlsTables\RtlResetRtlTranslations)
_LdrpInitializeExecutionOptions
_RtlpInitDeferredCriticalSection
RtlInitializeBitMap(Fls)
RtlInitializeBitMap(Tls)
RtlInitializeBitMap(TlsExpansion)
RtlInitializeCriticalSectionEx(
for
RtlAcquirePebLock)
_RtlInitializeHeapManager(use NtGlobalFlags)
RtlCreateHeap
RtlAllocateActivationContextStack
RtlInitializeSListHead(
for
Etw)
_TpInitializePackage
RtlReleaseMemoryStream
RtlpInitEnvironmentBlock
RtlpInitParameterBlock
ZwOpenDirectoryObject(use _LdrpKnownDllDirectoryHandle)
ZwOpenSymbolicLinkObject
ZwQuerySymbolicLinkObject(use _LdrpKnownDllPath)
ZwClose
_LdrpInitializeDllPath
_LdrpInitializeLoadContext
LdrpAllocateDataTableEntry
LdrpProcessMappedModule
RtlpInitCurrentDir
LdrpAllocateTls
LdrLoadDll(_LdrpKernel32DllName)
LdrGetProcedureAddress(_Kernel32ThreadInitThunkFunction)
LdrGetProcedureAddress(TermsrvGetWindowsDirectoryW)
LdrGetProcedureAddress(BaseQueryModuleData)
LdrpCodeAuthzInitialize
ZwQueryInformationProcess(ProcessExecuteFlags)
[B]SbObtainTraceHandle(Query pShimData)->LdrpInitShimEngine[
/B
]
LdrpAcquireLoaderLock(_LdrpModuleEnumLock\_LdrpLoaderLock)
LdrpPrepareModuleForExecution(->Load IAT Modules)
LdrpReleaseLoaderLock
kernel32!_IsSystemLUID
kernel32!_IsTSAppCompatEnabled
LdrpInitializePerUserWindowsDirectory(->TermsrvGetWindowsDirectoryW)
LdrpAcquireLoaderLock
LdrpReleaseLoaderLock
LdrpReleaseDllPath
ZwTestAlert
咱們要關心的是SbObtainTraceHandle,這個東西從PEB的pShimData(peb+0x1E8)拿回數據,而且ntdll下面就有一個判斷,判斷pShimData的數據是否是一個UNICODE字符串的指針,當你開啓兼容性模式後,這個字符串是C:\Windows\system32\apphelp.dll ,這個路徑是在內核建立進程就寫進去的,而後LdrpInitShimEngine獲得執行。
LdrpInitShimEngine中有一個無符號名稱的CALL,此CALL執行載入pShimData中指向的文件路徑,即apphelp:
這個call僅僅是Map apphelp和它依賴的模塊到內存而已,並不執行apphelp的DllMain,call返回後,下面接着就執行一個GetInterface函數,此函數從apphelp中取得指定的導出函數,用於接收來自ntdll的通知:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
SE_InitializeEngine -> SE核心初始化,這個函數最早被ntdll執行
NTSTATUS WINAPI SE_InitializeEngine(PUNICODE_STRING pusCoreDllFile,PUNICODE_STRING pusExecuteFileName,PVOID pShimData)
這個必須返回STATUS_SUCCESS
SE_InstallBeforeInit
BOOL WINAPI SE_InstallBeforeInit(PUNICODE_STRING pusExecuteFileName,PVOID pShimData) 這個必須返回TRUE
VOID WINAPI SE_InstallBeforeInit()
//WIN8
SE_InstallAfterInit -> 引擎初始化完成後這個函數會被調用
BOOL WINAPI SE_InstallAfterInit(PUNICODE_STRING pusExecuteFileName,PVOID pShimData)
這個必須返回TRUE
SE_ShimDllLoaded -> hModule == AcLayers
VOID WINAPI SE_ShimDllLoaded(HMODULE hModule)
SE_DllLoaded -> Dll載入通知,同LdrRegisterDllNotification
VOID WINAPI SE_DllLoaded(PLDR_DATA_TABLE_ENTRY pLdrModuleLoaded)
SE_DllUnloaded -> Dll卸載通知
VOID WINAPI SE_DllUnloaded(PLDR_DATA_TABLE_ENTRY pLdrModuleUnload)
SE_LdrEntryRemoved
VOID WINAPI SE_LdrEntryRemoved(PLDR_DATA_TABLE_ENTRY pLdrEntryRemoved);
SE_ProcessDying
VOID WINAPI SE_ProcessDying();
SE_LdrResolveDllName -> 常常被調用
VOID WINAPI SE_LdrResolveDllName(PUNICODE_STRING pusUnknown,PVOID pvModuleDataUnk,PUNICODE_STRING pusModuleFileName)
SE_GetProcAddressLoad -> WIN7纔有
VOID WINAPI SE_GetProcAddressLoad(PLDR_DATA_TABLE_ENTRY pLdrEntry)
SE_GetProcAddressForCaller -> 常常被調用
VOID WINAPI SE_GetProcAddressForCaller(PVOID pvUnknown0,PVOID pvUnknown1,PVOID pfnCallProcAddr,ULONG_PTR ulZero,PVOID pfnReturnToAddr)
ApphelpCheckModule
BOOL WINAPI ApphelpCheckModule(PUNICODE_STRING pusModuleName,PVOID pvUnknown1,PVOID pvUnknown2,PVOID pvUnknown3,PVOID pvUnknown4,PVOID pvUnknown5,PVOID pvUnknown6)
這些函數是Win8的ntdll中dump出來的,跟Win7有一點出入,這個函數表變更很大,好比Win8.1可能就有變化了。
在Win7上,若是任何一個導出函數地址取得失敗,ntdll就會卸載SE核心Dll,失敗返回。Win8卻是不會,不過若是拿不到完整的導出函數,則你的SE核心的功能就殘廢了。
當GetInterface成功返回後,ntdll才真正去執行apphelp和它的小夥伴們的DllMain。以上函數的地址已經被保存在ntdll的全局變量中,當有事件發生的時候,ntdll會調用這些函數。
DllMain執行後,ntdll接着就執行apphelp的SE_InitializeEngine導出函數,apphelp會在SE_InitializeEngine中判斷當前exe是不是開啓了兼容性模式,而且查詢功能實現的Dll文件名:
1
2
3
4
5
SE_InitializeEngine
HANDLE WINAPI SdbInitDatabaseEx(DWORD dwZero0,DWORD dwZero1,DWORD dwFlags); dwFlags == 0x14C
BOOL WINAPI SdbUnpackAppCompatData(HANDLE hInitData,LPWSTR lpszExeFile,PVOID pShimData,PVOID pvUnpackData);
VOID WINAPI SdbReleaseDatabase(HANDLE hInitData);
apphelp._SepSdbProcessShim@28->SdbGetDllPath
這裏涉及解包sysmain.sdb文件,獲取Tag數據,進行邏輯比對等等,我沒仔細看下去。想了解sdb文件可看這篇文章(中文):http://blog.csdn.net/celestialwy/article/details/707148
系統兼容性模式是AcLayers.dll文件,SdbGetDllPath返回文件名,這個文件名是硬編碼在sysmain.sdb中的,只是設置一個Layers邏輯的名稱而已,其實設置一個應用開啓兼容性模式僅僅須要下面一行代碼就行:
1
2
Private Declare Function SdbSetPermLayerKeys& Lib
"apphelp"
(ByVal lpszExeFile&, ByVal lpszSystemMode&, ByVal flags&)
SdbSetPermLayerKeys StrPtr(
"C:\1.exe"
), StrPtr(
"~ WINXPSP3"
), 0
這個函數只是簡單的NtSetValueKey,在下面這個註冊表添加一個字符串值:HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers
(其實添加字符串值完成後,應該還要執行NtApphelpCacheControl刷新一下注冊表緩存)
跑題了,繼續。apphelp要求功能實現Dll文件必須在AppPatch下,後面會進行字符串連接,因此僅須要文件名就好了。
AcLayers須要導出2個函數,一個讓apphelp拿到要Hook的API列表,一個接收來自ntdll->apphelp->AcLayers的通知。
apphelp會把AcLayers.dll寫入到SE_InitializeEngine的第一個UNICODE_STRING參數中,並返回,ntdll會接着從裏面拿到AcLayers.dll的完整路徑,而後載入AcLayers.dll並執行DllMain,而後通知apphelp的SE_ShimDllLoaded函數。
接着ntdll執行apphelp的SE_InstallBeforeInit函數,意爲「安裝前通知」,而後LdrpInitShimEngine返回。
接下來ntdll開始載入exe程序依賴的IAT表模塊,而且會一個個通知apphelp的SE_DllLoaded:
apphelp會根據從AcLayers拿到的Hook API列表和總數進行IAT Hook:
1
2
3
4
5
6
7
8
9
10
11
al.GetHookAPIs
PVOID WINAPI GetHookAPIs(LPCSTR lpszVerb,LPCWSTR lpszTagId,PVOID lpHookAPIs)
lpHookAPIs -> put Count;
return
struct SE_HOOKAPI{
LPCSTR lpszDllName;
LPCSTR lpszFuncName;
PVOID pfnHookToProc;
DWORD dwZero1;
DWORD dwZero2;
DWORD dwZero3;
}
GetHookAPIs根據前2個字符串參數來查表決定須要返回哪些Hook的API列表。
而後apphelp還會通知AcLayers:
1
2
3
4
5
al.NotifyShims
PVOID WINAPI NotifyShims(DWORD dwNotifyType,PVOID pvParameter)
pvParameter = PLDR_MODULE
dwNotifyType = 0x3:ModuleLoad
dwNotifyType = 0x69:ModuleUnload
全部IAT模塊遞歸載入和通知完成後,會執行「安裝後通知」SE_InstallAfterInit,返回值是一個BOOLEAN,這個若是執行失敗,Ldr就會卸載SE的Dll。
至此,_LdrpInitializeProcess已經接近返回,Shim Engine的Ldr初始化也結束了,剩下就是進程運行過程當中的Dll Load and Unload,apphelp和AcLayers會實時接到通知,進行Hook處理。
附帶一下,LdrInitializeThunk返回後:
ZwContinue->RtlUserThreadStart->RtlInitializeExceptionChain(TOP SEH)->ntdll_offset_000368A3(var_Kernel32ThreadInitThunkFunction)->kernel32!BaseThreadInitThunk->exe!ModuleEntryPoint->RtlExitUserThread
2)LdrInitShimEngineDynamic
你能夠在ntdll尚未初始化SE的Dll的時候(好比啓動時ShellCode改Eip注入Dll),執行LdrInitShimEngineDynamic函數,可讓你的Dll接收到Shim Engine的通知,前提是你的Dll導出上面列表中的函數,而且LdrInitShimEngineDynamic這個ntdll導出函數在Win7和Win8下居然參數不一樣:
1
2
NTSTATUS NTAPI LdrInitShimEngineDynamic(HMODULE hModule,PLDR_DATA_TABLE_ENTRY pLdrEntry)
//win8
NTSTATUS NTAPI LdrInitShimEngineDynamic(HMODULE hModule)
//win7
其的實現也很簡單,僅僅是判斷有沒有SE的Dll已經載入,沒有就執行一下上面說到的GetInterface函數:
3)利用Shim Engine來Dll注入
這個我相信有很多人已經在用了,其實就是在pShimData中寫入咱們的Dll文件,而且模擬成一個SE的Dll,ntdll會跟普通載入dll那樣載入。(Win8下你不導出函數也行,DllMain同樣獲得執行)
Dll源代碼在下面,把Win32Protect6.dll放到C盤下,執行exe便可。(僅在win8系統有效,我如今沒win7了)
exe源代碼(工程刪了):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
typedef struct _PROCESS_BASIC_INFORMATION{
NTSTATUS ExitStatus;
PVOID PebBaseAddress;
ULONG_PTR AffinityMask;
LONG BasePriority;
HANDLE UniqueProcessId;
HANDLE InheritedFromUniqueProcessId;
}PROCESS_BASIC_INFORMATION,*PPROCESS_BASIC_INFORMATION;
void main()
{
STARTUPINFO si = {};
si.cb = sizeof(STARTUPINFO);
PROCESS_INFORMATION pi;
CreateProcessW(L
"C:\\Windows\\notepad.exe"
,NULL,NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&si,&pi);
typedef NTSTATUS (NTAPI* fnNtQueryInformationProcess)(HANDLE,ULONG,PVOID,ULONG,PULONG);
PROCESS_BASIC_INFORMATION pbi;
((fnNtQueryInformationProcess)GetProcAddress(GetModuleHandleA(
"ntdll.dll"
),
"NtQueryInformationProcess"
))(pi.hProcess,0,&pbi,sizeof(PROCESS_BASIC_INFORMATION),NULL);
LPVOID lpShimData = (LPVOID)((ULONG_PTR)pbi.PebBaseAddress + 0x1E8);
PVOID pShimData = NULL;
ReadProcessMemory(pi.hProcess,lpShimData,&pShimData,sizeof(PVOID),NULL);
LPWSTR lpszDllFile = L
"C:\\Win32Project6.dll"
;
if
(pShimData) WriteProcessMemory(pi.hProcess,pShimData,lpszDllFile,lstrlenW(lpszDllFile) * sizeof(wchar_t),NULL);
ResumeThread(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
ExitProcess(0);
}
4)利用Shim Engine來Dll劫持(win8測試經過)
打開某個exe的文件屬性,選擇兼容性選項卡,勾選使用兼容性模式運行,下拉框隨便選一個系統,肯定保存。
進入C:\Windows\AppPatch下面找到AcLayers.dll,更名,好比改爲AcLayers.dl_。(須要管理員取得全部權)
本身寫一個Dll,名稱就是AcLayers.dll,放到AppPatch下面,Dll須要導出下面這2個名稱的函數:
1
2
PVOID WINAPI GetHookAPIs(LPCSTR lpszVerb,LPCWSTR lpszTagId,PVOID lpHookAPIs)
PVOID WINAPI NotifyShims(DWORD dwNotifyType,PVOID pvParameter)
DllMain裏面判斷載入咱們的Dll的進程是***.exe,就進行LoadLibrary核心Dll,balalalala...,而後把GetHookAPIs和NotifyShims這2個導出函數直接return 0。(不使用兼容性Hook,不過NotifyShims仍是能夠接到Dll和Load和Unload事件,附加福利啊haha~)
若是不是,LoadLibrary("AcLayers.dl_"),把GetHookAPIs和NotifyShims這2個導出函數JMP到AcLayers.dl_的上面。(實現其餘程序的兼容性模式設置)
順便附帶上32和64的win8 apphelp.dll的導出函數lib文件。
小弟最近失眠得厲害,也就暑假回家了纔有時間整理這篇文章出來,若是哪裏寫得很差,但願你們海涵~ 上傳的附件: