打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
详解AppLocker(Part 4)

                                 阅读量    1065169 |   稿费

分享到:
译文声明

本文是翻译文章,文章原作者tyranidslair,文章来源:tyranidslair.blogspot.com
原文地址:https://tyranidslair.blogspot.com/2019/11/the-internals-of-applocker-part-4.html


译文仅供参考,具体内容表达以及含义原文为准

 

0x00 前言

前几篇文章中,我介绍了AppLocker(AL)如何阻止进程创建操作的一些基本知识,在本文中,我将分析AL如何阻止DLL加载。如果我们深入研究过Windows的组策略编辑器,在为AL启用DLL规则时,就可以看到如下警告信息:

似乎微软并不推荐启用DLL阻止规则,但由于我已知找不到阻止DLL加载的官方文档,并且我们也要避免“知其然,不知其所以然”的问题,因此我们还是应当深入研究一下。

 

0x01 技术分析

根据Part 1我们可知,DLL.Applocker文件中包含适用于DLL的策略。我们可以按照Part 3的方式,使用Format-AppLockerSecurityDescriptor函数从文件中dump出安全描述符,验证其是否符合我们的预期。对应的DACL如下所示:

 - Type  : AllowedCallback - Name  : Everyone - Access: Execute|ReadAttributes|ReadControl|Synchronize - Condition: APPID://PATH Contains "%WINDIR%\*" - Type  : AllowedCallback - Name  : Everyone - Access: Execute|ReadAttributes|ReadControl|Synchronize - Condition: APPID://PATH Contains "%PROGRAMFILES%\*" - Type  : AllowedCallback - Name  : BUILTIN\Administrators - Access: Execute|ReadAttributes|ReadControl|Synchronize - Condition: APPID://PATH Contains "*" - Type  : Allowed - Name  : APPLICATION PACKAGE AUTHORITY\ALL APPLICATION PACKAGES - Access: Execute|ReadAttributes|ReadControl|Synchronize - Type  : Allowed - Name  : APPLICATION PACKAGE AUTHORITY\ALL RESTRICTED APPLICATION PACKAGES - Access: Execute|ReadAttributes|ReadControl|Synchronize

这里一切正常,可以在安全描述符中看到我们的规则。然而从结果中我们可以猜测,内核驱动可能会负责某些工作。如果查看APPID中的函数,可以找到一个SrpVerifyDll函数,我们可以以此为切入点开展研究。

通过跟踪函数引用信息,我们可以发现某个Device IO Control代码会调用SrpVerifyDll来处理APPID驱动对外提供的一个设备对象(\Device\SrpDevice)。经过一番逆向分析后,我们可以找到控制代码及对应的输入/输出结构,如下所示:

// 0x225804#define IOCTL_SRP_VERIFY_DLL CTL_CODE(FILE_DEVICE_UNKNOWN, 1537,             METHOD_BUFFERED, FILE_READ_DATA)struct SRP_VERIFY_DLL_INPUT {    ULONGLONG FileHandle;    USHORT FileNameLength;    WCHAR FileName[ANYSIZE_ARRAY];};struct SRP_VERIFY_DLL_OUTPUT {    NTSTATUS VerifyStatus;};

SrpVerifyDll函数本身并没有太多需要注意的地方,基本逻辑与Part 2与Part 3介绍的AL对进程创建的验证逻辑类似:

1、获取并复制访问检查令牌。如果令牌为受限令牌,则查询登录会话令牌;

2、检查令牌是否能通过检查策略,查看令牌中是否包含SANDBOX_INERT,或者对应某个服务;

3、调用AiGetFileAttributes函数,处理传入的文件句柄,获取安全属性;

4、使用AiSetTokenAttributes在令牌上设置安全属性;

5、使用策略安全描述符以及返回给Device IO Control输出的状态结果来执行访问权限检查。

之所以需要重新创建安全属性,是因为访问检查过程中AL需要了解正在加载的DLL(而不是原始的可执行文件)的相关信息。虽然文件名作为输入结构参数传入,但目前我发现文件名仅用于日志记录。

