標籤:
作業系統中有些資源是不能由使用者代碼直接存取的,比如線程進程,檔案等等,這些資源必須由系統級代碼由RING3層進入到RING0層操作,並且返回一些標識供使用者程式使用,一般調用某個函數陷入到核心,這樣的函數叫做系統調用,而有些不直接陷入到核心,一般叫做系統API,linux中使用系統調用,而windows中封裝了一系列的API。
windows對象與控制代碼windows對象
作業系統為了安全,提供了一種保護機制,這種機制會禁止使用者操作某些資源,避免使用者過於關注細節,或者由於操作不當而造成系統崩潰。windows中將所有這些資源封裝成了一個個對象。對象就是作業系統為了維護這些資源而定義的一系列的資料結構。對象這個詞我們很容易聯想到物件導向程式設計語言中的對象,對象其實就是對一些資料以及操作這些資料的一個封裝,而windows是採用物件導向思想開發的,所以我們可以將windows中的對象想象成物件導向中的對象,而windows針對每種對象都提供了操作函數,這些函數一般都會以控制代碼作為第一個參數,這就有點像類函數中傳入的this指標。而這操作對象的函數就是類函數。
windows中總共有三種對象:GUI對象、GDI對象、核心對象。
windows中的控制代碼
windows中對象的操作是由系統提供的一系列的API函數來完成,這些函數有一個共同特點,就是以HANDLE 控制代碼作為第一個參數,windows中採用控制代碼來唯一標識每個核心對象。在windows中控制代碼的定義如下:
typedef void * HANDLE
從上面的定義可以看出,這個控制代碼應該是指向這個對象的結構體的指標。由於每次在程式啟動時記憶體都是隨機分配的,所以控制代碼不要使用硬式編碼方式,同時在複製核心對像的時候,並不是簡單的複製它的控制代碼,對象的複製有專門的函數,DuplicateHandle,該函數原型如下:
BOOL DuplicateHandle( HANDLE hSourceProcessHandle, //來源物件所在進程控制代碼 HANDLE hSourceHandle, //來源物件控制代碼 HANDLE hTargetProcessHandle, //目標對象所在進程控制代碼 LPHANDLE lpTargetHandle, //返回目標對象的控制代碼 DWORD dwDesiredAccess, //以何種許可權複製 BOOL bInheritHandle, //複製的物件控點是否可繼承 DWORD dwOptions //標記一些特殊的動作);
下面是一個例子代碼:
HANDLE hMutex = CreateMutex(NULL, FALSE, NULL); HANDLE hMutexDup, hThread; DWORD dwThreadId; DuplicateHandle(GetCurrentProcess(),hMutex,GetCurrentProcess(),&hMutexDup, 0,FALSE,DUPLICATE_SAME_ACCESS);
上述代碼在本進程中複製了一個互斥對象對象的控制代碼。
windows 安全性實體模型
windows中的核心對象由進程和線程進行操作,而對象就好像一個被鎖上的房間,進程想要訪問對象,並對對象進程某種操作,就必須擷取這個對象的鑰匙,而線程就好像擁有鑰匙的人,只有鑰匙是對的,才可以訪問。這把鎖能夠由不同的鑰匙開啟,這些鑰匙資訊儲存在ACE中,而只有當ACE中的資訊與訪問字串這個鑰匙匹配才可以開啟。我們一般把這個鑰匙稱為訪問字串(Token)
訪問字串
訪問字串主要包括:使用者標識、組標識、優先權資訊、以及其他訪問資訊。
使用者標識:用於唯一標識每個使用者,就好像為每個使用者都分配了一個唯一的使用者ID
組標識:使用者所屬組的唯一標識ID
優先權:一般系統對每個使用者以及它所屬組分配了一些許可權,而有的時候這些許可權並不夠,這個時候需要通過這個優先權資訊額外新增一些許可權
當使用者登入windows系統時,系統就為這個使用者指派了一個帶有該使用者資訊的訪問字串,該使用者建立的每個安全性實體都有這個訪問字串的拷貝,當使用者開啟的進程試圖訪問某個安全性實體時系統就在對象的ACL中尋找該使用者是否有某項許可權。有這個許可權才能對對象進行這項操作。
子進程的訪問字串一般繼承與父進程,但是子進程也可以自行建立訪問字串來覆蓋原來的訪問字串。
操作訪問字串所使用的API主要有以下幾個:
OpenProcessToken //開啟進程的存取權杖
OpenThreadToken //開啟線程的存取權杖
AdjustTokenGroups //改變使用者組的存取權杖
AdjustTokenPrivileges //改變令牌的存取權限
GetTokenInformation //擷取存取權杖的資訊
SetTokenInformation //設定存取權杖資訊
下面主要介紹一下函數GetTokenInformation 的用法:
BOOL WINAPI GetTokenInformation( __in HANDLE TokenHandle, //訪問字串的控制代碼 __in TOKEN_INFORMATION_CLASS TokenInformationClass, //需要返回訪問字串哪方面的資訊 __out_opt LPVOID TokenInformation, //用於接收返回資訊的緩衝 __in DWORD TokenInformationLength, //緩衝的長度 __out PDWORD ReturnLength //實際所需的緩衝的長度);
這個函數可以返回存取權杖多方面的資訊,具體返回哪個方面的資訊,需要通過第二個參數來指定,這個參數是一個枚舉類型,表示需要擷取的資訊,每個方面的資訊都定義了對應的結構體,這些資訊可以由MSDN中查到。另外這個函數支援兩次調用,第一次傳入NULL指標和0長度,這樣通過最後一個參數可以得到具體所需要的緩衝的大小。
SID
訪問字串中使用者與使用者組採用安全性識別碼的方式唯一標識(Security Indentifer SID),系統中的SID是唯一的。它主要用來標識下面的這些內容:
1. 安全性描述元中的所有者和使用者組
2. 被ACE認可的訪問者
3. 訪問字串的使用者和組
SID的長度是可變的,在使用時不應該使用SID這個資料類型,因為這個時候還不知道需要的長度是多少,應該由系統來建立並返回它的指標,所以在使用時需要使用SID的指標。下面是一些操作SID的API
AllocateAndInitializeSid //初始化一個SID
FreeSid //釋放一個SID
CopySid //拷貝一個SID
EqualSid //判斷兩個SID是否相等
GetLengthSid //擷取SID的長度
IsValidSid //是否是有效SID
ConvertSidToStringSid //將SID轉化為字串的方式
下面是一個利用這些API擷取系統使用者組的SID和當前登入使用者的SID的例子:
BOOL GetLoginSid(HANDLE hToken, PSID *ppsid);//SID本身大小是不可知的,所以在傳入參數時應該傳入2級指標,由函數自己決定大小void FreeSid(PSID *ppsid);int _tmain(int argc, TCHAR* argv[]){ setlocale(LC_ALL, "chs"); HANDLE hProcess = GetCurrentProcess(); HANDLE hToken = NULL; OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken); PSID pSid = NULL; GetLoginSid(hToken, &pSid); LPTSTR pStringSid = NULL; ConvertSidToStringSid(pSid, &pStringSid); _tprintf(_T("當前登入的使用者sid = %s\n"), pStringSid); FreeSid(&pSid); return 0;}BOOL GetLoginSid(HANDLE hToken, PSID *ppsid){ TOKEN_GROUPS *ptg = NULL; BOOL bSuccess = FALSE; DWORD dwLength = 0; if(!GetTokenInformation(hToken, TokenGroups, ptg, 0, &dwLength)) { if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) { goto __CLEAN_UP; } ptg = (TOKEN_GROUPS*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength); if (!GetTokenInformation(hToken, TokenGroups, ptg, dwLength, &dwLength)) { goto __CLEAN_UP; } } _tprintf(_T("共找到%d個組SID\n"), ptg->GroupCount); LPTSTR pStringSid = NULL; for (int i = 0; i < ptg->GroupCount; i++) { ConvertSidToStringSid(ptg->Groups[i].Sid, &pStringSid); _tprintf(_T("\t id = %d sid = %s"), i, pStringSid); if ((ptg->Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID) { _tprintf(_T("此使用者為當前登入的使用者")); *ppsid = (PSID)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength); CopySid(dwLength, *ppsid, ptg->Groups[i].Sid); } _tprintf(_T("\n")); }__CLEAN_UP: if (ptg != NULL) { HeapFree(GetProcessHeap(), 0, ptg); } return bSuccess;}void FreeSid(PSID *ppsid){ HeapFree(GetProcessHeap(), 0, *ppsid); *ppsid = NULL;}
這個例子是MSDN中的一個例子,上述代碼中首先擷取進程的存取權杖,然後通過函數GetTokenInformation 擷取存取權杖的資訊。通過傳入TokenGroups這個值擷取目前使用者所在使用者組的訪問字串。並將資訊儲存到結構體TOKEN_GROUPS中最後通過(ptg->Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID這樣一個運算式來判斷當前的SID是否是登入使用者的。
優先權
優先權是由字串標識的局部唯一的標識符(LUID)
優先權是由系統管理員分配給對應的使用者,一般不能通過編程的方式提升使用者的優先權,但是有時候即使使用者具有某個優先權,但是它啟動的程式並不具有相關的優先權。這個時候可以通過編程的方式提升使用者進程特權。
系統有3個值代表一個優先權:
字串名,整個系統上都有意義,稱為全域名,這個字串並不是一個可讀的字串,顯示出來的資訊不一定能看得懂。
顯示給使用者的可讀名稱;如:改變系統時間(可以在組策略中查看)
每個電腦都不同的局部值;
下面有幾個常用的優先權:
#define SE_DEBUG_NAME TEXT("SeDebugPrivilege") //調試進程#define SE_LOAD_DRIVER_NAME TEXT("SeLoadDriverPrivilege") //裝載驅動#define SE_LOCK_MEMORY_NAME TEXT("SeLockMemoryPrivilege") //鎖定記憶體頁面#define SE_SHUTDOWN_NAME TEXT("SeShutdownPrivilege") //關機
下面是幾個優先權函數
LookupPrivilegeValue //查詢優先權的值
LookupPrivilegeDisplayName //查詢優先權的輸出名
LookupPrivilegeName //查詢優先權的名稱
PrivilegeCheck //優先權資訊檢查
下面是一個擷取使用者特權資訊的代碼:
int _tmain(int argc, TCHAR *argv[]){ setlocale(LC_ALL, "chs"); HANDLE hToken = NULL; HANDLE hProcess = GetCurrentProcess(); OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken); TOKEN_PRIVILEGES* ptg = NULL; DWORD dwTgSize = 0; TCHAR szName[255] = _T(""); TCHAR szDisplay[255] = _T(""); DWORD languageID = GetUserDefaultLangID(); if (!GetTokenInformation(hToken, TokenPrivileges, ptg, 0, &dwTgSize)) { if (ERROR_INSUFFICIENT_BUFFER != GetLastError()) { _tprintf(_T("擷取令牌失敗\n")); return 0; } ptg = (TOKEN_PRIVILEGES*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwTgSize); if (!GetTokenInformation(hToken, TokenPrivileges, ptg, dwTgSize, &dwTgSize)) { _tprintf(_T("讀取令牌資訊失敗\n")); return 0; } } _tprintf(_T("使用者的特權資訊:\n")); for(int i = 0; i < ptg->PrivilegeCount; i++) { DWORD dwName = sizeof(szName) / sizeof(TCHAR); DWORD dwDisplay = sizeof(szDisplay) / sizeof(TCHAR); LookupPrivilegeName(NULL, &ptg->Privileges[i].Luid, szName, &dwName); LookupPrivilegeDisplayName(NULL, szName, szDisplay, &dwDisplay, &languageID); _tprintf(_T("\t 特權名%s %s"), szName, szDisplay); if (ptg->Privileges[i].Attributes & ((SE_PRIVILEGE_ENABLED | SE_PRIVILEGE_ENABLED_BY_DEFAULT))) { _tprintf(_T("特權開放\n")); }else { _tprintf(_T("特權關閉\n")); } } HeapFree(GetProcessHeap(), 0, ptg); return 0;}
下面是進程提權的代碼:
int _tmain(int argc, TCHAR *argv[]){ HANDLE hToken = NULL; HANDLE hProcess = GetCurrentProcess(); SetPrivileges(hToken, SE_TCB_NAME , TRUE); return 0;}BOOL SetPrivileges(HANDLE hToken, LPTSTR lpPrivilegesName, BOOL bEnablePrivilege){ //擷取Token的特權資訊 LUID uid = {0}; LookupPrivilegeValue(NULL, lpPrivilegesName, &uid); TOKEN_PRIVILEGES tp = {0}; tp.PrivilegeCount = 1; tp.Privileges[0].Luid = uid; if (bEnablePrivilege) { tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; }else { tp.Privileges[0].Attributes = 0; } AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); if ( GetLastError() == ERROR_NOT_ALL_ASSIGNED ) { _tprintf(_T("分配指定的特權(%s)失敗. \n"),lpPrivilegesName); return FALSE; } return TRUE;}
安全性描述元
安全性描述元中通常包含資訊:所有者、主組、任選存取控制清單(DACL)、系統存取控制清單(SACL)
安全性描述元是以SECURITE_DESCRIPTOR結構開始,後面連續跟著安全性描述元的其它資訊
存取控制清單
存取控制清單(Access Control List ACL)主要由多個訪問存取控制入口(Access Control Entries ACE)組成。ACE用於標識一個使用者、組或局部組以及它們中每一個允許的訪問權;
安全性描述元的建立
在建立安全訪問對象的函數中一般都需要填入一個SECURITY_ATTRIBUTES結構體的指標,我們要麼給定一個NULL值使其具有預設的安全屬性,或者自己建立一個安全性描述元並將他的指標傳入。
建立一個安全性描述元主要有下面幾步:
1. 用函數 AllocateAndInitializeSid 建立使用者的SID。函數的定義如下:
BOOL WINAPI AllocateAndInitializeSid( __in PSID_IDENTIFIER_AUTHORITY pIdentifierAuthority, //用於表示該SID標識的頒發機構 __in BYTE nSubAuthorityCount, //SID有多少個子部分 __in DWORD dwSubAuthority0, //第0個子部分 __in DWORD dwSubAuthority1, __in DWORD dwSubAuthority2, __in DWORD dwSubAuthority3, __in DWORD dwSubAuthority4, __in DWORD dwSubAuthority5, __in DWORD dwSubAuthority6, __in DWORD dwSubAuthority7, __out PSID* pSid //返回一個PSID的指標);
SID主要由一個頒發機構以及一個或者多個32位的唯一的RID組成這些RID通過參數dwSubAuthority0到dwSubAuthority7產生。對應的使用者SID都有固定的組合。這個我還沒找到具體的使用者與SID是如何定義的。
2. 為該使用者SID分配存取控制許可權,主要通過填充結構體EXPLICIT_ACCESS成員來實現,這個結構體的定義如下:
typedef struct _EXPLICIT_ACCESS { DWORD grfAccessPermissions; //制定使用者權限 ACCESS_MODE grfAccessMode; //用於表示允許、拒絕、審查特定使用者的許可權 DWORD grfInheritance; //當前的許可權是否可以繼承 TRUSTEE Trustee; //這是一個訪問託管} EXPLICIT_ACCESS, *PEXPLICIT_ACCESS;
3.用API SetEntriesInAcl將上述結構體放入到ACL中
4. 分配並初始化SECURITY_DESCRIPTOR結構體,初始化該結構體所用到的API 是InitializeSecurityDescriptor
5. 將SECURITY_DESCRIPTOR結構加入到安全性描述元中,安全性描述元的結構體是:SECURITY_ATTRIBUTES
下面是一個具體的例子(這個例子來自MSDN):
SID_IDENTIFIER_AUTHORITY SIDAuthorityNT = SECURITY_WORLD_SID_AUTHORITY; SID_IDENTIFIER_AUTHORITY SIDAuthorityWord = SECURITY_NT_AUTHORITY; PSID pEveryOneSid = NULL; PSID pAdminSid = NULL; EXPLICIT_ACCESS ea[2] = {0}; PACL pAcl = NULL; //建立everyone使用者的SID AllocateAndInitializeSid(&SIDAuthorityWord, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pEveryOneSid); PSECURITY_DESCRIPTOR pSD = NULL; //定義everyone 的存取控制資訊 ea[0].grfAccessMode = SET_ACCESS; //用於表示允許、拒絕、審查特定使用者的許可權 ea[0].grfAccessPermissions = KEY_READ; //制定使用者權限 ea[0].grfInheritance = FALSE; ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[0].Trustee.ptstrName = (LPTSTR)pEveryOneSid; //建立administor使用者組的SID AllocateAndInitializeSid(&SIDAuthorityNT, 2, SECURITY_BUILTIN_DOMAIN_RID,DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminSid); ea[1].grfAccessMode = SET_ACCESS; ea[1].grfAccessPermissions = KEY_ALL_ACCESS; ea[1].grfInheritance = FALSE; ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID; ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP; ea[1].Trustee.ptstrName = (LPTSTR)pAdminSid; //將以上兩個SID加入到ACL中 SetEntriesInAcl(2, ea, NULL, &pAcl); pSD = (PSECURITY_DESCRIPTOR) HeapAlloc(GetProcessHeap(), 0, SECURITY_DESCRIPTOR_MIN_LENGTH); //初始化一個SECURITY_DESCRIPTOR結構 InitializeSecurityDescriptor(&pSD, SECURITY_DESCRIPTOR_REVISION); //將SECURITY_DESCRIPTOR結構加入到安全性描述元中 SECURITY_ATTRIBUTES sa = {0}; sa.bInheritHandle = FALSE; sa.lpSecurityDescriptor = pSD; sa.nLength = sizeof(SECURITY_ATTRIBUTES); HKEY hkSub = NULL; DWORD dwDisposition = 0; RegCreateKeyEx(HKEY_CURRENT_USER, _T("mykey"), 0, _T(""), 0, KEY_READ | KEY_WRITE, &sa, &hkSub, &dwDisposition); if (pEveryOneSid) { FreeSid(pEveryOneSid); } if (pAdminSid) { FreeSid(pAdminSid); } if (pAcl) { LocalFree(pAcl); } HeapFree(GetProcessHeap(), 0, pSD); if (hkSub) { RegCloseKey(hkSub); }
windows 安全模型簡介