Windows 用户、认证和对象安全

一、概述

  Windows系统具有很完善的安全和认证机制,称做访问控制机制。程序的执行主体(线程)在访问对象(文件,事件)时,系统会根据线程的“权限”和线程需要访问的对象所具有的“安全描述符”中的访问控制列表是否匹配来进行认证,决定一个线程是否可以操作一个对象。


二、定义

  关于权限、访问控制列表、安全描述符等在安全认证中所依赖的数据结构、并重点讲解安全认证的过程
  A需要访问(Access)B,A就是访问的主体,B就是访问的客体。A的“访问令牌”和B的安全描述符共同决定了A是否可以访问B
  访问的主体是进程。在系统中,线程才是程序执行的流程,因此只有线程才能操作对象。每个线程都是属于一个进程的,线程并没有属于自己的权限,而是来源于线程所属的进程。一个进程中的所有线程都具有同样的权限,可以把进程看做访问的主体,一个线程能访问哪些对象,都能进行哪些操作,是由线程权限决定的。访问的客体是安全对象,所有被访问的对象都具有安全描述符,包括了文件、注册表、事件(Event),互斥(Mutex)、管道等


三、访问令牌、权限和用户标识

3.1、进程的系统操作权限

  进程的权限特指进程是否能够进行各种系统操作,例如是否可以关闭系统、是否能够修改系统时间、是否能够加载设备驱动、权限是一个列表,没种权限是列表中的一项。权限列表存在与进程的访问令牌中
  权限有很多种,每一种表示了一个特定的操作是否能够进行,如果进程的访问令牌中的权限列表中有这个权限,则表示进程可以进行这种操作,比如SE_LOAD_DRIVER_NAME表示进程可以加载驱动。
#define SE_CREATE_TOKEN_NAME “SeCreateTokenPrivilege”

#define SE_ASSIGNPRIMARYTOKEN_NAME “SeAssignPrimaryTokenPrivilege”

#define SE_LOCK_MEMORY_NAME “SeLockMemoryPrivilege”

#define SE_INCREASE_QUOTA_NAME “SeIncreaseQuotaPrivilege”

#define SE_UNSOLICITED_INPUT_NAME “SeUnsolictedInputPrivilege”
更多权限请点击Privilege Content


3.2、安全对象

  Windows系统几乎所有的对象都有安全属性、包括文件、文件夹、注册表、线程同步对象、进程间通信对象、网络共享等,进程和线程也可以使其他进程的操作对象,所以进程和线程也是安全对象。在创建对象时都可以指定对象的安全属性,比如CreateFile,CreatePipe,CreateProcess,RegCreateKeyEx和RegSaveKeyEx等,SECURITY_ATTRIBUTES结构用于指定对象的安全属性。GetNamedSecurityInfo,GetSecurityInfo,SetSecurityInfo,SetKernelObjectSecurity,SetNameSecurityInfo等API函数可以获取和设置对象的安全属性。对象的安全属性是以安全描述符(Security Descriptor)的形式存在的,安全描述符中包括了访问控制列表。


3.3、访问控制列表(ACL)

&mesp; 每个安全对象都有访问控制列表。访问控制列表有两种, 一种是选择访问控制列表(discreionary access control list,DACL),另一种是系统访问控制列表(system access control list,SACL).DACL决定了用户或用户组是否能访问这个对象,SACL控制了尝试访问安全对象的检测信息的继承关系
  DACL是访问控制的关键,如图1,DACL中包括一个访问控制入口(AccessControl Entries,ACE)列表。ACE表明了用户(通过用户SID或用户组)是否能够进行操作以及能进行那种操作。在进行访问控制检测时,会依次检测DACL中的ACE,知道被允许或被拒绝

图1

图2


四:Windows下提升进程权限

  windows的每个用户登录系统后,系统会产生一个访问令牌(access token),其中关联了当前用户的权限信息,用户登录后创建的每一个进程都会有用户access token的拷贝,当进程试图执行某些需要特殊权限的操作或是访问受保护的内核对象,系统会检查其access token的权限信息以决定是否授权操作。Administrator组成员的access token中会含有一些可以执行系统级操作的特权(privilege),如终止任意进程,关闭、重启系统、加载设备驱动和更改系统时间等,不过这些特权默认是被禁用的,当administrator组成员创建的进程中包含一些需要特权的操作时,进程必须首先打开这些禁用的特权以提升自己的权限,否则系统将拒绝进程的操作。注意,非administrator组成员创建的进程无法提升自身的权限,因此下面提到的进程均指administrator组成员创建的进程
  Windows以字符串的形式表示系统特权,如”SeCreatePagefilePrivilege”表示该特权用于创建页面文件,“SeDebugPrivilege”表示该特权可用于调试及更改其他进程的内存。为了方便在代码中引用这些字符串,微软在winnt.h中定义了一组宏,如#define SE_DEBUG_NAME TEXT(“SeDebugPrivilege”)。完整的特权列表可以查阅MSDN(Privilege Constants)一章。虽然Windows使用字符串表示特权,但查询或更改特权的API需要LUID来引用相应的特权,LUID表示local unique identifier,它是一个64位值,在当前系统中是唯一的。为了提升进程权限到指定的特权,我们必须先找到该特权对应的LUID,这时要调用LookupPrivilegeValue函数

