Windows 8的用户模式Shim Engine小探及利用
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
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
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文件。
小弟最近失眠得厉害,也就暑假回家了才有时间整理这篇文章出来,如果哪里写得不好,希望大家海涵~
上传的附件:
- LdrInitShimEngineDynamic.gif(11.94kb,3次下载)
- LdrpInitShimEngine.gif(6.20kb,15次下载)
- LdrpInitShimEngine2.gif(3.17kb,14次下载)
- LdrpInitShimEngine3.gif(11.12kb,15次下载)
- LdrpInitShimEngine4.gif(12.69kb,15次下载)
- LdrpInitShimEngine5.gif(11.68kb,14次下载)
- LdrpInitShimEngine6.gif(10.53kb,8次下载)
- LdrpInitShimEngine7.gif(19.95kb,9次下载)
- LdrpInitShimEngine8.gif(12.20kb,2次下载)
- LdrpInitShimEngine9.gif(12.34kb,2次下载)
- LdrpInitShimEngine10.gif(8.82kb,3次下载)
- LdrpInitShimEngine11.gif(9.04kb,2次下载)
- 未命名.jpg(117.96kb,16次下载)
- Win32Project6.rar(5.97kb,151次下载)
- apphelp_lib.zip(18.98kb,122次下载)
- LdrpInitShimEngine12.gif(4.87kb,2次下载)
- 2013-07-15_131929.gif(6.83kb,3次下载)
Windows 8的用户模式Shim Engine小探及利用的更多相关文章
- linux 启动引导器 grub,单用户模式:
Linux启动引导器 安装linux操作系统的时候就已经将启动引导器安装到硬盘上去了,才能通过硬盘的读取方式启动操作系统. 引导器分为2种: Lilo:功能比较简单,使用比较麻烦,后续发行版中使用gr ...
- 【windows核心编程】 第八章 用户模式下的线程同步
Windows核心编程 第八章 用户模式下的线程同步 1. 线程之间通信发生在以下两种情况: ① 需要让多个线程同时访问一个共享资源,同时不能破坏资源的完整性 ② 一个线程需要通知其他线程 ...
- windows核心编程---第七章 用户模式下的线程同步
用户模式下的线程同步 系统中的线程必须访问系统资源,如堆.串口.文件.窗口以及其他资源.如果一个线程独占了对某个资源的访问,其他线程就无法完成工作.我们也必须限制线程在任何时刻都能访问任何资源.比如在 ...
- 理解Windows内核模式与用户模式
1.基础 执行 Windows 的计算机中的处理器有两个不同模式:"用户模式"和"内核模式". 依据处理器上执行的代码的类型,处理器在两个模式之间切换.应 ...
- 《windows核心编程系列》七谈谈用户模式下的线程同步
用户模式下的线程同步 系统中的线程必须访问系统资源,如堆.串口.文件.窗口以及其他资源.如果一个线程独占了对某个资源的访问,其他线程就无法完成工作.我们也必须限制线程在任何时刻都能访问任何资源.比如在 ...
- Windows系统的四个重要概念——进程、线程、虚拟内存、内核模式和用户模式
引言 本来在写一篇Windows内存管理的文章,写着写着就发现好多基础的概念都要先讲.更可怕的是,这些基础的概念我却不能完全讲清楚.只好再把这本<深入解析Windows操作系统>翻到第一章 ...
- Windows用户模式调试内部组件
简介 允许用户模式调试工作的内部机制很少得到充分的解释.更糟糕的是,这些机制在Windows XP中已经发生了根本性的变化,当许多支持被重新编写时,还通过将ntdll中的大多数例程作为本地API的一部 ...
- 【windows 操作系统】【CPU】用户模式和内核模式(用户层和内核层)
所有的现代操作系统中,CPU是在两种不同的模式下运行的: 注意以下内容来自微软: windows用户模式和内核模式 运行 Windows 的计算机中的处理器有两个不同模式:用户模式 和内核模式 . 用 ...
- Windows核心编程:第8章 用户模式下的线程同步
Github https://github.com/gongluck/Windows-Core-Program.git //第8章 用户模式下的线程同步.cpp: 定义应用程序的入口点. // #in ...
随机推荐
- 2018-2019-2 网络对抗技术 20165202 Exp1 PC平台逆向破解
一.基础知识 掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码. NOP指令为空指令,当运行该指令时CPU不做任何事情,但是会占用一个指令的时间,当指令间需要有延时,可以插入NOP指令 ...
- 部署python3.6下的django
首先是安装好nginx,配置web目录,配置文件在confi.d中, server { # the port your site will be served on listen ; # the do ...
- Python GIL 系列之再谈Python的GIL
1. 之前写过一篇<通过实例认识Python的GIL>的文章,感觉有些意犹未尽 2. 这次对例子作了些扩展,进一步的分析GIL对Python程序的影响 2.1 先来看例子: [python ...
- Android 自定义圆形旋转进度条,仿微博头像加载效果
微博 App 的用户头像有一个圆形旋转进度条的加载效果,看上去效果非常不错,如图所示: 据说 Instagram 也采用了这种效果.最近抽空研究了一下,最后实现的效果是这样: 基本上能模拟出个大概,代 ...
- flash存储原理
norflash 带有 SRAM接口,有足够的地址引脚来寻址,可以很容易地存取其内容每一字节:nandflash器件使用复杂的IO口串行的存取数据,读写操作采用512字节的块(也就是读/写某个字节,必 ...
- Centos中查看系统信息的常用命令
系统日志文件(可以通过cat或tail命令来查看) /var/log/message 系统启动后的信息和错误日志,是Red Hat Linux中最常用的日志之一 /var/log/secure 与安全 ...
- PHP webservice初探
背景:在最近的开发中,为了解决公司内部系统与外部系统的对接,开始接触到了webservice接口,外部公司提供接口供我们调用,已达到数据同步的目的,因此有必要普及一下web service的知识了! ...
- zzuli2228: 神奇的排名
题目描述 小明最近沉迷上了打codeforces的比赛,和acm不同的是,这种比赛是积分制的,按照做题用时,错误次数以及hack来计分的.在某一场比赛,共有n个人参加比赛,现在给出你所有人的分数,小明 ...
- (5)可变、不可变和hash函数
分类情况 与列表相似,列表用[],元组是()表示 内存角度看列表与数字的变与不变 列表 >>>l = [1,2,3,4] >>>id(l) 4392665160 & ...
- java新的语法糖:Java 8 Lambda表达式
***************************************************************************