zoukankan      html  css  js  c++  java
  • Windows 8的用户模式Shim Engine小探及利用

    转载:
    https://bbs.pediy.com/thread-175483.htm

    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(RtlInitNlsTablesRtlResetRtlTranslations)

    _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:Windowssystem32apphelp.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_USERSoftwareMicrosoftWindows NTCurrentVersionAppCompatFlagsLayers
    (其实添加字符串值完成后,应该还要执行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:WindowsAppPatch下面找到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文件。
    小弟最近失眠得厉害,也就暑假回家了才有时间整理这篇文章出来,如果哪里写得不好,希望大家海涵~ 上传的附件:

  • 相关阅读:
    python note 30 断点续传
    python note 29 线程创建
    python note 28 socketserver
    python note 27 粘包
    python note 26 socket
    python note 25 约束
    Sed 用法
    python note 24 反射
    python note 23 组合
    python note 22 面向对象成员
  • 原文地址:https://www.cnblogs.com/skyus/p/8673029.html
Copyright © 2011-2022 走看看