本篇文章內(nèi)容主要參考了
陳皓
《以程序的方式操縱NTFS的文件權(quán)限
》,然后加入了一些自己學(xué)習(xí)和理解的東西。
在NTFS文件系統(tǒng)出現(xiàn)后,在Windows系統(tǒng)(2K/XP/Vista..)下的對(duì)象,包括文件系統(tǒng),進(jìn)程、命名管道、打印機(jī)、網(wǎng)絡(luò)共享、或是注冊(cè)表等等,都可以設(shè)置用戶訪問(wèn)權(quán)限。
在Windows系統(tǒng)中,其是用一個(gè)安全描述符(Security
Descriptors)的結(jié)構(gòu)來(lái)保存其權(quán)限的設(shè)置信息,簡(jiǎn)稱為SD,其在Windows SDK中的結(jié)構(gòu)名是“SECURITY_DESCRIPTOR”,這是包括了安全設(shè)置信息的結(jié)構(gòu)體,其結(jié)構(gòu)體內(nèi)容定義如下:
typedef struct _SECURITY_DESCRIPTOR { UCHAR Revision; UCHAR Sbz1; SECURITY_DESCRIPTOR_CONTROL Control; PSID Owner; PSID Group; PACL Sacl; PACL Dacl; } SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
一個(gè)安全描述符包含以下安全信息:
- 兩個(gè)安全標(biāo)識(shí)符(Security identifiers),簡(jiǎn)稱為SID,分別是OwnerSid和GroupSid. 所謂SID就是每次當(dāng)我們創(chuàng)建一個(gè)用戶或一個(gè)組的時(shí)候,系統(tǒng)會(huì)分配給改用戶或組一個(gè)唯一SID,當(dāng)你重新安裝系統(tǒng)后,也會(huì)得到一個(gè)唯一的SID。SID是唯一的,不隨用戶的刪除而分配到另外的用戶使用。
請(qǐng)記住,SID永遠(yuǎn)都是唯一的SIF是由計(jì)算機(jī)名、當(dāng)前時(shí)間、當(dāng)前用戶態(tài)線程的CPU耗費(fèi)時(shí)間的總和三個(gè)參數(shù)決定以保證它的唯一性。
例: S-1-5-21-1763234323-3212657521-1234321321-500
- 一個(gè)DACL(Discretionary Access
Control List),其指出了允許和拒絕某用戶或用戶組的存取控制列表。 當(dāng)一個(gè)進(jìn)程需要訪問(wèn)安全對(duì)象,系統(tǒng)就會(huì)檢查DACL來(lái)決定進(jìn)程的訪問(wèn)權(quán)。如果一個(gè)對(duì)象沒(méi)有DACL,那么就是說(shuō)這個(gè)對(duì)象是任何人都可以擁有完全的訪問(wèn)權(quán)限。
- 一個(gè)SACL(System Access Control
List),其指出了在該對(duì)象上的一組存取方式(如,讀、寫(xiě)、運(yùn)行等)的存取控制權(quán)限細(xì)節(jié)的列表。
- 還有其自身的一些控制位。SECURITY_DESCRIPTOR_CONTROL
DACL和SACL構(gòu)成了整個(gè)存取控制列表Access Control List,簡(jiǎn)稱ACL,ACL中的每一項(xiàng),我們叫做ACE(Access Control Entry),ACL中的每一個(gè)ACE。
ACL結(jié)構(gòu)體的內(nèi)容如下:
typedef struct _ACL { BYTE AclRevision; BYTE Sbz1; WORD AclSize; WORD AceCount; WORD Sbz2; } ACL; typedef ACL *PACL;
我們的程序不用直接維護(hù)SD這個(gè)結(jié)構(gòu),這個(gè)結(jié)構(gòu)由系統(tǒng)維護(hù)。我們只用使用Windows 提供的相關(guān)的API函數(shù)來(lái)取得并設(shè)置SD中的信息就行了。不過(guò)這些API函數(shù)只有Windows NT/2K/XP才支持。
Windows提供了一系列的安全信息的存取,控制函數(shù),如
GetNamedSecurityInfo, SetNamedSecurityInfo,GetSecurityInfo, SetSecurityInfo等。
下圖說(shuō)明了,安全對(duì)象和DACL以及訪問(wèn)者之間的聯(lián)系(來(lái)源于MSDN)。注意,DACL表中的每個(gè)ACE的順序是有意義的,如果
前面的Allow(或denied)ACE通過(guò)了,那么,系統(tǒng)就不會(huì)檢查后面的ACE了。另外"拒絕"權(quán)限一般是優(yōu)先于其他權(quán)限的。ACE在DACL中的
順序非常重要。
系統(tǒng)會(huì)按照順序依次檢查所有的ACE規(guī)則,如下面的條件滿足,則退出:
1、
如果一個(gè)Access-Denied的ACE明顯地拒絕了請(qǐng)求者。
2、
如果某Access-Allowed的ACE明顯地同意了請(qǐng)求者。
3、
全部的ACE都檢查完了,但是沒(méi)有一條ACE明顯地允許或是拒絕請(qǐng)求者,那么系統(tǒng)將使用默認(rèn)值,拒絕請(qǐng)求者的訪問(wèn)。
更多的理論和描述,請(qǐng)參看MSDN。
實(shí)例例程
主要實(shí)現(xiàn)2個(gè)功能,獲取目錄的安全設(shè)置和為目錄增加一個(gè)安全設(shè)置項(xiàng)(該程序來(lái)源于MSDN,原作者
陳皓稍作修改,并加入了注解,我也稍作了點(diǎn)修改)
1、 對(duì)于文件、目錄、命令管道,我們不一定要使用GetNamedSecurityInfo和SetNamedSecurityInfo函數(shù),我們可以使用其專用函數(shù)GetFileSecurity和SetFileSecurity函數(shù)來(lái)取得或設(shè)置文件對(duì)象的SD,以設(shè)置其訪問(wèn)權(quán)限。需要使用這兩個(gè)函數(shù)并不容易,正如前面我們所說(shuō)的,我們還需要處理SD參數(shù),要處理SD,就需要處理DACL和ACE,以及用戶的相關(guān)SID,于是,一系統(tǒng)列的函數(shù)就被這兩個(gè)函數(shù)帶出來(lái)了。
2、
對(duì)于上一個(gè)例子中的使用硬編碼指定SID的處理方法是。調(diào)用LookupAccountName函數(shù)時(shí),先把SID,Domain名的參數(shù)傳為空
NULL,于是LookupAccountName會(huì)返回用戶的SID的長(zhǎng)度和Domain名的長(zhǎng)度,于是你可以根據(jù)這個(gè)長(zhǎng)度分配內(nèi)存,然后再次調(diào)用
LookupAccountName函數(shù)。于是就可以達(dá)到到態(tài)分配內(nèi)存的效果。對(duì)于ACL也一樣。
3、
對(duì)于給文件的ACL中增加一個(gè)ACE條目,一般的做法是先取出文件上的ACL,逐條取出ACE,和現(xiàn)需要增加的ACE比較,如果有沖突,則刪除已有的
ACE,把新加的ACE添置到最后。這里的最后,應(yīng)該是非繼承而來(lái)的ACE的最后。關(guān)于ACL繼承,NTFS中,你可以設(shè)置文件和目錄是否繼承于其父目錄
的設(shè)置。在程序中同樣可以設(shè)置。
還是請(qǐng)看例程,這個(gè)程序比較長(zhǎng),來(lái)源于MSDN,我做了一點(diǎn)點(diǎn)修改,并把自己的理解加在注釋中,所以,請(qǐng)注意代碼中的注釋:
// SetSD.cpp : コンソール アプリケーションのエントリ ポイントを定義します。 //
#include <stdio.h> #include <bitset> #include <tchar.h> #include <windows.h> #include <string> #include <iostream>
using std::bitset; using std::string; using std::cout; using std::endl;
//使用Windows的HeapAlloc函數(shù)進(jìn)行動(dòng)態(tài)內(nèi)存分配 #define myheapalloc(x) (HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, x)) #define myheapfree(x) (HeapFree(GetProcessHeap(), 0, x))
typedef BOOL (WINAPI *SetSecurityDescriptorControlFnPtr)( IN PSECURITY_DESCRIPTOR pSecurityDescriptor, IN SECURITY_DESCRIPTOR_CONTROL ControlBitsOfInterest, IN SECURITY_DESCRIPTOR_CONTROL ControlBitsToSet); typedef BOOL (WINAPI *AddAccessAllowedAceExFnPtr)( PACL pAcl, DWORD dwAceRevision, DWORD AceFlags, DWORD AccessMask, PSID pSid );
//【接口】 // BOOL AddAccessRights(TCHAR *lpszFileName, TCHAR *lpszAccountName, DWORD dwAccessMask) //【機(jī)能概要】 // 為文件(目錄)添加一個(gè)帳戶(組)的權(quán)限 //【入力】 // TCHAR *lpszFileName 文件(目錄) // TCHAR *lpszAccountName 帳戶(組) // DWORD dwAccessMask 權(quán)限設(shè)置(如GENERIC_ALL,GENERIC_READ等) //【輸出】 // 無(wú) //【輸入輸出】 // 無(wú) //【返回值】 // BOOL //【例外】 // 無(wú) //--------------------------------------------------------------------------- BOOL AddAccessRights(TCHAR *lpszFileName, TCHAR *lpszAccountName, DWORD dwAccessMask) { // 聲明SID變量 SID_NAME_USE snuType; // 聲明和LookupAccountName相關(guān)的變量(注意,全為0,要在程序中動(dòng)態(tài)分配) TCHAR * szDomain = NULL; DWORD cbDomain = 0; LPVOID pUserSID = NULL; DWORD cbUserSID = 0; // 和文件相關(guān)的安全描述符 SD 的變量 PSECURITY_DESCRIPTOR pFileSD = NULL; // 結(jié)構(gòu)變量 DWORD cbFileSD = 0; // SD的size // 一個(gè)新的SD的變量,用于構(gòu)造新的ACL(把已有的ACL和需要新加的ACL整合起來(lái)) SECURITY_DESCRIPTOR newSD; // 和ACL 相關(guān)的變量 PACL pACL = NULL; BOOL fDaclPresent; BOOL fDaclDefaulted; ACL_SIZE_INFORMATION AclInfo; // 一個(gè)新的 ACL 變量 PACL pNewACL = NULL; //結(jié)構(gòu)指針變量 DWORD cbNewACL = 0; //ACL的size // 一個(gè)臨時(shí)使用的 ACE 變量 LPVOID pTempAce = NULL; UINT CurrentAceIndex = 0; //ACE在ACL中的位置 UINT newAceIndex = 0; //新添的ACE在ACL中的位置 //API函數(shù)的返回值,假設(shè)所有的函數(shù)都返回失敗。 BOOL fResult = FALSE; BOOL fAPISuccess = FALSE; SECURITY_INFORMATION secInfo = DACL_SECURITY_INFORMATION; // 下面的兩個(gè)函數(shù)是新的API函數(shù),僅在Windows 2000以上版本的操作系統(tǒng)支持。 // 在此將從Advapi32.dll文件中動(dòng)態(tài)載入。如果你使用VC++ 6.0編譯程序,而且你想 // 使用這兩個(gè)函數(shù)的靜態(tài)鏈接。則請(qǐng)為你的編譯加上:/D_WIN32_WINNT=0x0500 // 的編譯參數(shù)。并且確保你的SDK的頭文件和lib文件是最新的。 SetSecurityDescriptorControlFnPtr _SetSecurityDescriptorControl = NULL; AddAccessAllowedAceExFnPtr _AddAccessAllowedAceEx = NULL; __try { // // STEP 1: 通過(guò)用戶名取得SID // 在這一步中LookupAccountName函數(shù)被調(diào)用了兩次,第一次是取出所需要 // 的內(nèi)存的大小,然后,進(jìn)行內(nèi)存分配。第二次調(diào)用才是取得了用戶的帳戶信息。 // LookupAccountName同樣可以取得域用戶或是用戶組的信息。(請(qǐng)參看MSDN) // fAPISuccess = LookupAccountName(NULL, lpszAccountName, pUserSID, &cbUserSID, szDomain, &cbDomain, &snuType); // 以上調(diào)用API會(huì)失敗,失敗原因是內(nèi)存不足。并把所需要的內(nèi)存大小傳出。 // 下面是處理非內(nèi)存不足的錯(cuò)誤。 if (fAPISuccess) __leave; else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { _tprintf(TEXT("LookupAccountName() failed. Error %d\n"), GetLastError()); __leave; } pUserSID = myheapalloc(cbUserSID); if (!pUserSID) { _tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError()); __leave; } szDomain = (TCHAR *) myheapalloc(cbDomain * sizeof(TCHAR)); if (!szDomain) { _tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError()); __leave; } fAPISuccess = LookupAccountName(NULL, lpszAccountName, pUserSID, &cbUserSID, szDomain, &cbDomain, &snuType); if (!fAPISuccess) { _tprintf(TEXT("LookupAccountName() failed. Error %d\n"), GetLastError()); __leave; } // // STEP 2: 取得文件(目錄)相關(guān)的安全描述符SD // 使用GetFileSecurity函數(shù)取得一份文件SD的拷貝,同樣,這個(gè)函數(shù)也 // 是被調(diào)用兩次,第一次同樣是取SD的內(nèi)存長(zhǎng)度。注意,SD有兩種格式:自相關(guān)的 // (self-relative)和 完全的(absolute),GetFileSecurity只能取到“自 // 相關(guān)的”,而SetFileSecurity則需要完全的。這就是為什么需要一個(gè)新的SD, // 而不是直接在GetFileSecurity返回的SD上進(jìn)行修改。因?yàn)?#8220;自相關(guān)的”信息 // 是不完整的。 fAPISuccess = GetFileSecurity(lpszFileName, secInfo, pFileSD, 0, &cbFileSD); // 以上調(diào)用API會(huì)失敗,失敗原因是內(nèi)存不足。并把所需要的內(nèi)存大小傳出。 // 下面是處理非內(nèi)存不足的錯(cuò)誤。 if (fAPISuccess) __leave; else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { _tprintf(TEXT("GetFileSecurity() failed. Error %d\n"), GetLastError()); __leave; } pFileSD = myheapalloc(cbFileSD); if (!pFileSD) { _tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError()); __leave; } fAPISuccess = GetFileSecurity(lpszFileName, secInfo, pFileSD, cbFileSD, &cbFileSD); if (!fAPISuccess) { _tprintf(TEXT("GetFileSecurity() failed. Error %d\n"), GetLastError()); __leave; } // // STEP 3: 初始化一個(gè)新的SD // if (!InitializeSecurityDescriptor(&newSD, SECURITY_DESCRIPTOR_REVISION)) { _tprintf(TEXT("InitializeSecurityDescriptor() failed.") TEXT("Error %d\n"), GetLastError()); __leave; } // // STEP 4: 從GetFileSecurity 返回的SD中取DACL // if (!GetSecurityDescriptorDacl(pFileSD, &fDaclPresent, &pACL, &fDaclDefaulted)) { _tprintf(TEXT("GetSecurityDescriptorDacl() failed. Error %d\n"), GetLastError()); __leave; } // // STEP 5: 取 DACL的內(nèi)存size // GetAclInformation可以提供DACL的內(nèi)存大小。只傳入一個(gè)類型為 // ACL_SIZE_INFORMATION的structure的參數(shù),需DACL的信息,是為了 // 方便我們遍歷其中的ACE。 AclInfo.AceCount = 0; // Assume NULL DACL. AclInfo.AclBytesFree = 0; AclInfo.AclBytesInUse = sizeof(ACL); if (pACL == NULL) fDaclPresent = FALSE; // 如果DACL不為空,則取其信息。(大多數(shù)情況下“自關(guān)聯(lián)”的DACL為空) if (fDaclPresent) { if (!GetAclInformation(pACL, &AclInfo, sizeof(ACL_SIZE_INFORMATION), AclSizeInformation)) { _tprintf(TEXT("GetAclInformation() failed. Error %d\n"), GetLastError()); __leave; } } // // STEP 6: 計(jì)算新的ACL的size // 計(jì)算的公式是:原有的DACL的size加上需要添加的一個(gè)ACE的size,以 // 及加上一個(gè)和ACE相關(guān)的SID的size,最后減去兩個(gè)字節(jié)以獲得精確的大小。 cbNewACL = AclInfo.AclBytesInUse + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(pUserSID) - sizeof(DWORD);
// // STEP 7: 為新的ACL分配內(nèi)存 // pNewACL = (PACL) myheapalloc(cbNewACL); if (!pNewACL) { _tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError()); __leave; } // // STEP 8: 初始化新的ACL結(jié)構(gòu) // if (!InitializeAcl(pNewACL, cbNewACL, ACL_REVISION2)) { _tprintf(TEXT("InitializeAcl() failed. Error %d\n"), GetLastError()); __leave; }
// // STEP 9 如果文件(目錄) DACL 有數(shù)據(jù),拷貝其中的ACE到新的DACL中 // // 下面的代碼假設(shè)首先檢查指定文件(目錄)是否存在的DACL,如果有的話, // 那么就拷貝所有的ACE到新的DACL結(jié)構(gòu)中,我們可以看到其遍歷的方法是采用 // ACL_SIZE_INFORMATION結(jié)構(gòu)中的AceCount成員來(lái)完成的。在這個(gè)循環(huán)中, // 會(huì)按照默認(rèn)的ACE的順序來(lái)進(jìn)行拷貝(ACE在ACL中的順序是很關(guān)鍵的),在拷 // 貝過(guò)程中,先拷貝非繼承的ACE(我們知道ACE會(huì)從上層目錄中繼承下來(lái)) // newAceIndex = 0; if (fDaclPresent && AclInfo.AceCount) { for (CurrentAceIndex = 0; CurrentAceIndex < AclInfo.AceCount; CurrentAceIndex++) { // // STEP 10: 從DACL中取ACE // if (!GetAce(pACL, CurrentAceIndex, &pTempAce)) { _tprintf(TEXT("GetAce() failed. Error %d\n"), GetLastError()); __leave; } // // STEP 11: 檢查是否是非繼承的ACE // 如果當(dāng)前的ACE是一個(gè)從父目錄繼承來(lái)的ACE,那么就退出循環(huán)。 // 因?yàn)?,繼承的ACE總是在非繼承的ACE之后,而我們所要添加的ACE // 應(yīng)該在已有的非繼承的ACE之后,所有的繼承的ACE之前。退出循環(huán) // 正是為了要添加一個(gè)新的ACE到新的DACL中,這后,我們?cè)侔牙^承的 // ACE拷貝到新的DACL中。 // if (((ACCESS_ALLOWED_ACE *)pTempAce)->Header.AceFlags & INHERITED_ACE) break; // // STEP 12: 檢查要拷貝的ACE的SID是否和需要加入的ACE的SID一樣, // 如果一樣,那么就應(yīng)該廢掉已存在的ACE,也就是說(shuō),同一個(gè)用戶的存取 // 權(quán)限的設(shè)置的ACE,在DACL中應(yīng)該唯一。這在里,跳過(guò)對(duì)同一用戶已設(shè)置 // 了的ACE,僅是拷貝其它用戶的ACE。 // if (EqualSid(pUserSID, &(((ACCESS_ALLOWED_ACE *)pTempAce)->SidStart))) { ACCESS_ALLOWED_ACE pTempAce2 = *(ACCESS_ALLOWED_ACE *)pTempAce;
ACCESS_DENIED_ACE pTempAce4 = *(ACCESS_DENIED_ACE *)pTempAce; int a = -1; if (pTempAce2.Header.AceType == ACCESS_ALLOWED_ACE_TYPE) { a = 0; } else if (pTempAce2.Header.AceType == ACCESS_DENIED_ACE_TYPE) { a = 1; } else a = 2; continue; } // // STEP 13: 把ACE加入到新的DACL中 // 下面的代碼中,注意 AddAce 函數(shù)的第三個(gè)參數(shù),這個(gè)參數(shù)的意思是 // ACL中的索引值,意為要把ACE加到某索引位置之后,參數(shù)MAXDWORD的 // 意思是確保當(dāng)前的ACE是被加入到最后的位置。 // if (!AddAce(pNewACL, ACL_REVISION, MAXDWORD, pTempAce, ((PACE_HEADER) pTempAce)->AceSize)) { _tprintf(TEXT("AddAce() failed. Error %d\n"), GetLastError()); __leave; } newAceIndex++; } }
// // STEP 14: 把一個(gè) access-allowed 的ACE 加入到新的DACL中 // 前面的循環(huán)拷貝了所有的非繼承且SID為其它用戶的ACE,退出循環(huán)的第一件事 // 就是加入我們指定的ACE。請(qǐng)注意首先先動(dòng)態(tài)裝載了一個(gè)AddAccessAllowedAceEx // 的API函數(shù),如果裝載不成功,就調(diào)用AddAccessAllowedAce函數(shù)。前一個(gè)函數(shù)僅 // 在Windows 2000以后的版本支持,NT則沒(méi)有,我們?yōu)榱耸褂眯掳姹镜暮瘮?shù),我們首 // 先先檢查一下當(dāng)前系統(tǒng)中可不可以裝載這個(gè)函數(shù),如果可以則就使用。使用動(dòng)態(tài)鏈接 // 比使用靜態(tài)鏈接的好處是,程序運(yùn)行時(shí)不會(huì)因?yàn)闆](méi)有這個(gè)API函數(shù)而報(bào)錯(cuò)。 // // Ex版的函數(shù)多出了一個(gè)參數(shù)AceFlag(第三人參數(shù)),用這個(gè)參數(shù)我們可以來(lái)設(shè)置一 // 個(gè)叫ACE_HEADER的結(jié)構(gòu),以便讓我們所設(shè)置的ACE可以被其子目錄所繼承下去,而 // AddAccessAllowedAce函數(shù)不能定制這個(gè)參數(shù),在AddAccessAllowedAce函數(shù) // 中,其會(huì)把ACE_HEADER這個(gè)結(jié)構(gòu)設(shè)置成非繼承的。 // _AddAccessAllowedAceEx = (AddAccessAllowedAceExFnPtr) GetProcAddress(GetModuleHandle(TEXT("advapi32.dll")), "AddAccessAllowedAceEx"); if (_AddAccessAllowedAceEx) { if (!_AddAccessAllowedAceEx(pNewACL, ACL_REVISION2, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE , dwAccessMask, pUserSID)) { _tprintf(TEXT("AddAccessAllowedAceEx() failed. Error %d\n"), GetLastError()); __leave; } }else{ if (!AddAccessAllowedAce(pNewACL, ACL_REVISION2, dwAccessMask, pUserSID)) { _tprintf(TEXT("AddAccessAllowedAce() failed. Error %d\n"), GetLastError()); __leave; } } // // STEP 15: 按照已存在的ACE的順序拷貝從父目錄繼承而來(lái)的ACE // bitset<32> bit(dwAccessMask); if (fDaclPresent && AclInfo.AceCount) { for (; CurrentAceIndex < AclInfo.AceCount; CurrentAceIndex++) { // // STEP 16: 從文件(目錄)的DACL中繼續(xù)取ACE // if (!GetAce(pACL, CurrentAceIndex, &pTempAce)) { _tprintf(TEXT("GetAce() failed. Error %d\n"), GetLastError()); __leave; } // // STEP 17: 把ACE加入到新的DACL中 // if (!AddAce(pNewACL, ACL_REVISION, MAXDWORD, pTempAce, ((PACE_HEADER) pTempAce)->AceSize)) { _tprintf(TEXT("AddAce() failed. Error %d\n"), GetLastError()); __leave; } } } // // STEP 18: 把新的ACL設(shè)置到新的SD中 // if (!SetSecurityDescriptorDacl(&newSD, TRUE, pNewACL, FALSE)) { _tprintf(TEXT("SetSecurityDescriptorDacl() failed. Error %d\n"), GetLastError()); __leave; } // // STEP 19: 把老的SD中的控制標(biāo)記再拷貝到新的SD中,我們使用的是一個(gè)叫 // SetSecurityDescriptorControl() 的API函數(shù),這個(gè)函數(shù)同樣只存在于 // Windows 2000以后的版本中,所以我們還是要?jiǎng)討B(tài)地把其從advapi32.dll // 中載入,如果系統(tǒng)不支持這個(gè)函數(shù),那就不拷貝老的SD的控制標(biāo)記了。 // _SetSecurityDescriptorControl =(SetSecurityDescriptorControlFnPtr) GetProcAddress(GetModuleHandle(TEXT("advapi32.dll")), "SetSecurityDescriptorControl"); if (_SetSecurityDescriptorControl) { SECURITY_DESCRIPTOR_CONTROL controlBitsOfInterest = 0; SECURITY_DESCRIPTOR_CONTROL controlBitsToSet = 0; SECURITY_DESCRIPTOR_CONTROL oldControlBits = 0; DWORD dwRevision = 0; if (!GetSecurityDescriptorControl(pFileSD, &oldControlBits, &dwRevision)) { _tprintf(TEXT("GetSecurityDescriptorControl() failed.") TEXT("Error %d\n"), GetLastError()); __leave; } if (oldControlBits & SE_DACL_AUTO_INHERITED) { controlBitsOfInterest = SE_DACL_AUTO_INHERIT_REQ | SE_DACL_AUTO_INHERITED ; controlBitsToSet = controlBitsOfInterest; } else if (oldControlBits & SE_DACL_PROTECTED) { controlBitsOfInterest = SE_DACL_PROTECTED; controlBitsToSet = controlBitsOfInterest; } if (controlBitsOfInterest) { if (!_SetSecurityDescriptorControl(&newSD, controlBitsOfInterest, controlBitsToSet)) { _tprintf(TEXT("SetSecurityDescriptorControl() failed.") TEXT("Error %d\n"), GetLastError()); __leave; } } } // // STEP 20: 把新的SD設(shè)置設(shè)置到文件的安全屬性中(千山萬(wàn)水啊,終于到了) // if (!SetFileSecurity(lpszFileName, secInfo, &newSD)) { _tprintf(TEXT("SetFileSecurity() failed. Error %d\n"), GetLastError()); __leave; } fResult = TRUE; } __finally { // // STEP 21: 釋放已分配的內(nèi)存,以免Memory Leak // if (pUserSID) myheapfree(pUserSID); if (szDomain) myheapfree(szDomain); if (pFileSD) myheapfree(pFileSD); if (pNewACL) myheapfree(pNewACL); } return fResult; }
//【接口】 // BOOL GetAccountRights(TCHAR *lpszFileName, TCHAR *lpszAccountName, int (&arrRights)[32]) //【機(jī)能概要】 // 獲取該文件(目錄)指定帳戶(組)的權(quán)限 //【入力】 // TCHAR *lpszFileName 文件(目錄) // TCHAR *lpszAccountName 帳戶(組) // int (&arrRights)[32] 數(shù)組引用,要求傳入?yún)?shù)必須是32個(gè)int數(shù)組 //【輸出】 // 無(wú) //【輸入輸出】 // 無(wú) //【返回值】 // BOOL //【例外】 // 無(wú) //--------------------------------------------------------------------------- BOOL GetAccountRights(TCHAR *lpszFileName, TCHAR *lpszAccountName, int (&arrRights)[32]) { //將參數(shù)arrRights初始化為0 for (int i = 0; i < 32; i++) { arrRights[i] = 0; } // 聲明SID變量 SID_NAME_USE snuType; // 聲明和LookupAccountName相關(guān)的變量(注意,全為0,要在程序中動(dòng)態(tài)分配) TCHAR * szDomain = NULL; DWORD cbDomain = 0; LPVOID pUserSID = NULL; DWORD cbUserSID = 0; // 和文件相關(guān)的安全描述符 SD 的變量 PSECURITY_DESCRIPTOR pFileSD = NULL; // 結(jié)構(gòu)變量 DWORD cbFileSD = 0; // SD的size // 和ACL 相關(guān)的變量 PACL pACL = NULL; BOOL fDaclPresent; BOOL fDaclDefaulted; ACL_SIZE_INFORMATION AclInfo; // 一個(gè)臨時(shí)使用的 ACE 變量 LPVOID pTempAce = NULL; UINT CurrentAceIndex = 0; //ACE在ACL中的位置 //API函數(shù)的返回值,假設(shè)所有的函數(shù)都返回失敗。 BOOL fResult = FALSE; BOOL fAPISuccess = FALSE; SECURITY_INFORMATION secInfo = DACL_SECURITY_INFORMATION; __try { // // STEP 1: 通過(guò)用戶名取得SID // 在這一步中LookupAccountName函數(shù)被調(diào)用了兩次,第一次是取出所需要 // 的內(nèi)存的大小,然后,進(jìn)行內(nèi)存分配。第二次調(diào)用才是取得了用戶的帳戶信息。 // LookupAccountName同樣可以取得域用戶或是用戶組的信息。(請(qǐng)參看MSDN) // fAPISuccess = LookupAccountName(NULL, lpszAccountName, pUserSID, &cbUserSID, szDomain, &cbDomain, &snuType); // 以上調(diào)用API會(huì)失敗,失敗原因是內(nèi)存不足。并把所需要的內(nèi)存大小傳出。 // 下面是處理非內(nèi)存不足的錯(cuò)誤。 if (fAPISuccess) __leave; else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { _tprintf(TEXT("LookupAccountName() failed. Error %d\n"), GetLastError()); __leave; } pUserSID = myheapalloc(cbUserSID); if (!pUserSID) { _tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError()); __leave; } szDomain = (TCHAR *) myheapalloc(cbDomain * sizeof(TCHAR)); if (!szDomain) { _tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError()); __leave; } fAPISuccess = LookupAccountName(NULL, lpszAccountName, pUserSID, &cbUserSID, szDomain, &cbDomain, &snuType); if (!fAPISuccess) { _tprintf(TEXT("LookupAccountName() failed. Error %d\n"), GetLastError()); __leave; } // // STEP 2: 取得文件(目錄)相關(guān)的安全描述符SD // 使用GetFileSecurity函數(shù)取得一份文件SD的拷貝,同樣,這個(gè)函數(shù)也 // 是被調(diào)用兩次,第一次同樣是取SD的內(nèi)存長(zhǎng)度。注意,SD有兩種格式:自相關(guān)的 // (self-relative)和 完全的(absolute),GetFileSecurity只能取到“自 // 相關(guān)的”,而SetFileSecurity則需要完全的。這就是為什么需要一個(gè)新的SD, // 而不是直接在GetFileSecurity返回的SD上進(jìn)行修改。因?yàn)?#8220;自相關(guān)的”信息 // 是不完整的。 fAPISuccess = GetFileSecurity(lpszFileName, secInfo, pFileSD, 0, &cbFileSD); // 以上調(diào)用API會(huì)失敗,失敗原因是內(nèi)存不足。并把所需要的內(nèi)存大小傳出。 // 下面是處理非內(nèi)存不足的錯(cuò)誤。 if (fAPISuccess) __leave; else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { _tprintf(TEXT("GetFileSecurity() failed. Error %d\n"), GetLastError()); __leave; } pFileSD = myheapalloc(cbFileSD); if (!pFileSD) { _tprintf(TEXT("HeapAlloc() failed. Error %d\n"), GetLastError()); __leave; } fAPISuccess = GetFileSecurity(lpszFileName, secInfo, pFileSD, cbFileSD, &cbFileSD); if (!fAPISuccess) { _tprintf(TEXT("GetFileSecurity() failed. Error %d\n"), GetLastError()); __leave; } // // STEP 3: 從GetFileSecurity 返回的SD中取DACL // if (!GetSecurityDescriptorDacl(pFileSD, &fDaclPresent, &pACL, &fDaclDefaulted)) { _tprintf(TEXT("GetSecurityDescriptorDacl() failed. Error %d\n"), GetLastError()); __leave; } // // STEP 4: 取 DACL的內(nèi)存size // GetAclInformation可以提供DACL的內(nèi)存大小。只傳入一個(gè)類型為 // ACL_SIZE_INFORMATION的structure的參數(shù),需DACL的信息,是為了 // 方便我們遍歷其中的ACE。 AclInfo.AceCount = 0; // Assume NULL DACL. AclInfo.AclBytesFree = 0; AclInfo.AclBytesInUse = sizeof(ACL); if (pACL == NULL) fDaclPresent = FALSE; // 如果DACL不為空,則取其信息。(大多數(shù)情況下“自關(guān)聯(lián)”的DACL為空) if (fDaclPresent) { if (!GetAclInformation(pACL, &AclInfo, sizeof(ACL_SIZE_INFORMATION), AclSizeInformation)) { _tprintf(TEXT("GetAclInformation() failed. Error %d\n"), GetLastError()); __leave; } } // // STEP 5 如果文件(目錄) DACL 有數(shù)據(jù),將指定帳戶的ACE的訪問(wèn)權(quán)限轉(zhuǎn)換到整型數(shù)組 // // 下面的代碼假設(shè)首先檢查指定文件(目錄)是否存在的DACL,如果有的話, // 那么就將指定帳戶的ACE的訪問(wèn)權(quán)限轉(zhuǎn)換到整型數(shù)組,我們可以看到其遍歷的方法 // 是采用ACL_SIZE_INFORMATION結(jié)構(gòu)中的AceCount成員來(lái)完成的。在這個(gè)循環(huán)中, // 查找和指定賬戶相關(guān)的ACE // if (fDaclPresent && AclInfo.AceCount) { for (CurrentAceIndex = 0; CurrentAceIndex < AclInfo.AceCount; CurrentAceIndex++) { // // STEP 10: 從DACL中取ACE // if (!GetAce(pACL, CurrentAceIndex, &pTempAce)) { _tprintf(TEXT("GetAce() failed. Error %d\n"), GetLastError()); __leave; } // // // STEP 6: 檢查要拷貝的ACE的SID是否和需要加入的ACE的SID一樣, // 如果一樣,那么就將該ACE的訪問(wèn)權(quán)限轉(zhuǎn)換到整型數(shù)組, // 否則跳過(guò),進(jìn)行下一個(gè)循環(huán) // int nAceType = 1; if (EqualSid(pUserSID, &(((ACCESS_ALLOWED_ACE *)pTempAce)->SidStart))) { if(((PACE_HEADER)pTempAce)->AceType == ACCESS_DENIED_ACE_TYPE) { nAceType = 2; } else { nAceType = 1; } //bitset類代表的整型數(shù)值的順序是從0到N-1 bitset<32> bitAccessMask(((ACCESS_ALLOWED_ACE*)pTempAce)->Mask); for (int i = 0; i < 32; i++) { if (bitAccessMask[i] != 0 && arrRights[i] != 2) { arrRights[i] = nAceType; } } } else { continue; } } } fResult = TRUE; } __finally { // // STEP 7: 釋放已分配的內(nèi)存,以免Memory Leak // if (pUserSID) myheapfree(pUserSID); if (szDomain) myheapfree(szDomain); if (pFileSD) myheapfree(pFileSD); } return fResult; }
int _tmain(int argc, TCHAR *argv[]) { if (argc < 3) { _tprintf(TEXT("usage: \"%s\" <FileName> <AccountName>\n"), argv[0]); return 1; } //關(guān)于ACCESS_MASK中各個(gè)位代表的含義請(qǐng)參考MSDN string filedesc[] = {"FILE_READ_DATA", "FILE_WRITE_DATA", "FILE_APPEND_DATA", "FILE_READ_EA",
"FILE_WRITE_EA", "FILE_EXECUTE", "FILE_DELETE_CHILD", "FILE_READ_ATTRIBUTES",
"FILE_WRITE_ATTRIBUTES", " ", " ", " ",
" ", " ", " ", " ",
"DELETE ", "READ_CONTROL", "WRITE_DAC", "WRITE_OWNER",
"SYNCHRONIZE ", " ", " "," ",
"ACCESS_SYSTEM_SECURITY", "MAXIMUM_ALLOWED", " "," ",
"GENERIC_ALL", "GENERIC_EXECUTE", "GENERIC_WRITE","GENERIC_READ"};
string rights[] = {"Allow", "Deny"};
//獲取ACE中的訪問(wèn)權(quán)限 //ACE中的訪問(wèn)權(quán)限是通過(guò)DWORD類型的ACCESS_MASK記錄的 //GetAccountRights函數(shù)將ACCESS_MASK轉(zhuǎn)為了一個(gè)32個(gè)元素的整型數(shù)組,并傳出 int arrRights[32] = {0}; if (!GetAccountRights(argv[1], argv[2], arrRights)) { _tprintf(TEXT("GetAccountRights() failed.\n")); } else { _tprintf(TEXT("The access rights of the file is..\n")); for (int i = 0; i < 32; i++) { int nTmpRight = arrRights[i]; if (nTmpRight != 0) { cout<<filedesc[i]<<": "<<rights[nTmpRight-1]<<endl; } } }
// argv[1] – 文件(目錄)名 // argv[2] – 用戶(組)名 // GENERIC_ALL表示所有的權(quán)限,其是一系列的NTFS權(quán)限的或 // NTFS的文件權(quán)限很細(xì),還請(qǐng)參看MSDN。 if (!AddAccessRights(argv[1], argv[2], GENERIC_ALL)) { _tprintf(TEXT("AddAccessRights() failed.\n")); return 1; } else { _tprintf(TEXT("AddAccessRights() succeeded.\n")); return 0; } }
函數(shù)AddAccessRights實(shí)現(xiàn)為文件(目錄)添加一個(gè)帳戶(組)的權(quán)限 函數(shù)GetAccountRights實(shí)現(xiàn)獲取該文件(目錄)指定帳戶(組)的權(quán)限
三、
一些相關(guān)的API函數(shù)
通過(guò)以上的示例,相信你已知道如何操作NTFS文件安全屬性了,還有一些API函數(shù)需要介紹一下。
1、
如果你要加入一個(gè)Access-Denied 的ACE,你可以使用AddAccessDeniedAce函數(shù)
2、
如果你要?jiǎng)h除一個(gè)ACE,你可以使用DeleteAce函數(shù)
3、
如果你要檢查你所設(shè)置的ACL是否合法,你可以使用IsValidAcl函數(shù),同樣,對(duì)于SD的合法也有一個(gè)叫IsValidSecurityDescriptor的函數(shù)
4、
MakeAbsoluteSD和MakeSelfRelativeSD兩個(gè)函數(shù)可以在兩種SD的格式中進(jìn)行轉(zhuǎn)換。
5、
使用SetSecurityDescriptorDacl 和 SetSecurityDescriptorSacl可以方便地把ACL設(shè)置到SD中。
6、
使用GetSecurityDescriptorDacl or GetSecurityDescriptorSacl可以方便地取得SD中的ACL結(jié)構(gòu)。
我們把一干和SD/ACL/ACE相關(guān)的API函數(shù)叫作Low-Level
Security Descriptor Functions,其詳細(xì)信息還請(qǐng)參看MSDN。
|