Windows下如何創建低許可權進程

来源:http://www.cnblogs.com/liaoguifa/archive/2017/10/10/lower-process-integrity.html
-Advertisement-
Play Games

1. 前言 在使用 Sysinternals 出品的 Process Explorer 過程中,對 “Run as Limited User” 功能的實現方式頗感興趣,一番搜尋之下發現Mark大神在《Running as Limited User – the Easy Way》中對該功能的實現做了相 ...


   1.  前言

在使用 Sysinternals 出品的 Process Explorer 過程中,對 “Run as Limited User” 功能的實現方式頗感興趣,一番搜尋之下發現Mark大神在《Running as Limited User – the Easy Way》中對該功能的實現做了相關的闡述:

use the CreateRestrictedToken API to create a security context, called a token, that’s a stripped-down version of its own, removing administrative privileges and group membership. After generating a token that looks like one that Windows assigns to standard users Process Explorer calls CreateProcessAsUser to launch the target process with the new token.

使用 CreateRestrictedToken API來創建安全上下文,降低令牌(Token)的管理員許可權和組成員資格,使其創建的令牌看起來像Windows賦予普通用戶時一樣,然後使用此令牌作為傳入參數調用CreateProcessAsUser來創建新的子進程。

Process Explorer queries the privileges assigned to the Users group and strips out all other privileges, including powerful ones like SeDebugPrivilege, SeLoadDriverPrivilege and SeRestorePrivilege.

查詢賦予用戶組的特權並從當前進程許可權中剔除這些許可權比如SeDebugPrivilege、SeLoadDriverPrivilege和SeRestorePrivilege。

 

剛好最近有個項目需要實現降低進程許可權的功能,在一翻折騰下就將其實現了,下麵將談談實現的歷程,如果紕漏之處,不吝指出。

   2.  知識背書

在列出代碼前需要瞭解一下一些實現原理,下麵是一些相關的知識點,如果無耐心往下看,可以直接點擊這裡跳到代碼實現處。

 

安全對象

有資格擁有安全描述符的對象如文件、管道、進程、進程間同步對象等。所有已命名的Windows對象都是安全的,那些未被命名的對象比如線程或進程對象也可以擁有安全描述符。

對於大多數的安全對象,當創建該對象時可以指定它的安全描述符。當一個安全對象被創建時,系統會對其賦予一個安全描述符,安全描述符包含由其創建者指定的安全信息,或者預設的安全信息(如果沒有特意進行指定的話)。

  • ž   應用程式可以使特定的函數對已有的對象進行操作以來獲取和設置安全信息。
  • ž   每種類型的安全對象定義了它自身的訪問許可權和自身映射的通用訪問許可權。

更詳細內容見:https://msdn.microsoft.com/en-us/library/windows/desktop/aa379557(v=vs.85).aspx

 

安全描述符(security descriptor)

包含用於保護安全對象的安全信息。

安全描述符描述 對象的所有者(SIDs) 和 以下的訪問控制列表:

  • 自由訪問控制列表(DACL):指明特定用戶或組對該對象的訪問是允許或拒絕;
  • 安全訪問控制列表(SACL):控制系統審計如何訪問對象。

 

安全標識(Security Identifiers)

一定長度用來表示托管的唯一值。

安全標識主要運用於如下幾個方面:

  • 在安全描述符中定義對象的所有者和基本組;
  • 在訪問控制項中定義托管的許可權是允許、拒絕或是審計;
  • 在訪問令牌中定義用戶和用戶所在的組。

 

訪問令牌

包含登錄用戶的信息。用來描述一個進程或線程的安全上下文的對象,令牌的信息包含關聯到進程或線程的賬號的標識和特權。

當一個用戶登錄時,系統對用戶的賬號和密碼進行認證,如果登錄成功,系統則創建一個訪問令牌,每個進程運行時都有一個訪問令牌代表當前的用戶,訪問令牌中的安全描述符指明當前用戶所屬的賬號和所屬的組賬號,令牌也包含一系列由用戶或用戶所在組進行維護的許可權,在一個進程試圖進行訪問安全對象或執行系統管理員任務過程中需要許可權時,系統通過這個令牌來確認關聯的用戶。

 

