win7及以上系统检测目标进程对文件和文件夹的访问权限

原文地址:http://www.freebuf.com/articles/4546.html

Author: Pnig0s[FreeBuf]

阅读本文的朋友需要对Windows访问控制模型有初步的了解,了解Token(访问令牌),ACL(访问控制列表),DACL(选择访问控制列表),ACE(访问控制列表项)等与访问控制模型相关的名词含义及之间的关系,当然我也会在文中简要科普一下ACM。 

写这篇文章的目的主要是最近在写一个Win下本地提权的东西,涉及到了对ACL的操作,以前对ACL总是避而远之,Windows访问控制模型比较头疼,一个API会牵出一大把要用的API。毕竟涉及到用户访问的安全,肯定不能让编程人员随意更改这些机制,复杂一些也可以理解,可是能参考的资料很少,MSDN上关于一些访问控制相关API的使用和结构体的描述也含糊不清也没有什么代码实例。这篇文章也是在查阅国外了一些文献加上自己研究测试后完成的,发出来希望对涉及这方面编程的朋友有帮助。

  -----》》熟悉Windows访问控制机制的可以跳过本段:

因为是科普我这里简单介绍下Windows访问控制模型(ACM),别嫌我啰嗦,懂得直接Pass往下看。ACM中最重要的两部分是访问令牌(Access Token)和安全描述符表(Security Descriptor)。访问令牌存在于访问主体中,安全描述符表存在于访问客体中。比如我去米国,我就是访问主体,米国就是访问客体,我持有的签证就是访问令牌。系统中访问主体是进程客体是一切系统对象。访问令牌中有当前用户的唯一标识SID,组唯一标识SID以及一些权限标志(Privilege)。安全描述符表(SD)存在于Windows系统中的任何对象中(文件,注册表,互斥量,信号量等等)。SD中包含对象所有者的SID,组SID以及两个非常重要的数据结构选择访问控制列表(DACL)和系统访问控制列表(SACL),其中SACL涉及系统日志用的很少可以先无视。DACL中包含一个个ACE访问控制入口也是权限访问判断的核心,当一个进程访问某一对象的时候,对象会将进程的Token与自身的ACE依次比对,直到被允许或被拒绝,前面的ACE优于后面的ACE。整体的一个权限检查过程如下图: 

  -----》》


 

上面简单介绍了本文要用到的也是Windows访问控制模型核心部分的一些知识,下面来介绍下如何编程实现遍历ACL来进行访问权限的检查。本文主要针对文件对象进行介绍,其他类型的对象大同小异。要用到的两个主要API为GetFileSecurity()和AccessCheck()。GetFileSecurity能够获取指定文件的安全描述符表,而AccessCheck可以指定要检查的权限,该函数能够将获得的安全描述符表与当前进程的Token进行检查来判断进程对该文件对象是否允许相应的权限。不过这两个API并不那么容易用,因为其中要涉及到安全描述符表和访问令牌的获取,因此又牵扯出一大把API也涉及一些访问控制的知识。下面依次介绍要使用到的API然后给出整体的代码。GetFileSecurity的函数原型如下:

