windows的每个用户登录系统后,系统会产生一个访问令牌(access token) ,其中关联了当前用户的权限信息,用户登录后创建的每一个进程都含有用户access token的拷贝,当进程试图执行某些需要特殊权限的操作或是访问受保护的内核对象时,系统会检查其acess token中的权限信息以决定是否授权操作。Administrator组成员的access token中会含有一些可以执行系统级操作的特权(privileges) ,如终止任意进程、关闭/重启系统、加载设备驱动和更改系统时间等,不过这些特权默认是被禁用的,当Administrator组成员创建的进程中包含一 些需要特权的操作时,进程必须首先打开这些禁用的特权以提升自己的权限,否则系统将拒绝进程的操作。注意,非Administrator组成员创建的进程 无法提升自身的权限,因此下面提到的进程均指Administrator组成员创建的进程。
Windows以字符串的形式表示系统特权,如“SeCreatePagefilePrivilege”表示该特权用于创建页面文 件,“SeDebugPrivilege”表示该特权可用于调试及更改其它进程的内存,为了便于在代码中引用这些字符串,微软在winnt.h中定义了一 组宏,如 #define SE_DEBUG_NAME TEXT("SeDebugPrivilege")。完整的特权列表可以查阅msdn的security一章。虽然Windows使用字符串表示特权,但 查询或更改特权的API需要LUID来引用相应的特权,LUID表示local unique identifier,它是一个64位值,在当前系统中是唯一的。为了提升进程权限到指定的特权,我们必须先找到该特权对应的LUID,这时要调用 LookupPrivilegeValue函数。
获得特权对应的LUID之后,我们要打开该特权。此时要用到LUID_AND_ATTRIBUTES结构,其定义如下:
typedef struct _LUID_AND_ATTRIBUTES { LUID Luid; DWORD Attributes; } LUID_AND_ATTRIBUTES, * PLUID_AND_ATTRIBUTES;
Attributes取SE_PRIVILEGE_ENABLED时将打开Luid对应的特权。设置完成后,我们需要调用 AdjustTokenPrivileges函数通知操作系统将指定的access token权限中的特权置为打开状态,前面我们说过,进程执行需要特列权限的操作时系统将检查其access token,因此更改了进程的access token特权设置,也就是更改了所属进程的特权设置。AdjustTokenPrivileges函数的原型如下:
BOOL WINAPI AdjustTokenPrivileges( __in HANDLE TokenHandle, __in BOOL DisableAllPrivileges, __in_opt PTOKEN_PRIVILEGES NewState, __in DWORD BufferLength, __out_opt PTOKEN_PRIVILEGES PreviousState, __out_opt PDWORD ReturnLength );
TokenHandle是要更改特权设置的acess token的句柄,DisableAllPrivileges表示是否禁用该access token的所有特权,NewState用来传递要新的特权设置,注意它的类型是 PTOKEN_PRIVILEGES,PTOKEN_PRIVILEGES是TOKEN_PRIVILEGES结构的指针,定义如下:
typedef struct _TOKEN_PRIVILEGES { DWORD PrivilegeCount; LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY]; } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
其中ANYSIZE_ARRAY被定义为1,可以看到TOKEN_PRIVILEGES中包含了用于设置特权信息的 LUID_AND_ATTRIBUTES结构,在使用时,只需要将PrivilegeCount赋为1,然后把Privileges数组的第1个元素 (Privileges[0])的Luid域设置为指定特权的Luid,再将其Attributes域设置为SE_PRIVILEGE_ENABLED, 就可以完成TokenHandle表示的access token权限的提升了。
下面是一个实际的例子,用来将执行promoteProcessPrivilege的当前进程的指定特权打开,函数参数为指定的特权名,可以传递其宏定义,也可以是完整的字符串表示:
BOOL promoteProcessPrivileges(const TCHAR* newPrivileges) { HANDLE tokenHandle; //获得当前进程的access token句柄 if(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &tokenHandle) == FALSE) return FALSE; TOKEN_PRIVILEGES structTkp; //查找newPrivileges参数对应的Luid,并将结果写入structTkp.Privileges[0]的Luid域中 if(::LookupPrivilegeValue(NULL, newPrivileges, &structTkp.Privileges[0].Luid) == FALSE){ CloseHandle(tokenHandle); return FASLE; } //设置structTkp结构 structTkp.PrivilegeCount = 1; structTkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; //通知操作系统更改权限 if(::AdjustTokenPrivileges(tokenHandle, FALSE, &structTkp, sizeof(structTkp), NULL, NULL) == FALSE){ CloseHandle(tokenHandle); return FALSE; } CloseHandle(tokenHandle); return TRUE; }
我们来用一个简单的例子验证promoteProcessPrivileges函数。下面的traceSystemProcess用于遍历当前系统进程, 如果调用traceSystemProcess函数的进程以默认权限运行,对于如csrss.exe之类的进程,函数将没有足够的权限获得其模块名。如果 在traceSystemProcess之前调用了promoteProcessPrivileges将进程权限提升至SE_DEBUG_NAME级 别,traceSystemProcess函数将能正确打印出如csrss.exe等关键进程的路径:
BOOL traceSystemProcess() { PROCESSENTRY32 processEntry; processEntry.dwSize = sizeof(processEntry); HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); if(hProcessSnap == INVALID_HANDLE_VALUE){ _tprintf(_T("CreateToolhelp32Snapshot 调用失败!/n")); return FALSE; } BOOL bMore = ::Process32First(hProcessSnap, &processEntry); while(bMore){ _tprintf(_T("进程名称:%s /n"), processEntry.szExeFile); _tprintf(_T("进程ID号:%u /n"), processEntry.th32ProcessID); HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processEntry.th32ProcessID); if(hProcess != NULL){ TCHAR szBuffer[MAX_PATH] = {_T('/0')}; if(::GetModuleFileNameEx(hProcess, NULL, szBuffer, MAX_PATH)){ _tprintf(_T("进程路径:%s /n"), szBuffer); } ::CloseHandle(hProcess); } _tprintf(_T("/n")); bMore = ::Process32Next(hProcessSnap,&processEntry); } ::CloseHandle(hProcessSnap); return TRUE; }
BOOL
promoteProcessPrivileges(
const
TCHAR
* newPrivileges)
{
HANDLE
tokenHandle;
//获得当前进程的access token句柄
if
(::OpenProcessToken(::GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &tokenHandle) == FALSE)
return
FALSE;
TOKEN_PRIVILEGES structTkp;
//查找newPrivileges参数对应的Luid,并将结果写入structTkp.Privileges[0]的Luid域中
if
(::LookupPrivilegeValue(NULL, newPrivileges, &structTkp.Privileges[0].Luid) == FALSE){
CloseHandle(tokenHandle);
return
FASLE;
}
//设置structTkp结构
structTkp.PrivilegeCount = 1;
structTkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
//通知操作系统更改权限
if
(::AdjustTokenPrivileges(tokenHandle, FALSE, &structTkp,
sizeof
(structTkp), NULL, NULL) == FALSE){
CloseHandle(tokenHandle);
return
FALSE;
}
CloseHandle(tokenHandle);
return
TRUE;
}
BOOL
traceSystemProcess()
{
PROCESSENTRY32 processEntry;
processEntry.dwSize =
sizeof
(processEntry);
HANDLE
hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0);
if
(hProcessSnap == INVALID_HANDLE_VALUE){
_tprintf(_T(
"CreateToolhelp32Snapshot 调用失败!/n"
));
return
FALSE;
}
BOOL
bMore = ::Process32First(hProcessSnap, &processEntry);
while
(bMore){
_tprintf(_T(
"进程名称:%s /n"
), processEntry.szExeFile);
_tprintf(_T(
"进程ID号:%u /n"
), processEntry.th32ProcessID);
HANDLE
hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
FALSE, processEntry.th32ProcessID);
if
(hProcess != NULL){
TCHAR
szBuffer[MAX_PATH] = {_T(
'/0'
)};
if
(::GetModuleFileNameEx(hProcess, NULL, szBuffer, MAX_PATH)){
_tprintf(_T(
"进程路径:%s /n"
), szBuffer);
}
::CloseHandle(hProcess);
}
_tprintf(_T(
"/n"
));
bMore = ::Process32Next(hProcessSnap,&processEntry);
}
::CloseHandle(hProcessSnap);
return
TRUE;
}