訪問控制列表及其訪問控制項

自由訪問控制列表(DACL)包含若幹個訪問控制項(ACEs)。

約定的執行規則如下:

  • ž   如果對象沒有自由訪問控制列表(DACL),則任何用戶對其均有完全的訪問許可權;
  • ž   如果對象擁有DACL,那麼系統僅允許那些在訪問控制項(ACE)顯式指明的訪問許可權;
  • ž   如果在訪問控制列表(DACL)中不存在訪問控制項(ACE),那麼系統不允許任何用戶對其進行訪問;
  • ž   如果訪問控制列表中的訪問控制項對准許訪問的用戶或組數目有限,那麼系統會隱式拒絕那些不在訪問控制項中的其他托管的訪問

需要註意的是訪問控制項的排序很重要。因為系統按照隊列的方式讀取訪問控制項,直到訪問被拒絕或允許。用戶的訪問拒絕ACE必須放在訪問允許ACE的前頭,否則當系統讀到對組的訪問允許ACE時,它會給當前限制的用戶賦予訪問的許可權。系統在檢測到請求訪問被允許或拒絕後就不再往下檢查。

你可以通過標識允許訪問的ACE來控制對對象的訪問,你無需顯式地拒絕一個對象的訪問。

 

線程和安全對象間的交互

當一個線程想要使用一個安全對象時,系統線上程執行前會進行訪問審核,在訪問審核中,系統將線程訪問令牌中的安全信息與對象安全描述符中的安全信息進行比對。

訪問令牌中包含的安全標識(SIDs)可以指明與線程關聯的用戶,系統查看線程訪問令牌中用戶或組的SID,同時檢查對象的自由訪問控制列表(DACL),自由訪問控制列表(DACL)中包含存儲有指明對指定的用戶或組的訪問許可權是允許或拒絕信息的訪問控制項(ACE),系統檢查每個訪問控制項(ACE)直至出現指明針對此線程(的用戶或組的SID)的訪問許可權是允許還是拒絕的ACE,或者到最終都沒有對應的ACEs可以檢查。

 