BOOL WINAPI GetFileSecurity(
__in LPCTSTR lpFileName,
__in SECURITY_INFORMATION RequestedInformation,
__out_opt PSECURITY_DESCRIPTOR pSecurityDescriptor,
__in DWORD nLength,
__out LPDWORD lpnLengthNeeded
);
lpFileName指定了要获取SD的文件。首先要定义一个PSECURITY_DESCRIPTOR的安全描述符表指针,因为描述符表大小未知,所以要调用两次GetFileSecurity()第一次将nLength置0,函数会返回实际大小,然后第二次用获取的大小去接收完整的SD,代码如下:
文件开始部分定义的内存分配释放函数常量:
    #define AllocMem(x) (HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,x)) 
    #define FreeMem(x) (HeapFree(GetProcessHeap(),HEAP_ZERO_MEMORY,x)) 
    ... 
    ... 
    BOOL bRs = FALSE; 
    DWORD dwSizeNeeded = 0; 
    PSECURITY_DESCRIPTOR psd = NULL; 
    SECURITY_INFORMATION si = OWNER_SECURITY_INFORMATION 
    | GROUP_SECURITY_INFORMATION|DACL_SECURITY_INFORMATION;         
             bRs = GetFileSecurity(lpFileName,si,psd,0,&dwSizeNeeded); 
             //第一次调用获得SD实际大小 
             if(!bRs) 
             { 
                       if(GetLastError() == ERROR_INSUFFICIENT_BUFFER) 
                       { 
                                psd = (PSECURITY_DESCRIPTOR)AllocMem(dwSizeNeeded); 
                                //根据获取到的大小对psd分配内存 
                       }else 
                       { 
                                printf("\n[-]Get SD failed:%d",GetLastError()); 
                                return bRs; 
                       } 
             }         if(!GetFileSecurity(lpFileName,si,psd,dwSizeNeeded,&dwSizeNeeded)) 
             { 
                       printf("\n[-]Get SD failed:%d",GetLastError()); 
                       return bRs; 
             } 

至此针对指定文件对象的安全描述符表已经得到,下一步需要提取出访问进程的访问令牌(Token)。首先调用OpenProcessToken()获得本进程的Token,参数比较简单参考MSDN吧。 然后有个比较重要的内容:我们需要模拟获得的令牌,因为OpenProcessToken获得的是进程的初始Token,不能直接用于访问权限的判断,我们要调用DuplicateToken()以当前用户的身份模拟一个同样的Token出来,具体使用待会儿看代码吧。 下面到了会让人比较困惑的地方:就是GENERIC_MAPPING这个结构体,这个开始看MSDN一直一头雾水,没理解到底怎么使用,MSDN上也没有代码实例。鼓捣了一上午最后发现其实很简单。比如我们使用CreateFile()创建一个文件的时候可以指定一些权限访问的标志如GENERIC_WRITE,GENERIC_READ等等。但是这些权限标志都是通用的标志,还可以用这些标志来创建或打开其他类型的对象。在表示文件对象的时候,这些通用标志所包含的实际文件对象特有的权限标志列表如下:


比如当我们想使用AccessCheck()检查当前进程对某文件是否有某种权限的时候,我们必须要调用MapGenericMask()把GENERIC_READ,GENERIC_WRITE,

GENERIC_EXECUTE等等这类通用权限控制标志映射成该类型的对象特有的权限控制标志,对于文件就是FILE_GENERIC_READ等。而这个函数中就用到了GENERIC_MAPPING这个结构体。最后就是调用AccessCheck(),参数还是比较复杂的,我这里简单介绍下,函数原型如下:

BOOL WINAPI AccessCheck(  
  __in          PSECURITY_DESCRIPTOR pSecurityDescriptor,  
  __in          HANDLE ClientToken,  
  __in          DWORD DesiredAccess,  
  __in          PGENERIC_MAPPING GenericMapping,  
  __out_opt     PPRIVILEGE_SET PrivilegeSet,  
  __in_out      LPDWORD PrivilegeSetLength,  
  __out         LPDWORD GrantedAccess,  
  __out         LPBOOL AccessStatus  
); 

pSecurityDescriptor是安全描述符表的指针没啥说的,ClientToken是模拟之后的令牌句柄。DesiredAccess是通用的权限控制标志。GenericMapping就是用MapGenericMask()映射后的针对特定对象的权限控制标志。 PrivilegeSet是我们之前提到过的访问令牌中的Privilege,用来检查一些系统操作的权限,比如开关机,修改系统时间等等,一般情况下初始化为0。PrivilegeSetLength是跟着之前PrivilegeSet的,这里既然不去检查权限也置为0。最后GrantedAccess和AccessStatus比较有用,AccessStatus会返回指定的权限是否被允许访问该对象,允许则为TRUE,否则为FALSE。如果AccessStatus为TRUE,该函数会把当前的ACE中的所有允许的权限操作标志赋给GrantedAccess。