LookupPrivilegeValue的原型
BOOL WINAPI LookupPrivilegeValue(   
  __in_opt      LPCTSTR lpSystemName,   
  __in          LPCTSTR lpName,   
  __out         PLUID lpLuid   
);  
  1. lpSystemName:系统的名字,如果为NULL,就是本地名字(这里就填NULL)
  2. lpName:特权的名字,点击查看具体的特权内容
  3. lpLuid:通过指针返回一个LUID类型的Luid的标识,通过这个值就可以填入_LUID_AND_ATTRIBUTES 结构体内。

  获得特权对应的LUID之后,我们要打开该特权。此时要用到LUID_AND_ATTRIBUTES结构体,其定义如下:

typedef struct _LUID_AND_ATTRIBUTES {
  LUID  Luid;
  ULONG Attributes;
} LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES;
  1. Luid: 是一个标识,不同的Luid代表着各种不同的特权类型
  2. Attributes: 要这个特权干什么,如启用这个特权(SE_PRIVILEGE_ENABLE)
  3. 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
);
  1. TokenHandle:需要更改特权设置的access token的句柄=OpenProcessToken第三个指针参数传出的句柄值。
  2. DisableAllPrivileges:表示是否禁用该access token的所有特权
  3. NewState:用来传递要更新的特权设置,注意它的类型是PTOKEN_PRIVILEGES,PTOKEN_PRIVILEGES
  4. BufferLength:TOKEN_PRIVILEGES结构体的字节长度
  5. PreviousState:接收原先的特权的结构体,如果不是NULL,在OpenProcessToken加特权时需要指定TOKEN_PRIVILEGES还必须指定TOKEN_QUERY.如果是NULL,就不用再指定附加的TOKEN_QUERY的特权。
  6. ReturnLength:TOKEN_PRIVILEGES结构体的字节长度
  7. 返回值:就算这个函数为真,还要调用GetLastError()来检验是否完全成功。如果GetLastError()==ERROR_SUCCESS就代表修改非常成功,这个非常重要,还有就是Vista和Window7里,一定要开管理员模式才能获得成功。

  TOKEN_PRIVILEGES结构的指针,定义如下:

typedef struct _TOKEN_PRIVILEGES {
  ULONG               PrivilegeCount;
  LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY];
} TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES;
  1. PrivilegeCount:要修改的特权数据
  2. Privileges:特权数组
  3. 其中ANYSIZE_ARRAY被定义为1,可以看到TOKEN_PRIVILEGES中包含了用于设置特权信息的LUID_AND_ATTRIBUTES结构,在使用时,只需要进PrivilegeCount赋值为1,然后把Privilege数组的第一个元素(Privileges[0])的Luid域设置为指定特权的Luid,再将其Attributes域设置为SE_PRIVILEGE_ENABLED,就可以完成TokenHandle表示的access token权限的提升。

实例1

#include <windows.h>      
#include <iostream>      
using namespace std;         
void main()      
{      
        BOOL retn;      
        HANDLE hToken;      
        retn = OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken);      
        if(retn != TRUE)      
        {      
                    cout<<"获取令牌句柄失败!"<<endl;      
                    return;      
        }      

        TOKEN_PRIVILEGES tp; //新特权结构体      
        LUID Luid;      
        retn = LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&Luid); 
        if(retn != TRUE)      
        {      
                    cout<<"获取Luid失败"<<endl;      
                    return;      
        }      
                //给TP和TP里的LUID结构体赋值      
        tp.PrivilegeCount = 1;      
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;      
        tp.Privileges[0].Luid = Luid;      

        AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),NULL,NULL);      
        if(GetLastError() != ERROR_SUCCESS)      
        {      
                    cout<<"修改特权不完全或失败!"<<endl;      
        }      
       else    
       {      
                    cout<<"修改成功!"<<endl;      
       }      
}  
函数解释
BOOL WINAPI OpenProcessToken(   
  __in          HANDLE ProcessHandle,   
  __in          DWORD DesiredAccess,   
  __out         PHANDLE TokenHandle   
);  
  1. ProcessHandle:要修改访问权限的进程句柄(当前进程为GetCurrentProcess()为参数)
  2. DesiredAccess:指定你要进行的操作类型,如要修改访问令牌的特权,我们要指定第二个参数为TOKEN_ADJUST_PRIVILEGES = &H20(其它一些参数可参考Platform SDK)
  3. 返回的参数,即令牌的句柄==AdjustTokenPrivilege的第一个参数

