轉載:https://www.jianshu.com/p/c0d48a675e55
問題
在枚舉/結束系統進程或操作系統服務時,會出現權限不足而失敗的情況,這時就需要提升自己進程到系統權限
前置知識
windows的每個用戶登錄系統后,系統會產生一個訪問令牌(access token),其中關聯了當前用戶的權限信息,用戶登錄后創建的每一個進程都含有用戶access token的拷貝,當進程試圖執行某些需要特殊權限的操作或是訪問受保護的內核對象時,系統會檢查其acess token中的權限信息以決定是否授權操作。Administrator組成員的access token中會含有一些可以執行系統級操作的特權(privileges) ,如終止任意進程、關閉/重啟系統、加載設備驅動和更改系統時間等,不過這些特權默認是被禁用的,當Administrator組成員創建的進程中包含一些需要特權的操作時,進程必須首先打開這些禁用的特權以提升自己的權限,否則系統將拒絕進程的操作。注意,非Administrator組成員創建的進程無法提升自身的權限,因此下面提到的進程均指Administrator組成員創建的進程。(命令行輸入net user可查看或net user 具體用戶名稱)
Windows以字符串的形式表示系統特權,如“SeCreatePagefilePrivilege”表示該特權用於創建頁面文件,“SeDebugPrivilege”表示該特權可用於調試及更改其它進程的內存,為了便於在代碼中引用這些字符串,微軟在winnt.h中定義了一組宏,如 #define SE_DEBUG_NAME TEXT("SeDebugPrivilege")。完整的特權列表可以查閱msdn的security一章。雖然Windows使用字符串表示特權,但查詢或更改特權的API需要LUID來引用相應的特權,LUID表示local unique identifier,它是一個64位值,在當前系統中是唯一的。為了提升進程權限到指定的特權,我們必須先找到該特權對應的LUID,這時要調用LookupPrivilegeValue函數。
簡介
要對一個任意進程(包括系統安全進程和服務進程)進行指定了寫相關的訪問權的OpenProcess操作,只要當前進程具有SeDeDebug權限就可以了。要是一個用戶是Administrator或是被給予了相應的權限,就可以具有該權限。可是,就算我們用Administrator帳號對一個系統安全進程執OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwProcessID)還是會遇到“訪問拒絕”的錯誤。什么原因呢?原來在默認的情況下進程的一些訪問權限是沒有被啟用(Enabled)的,所以我們要做的首先是啟用這些權限。
流程
1.打開進程訪問令牌
2.取得特權的LUID值
3.調整訪問令牌特權值
使用函數
OpenProcessToken();
LookupPrivilegeValue();
AdjustTokenPrivileges();
函數介紹及大致流程
首先要獲得進程訪問令牌的句柄,這可以通過OpenProcessToken得到,函數的原型如下:
BOOL OpenProcessToken( HANDLE ProcessHandle, //要修改訪問權限的進程句柄 DWORD DesiredAccess, //要對令牌進行何種操作 PHANDLE TokenHandle //返回的訪問令牌指針 );
第一參數是要修改訪問權限的進程句柄;第三個參數就是返回的訪問令牌指針;第二個參數指定你要進行的操作類型,如要修改訪問令牌的特權,我們要指定第二個參數TOKEN_ADJUST_PRIVILEGES。通過這個函數我們就可以得到當前進程的訪問令牌的句柄(指定函數的第一個參數為GetCurrentProcess()就可以了)。接着我們可以調用AdjustTokenPrivileges對這個訪問令牌進行修改。
AdjustTokenPrivileges的原型如下:
BOOL AdjustTokenPrivileges( HANDLE TokenHandle, // handle to token BOOL DisableAllPrivileges, // disabling option PTOKEN_PRIVILEGES NewState, // privilege information DWORD BufferLength, // size of buffer PTOKEN_PRIVILEGES PreviousState, // original state buffer PDWORD ReturnLength // required buffer size );
第一個參數是訪問令牌的句柄;第二個參數決定是進行權限修改還是喪失(Disable)所有權限;第三個參數指明要修改的權限,是一個指向TOKEN_PRIVILEGES結構的指針,該結構包含一個數組,數據組的每個項指明了權限的類型和要進行的操作; 第四個參數是結構PreviousState 指針所指向的緩沖區的大小,如果PreviousState參數為空,該參數應為NULL;第五個參數也是一個指向TOKEN_PRIVILEGES結構的指針,存放修改前的訪問權限的信息,可空;最后一個參數為實際PRIVILEGES NewState結構返回的大小。在使用這個函數前再看一下TOKEN_PRIVILEGES這個結構,其聲明如下:
typedef struct _TOKEN_PRIVILEGES { DWORD PrivilegeCount; LUID_AND_ATTRIBUTES Privileges[]; } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
PrivilegeCount指的數組元素的個數,接着是一個LUID_AND_ATTRIBUTES類型的數組,再來看一下LUID_AND_ATTRIBUTES這個結構的內容,聲明如下:
typedef struct _LUID_AND_ATTRIBUTES { LUID Luid; DWORD Attributes; } LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES
第二個參數就指明了我們要進行的操作類型,其特權屬性Attributes可以是如下常量:
SE_PRIVILEGE_ENABLED //使特權有效 SE_PRIVILEGE_ENABLED_BY_DEFAULT //使特權默認有效 SE_PRIVILEGE_REMOVED //移除該特權 SE_PRIVILEGE_USED_FOR_ACCESS //取得對象或服務的訪問權
要使用一個權限就指定Attributes為SE_PRIVILEGE_ENABLED。第一個參數就是指權限的類型,是一個LUID的值,LUID就是指locally unique identifier(局部唯一標識符),我想GUID大家是比較熟悉的,和GUID的要求保證全局唯一不同,LUID只要保證局部唯一,就是指在系統的每一次運行期間保證是唯一的就可以了。另外和GUID相同的一點,LUID也是一個64位的值,相信大家都看過GUID那一大串的值,我們要怎么樣才能知道一個權限對應的LUID值是多少呢?
這就要用到另外一個API函數LookupPrivilegevalue,其原形如下:
BOOL LookupPrivilegevalue( LPCTSTR lpSystemName, // system name LPCTSTR lpName, // privilege name PLUID lpLuid // locally unique identifier );
第一個參數是系統的名稱,如果是本地系統只要指明為NULL就可以了,第三個參數就是返回LUID的指針,第二個參數就是指明了權限的名稱,如“SeDebugPrivilege”。在Winnt.h中還定義了一些權限名稱的宏,如:
#define SE_BACKUP_NAME TEXT("SeBackupPrivilege") #define SE_RESTORE_NAME TEXT("SeRestorePrivilege") #define SE_SHUTDOWN_NAME TEXT("SeShutdownPrivilege") #define SE_DEBUG_NAME TEXT("SeDebugPrivilege")
這樣通過這三個函數的調用,我們就可以用OpenProcess(PROCESS_ALL_ACCESS,FALSE, dwProcessID)來打獲得任意進程的句柄,並且指定了所有的訪問權
代碼實現
#include <stdio.h> #include <windows.h> int main(void) { HANDLE token_handle; //打開訪問令牌 if(!OpenProcessToken(GetCurrentProcess(), //要修改權限的進程句柄 TOKEN_ALL_ACCESS, //要對令牌進行何種操作 &token_handle //訪問令牌 )) { printf("openProcessToken error"); } LUID luid; if(!LookupPrivilegeValue(NULL, //查看的系統,本地為NULL SE_DEBUG_NAME, //要查看的特權名稱 &luid //用來接收標識符 )) { printf("lookupPrivilegevalue error"); } TOKEN_PRIVILEGES tkp; tkp.PrivilegeCount = 1; tkp.Privileges[0].Luid = luid; tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; //調整訪問令牌權限 if(!AdjustTokenPrivileges(token_handle, //令牌句柄 FALSE, //是否禁用權限 &tkp, //新的特權的權限信息 sizeof(tkp), //特權信息大小 NULL, //用來接收特權信息當前狀態的buffer NULL //緩沖區大小 )) { printf("adjust error"); } printf("sucessful"); return 0; }