下面给出获取令牌到检查权限部分的代码:

    HANDLE hToken;        
    if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ALL_ACCESS,&hToken)) 
             { 
                       return bRs; 
             } 
     
             HANDLE hImpersonatedToken = NULL; 
             if(DuplicateToken(hToken, 
                       SecurityImpersonation,&hImpersonatedToken)) 
             //模拟令牌 
     
             { 
                       DWORD dwGenericAccessMask = GENERIC_READ|GENERIC_WRITE; 
                       GENERIC_MAPPING genMap ; 
                       PRIVILEGE_SET privileges = {0}; 
                       DWORD grantAccess = 0; 
                       DWORD privLength = sizeof(privileges); 
                       BOOL bGrantAccess = FALSE; 
                       //将通用权限控制标志和特定类型对象权限控制标志挂钩 
                       genMap.GenericRead = FILE_GENERIC_READ; 
                       genMap.GenericWrite = FILE_GENERIC_WRITE; 
                       genMap.GenericExecute = FILE_GENERIC_EXECUTE; 
                       genMap.GenericAll = FILE_ALL_ACCESS; 
     
                       MapGenericMask(&dwGenericAccessMask,&genMap); 
                       //映射通用权限控制标志 
                       if(AccessCheck(psd,hImpersonatedToken, 
                                dwGenericAccessMask,                            &genMap,&privileges,&privLength,&grantAccess,&bGrantAccess)) 
                       { 
                                bRs = bGrantAccess; 
                                return bRs; 
                       }else 
                       { 
                                printf("\n[-]Access check failed:%d",GetLastError()); 
                               return bRs; 
                      } 
             } 
最后上图上真相吧:

文章到此结束了,拙作一篇,侧重于C+API编程实现对访问控制列表的遍历和权限的判断。只希望能让以后进行相关编程的同学能图个方便。Any comment is welcomed。

------------------------------------------------------------------------------------------------------------------------------------

可能有人会问,检测对文件或者文件夹的访问权限有什么用呢?哈哈,当然是有用的。假定目标程序是以标准用户权限运行的,QQ就是以标准用户权限运行的,不是一启动就申请管理员权限的,如果是一启动就要申请管理员权限,则在系统UAC打开时,每次启动都会弹出UAC提示框。并且在标准用户登录下,不使用管理员账户提权,是没法运行程序的。比如在实现IM中的文件传输功能时,文件接收端在另存文件时,是不能保存到一些敏感的系统目录C:\Windows、C:\Windows\System32,如果选择存到这些目录中,会被重定向到系统对应的虚拟路径中,导致用户找不到文件的。文件重定向的具体细节,可以参看http://blog.csdn.net/chenlycly/article/details/53408212

最后附上本人最终用到实际工程中的完整代码 by chenlycly(xingpacer):(代码已经在win7、win8和win10中验证通过,要注意考虑的周全,该释放的资源的地方要记得释放,比如CloseHandle)

