|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
EnvironmentWin2K+ only, VC6+, MS SDK (Platform SDK), DDK for Win2K+, Process Explorer, Local Administrator Identity. Prerequisite KnowledgeWin2K Security stuff (SID, Token, ACL, Privilege, WinStation/Desktop, and so on), NT Service, SEH Note:
Important Note: Before the time of this writing (November 2003), I planed to include several sample programs from the book Programming Server-Side Applications for Microsoft Windows 2000, ISBN 0-7356-0753-2, Microsoft Press, 1999 to save my re-creation of a similar helper program. But, I can NOT get permission from the author, so I included my own programs I wrote years ago when I read this book. The point is that I rewrote these original samples' GUI framework with MFC and extended the user interaction (e.g. I list all Process ID in a combo to choose with clicking instead of edit box you need to type with a number found in Task Manager). Warning: I only expect that these helper programs will help you understand what I will be talking about here concerning this RunAsEx tool. You may find a few menus/button handlers empty there, while most do give you massive information. Again, by no means are they serious programs; they are only my coding practice when reading a cool book as a habit. And, I have no intention to polish them later. Oh, btw, at the time of writing these program, Visual Studio 6.0 Japanese Enterprise Version is my standard IDE, so the wizard generated all comments in Japanese and they look weird in the English version of Windows, but I have re-opened them and make sure they can be compiled on an English OS. Just ignore all Japanese (unreadable) comments. 1. What's Unique in This
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Flag |
OS |
Primary Token |
Special Group SID Included (SID, Name, Domain) |
Comment |
|
INTERACTIVE |
|
y |
S-1-5-4, INTERACTIVE, NT AUTHORITY |
The logged-on user must have been granted/enabled SeInteractiveLogonRight NT Rights (not the caller!!!). The most-used logon type, User will be granted a unique Logon SID. Tokens received with this logon will be cached with the system. This means that the local system can lose connection with the authenticating machine but still make future successful calls to LogonUser using cached credentials. After |
|
BATCH |
|
y |
S-1-5-3, BATCH, NT AUTHORITY |
The logged-on user must have been granted/enabled SeBatchLogonRight NT Rights (not the caller!!!). A unique Logon SID will be issued Tokens received with this logon type are not cached, increasing the performance of After |
|
SERVICE |
|
y |
S-1-5-6, SERVICE, NT AUTHORITY |
The logged-on user must been granted/enabled SeServiceLogonRight NT Rights (not the caller!!!). A unique Logon SID will be issued. This token will be cached for future calls to LogonUser if the machine loses connection to the authenticating agent. LogonUser returns a primary token. After *Note: You could (and should) deem the SERVICE and BATCH types as the same (except the cache); there is really not much difference, although it is the System SCM that takes care of Service logon and COM SCM that take cares of Batch Logon. |
|
UNLOCK |
|
y |
S-1-5-4, INTERACTIVE, NT AUTHORITY |
This intended for GINA DLLs only, but actual effect the the same as interactive! |
|
NETWORK |
|
n |
|
The logged-on user must have been granted/enabled SeNetworkLogonRight NT Rights (not the caller!!!). Tokens received with this logon type are not cached. In addition, this token will be an impersonation token and a "network token." The impersonate thread can access network resources. |
|
NETWORK_CLEARTEXT |
Win2K+ |
n |
|
The logged-on user must have been granted/enabled SeNetworkLogonRight NT Rights (not the caller!!!). This logon type returns an impersonation token while preserving a copy of the trustee's credentials so that network access is possible using the resulting token. The token has to be duplicated to a primary token before it can be used in calls to The impersonate thread can access network resources. |
|
NEW_CREDENTIALS |
Win2K+ |
y |
|
This logon type makes a copy of the calling thread's process token and adds a second identity to the token. This second identity will be the token's identity for all network access, whereas the token's identity for the local machine will remain the same as that of the original token. This makes the The impersonate thread can access network resource. |
|
List of Common Group SID inside User Process (non-system Process)
List of Common Group SID inside System Process (lsass, winlogon, rpcss, csrss)
| ||||
Several points need to be cleared here, which could help some readers:
RunAsEx as an example, I need TCB privileges; if you do not have them, I call LSA family API to grant one. Why do I log you off? explorer.exe (the shell) is your parent process which is started before I make the grant. Your modification will not take effect unless you restart explorer.exe. (Well, you can do that by killing the explorer and starting one from the Task Manager, which is too harsh.)
LogonUser, to be safe, you need TCB, ChangeNotify; to CreateProcessAsUser you need IncreaseQuota, AssignPrimaryToken; I add TakeOwnership for I want more to cope with Winstat\Desktop. I need CreateToken because I call ZwCreateToken
LogonUser need have SeServiceLogonRight and SeBatchLogonRight NT Rights, not the caller; the same requirement on Interactive and Network is SeInteractiveLogonRight and SeNetworkLogonRight.
Following table is a mapping Token-Elements and how to obtain them:
|
Name |
API to Call |
Function Name in |
|
User SID |
|
|
|
Group SIDs |
|
... <CoreCode.CPP> |
|
Logon SID (inside Group SIDs) |
No Way To Create From Scratch (but may be able to steal from elsewhere) |
always in format S-1-5-5-0-XXXX SYSTEM process (winlogon, lsass, etc...) does not have this User processes and other system-level processes (such as svchost running under local_service and network_service) have this member. A anonymous token token (with authentication ID = 998) does not have this member. Check this link. |
|
Privileges |
|
|
|
Token Owner |
|
|
|
Token Primary Group |
|
Same as Group SIDs |
|
Token Default DACL |
|
Importable from caller; RunAsEx uses |
|
Token Source |
|
An 8-char string; RunAsEx uses "RunAsEx+" |
|
|
|
<CoreCode.CPP> Note: In the real world, all LUID members here set 0 as RunAsEx will assign random values to these members because they are not critically important. |
|
|
|
N/A Only Meaningful When Impersonate Token |
|
|
|
Always 500 |
|
|
|
Undecided; 420 by default |
|
|
|
Checks count of Group SIDs |
|
|
|
Checks count of Privileges |
|
|
The first 1000 LUIDs are reserved (0x3E7 = 999). |
Caller pass. * Check RunAsEx desktop combo box and you will find Service-0x0-3e7$, Service-0x0-3e5$, and Service-0x0-3e4$. These are service logon sessions that get their names from this authenticate ID. Note: This is different from Logon SID S-1-5-5-0-XXXX. I mean It is the Logon Session LUID. Use the ZTokenMan check system, and you will see its authentication ID is 999. It is just the Logon Session associated with WinStat Service-0x0-3e7$. Use ProcessExplorer to check it! All Tokens I met RunAsEx uses 999 as the default when the caller does not pass into. This member is closely related to the WinStat name. |
Note: Authentication ID is closely related to the WinStat Name when you pass the token to
CreateProcessAsUserwith an Empty Desktop Name "". Take this as an example: Configure a NT Service running as a user (instead of the default system); it should be put into a WinStation called "Service-0x0-XXX$". Go to ZTokenMan and have a look at its authentication ID; it should be the same value.
Note: The word Session is overused indeed. When I mention Logon Session SID, it is a SID embedded in Token. When I say Logon Session LUID, it is a LUID (64bits) identifying a logon session and shared, usually, by some processes. When I Session ID, when in a WTS environment, it is a 0-based integer showing the current connection's order.
Now, let's review what we need to do to RunAs and the corresponding API:
SetTokenInformation, Enumerate Group SID to see if S-1-5-32-544 (alias S-1-5-32-544) there or not
LoadLibrary, GetProcAddress
LookupPrivilegeValue, OpenProcessToken, AdjustTokenPrivileges
GetUserObjectInformation
SetProcessWinStat (after closing All Windows, Hooks of Current Process)
OpenWindowStation, CreateWindowStation, CreateDesktop
LogonUser, call it to get Token—LogonUser
ZwCreateToken
SetTokenInformation
CreateService, OpenSCManager...
AllocateACE, InitializeAcl, AddAce...
CreateProcessAsUser it
DeleteService, ... Although listing all the code (>3000 lines, compact with reasonable comments) seems daunting, I give the key code here so you can get something instantly without downloading, decompressing, and opening. Please note, you must handle exceptions if something wrong happened, especially in your NT service handler; otherwise, you will be stuck miserably (you can kill process, and modify the Registry manually if you like).
RunAsUser Function (showing handling of WinStat\Desktop affairs)BOOL RunAsUser( LPTSTR pszEXE, LPTSTR pszCmdLine, //in, Target Program and Command //Line LPTSTR pszDomainName, LPTSTR pszUserName, LPTSTR pszPassword, //in LPTSTR pszDesktop, //in, Must Not be NULL, can be "NULL", "EMPTY", //"WinStat\Desktop" BOOL bCreateTokenDirectly, //in, True: ZwCreateToken; //FALSE: LogonUser DWORD dwSession, //in, -1: No WTS; n (0-based): Target SessionID BOOL bLoadProfile, //in, TRUE: Load User Profile BOOL bCopyTokenPropFromCaller, //in, TRUE: All Token Information //Use Caller Process's BOOL bKeepPriv, //in, TRUE: Use Only Privileges User Holding; //FALSE: Set All Privileges DWORD dwLogonType, //in, Same As LogonUser DWORD dwLogonProvider //in, Reserved ) { //LocalSystem no profile if(pszUserName == NULL && bLoadProfile) return FALSE; TCHAR szSrcWinStat[MAX_PATH]; TCHAR szSrcDesktop[MAX_PATH]; TCHAR szWinStat[MAX_PATH]; TCHAR szDesktop[MAX_PATH]; HWINSTA hSrcWinStat = ::GetProcessWindowStation(); HDESK hSrcDesktop = ::GetThreadDesktop(::GetCurrentThreadId()); DWORD dwFakeLen; //Get Target User Token --> his.her SID HANDLE hToken = NULL; BOOL fProcess = FALSE; BOOL fSuccess = FALSE; PROCESS_INFORMATION pi = {NULL, NULL, 0, 0}; STARTUPINFO si; PSECURITY_DESCRIPTOR pSD = NULL; BOOL bRet = FALSE; //save current win station and desktop to make return journey //smoothly if ... HWINSTA hwinstaOld = NULL; HDESK hdeskOld = NULL; HWINSTA hwinstaNew = NULL; HDESK hdeskNew = NULL; BOOL bWinStatCreated = FALSE; BOOL bDeskCreated = FALSE; BOOL bSameWinStat = FALSE; BOOL bSameDesktop = FALSE; HANDLE hTokenSelf = NULL; if(!OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hTokenSelf)) err; PROFILEINFO profInfo = { sizeof(profInfo), 0, pszUserName }; void* pEnvBlock = NULL; CreateEnvironmentBlock _CreateEnvironmentBlock; DestroyEnvironmentBlock _DestroyEnvironmentBlock; LoadUserProfileW _LoadUserProfileW; UnloadUserProfile _UnloadUserProfile; HMODULE hEvnModule = NULL; if(bLoadProfile && (hEvnModule = LoadLibrary(_T("userenv.dll"))) == NULL) err; if(hEvnModule) { //this unlikely fails _CreateEnvironmentBlock = reinterpret_cast<CreateEnvironmentBlock> (GetProcAddress(hEvnModule, "CreateEnvironmentBlock")); _DestroyEnvironmentBlock = reinterpret_cast<DestroyEnvironmentBlock> (GetProcAddress(hEvnModule, "DestroyEnvironmentBlock")); _LoadUserProfileW = reinterpret_cast<LoadUserProfileW> (GetProcAddress(hEvnModule, "LoadUserProfileW")); _UnloadUserProfile = reinterpret_cast<UNLOADUSERPROFILE> (GetProcAddress(hEvnModule, "UnloadUserProfile")); if(!_CreateEnvironmentBlock || !_DestroyEnvironmentBlock || !_LoadUserProfileW || !_UnloadUserProfile) err; } BOOL bNullDesktop = FALSE; BOOL bEmptyDesktop = FALSE; //a long try_finally block __try { if(!IsAdministrorMember()) err; if(!::EnablePrivilege(L"SeTakeOwnershipPrivilege", TRUE)) err; EnablePrivilege(L"SeTcbPrivilege", TRUE); EnablePrivilege(L"SeChangeNotifyPrivilege", TRUE); EnablePrivilege(L"SeIncreaseQuotaPrivilege", TRUE); EnablePrivilege(L"SeAssignPrimaryTokenPrivilege", TRUE); EnablePrivilege(L"SeCreateTokenPrivilege", TRUE); //get caller thread's WinStat and Desktop, //used to decide whether SetProcessWinStat is needed bRet = GetUserObjectInformation( hSrcWinStat, // handle to object UOI_NAME, // type of information to retrieve (LPVOID)szSrcWinStat, // information buffer MAX_PATH * sizeof(TCHAR), // size of the buffer &dwFakeLen // receives required buffer size ); if(!bRet || dwFakeLen > MAX_PATH * sizeof(TCHAR)) err; bRet = GetUserObjectInformation( hSrcDesktop, // handle to object UOI_NAME, // type of information to retrieve (LPVOID)szSrcDesktop, // information buffer MAX_PATH * sizeof(TCHAR), // size of the buffer &dwFakeLen // receives required buffer size ); if(!bRet || dwFakeLen > MAX_PATH * sizeof(TCHAR)) err; if(pszDesktop == NULL) //|| _tcsstr(pszDesktop, _T("\\")) //== NULL) { ::lstrcpy(szWinStat, szSrcWinStat); ::lstrcpy(szDesktop, szSrcDesktop); } else if(::lstrcmpi(pszDesktop, _T("NULL")) == 0) { bNullDesktop = TRUE; } else if(::lstrcmpi(pszDesktop, _T("EMPTY")) == 0) { bEmptyDesktop = TRUE; } else { //check the integrity of the pszDesktop TCHAR* pSlash1 = _tcsstr(pszDesktop, _T("\\")); TCHAR* pSlash2 = _tcsrchr(pszDesktop, TCHAR('\\')); if(pSlash1 != pSlash2) return FALSE; TCHAR* psz = (TCHAR*)pszDesktop; ::lstrcpyn(szWinStat, (LPCTSTR)pszDesktop, pSlash1 - psz + 1); ::lstrcpy(szDesktop, pSlash1 + 1); } if(!bNullDesktop && !bEmptyDesktop) { if(::lstrcmp(szWinStat, szSrcWinStat) == 0) //same winstat { bSameWinStat = TRUE; if(::lstrcmp(szDesktop, szSrcDesktop) == 0) //same desktop bSameDesktop = TRUE; else bSameDesktop = FALSE; } else { bSameWinStat = FALSE; bSameDesktop = FALSE; } if(!bSameDesktop || !bSameWinStat) { //for quick reversion hwinstaOld = GetProcessWindowStation(); hdeskOld = GetThreadDesktop(GetCurrentThreadId()); } } if(!bNullDesktop && !bEmptyDesktop && !bSameWinStat) { //To Test The existing of a WinStat, you can //1. EnumWindowStations and compare the string returned //2. Call OpenWindowStation with WINSTA_ENUMERATE and test // the handle //Way 2: //Because the caller func is from a Admin Grp Member, //this call is always OK unless WinStat not exists ::SetLastError(ERROR_SUCCESS); hwinstaNew = ::OpenWindowStation(szWinStat, FALSE, WINSTA_ENUMERATE); if(!hwinstaNew) { //winstat not existing ::CloseWindowStation(hwinstaNew); bWinStatCreated = TRUE; } else if(::GetLastError() == ERROR_ACCESS_DENIED) err; else { bWinStatCreated = FALSE; } } if(pszUserName == NULL || ::lstrlen(pszUserName) == 0 ) //LogOn as LocalSystem { if(!bCreateTokenDirectly) { hToken = GetLSAToken(); if(hToken == NULL) err; } else { if(!CreateTokenDirectlyEx(hToken, bCopyTokenPropFromCaller, pszUserName, pszDomainName, "RunAsEx+", NULL, NULL, NULL, TRUE, bKeepPriv, FALSE, NULL, NULL, NULL, dwLogonType, dwLogonProvider) || hToken == NULL) err; } } else { if(!bCreateTokenDirectly && !LogonUser(pszUserName, pszDomainName, pszPassword, dwLogonType, dwLogonProvider, &hToken)) { err; } else if(bCreateTokenDirectly && !CreateTokenDirectlyEx(hToken, bCopyTokenPropFromCaller, pszUserName, pszDomainName, "RunAsEx+", NULL, NULL, NULL, TRUE, bKeepPriv, FALSE, NULL, NULL, NULL, dwLogonType, dwLogonProvider)) { err; } } //Set Token Seesion ID if(dwSession != (DWORD)-1) { //need to set? DWORD dwSelfSession; if(ProcessIdToSessionId(::GetCurrentProcessId(), &dwSelfSession)) { if(dwSelfSession != dwSession) { if(!SetTokenInformation(hToken, TokenSessionId, &dwSession, sizeof(DWORD))) { if(GetLastError() == ERROR_ACCESS_DENIED) { //try again if (!ModifySecurity(hToken, TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY | TOKEN_ADJUST_SESSIONID)) { err; } if(!SetTokenInformation(hToken, TokenSessionId, &dwSession, sizeof(DWORD))) err; } } } } } pSD = HeapAlloc(GetProcessHeap(), 0, SECURITY_DESCRIPTOR_MIN_LENGTH); if(pSD == NULL) err; // We now have an empty security descriptor if (!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION)) err; if(!SetSecurityDescriptorDacl(pSD, TRUE, NULL, FALSE)) err; // Then we point to our SD from a SECURITY_ATTRIBUTES structure SECURITY_ATTRIBUTES sa = {0}; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = pSD; if(!bNullDesktop && !bEmptyDesktop && bWinStatCreated) { //Create a WinStat and naturally a new desktop //First make it a NULL DACL :=) hwinstaNew = CreateWindowStation(szWinStat, 0, MAXIMUM_ALLOWED, &sa); //using default security is good here since we are the owner if(!hwinstaNew) __leave; //We must SetProcessWindowStation when new desktop //needs created on a different WinStat if(!SetProcessWindowStation(hwinstaNew)) { ::CloseWindowStation(hwinstaNew); hwinstaNew = NULL; err; } hdeskNew = ::CreateDesktop(szDesktop, NULL, NULL, 0, //or DF_ALLOWOTHERACCOUNTHOOK MAXIMUM_ALLOWED, &sa); if(hdeskNew == NULL) { SetProcessWindowStation(hwinstaOld); ::CloseWindowStation(hwinstaNew); hwinstaNew = NULL; err; } if(!AllowTokenFullAccessToObject(hTokenSelf, hwinstaNew, SE_WINDOW_OBJECT, _T("WinStat"))) err; if(!AllowTokenFullAccessToObject(hTokenSelf, hdeskNew, SE_WINDOW_OBJECT, _T("Desktop"))) err; if(!AllowTokenFullAccessToObject(hToken, hwinstaNew, SE_WINDOW_OBJECT, _T("WinStat"))) err; if(!AllowTokenFullAccessToObject(hToken, hdeskNew, SE_WINDOW_OBJECT, _T("Desktop"))) err; } else if(!bNullDesktop && !bEmptyDesktop)//the WinStat exists { hwinstaNew = OpenWindowStation(szWinStat, FALSE, READ_CONTROL | WRITE_DAC); if(hwinstaNew == NULL) err; //give self such rights -- //WINSTA_CREATEDESKTOP WINSTA_ENUMDESKTOPS if(!bSameWinStat && !SetProcessWindowStation(hwinstaNew)) { ::CloseWindowStation(hwinstaNew); hwinstaNew = NULL; err; } //if bSameWinStat -- //if not -- SetProcessWindowStation called //check the desk //Does the desktop exists? ::SetLastError(ERROR_SUCCESS); hdeskNew = OpenDesktop(szDesktop, 0, //DF_ALLOWOTHERACCOUNTHOOK, FALSE, READ_CONTROL | WRITE_DAC); if(hdeskNew == NULL) { if(::GetLastError() == ERROR_ACCESS_DENIED) err; else //not existing { bDeskCreated = TRUE; hdeskNew = ::CreateDesktop(szDesktop, NULL, NULL, 0, //or DF_ALLOWOTHERACCOUNTHOOK MAXIMUM_ALLOWED, &sa); if(hdeskNew == NULL) err; } } else bDeskCreated = FALSE; //modify DACL of the desk if(!AllowTokenFullAccessToObject(hTokenSelf, hwinstaNew, SE_WINDOW_OBJECT, _T("WinStat"))) err; if(!AllowTokenFullAccessToObject(hTokenSelf, hdeskNew, SE_WINDOW_OBJECT, _T("Desktop"))) err; if(!AllowTokenFullAccessToObject(hToken, hwinstaNew, SE_WINDOW_OBJECT, _T("WinStat"))) err; if(!AllowTokenFullAccessToObject(hToken, hdeskNew, SE_WINDOW_OBJECT, _T("Desktop"))) err; } //ready to launch if(bLoadProfile) { // load the user profile // PROFILEINFO profInfo = { sizeof(profInfo), 0, // pszUserName }; if (!_LoadUserProfileW( hToken, &profInfo)) err; // set up an environment block //void* pEnvBlock = NULL; if(!_CreateEnvironmentBlock( &pEnvBlock, hToken, FALSE)) err; } si.cb = sizeof(si); //Desktop or WinStat\Desktop //MSDN Error Here! Note: to be 100% safe use the latter!!! TCHAR szFullDesktop[MAX_PATH]; ::lstrcpy(szFullDesktop, szWinStat); ::lstrcat(szFullDesktop, _T("\\")); ::lstrcat(szFullDesktop, szDesktop); //si.lpDesktop = bSameWinStat && bSameDesktop ? NULL : // szFullDesktop; if(!bNullDesktop && !bEmptyDesktop) { si.lpDesktop = szFullDesktop; } else if(bNullDesktop) { si.lpDesktop = NULL; } else //if bEmptyDesktop { si.lpDesktop = _T(""); } si.lpTitle = NULL; si.dwFlags = 0; si.cbReserved2 = 0; si.lpReserved = NULL; si.lpReserved2 = NULL; TCHAR szLocalCmdLine[2 * MAX_PATH]; ::SetLastError(ERROR_SUCCESS); ::lstrcpy(szLocalCmdLine, _T("\"")); ::lstrcat(szLocalCmdLine, pszEXE); ::lstrcat(szLocalCmdLine, _T("\"")); ::lstrcat(szLocalCmdLine, _T(" ")); if(pszCmdLine) ::lstrcat(szLocalCmdLine, pszCmdLine); //LPCTSTR-->LPTSTR fProcess = CreateProcessAsUser(hToken, NULL, (LPTSTR)szLocalCmdLine, &sa, &sa, //lpProcessAttributes, lpThreadAttributes FALSE, bLoadProfile ? CREATE_UNICODE_ENVIRONMENT : 0, bLoadProfile ? pEnvBlock : NULL, NULL, &si, &pi); if(!fProcess) err; fSuccess = TRUE; } __finally { if(bLoadProfile && pEnvBlock) // free the environment block _DestroyEnvironmentBlock(pEnvBlock); if(bLoadProfile && hToken && profInfo.hProfile) // unload the user profile _UnloadUserProfile( hToken, profInfo.hProfile ); if(pSD) HeapFree(GetProcessHeap(), 0, pSD); if(hToken) CloseHandle(hToken); if(hwinstaOld) ::SetProcessWindowStation(hwinstaOld); if(hdeskOld) ::SetThreadDesktop(hdeskOld); //Do NOT DO THAT! //if(hdeskNew) ::CloseDesktop(hdeskNew); //if(hwinstaNew) ::CloseWindowStation(hwinstaNew); if (fProcess) { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } if(hdeskOld) ::CloseDesktop(hdeskOld); if(hwinstaOld) ::CloseWindowStation(hwinstaOld); } return(fSuccess); }
CreatePureSystemToken (showing usage of ZwCreateToken)//LocalSystemToken will have 3 group item -- //S-1-5-32-544 --- Administrators BUILTIN //S-1-1-0 --- Everyone //S-1-5-11 --- Authenticated Users //its (token) owner is //S-1-5-18 --- SYSTEM //All priv will be ganted and enabled for simplicity //Default DACL like following //ACCESS ALLOWED NT AUTHORITY/SYSTEM //ACCESS_MASK: 00010000000000000000000000000000 // GENERIC_ALL // //ACCESS ALLOWED BUILTIN/Administrators //ACCESS_MASK: 10100000000000100000000000000000 // READ_CONTROL // GENERIC_EXECUTE // GENERIC_READ //the token source will be "*SYSTEM*" BOOL CreatePureSystemToken( /*out*/ HANDLE &hToken) { hToken = NULL; BOOL bRet = FALSE; if(!EnablePrivilege(SE_CREATE_TOKEN_NAME, TRUE)) err; PSID lpSidOwner = ::GetLocalSystemSID(); if(lpSidOwner == NULL) err;{ NTSTATUS ntStatus = 0; //SID_AND_ATTRIBUTES grpSIDAttr[3]; //SYSTEM Token has 3 group //SIDs PTOKEN_GROUPS lpGroupToken = NULL; DWORD dwGroupNumber = 3; PTOKEN_PRIVILEGES lpPrivToken = NULL; DWORD dwPrivGranted = 0; TOKEN_OWNER ownerToken; ownerToken.Owner = NULL; TOKEN_PRIMARY_GROUP primGroupToken; primGroupToken.PrimaryGroup = ::GetLocalSystemSID(); if(primGroupToken.PrimaryGroup == NULL) { ::FreeSid(lpSidOwner); err; } PTOKEN_DEFAULT_DACL lpDaclToken = NULL; SID_AND_ATTRIBUTES lpEachGrp[4]; lpEachGrp[0].Sid = NULL; lpEachGrp[1].Sid = NULL; lpEachGrp[2].Sid = NULL; __try { lpEachGrp[0].Sid = ::GetAliasAdministratorsSID(); lpEachGrp[0].Attributes = SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_OWNER; lpEachGrp[1].Sid = ::GetEveryoneSID(); lpEachGrp[1].Attributes = SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_MANDATORY; lpEachGrp[2].Sid = ::GetAuthenticatedUsersSID(); lpEachGrp[2].Attributes = SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_MANDATORY; lpEachGrp[3].Sid = NULL; lpEachGrp[3].Attributes = 0; if(lpEachGrp[0].Sid == NULL || lpEachGrp[1].Sid == NULL || lpEachGrp[2].Sid == NULL) { err; } lpGroupToken = ::CreateTokenGroups(lpEachGrp); if(lpGroupToken == NULL) err; lpPrivToken = ::CreateTokenPriv(dwPrivGranted, NULL, TRUE); if(lpPrivToken == NULL) err; ownerToken.Owner = ::GetLocalSystemSID(); if(ownerToken.Owner == NULL) err; TOKEN_USER userToken; userToken.User.Sid = ::GetLocalSystemSID(); userToken.User.Attributes = 0; //no use if(userToken.User.Sid == NULL) err; bRet = AllocateLocallyUniqueId(&luid); //this luid only //unique in this //session if(!bRet) err; TOKEN_SOURCE sourceToken = {{'*', 'S', 'Y', 'S', 'T', 'E', 'M', '*'}, {luid.LowPart, luid.HighPart}}; // Allocate the System Luid. The first 1000 LUIDs are reserved. // Use #999 here (0x3E7 = 999) // //#define SYSTEM_LUID { 0x3E7, 0x0 } //#define ANONYMOUS_LOGON_LUID { 0x3e6, 0x0 } //#define LOCALSERVICE_LUID { 0x3e5, 0x0 } //#define NETWORKSERVICE_LUID { 0x3e4, 0x0 } LUID authid = SYSTEM_LUID; lpStatsToken = ::CreateTokenStatistics(NULL, NULL, NULL, NULL, NULL, NULL, NULL, &dwGroupNumber, &dwPrivGranted, NULL); if(lpStatsToken == NULL) err; //typedef enum _SECURITY_IMPERSONATION_LEVEL { //SecurityAnonymous, //SecurityIdentification, //SecurityImpersonation, //SecurityDelegation //} SECURITY_IMPERSONATION_LEVEL, * // PSECURITY_IMPERSONATION_LEVEL; //typedef struct _SECURITY_QUALITY_OF_SERVICE { //DWORD Length; //SECURITY_IMPERSONATION_LEVEL ImpersonationLevel; //SECURITY_CONTEXT_TRACKING_MODE ContextTrackingMode; //BOOLEAN EffectiveOnly; //} SECURITY_QUALITY_OF_SERVICE, * PSECURITY_QUALITY_OF_SERVICE; //#define SECURITY_DYNAMIC_TRACKING (TRUE) //#define SECURITY_STATIC_TRACKING (FALSE) //typedef BOOLEAN SECURITY_CONTEXT_TRACKING_MODE, // * PSECURITY_CONTEXT_TRACKING_MODE; //fake value,,, NT::SECURITY_QUALITY_OF_SERVICE sqos = {sizeof(sqos), NT::SecurityAnonymous, SECURITY_STATIC_TRACKING, FALSE}; NT::OBJECT_ATTRIBUTES oa = {sizeof(oa), 0, 0, 0, 0, &sqos}; ntStatus = NT::ZwCreateToken(&hToken, TOKEN_ALL_ACCESS, &oa, TokenPrimary, NT::PLUID(&authid), // NT::PLUID(&stats->AuthenticationId), NT::PLARGE_INTEGER(&lpStatsToken->ExpirationTime), &userToken, lpGroupToken, lpPrivToken, &ownerToken, &primGroupToken, lpDaclToken, &sourceToken); //You may get 0xC0000061 which is STATUS_PRIVILEDGE_NOT_HELD, //If so, try to GRANT SeCreateTokenPrivilege and relog once... //STATUS_PRIVILEDGE_NOT_HELD; //#define STATUS_ACCESS_VIOLATION ((NTSTATUS)0xC0000005L) // winnt //An invalid parameter was passed to a service or function. //#define STATUS_INVALID_PARAMETER ((NTSTATUS)0xC000000DL) if(ntStatus == STATUS_SUCCESS) bRet = TRUE; else err; } __finally { if(lpSidOwner) ::FreeSid(lpSidOwner); if(primGroupToken.PrimaryGroup) ::FreeSid(primGroupToken.PrimaryGroup); //free DACL if you allocate it yourself inside this Func if(lpDaclToken) {} if(lpEachGrp[0].Sid) ::FreeSid(lpEachGrp[0].Sid); if(lpEachGrp[1].Sid) ::FreeSid(lpEachGrp[1].Sid); if(lpEachGrp[2].Sid) ::FreeSid(lpEachGrp[2].Sid); //Free Inside SID if(lpGroupToken && lpGroupToken->GroupCount > 0) { SID_AND_ATTRIBUTES* lpEachGrp = lpGroupToken->Groups; for(int i = 0; i < (int)lpGroupToken->GroupCount; i++) { ::FreeSid(lpEachGrp->Sid); lpEachGrp++; } } if(lpGroupToken) ::LocalFree(lpGroupToken); if(lpStatsToken) ::LocalFree(lpStatsToken); if(lpPrivToken) ::LocalFree(lpPrivToken); return bRet; } }
CreatePureUserToken (showing more sophisticated usage of ZwCreateToken)BOOL CreatePureUserToken(
HANDLE &hToken, //out
LPCTSTR pszUserName, //in, Trustee Info
LPCTSTR pszDomainName, //in, Trustee Info
char* szTokenSource, //in, maximum 8 char plain text
PLUID lpluidLogonSID, //in, Nullable, For making LogonSID
PLUID lpluidLogonSessionID, //in, Nullable, For AuthenticateID
LPCTSTR* szPrivNeeded, //in, string array of Privileges to
//Add
BOOL bAllPriv, //in, if true, add all Privileges
//to Token
BOOL bKeepPriv, //in, if true, query pszUserName's
//holding Provileges
BOOL bDisableAllRelatedGroup,//in, if true, only use Group SID
//from pNewAddedGroup
PSID* pNewAddedGroup, //in, Nullable, SID array of Group
//SID to Add
PSID sidPrimaryGroup,
//in, Nuallable, PSID of Primary Group. Default: Everyone.
//Not Critical Para
PACL lpDefaultDACL, //in, Nullable, PDACL
DWORD dwLogonType, //in, Same as LogonUser
DWORD dwLogonProvider //in, Reserved
)
{
if(pszUserName == NULL || ::lstrlen(pszUserName) == 0)
{
return CreatePureSystemToken(hToken);
}
hToken = NULL;
BOOL bRet = FALSE;
if(!EnablePrivilege(SE_CREATE_TOKEN_NAME, TRUE)) err;
PSID lpSidOwner = ::Name2SID(pszUserName, pszDomainName);
if(lpSidOwner == NULL) err;
LUID luid;
PTOKEN_STATISTICS lpStatsToken = NULL;
NTSTATUS ntStatus = 0;
//SID_AND_ATTRIBUTES grpSIDAttr[3];
PTOKEN_GROUPS lpGroupToken = NULL;
DWORD dwGroupNumber = 0;
PSID psidLogonSID = NULL;
PTOKEN_PRIVILEGES lpPrivToken = NULL;
DWORD dwPrivGra