(圖片出處:https://msdn.microsoft.com/en-us/library/windows/desktop/aa378890(v=vs.85).aspx

 

系統按照序列檢查每個ACE,查詢ACE中的托管與定義線上程中的托管(根據托管的SID)一致的ACE,直到如下的情況出現:

  • ž   表明訪問拒絕的ACE顯式拒絕線上程的訪問令牌中的一個托管的任何訪問許可權;
  • ž   一個或多個表明訪問允許的ACEs顯式地為線程訪問令牌中的托管提供所有訪問許可權;
  • ž   所有ACEs已經比對審核完但至少有一個請求訪問許可權沒有顯式允許,這種情況下該訪問許可權則被隱式拒絕。

一個訪問控制列表(ACL)可以有多個的訪問控制項(ACE)針對令牌的(同一個)SIDs,這種情況下每個ACE授予的訪問許可權是可以進行累積疊加,比如,如果一個ACE對一個組允許讀的訪問許可權,而另一個ACE對該組內的一個用戶允許寫的訪問許可權,那麼該用戶對於當前對象就擁有了讀和寫的訪問許可權。

 

(圖片出處:https://msdn.microsoft.com/en-us/library/windows/desktop/aa446597(v=vs.85).aspx

如上圖所示,對於線程A,儘管在ACE@3中允許寫許可權,但因為在ACE@1中已經顯式拒絕“Andrew”用戶的訪問許可權,所以該安全對象對於線程A是不可訪問的;對於線程B,在ACE@2中顯式指明A組用戶可以有寫的許可權,並且在ACE@3中允許任何用戶讀和執行的許可權,所以線程B對這個安全對象擁有讀、寫以及執行的許可權。

 

完整性級別

Windows完整性機制是對Windows安全架構的擴展,該完整性機制通過添加完整性等級到安全訪問令牌和添加強制性標記訪問控制項到安全描述符中的系統訪問控制列表(SACL)

進程在安全訪問令牌中定義完整性等級,IE在保護模式下的完整性等級為低,從開始菜單運行的應用程式的等級為中等,需要管理員許可權並以管理員許可權運行的應用程式的等級為高。

保護模式能夠有效地減少IE進程附帶的攻擊行為如篡改和摧毀數據、安裝惡意程式;相比其他程式,連接網路的程式更容易遭受網路的攻擊因為它們更可能從未知源地址下載未受信任的內容,通過降低完整性等級或限制對其的允許許可權,可以減少篡改系統或污染用戶數據文件的風險。

在系統訪問控制列表(SACL)中有一個稱為強制標識的訪問控制項(ACE),該控制項的安全描述符定義完整性等級或允許訪問當前對象需要達到的等級,安全對象如果沒有該控制項則預設擁有中等的完整性等級;

即使用戶在自由訪問控制列表(DACL)中已經明確授予相應的寫許可權,低等級的進程也不能獲取比其高等級的安全對象的寫許可權,完整性等級檢驗在用戶訪問許可權審查之前完成。

 

所有的文件和註冊表鍵在預設下的完整性等級為中,而由低等完整性進程創建的安全對象,系統會自動地賦予其低等完整性強制標誌,同樣,由低等完整性進程創建的子進程也是在低完整性等級下運行。

 

完整性訪問等級(IL)

系統許可權

安全標識

 

System

System

S-1-16-16384

 

High

Administrative

 

S-1-16-12288

可安裝文件到Program Files文件夾、往敏感的註冊表中如HKEY_LOCAL_MACHINE寫數據

Medium

User

S-1-16-8192

創建和修改用戶文檔中的文件、往特定用戶的註冊表如HKEY_CURRENT_USER中寫數據

Low

Untrusted

S-1-16-4096

僅能往低等級位置寫數據如臨時網路文件夾和註冊表

HKEY_CURRENT_USER\Software\LowRegistry

 

低完整性進程可以往用戶存檔文件下寫文件,通常為%USER PROFILE%\AppData\LocalLow,可以通過調用SHGetKnownFolderPath 函數並傳入FOLDERID_LocalAppDataLow參數來獲取完整的路徑名稱

SHGetKnownFolderPath(FOLDERID_LocalAppDataLow, 0, NULL, szPath, ARRAYSIZE(szPath));

同樣低完整性進程可以往指定的註冊表下創建和修改子鍵,該註冊表路徑通常為HKEY_CURRENT_USER\Software\AppDataLow

   3.  代碼實現

實現思路

  1. 創建新的普通用戶組和系統管理員的安全描述符標識;
  2. 獲取當前進程的令牌,並根據令牌句柄獲取當前進程所擁有的特權;
  3. 通過已創建的普通用戶組的安全描述符標識獲取普通用戶組所擁有的特權;
  4. 給當前進程令牌中的管理員安全描述符添加Deny-only屬性,以此達到避免新創建的進程以管理員作為其所有者;
  5. 從當前進程擁有的特權中剔除普通用戶組所沒有的特權;
  6. 從新的受限令牌中複製為模擬令牌;
  7. 將模擬令牌的完整性特權設為低級,以限制新創建的進程對普通文檔、可執行程式的寫、執行等訪問許可權;

代碼實現

  1 void CreateRestrictedProcess() 
  2 {
  3     SECURITY_ATTRIBUTES sa;
  4     sa.nLength = sizeof(SECURITY_ATTRIBUTES);
  5     sa.bInheritHandle = TRUE;
  6     sa.lpSecurityDescriptor = NULL;
  7 
  8     TCHAR szCmdLine[CMDLINE_SIZE] = {0};
  9     HANDLE hToken = NULL;
 10     HANDLE hNewToken = NULL;
 11     HANDLE hNewExToken = NULL;
 12 
 13     CHAR szIntegritySid[20] = "S-1-16-4096";
 14     PSID pIntegritySid = NULL;
 15     PSID pUserGroupSID = NULL;
 16     PSID pAdminSID = NULL;
 17 
 18     TOKEN_MANDATORY_LABEL tml = {0};
 19 
 20     PROCESS_INFORMATION pi;
 21     STARTUPINFO si;
 22 
 23     BOOL bSuc = FALSE;
 24     ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
 25 
 26     ZeroMemory(&si, sizeof(STARTUPINFO));
 27     si.cb = sizeof(STARTUPINFO);
 28     GetStartupInfo(&si);
 29     DWORD fdwCreate = 0;
 30 
 31     __try
 32     {
 33 
 34         if (!OpenProcessToken(GetCurrentProcess(),
 35             //MAXIMUM_ALLOWED,
 36             TOKEN_DUPLICATE |
 37             TOKEN_ADJUST_DEFAULT |
 38             TOKEN_QUERY |
 39             TOKEN_ASSIGN_PRIMARY,
 40             &hToken))
 41         {
 42             char szMsg[DEFAULT_MSG_SIZE] = {0};
 43             Dbg("OpenProcessToken failed, GLE = %u.", GetLastError());
 44             __leave;
 45         }
 46 
 47         Dbg("Using RestrictedTokens way !!!");
 48         DWORD dwSize = 0;
 49         DWORD dwTokenInfoLength = 0;
 50         SID_IDENTIFIER_AUTHORITY SIDAuth = SECURITY_NT_AUTHORITY;
 51         SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;
 52         if(!AllocateAndInitializeSid(
 53             &SIDAuthNT, 
 54             0x2, 
 55             SECURITY_BUILTIN_DOMAIN_RID/*0×20*/, 
 56             DOMAIN_ALIAS_RID_USERS, 
 57             0, 0, 0, 0, 0, 0,
 58             &pUserGroupSID))
 59         {
 60             Dbg("AllocateAndInitializeSid for UserGroup Error %u", GetLastError());
 61             __leave;
 62         }
 63 
 64         // Create a SID for the BUILTIN\Administrators group.
 65         if(! AllocateAndInitializeSid( &SIDAuth, 2,
 66             SECURITY_BUILTIN_DOMAIN_RID,
 67             DOMAIN_ALIAS_RID_ADMINS,
 68             0, 0, 0, 0, 0, 0,
 69             &pAdminSID) ) 
 70         {
 71             Dbg("AllocateAndInitializeSid for AdminGroup Error %u", GetLastError());
 72             __leave;
 73         }
 74 
 75         SID_AND_ATTRIBUTES SidToDisable[1] = {0};
 76         SidToDisable[0].Sid = pAdminSID;
 77         SidToDisable[0].Attributes = 0;
 78 
 79         PTOKEN_PRIVILEGES pTokenPrivileges = NULL;
 80         PTOKEN_PRIVILEGES pTokenPrivilegesToDel = NULL;
 81         if(!GetTokenInformation(hToken, TokenPrivileges, NULL, 0, &dwSize))
 82         {
 83             if(GetLastError() == ERROR_INSUFFICIENT_BUFFER)
 84             {
 85                 pTokenPrivileges = (PTOKEN_PRIVILEGES)LocalAlloc(0, dwSize);
 86                 pTokenPrivilegesToDel = (PTOKEN_PRIVILEGES)LocalAlloc(0, dwSize);
 87                 if(pTokenPrivileges != NULL && pTokenPrivilegesToDel != NULL)
 88                 {
 89                     if(!GetTokenInformation(hToken, TokenPrivileges, pTokenPrivileges, dwSize, &dwSize))
 90                     {
 91                         Dbg("GetTokenInformation about TokenPrivileges failed GTE = %u.", GetLastError());
 92                         __leave;
 93                     }
 94                 }
 95                 else
 96                 {
 97                     char szMsg[DEFAULT_MSG_SIZE] = {0};
 98                     Dbg("LocalAlloc for pTokenPrivileges failed GTE = %u.", GetLastError());
 99                     __leave;
100                 }
101             }
102         }
103 
104         LUID_AND_ATTRIBUTES *pTokenLUID = pTokenPrivileges->Privileges;
105         Dbg("CurrentToken's TokenPrivileges Count: %u", pTokenPrivileges->PrivilegeCount);
106         DWORD dwLuidCount = 0;
107         PLUID pPrivilegeLuid = NULL;
108         if(!CTWProcHelper::GetPrivilegeLUIDWithSID(pUserGroupSID, &pPrivilegeLuid, &dwLuidCount))
109         {
110             Dbg("GetPrivilegeLUIDWithSID failed GTE = %u.", GetLastError());
111             if(pPrivilegeLuid)
112             {
113                 //HeapFree(GetProcessHeap(), 0, pPrivilegeLuid);
114                 LocalFree(pPrivilegeLuid);
115                 pPrivilegeLuid = NULL;
116             }
117             __leave;
118         }
119         Dbg("UserGroup's TokenPrivileges Count: %u", dwLuidCount);
120 
121         DWORD dwDelPrivilegeCount = 0;
122         for(DWORD dwIdx=0; dwIdx<(pTokenPrivileges->PrivilegeCount); dwIdx++)
123         {
124             BOOL bFound = FALSE;
125             DWORD dwJdx = 0;
126             for(; dwJdx<dwLuidCount; dwJdx++)
127             {
128                 //if(memcmp(&(pTokenLUID[dwIdx].Luid), &(pPrivilegeLuid[dwJdx]), sizeof(LUID)) == 0)
129                 if((pTokenLUID[dwIdx].Luid.HighPart == pPrivilegeLuid[dwJdx].HighPart)
130                     &&
131                     (pTokenLUID[dwIdx].Luid.LowPart == pPrivilegeLuid[dwJdx].LowPart))
132                 {
133                     bFound = TRUE;
134                     break;
135                 }
136             }
137             if(!bFound)
138             {
139                 char szPrivilegeName[MAX_PATH] = {0};
140                 DWORD dwNameSize = MAX_PATH;
141                 if(!LookupPrivilegeName(NULL, &(pTokenLUID[dwIdx].Luid), szPrivilegeName, &dwNameSize))
142                 {
143                     Dbg("LookupPrivilegeName failed GTE = %u.", GetLastError());
144                     //Dbg("NoFound[%u]: i=%u, j=%u", dwDelPrivilegeCount, dwIdx, dwJdx);
145                 }
146                 //else
147                 //{
148                 //    Dbg("NoFound[%u]: i=%u, j=%u -> %s", dwDelPrivilegeCount, dwIdx, dwJdx, szPrivilegeName);
149                 //}
150                 pTokenPrivilegesToDel->Privileges[dwDelPrivilegeCount++].Luid = pTokenLUID[dwIdx].Luid;
151             }
152         }
153         pTokenPrivilegesToDel->PrivilegeCount = dwDelPrivilegeCount;
154         Dbg("TokenPrivileges to delete Count: %u", dwDelPrivilegeCount);
155         if(pPrivilegeLuid)
156         {
157             //HeapFree(GetProcessHeap(), 0, pPrivilegeLuid);
158             LocalFree(pPrivilegeLuid);
159             pPrivilegeLuid = NULL;
160         }
161 
162         if(!CreateRestrictedToken(hToken,
163             0,
164             1, SidToDisable,
165             //0, NULL,
166             dwDelPrivilegeCount, pTokenPrivilegesToDel->Privileges,
167             0, NULL,
168             &hNewToken
169             ))
170         {
171             char szMsg[DEFAULT_MSG_SIZE] = {0};
172             Dbg("CreateRestrictedToken failed GTE = %u.", GetLastError());
173             __leave;
174         }
175 
176         // Duplicate the primary token of the current process.
177         if (!DuplicateTokenEx(hNewToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, 
178             TokenPrimary, &hNewExToken))
179         {
180             Dbg("DuplicateTokenEx failed GTE = %u.", GetLastError());
181             hNewExToken = NULL;
182             //__leave;
183         }
184         else
185         {
186             if (ConvertStringSidToSid(szIntegritySid, &pIntegritySid))
187             {
188                 tml.Label.Attributes = SE_GROUP_INTEGRITY;
189                 tml.Label.Sid = pIntegritySid;
190 
191                 // Set the process integrity level
192                 if (!SetTokenInformation(hNewExToken, TokenIntegrityLevel, &tml,
193                     sizeof(TOKEN_MANDATORY_LABEL) + GetLengthSid(pIntegritySid)))
194                 {
195                     Dbg("SetTokenInformation failed GTE = %u.", GetLastError());
196                     //__leave;
197                 }
198                 else
199                 {
200                     CloseHandle(hNewToken);
201                     hNewToken = hNewExToken;
202                     hNewExToken = NULL;
203                     Dbg("Assign Low Mandatory Level to New Token which used to CreateProcessAsUser.");
204                 }
205             }
206 
207         }
208 
209         if(!(bSuc = CreateProcessAsUser(hNewToken, NULL,
210             szCmdLine,     // command line 
211             NULL,          // TODO: process security attributes 
212             NULL,          // TODO: primary thread security attributes 
213             TRUE,         // handles are inherited ?? 
214             fdwCreate,     // creation flags
215             NULL,          // use parent's environment 
216             NULL,          // use parent's current directory 
217             &si,           // STARTUPINFO pointer 
218             &pi)))         // receives PROCESS_INFORMATION 
219         {
220             Dbg("CreateProcessAsUser failed GTE = %u.", GetLastError());
221             __leave;
222         }
223 
224         if(pTokenPrivileges)
225         {
226             LocalFree(pTokenPrivileges);
227         }
228         if(pTokenPrivilegesToDel)
229         {
230             LocalFree(pTokenPrivilegesToDel);
231         }
232     }
233     __finally
234     {
235         if(pIntegritySid)
236         {
237             LocalFree(pIntegritySid);
238         }
239         if(pUserGroupSID)
240         {
241             LocalFree(pUserGroupSID);
242         }
243         if(pAdminSID)
244         {
245             LocalFree(pAdminSID);
246         }
247         // 
248         // Close the access token.
249         // 
250         if (hToken)
251         {
252             CloseHandle(hToken);
253         }
254         if(hNewToken)
255         {
256             CloseHandle(hNewToken);
257         }
258         if(hNewExToken)
259         {
260             CloseHandle(hNewExToken);
261         }
262         if(!bSuc)
263         {
264             Dbg("Retry to Create process in normal way.");
265             //Create process.
266             bSuc = CreateProcess(NULL, 
267                 szCmdLine,     // command line 
268                 NULL,          // TODO: process security attributes 
269                 NULL,          // TODO: primary thread security attributes 
270                 TRUE,          // handles are inherited ?? 
271                 fdwCreate,     // creation flags
272                 NULL,          // use parent's environment 
273                 NULL,          // use parent's current directory 
274                 &si,           // STARTUPINFO pointer 
275                 &pi);          // receives PROCESS_INFORMATION 
276         }
277     }
278 }


其中 GetPrivilegeLUIDWithSID 函數的實現如下:

 1 BOOL GetPrivilegeLUIDWithSID(PSID pSID, PLUID *pLUID, PDWORD pDwCount)
 2 {
 3     LSA_OBJECT_ATTRIBUTES ObjectAttributes;
 4     NTSTATUS ntsResult;
 5     LSA_HANDLE lsahPolicyHandle;
 6 
 7     // Object attributes are reserved, so initialize to zeros.
 8     ZeroMemory(&ObjectAttributes, sizeof(ObjectAttributes));
 9 
10     // Get a handle to the Policy object.
11     ntsResult = LsaOpenPolicy(
12         NULL,    //Name of the target system.
13         &ObjectAttributes, //Object attributes.
14         POLICY_ALL_ACCESS, //Desired access permissions.
15         &lsahPolicyHandle  //Receives the policy handle.
16         );
17 
18     if (ntsResult != STATUS_SUCCESS)
19     {
20         printf("OpenPolicy failed returned %lu", LsaNtStatusToWinError(ntsResult));
21         return FALSE;
22     } 
23 
24     PLSA_UNICODE_STRING UserRights = NULL;
25     ULONG uRightCount;
26     ntsResult = LsaEnumerateAccountRights(lsahPolicyHandle, pSID, &UserRights, &uRightCount);
27     if (ntsResult != STATUS_SUCCESS)
28     {
29         printf("LsaEnumerateAccountRights failed returned %lu", LsaNtStatusToWinError(ntsResult));
30         LsaClose(lsahPolicyHandle);
31         return FALSE;
32     }
33     
34     printf("LsaEnumerateAccountRights returned Right count: %lu", uRightCount);
35 
36     (*pDwCount) = 0;
37     //pLUID = (PLUID)HeapAlloc(GetProcessHeap(), 0, uRightCount*sizeof(LUID));
38     (*pLUID) = (PLUID)LocalAlloc(LPTR, uRightCount*sizeof(LUID));
39     if((*pLUID) == NULL)
40     {
41         printf("HeapAlloc for PLUID failed returned %u", GetLastError());
42         LsaClose(lsahPolicyHandle);
43         return FALSE;
44     }
45 
46     for(ULONG uIdx=0; UserRights != NULL && uIdx<uRightCount; uIdx++)
47     {
48         int nLenOfMultiChars = WideCharToMultiByte(CP_ACP, 0, UserRights[uIdx].Buffer, UserRights[uIdx].Length,
49             NULL, 0, NULL, NULL);
50         PTSTR pMultiCharStr = (PTSTR)HeapAlloc(GetProcessHeap(), 0, nLenOfMultiChars*sizeof(char));
51         if(pMultiCharStr != NULL)
52         {
53             WideCharToMultiByte(CP_ACP, 0, UserRights[uIdx].Buffer, UserRights[uIdx].Length,
54                 pMultiCharStr, nLenOfMultiChars, NULL, NULL);
55             LUID luid;
56             if(!LookupPrivilegeValue(NULL, pMultiCharStr, &luid))
57             {
58                 printf("LookupPrivilegeValue about %s failed, GLE=%u.", pMultiCharStr, GetLastError());
59                 HeapFree(GetProcessHeap(), 0, pMultiCharStr);
60                 continue;
61             }
62             (*pLUID)[(*pDwCount)++] = luid;
63             HeapFree(GetProcessHeap(), 0, pMultiCharStr);
64         }
65     }
66 

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 情況:Oracle安裝後使用的好好的,放假關機後重新開機居然發現不能正常連線,客戶報錯:TNS-12541: TNS: 無監聽程式 2. 檢查: 1. 伺服器上Oracle啟動正常 2. 使用tnsping檢查,發現伺服器上使用 【tnsping 配置名稱】返回正常,而使用【tnsping I ...
  • 參考資料:https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_unix-timestamp UNIX_TIMESTAMP() :returns a Unix timestamp (seconds ...
  • 新建xxx.bat文件,內容如下: @echo offset hour=%time:~0,2%if "%time:~0,1%"==" " set hour=0%time:~1,1%set now=%Date:~0,4%%Date:~5,2%%Date:~8,2%%hour%%Time:~3,2%%T ...
  • 參考資料: plsql過期解決方法 plsql永久註冊碼適用個版本 方法一: 1.首先,登陸PL/SQL Developer,PL/SQL Developer要到期了 2.輸入指令“regedit”打開註冊表,如圖所示 3.然後,在註冊表裡按HKEY_CURRENT_USER\Software\Al ...
  • 用PL/SQL Developer登錄Oracle時提示:Using a filter for all users can lead to poor performance! 分析:與Oracle的配置無關,在使用plsql左側的樹形目錄時候會看到非常多的和當前工作無關的表、視圖、序列等,導致打開速 ...
  • 百度的資料,保存下來: 在寫按時間段查詢的sql語句的時候 一般我們會這麼寫查詢條件: where date>='2010-01-01' and date<='2010-10-1'。 但是在實執行Sql時些語句會轉換成這樣: where date>='2010-01-01 0:00:00' and ...
  • 國慶假期終於有時間做點事情 因為平常工作會做些資料庫操作 可能會有所操作失誤 參考一下 方法一 ApexSql 2016一個軟體 http://www.cnblogs.com/gsyifan/p/ApexSql_Log_2016_Crack.html 方法二 利用資料庫的日誌備份回滾 http:// ...
  • 問題:在工作的時候遇到電腦桌面顯示不出來 解決方案: 1.結束explorer.exe進程 2.新建一個explorer.exe進程 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...