// 将要检测的权限GENERIC_XXXXXX传递给dwGenericAccessMask,可检测对
// 文件或者文件夹的权限
BOOL CanAccessFile( CString strPath, DWORD dwGenericAccessMask )  
{  
    DWORD dwSize = 0;  
    PSECURITY_DESCRIPTOR psd = NULL;  
    SECURITY_INFORMATION si = OWNER_SECURITY_INFORMATION | 
        GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION;  

    // 获取文件权限信息结构体大小  
    BOOL bRet = GetFileSecurity( strPath, si, psd, 0, &dwSize );  
    if ( bRet || GetLastError() != ERROR_INSUFFICIENT_BUFFER )  
    {  
        return FALSE;  
    }  

    char* pBuf = new char[dwSize]; // 如果考虑频繁申请内存会失败,可考虑使用VirtualAlloc
    ZeroMemory( pBuf, dwSize );
    psd = (PSECURITY_DESCRIPTOR)pBuf;  

    // 获取文件权限信息结构体大小  
    bRet = GetFileSecurity( strPath, si, psd, dwSize, &dwSize );  
    if ( !bRet )  
    {  
        delete []pBuf;
        return FALSE;  
    }  

    HANDLE hToken = NULL;  
    if ( !OpenProcessToken( GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken ) )  
    {  
        delete []pBuf;
        return FALSE;  
    }  

    // 模拟令牌  
    HANDLE hImpersonatedToken = NULL;  
    if( !DuplicateToken( hToken, SecurityImpersonation, &hImpersonatedToken ) )  
    {  
        delete []pBuf;
        CloseHandle( hToken );
        return FALSE;  
    }  
  
    // 在检测是否有某个权限时,将GENERIC_WRITE等值传给本函数的第二个参数dwGenericAccessMask
    // GENERIC_WRITE等参数在调用CreateFile创建文件时会使用到,下面调用MapGenericMask将
    // GENERIC_WRITE等转换成FILE_GENERIC_WRITE等
    // 将GENERIC_XXXXXX转换成FILE_GENERIC_XXXXXX
    GENERIC_MAPPING genMap;
    genMap.GenericRead = FILE_GENERIC_READ;  
    genMap.GenericWrite = FILE_GENERIC_WRITE;  
    genMap.GenericExecute = FILE_GENERIC_EXECUTE;  
    genMap.GenericAll = FILE_ALL_ACCESS;  
    MapGenericMask( &dwGenericAccessMask, &genMap );  

    // 调用AccessCheck来检测是否有指定的权限
    PRIVILEGE_SET privileges = { 0 };  
    DWORD dwGrantedAccess = 0;  
    DWORD privLength = sizeof(privileges);  
    BOOL bGrantedAccess = FALSE;  
    if( AccessCheck( psd, hImpersonatedToken, dwGenericAccessMask, 
        &genMap, &privileges, &privLength, &dwGrantedAccess, &bGrantedAccess ) )  
    {    
    }
    else
    {
        bGrantedAccess = FALSE;
    }

    delete []pBuf;
    CloseHandle( hImpersonatedToken );
    CloseHandle( hToken );
    return bGrantedAccess;  
}  

经产品测试人员测试发现,上述代码在win7中没问题,但是在win10中判断是失效的,比如对于一些敏感的系统目录C:\Windows、C:\Windows\System32,如果目标程序没有管理员权限(以标准用户权限运行),肯定是没有写权限的!但是上述代码却判断不出来,后来通过添加AccessCheck调用后的参数信息的打印,找到了解决办法,需要对上述代码做部分改动!

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
当在Windows 10中访问目标文件夹时遇到"访问被拒绝"的错误提示,这意味着当前用户没有足够的权限来执行所需的操作。要解决该问题,可以采取以下步骤: 1. 确保你使用的是管理员账户。只有管理员账户拥有对系统文件文件夹的完全控制权限。尝试使用管理员账户重新访问目标文件夹。 2. 对目标文件夹应用适当的权限。右键单击目标文件夹,选择"属性"。在"安全"选项卡中,点击"编辑"按钮并选择当前用户或管理员账户。检查是否拥有"完全控制"的权限,如果没有,请点击"允许"并勾选。 3. 禁用组策略设置。在开始菜单中搜索"组策略"并打开。导航到"计算机配置" > "Windows设置" > "安全设置" > "本地策略" > "安全选项"。在右侧窗口中找到"用户帐户控制:以管理员批准模式运行所有管理员",将其设置为"已禁用"。 4. 尝试使用命令行来授予权限。打开命令提示符,右键单击并选择"以管理员身份运行"。然后使用"icacls"命令为目标文件夹授予完全控制权限。例如,输入“icacls C:\目标文件夹 /grant 用户名称:F”,其中用户名称是你的用户名。 5. 检查目标文件夹的属性。确保目标文件夹没有设置为"只读"或"隐藏"。在文件夹上右键单击,选择"属性",然后取消选中"只读"选项。 以上是解决Windows 10目标文件夹访问被拒绝需要权限的一些常见方法。根据具体情况尝试这些方法,有助于您获得所需的权限并成功访问目标文件夹

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值