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文件。
    小弟最近失眠得厉害,也就暑假回家了才有时间整理这篇文章出来,如果哪里写得不好,希望大家海涵~ 上传的附件:

  • 相关阅读:
    memcache安装 基于Red Hat 7.4
    LNMP源码编译
    LAMP源码编译
    Red Hat 7.4 安装laravel框架 基于xampp集成环境
    PHP 扩展开发检测清单(扩展开发必读)
    20 个 Laravel Eloquent 必备的实用技巧
    [项目推荐] Corcel 让你在 WordPress 中使用 Laravel
    Tumblr:我们是如何从 PHP 5 升级到 PHP 7 的
    PHP / Laravel 月刊 #23
    十个你需要在 PHP 7 中避免的坑
  • 原文地址:https://www.cnblogs.com/skyus/p/8673029.html
Copyright © 2011-2022 走看看