例子:

void mian()
{
HANDLE hToken;      
bool retn = OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken);   
//以一个能修改令牌特权的方式 获取令牌句柄     
if(!retn)    
      return; //获取令牌失败。。
}   

实例2

  下面是一个实际的例子,用来将执行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;
}

  PROCESSENTRY32:用来存放快照进程信息的一个结构体。(存放进程信息和调用成员输出进程信息)用来 Process32First指向第一个进程信息,并将进程信息抽取到PROCESSENTRY32中。用Process32Next指向下一条进程信息

  综上,Windows用户权限设置和进程权限提升需要用到一组Windows API提升进程权限,需要的函数有:

  1. OpenProcessToken
  2. LookupPrivilegeValue
  3. AdjustTokenPrivileges

  使用这组函数提升权限的前提是该进程具备该权限,只是访问令牌中没有启用该权限。如果进程的访问令牌中本身就没有关联该权限,使用AdjustTokenPrivileges函数就会返回ERROR_NOT_ALL_ASSIGNED(值为1300L)的错误码,如何让进程具有该权限?可以通过“控制面板->管理工具-本地安全策略-本地策略-用户权限指派”设置将该权限关联到指定的用户分组或用户上(Win8某个版本没有本地策略,需要升级),具体设置如下:

这里写图片描述

  利用AdjustTokenPrivileges提升权限,准确的说不是提升,而是将访问令牌中禁用的权限启用

实例3

BOOL SetPrivilege(  
        HANDLE hToken,          // access token handle  
        LPCTSTR lpszPrivilege,  // name of privilege to enable/disable  
        BOOL bEnablePrivilege   // to enable or disable privilege  
        )   
{  
    TOKEN_PRIVILEGES tp;  
    LUID luid;  

    if ( !LookupPrivilegeValue(   
        NULL,            // lookup privilege on local system  
        lpszPrivilege,   // privilege to lookup   
        &luid ) )        // receives LUID of privilege  
    {  
        printf("LookupPrivilegeValue error: %u\n", GetLastError() );   
        return FALSE;   
    }  

    tp.PrivilegeCount = 1;  
    tp.Privileges[0].Luid = luid;  
    if (bEnablePrivilege)  
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;  
    else  
        tp.Privileges[0].Attributes = 0;  

    // Enable the privilege or disable all privileges.  

    if ( !AdjustTokenPrivileges(  
        hToken,   
        FALSE,   
        &tp,   
        sizeof(TOKEN_PRIVILEGES),   
        (PTOKEN_PRIVILEGES) NULL,   
        (PDWORD) NULL) )  
    {   
        printf("AdjustTokenPrivileges error: %u\n", GetLastError() );   
        return FALSE;   
    }   

    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)  

    {  
        printf("The token does not have the specified privilege. \n");  
        return FALSE;  
    }   

    return TRUE;  
}  

void main( )  
{  
    HANDLE hToken;  
    BOOL bRet = OpenProcessToken(GetCurrentProcess(),TOKEN_ALL_ACCESS,&hToken);  
    SetPrivilege(hToken,SE_DEBUG_NAME,TRUE);  
}  

  这段代码在xp上没有问题,但如果在Window7或者vista上,如果程序以标准用户启动,AdjustTokenPrivileges将会调用失败,以管理员身份启动没有问题
  这是因为在Window7上,标准用户权限很少,没有Debug权限,更无从谈起启用Debug权限,用户可以以管理员和标准用户两种方式启用控制台,输入命令whoami/All来查看两种权限下权限的不同。
  即使提升调试权限,也不意味着对其他进程调用OpenProcess会成功(例如win7系统下的system和audiodg进程)

http://blog.csdn.net/zacklin/article/details/7663129

http://blog.csdn.net/yockie/article/details/17029293

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值