与Part 3中分析的过程相比,这里步骤1中有比较大的一个不同点。在Part 3介绍的阻止进程创建过程中,如果当前令牌为未提升的UAC令牌,那么代码就会查询完整的令牌,然后使用该令牌来检查访问权限。这意味着即使我们以非提升用户权限来创建进程,访问权限检查过程中AL仍会把我们看成管理员来处理。然而在DLL阻止过程中并没有包含这个步骤,因此这里可能存在一种奇怪的情况:我们可以在任意位置创建进程,但没办法在同一个目录中加载任何DLL。我不知道这是微软故意为之还是没有考虑到的一种情况。

那么谁调用Device IO Control来验证DLL?为了节省时间,我使用内核调试器在SrpVerifyDll上设置断点,然后dump栈布局,寻找调用方:

Breakpoint 1 hitappid!SrpVerifyDll:fffff803`38cff100 48895c2410      mov     qword ptr [rsp+10h],rbx0: kd> kc # Call Site00 appid!SrpVerifyDll01 appid!AipDeviceIoControlDispatch02 nt!IofCallDriver03 nt!IopSynchronousServiceTail04 nt!IopXxxControlFile05 nt!NtDeviceIoControlFile06 nt!KiSystemServiceCopyEnd07 ntdll!NtDeviceIoControlFile08 ADVAPI32!SaferpIsDllAllowed09 ADVAPI32!SaferiIsDllAllowed0a ntdll!LdrpMapDllNtFileName0b ntdll!LdrpMapDllFullPath0c ntdll!LdrpProcessWork0d ntdll!LdrpLoadDllInternal0e ntdll!LdrpLoadDll

非常简单,看来SaferiIsDllAllowed是调用方,而该函数由LdrLoadDll调用。这一点非常正常,然而有趣的是NTDLL正在调用ADVAPI32中的函数,微软难道没考虑到分层隔离机制吗?让我们看一下LdrpMapDllNtFileName函数,这是系统转到ADVAPI32前使用的最后一个NTDLL函数。调用SaferiIsDllAllowed的代码如下所示:

NTSTATUS status;if ((LoadInfo->LoadFlags & 0x100) == 0         && LdrpAdvapi32DllHandle) {  status = LdrpSaferIsDllAllowedRoutine(        LoadInfo->FileHandle, LoadInfo->FileName);}

SaferiIsDllAllowed的调用实际上用到了某个全局函数指针,这是因为NTDLL无法直接链接到ADVAPI32。负责初始化这些值的函数为LdrpCodeAuthzInitialize,任何非系统代码在新进程中运行之前,系统会调用这个初始化函数。函数首先会检查某些注册表键值(最重要的是\Registry\Machine\System\CurrentControlSet\Control\Srp\GP\DLL),判断是否存在任何子项。如果满足条件,代码就会使用LdrLoadDll加载ADVAPI32库,查询SaferiIsDllAllowed导出函数。代码会将DLL句柄存放在LdrpAdvapi32DllHandle中,将函数指针经过“异或”加密处理后,存放在LdrpSaferIsDllAllowedRoutine中。

调用SaferiIsDllAllowed后,AL会检查返回状态。如果状态码不等于STATUS_SUCCESS,那么加载程序将退出,拒绝继续加载DLL。需要再次强调的是,这个处理过程与WDAC有所不同。WDAC会在内核映像映射过程中检查操作安全性,如果已启用WDAC且不符合已设置的策略时,我们甚至无法成功创建映射映像区块(image section)。然而对于AL,如果想加载DLL,我们只需要在用户模式组件中绕过检查机制即可。

回头看LdrpMapDllNtFileName中的调用代码,可以发现其中存在两个条件:LoadFlags必须未设置0x100标志且LdrpAdvapi32DllHandle必须不为0,在执行检查操作之前必须满足这两个条件。

这里最明显可以被修改的是LdrpAdvapi32DllHandle。如果我们已经运行了某些代码(比如VBA代码),那么可以使用WriteProcessMemory,将LdrpAdvapi32DllHandle对应的内存位置修改为0。这样处理后,对LoadLibrary的任何调用都不会被检查,我们可以绕过策略来加载任意DLL。从理论上讲,我们可能还可以让ADVAPI32加载失败,然而除非LdrLoadDll在DLL加载过程中返回STATUS_NOT_FOUND,这种错误才能让进程在初始化阶段加载失败。由于ADVAPI32属于KnownDLLs(系统已知的一些DLL),因此我找不到特别简单的解决办法(我已经尝试过之前在AMSI绕过技术中使用过技巧)。

另一个条件(LoadFlags)则更为有趣。在官方文档中提到过一个标志:LOAD_IGNORE_CODE_AUTHZ_LEVEL,我们可以将该标志传入LoadLibraryEx来绕过AppLocker的DLL验证过程。然而与SANDBOX_INERT类似,打上KB2532445补丁后,从理论上讲这种操作只适用于System或者TrustedInstaller(然而根据Stefan Kanthak的研究,这种行为可能还不会被阻止)。也就是说,在Windows 10 1909上,我无法使用该标志来执行各种操作,并且跟踪LdrLoadDll的处理流程后,我也找不到该标志被使用的痕迹。那么0x100标志源自何处?似乎LdrLoadDll会在刚开始时调用LDrpDllCharacteristicsToLoadFlags函数来设置该标志,代码如下所示:

int LdrpDllCharacteristicsToLoadFlags(int DllCharacteristics) {  int load_flags = 0;  // ...  if (DllCharacteristics & 0x1000)    load_flags |= 0x100;  return load_flags;}

如果我们将0x1000DllCharacteristics参数形式传入(这也是LdrLoadDll的第二个参数),那么AL就不会验证DLL是否匹配策略。根据官方文档0x1000这个值对应的是IMAGE_DLLCHARACTERISTICS_APPCONTAINER,但我不知道哪个API在调用LdrLoadDll时设置了该标志。最初我猜测的是LoadPackagedLibrary,然而事实并非如此。

测试该标志的简单PowerShell脚本如下所示:

Import-Module NtObjectManagerfunction Start-Dll {    Param(        [Parameter(Mandatory, Position = 0)]        [string]$Name,        [Parameter(Position = 1)]        [int]$DllChars = 0    )    $src = @'using NtApiDotNet;using System;using System.Runtime.InteropServices;public static class DllLoader{    [DllImport("ntdll.dll")]    static extern NtStatus LdrLoadDll(int dwFlags, ref int DllCharacteristics,        [In] UnicodeString DllName, out IntPtr BaseAddress);    public static IntPtr Load(string path, int dll_chars)    {        IntPtr base_address;        LdrLoadDll(1, ref dll_chars, new UnicodeString(path), out base_address).ToNtException();        return base_address;    }}'@    $asm = [NtApiDotNet.NtFile].Assembly.Location    Add-Type -TypeDefinition $src -ReferencedAssemblies $asm    [DllLoader]::Load($Name, $DllChars)}

如果我们执行Start-Dll "Path\To\Any.DLL"命令,且目标DLL不在策略允许的位置中,那么DLL会加载失败。然而如果我们执行Start-Dll "Path\To\Any.DLL" 0x1000命令,可以发现DLL能被正常加载。

 

0x02 总结

从实际层面来看,DLL阻止机制还有更多细节,不单单是通过DLL加载程序绕过进程阻止策略这么简单。如果没办法调用LdrLoadDll或者写入进程内存,我们就没有那么容易绕过DLL验证机制(但这并非不可完成的任务)。

这是详解AppLocker的最后一篇文章,回头我可能会继续讨论AppX支持、SmartLocker以及其他一些有趣的技巧。

本文翻译自 tyranidslair.blogspot.com, 原文链接 。如若转载请注明出处。
AppLocker
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
NT内核简介:HIPS与现代木马
怎样才能让程序在XP启动图形界面之前运行? 就像启动时自动运行的Chkdsk命令或convert(将C盘转换为NTFS格式时)命令的运行时机一样?
【原创】使用IDA PRO+OllyDbg+PEview 追踪windows API 动态链接库函数的调用过程。
dll 高级技术中--函数转发器、KNOWDLL、DLL重定向知识收集
成功解决(Win32): 已加载“C:\Windows\SysWOW64\ntdll.dll”。无法查找或打开 PDB 文件。
P/Invoke各种总结(九、如何快速获取P/Invoke方法签名)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服