提示:本文修改后包含编程方法以及附注的工具方法,传统的资源管理器交互方法等等。
由于文件是安全对象,因此访问它们受访问控制模型控制,该模型控制对 Windows 中所有其他安全对象的访问。
更改文件或目录对象的安全描述符,需要调用 SetNamedSecurityInfo 或 SetSecurityInfo 等函数。
ACL:Access Control List,用来表示用户(组)权限的列表,包括 DACL 和 SACL ;
ACE:Access Control Entry,ACL 中的元素;
DACL:Discretionary Access Control List,用来表示安全对象权限的列表;
SACL:System Access Control List,用来记录对安全对象访问的日志。
关于这几个概念已经有很多资料解释,这里就不详细展开了。(以下仅介绍通过 Win32API 修改文件安全属性的一些内容)。
本文以 SetNamedSecurityInfo 函数为例,简单讲解如何通过编程修改指定文件的 ACL 主体和添加 ACE 条目。后文修改的文件比较特殊,它们受到本地内置安全主体 TrustedInstaller 的完全控制保护,本文旨在通过相关 API 实现修改这类文件的完全控制权限。有关内置安全主体 TrustedInstaller 的信息,需要具有一定的知识基础,限于篇幅原因这里不详细展开。
SetNamedSecurityInfo 函数用于在指定对象的安全描述符(ACL)中设置指定的安全信息。 调用方按名称标识对象。
这里的按名称标识对象,表示设置安全信息时候,目标对象名是字符串格式,且对于不同对象类型的字符串格式,可以参阅说明:SE_OBJECT_TYPE。我们这里要修改NTFS文件系统中指定文件的安全属性,按照标准需要填写文件绝对路径的宽字符串,其中路径以反斜杠 (" / ") 或者双斜杠 (" \\ ") 给出,并且要以 NULL 结尾。
下面给出该函数的参数:
DWORD SetNamedSecurityInfoW(
[in] LPWSTR pObjectName,
[in] SE_OBJECT_TYPE ObjectType,
[in] SECURITY_INFORMATION SecurityInfo,
[in, optional] PSID psidOwner,
[in, optional] PSID psidGroup,
[in, optional] PACL pDacl,
[in, optional] PACL pSacl
);
这些参数的定义是:
pObjectName 指定目标对象的名称;
ObjectType 指示 pObjectName 的类型,如果修改 NTFS 文件则填写为 SE_FILE_OBJECT;
SecurityInfo 指示要设置的安全信息的类型;
psidOwner 指向标识对象所有者的 SID 结构的指针;
psidGroup 指向标识对象主组的 SID 的指针;
pDacl 指向 对象的新 DACL 的指针;
pSacl 指向 对象的新 SACL 的指针。
其中,如果 SecurityInfo 填写为 OWNER_SECURITY_INFORMATION 表明对安全主体的操作(也就是控制列表所有者);如果填写为 DACL_SECURITY_INFORMATION 则表明对安全对象(ACE)的权限的操作。这里需要注意:如果调用方令牌对应的账户标识符(SID)不是文件安全主体指向的账户标识符,则对安全对象权限的操作会被拒绝。
修改文件安全属性需要进程的令牌具有一定的访问控制权限,这里我们需要获取两个权限 SE_SECURITY_NAME 和 SE_TAKE_OWNERSHIP_NAME 。这两个权限可以通过 AdjustTokenPrivileges 函数获得,而赋予新的令牌则需要调用方线程具有管理员权限。所以进程需要以提升的令牌启动才能正确获取权限。
从零开始的项目都是不容易的,有一个参考最好不过了。这里我们可以参考资源管理器的功能实现文件安全属性的修改,下面的图片展示了通过资源管理器查看的目标文件的安全属性页面信息:
通过观察,我们可以发现,当前的所有者为 TrustedInstaller 并且只有它具有完全控制权限,其他安全对象只具有读取和执行的权限。此外我们还发现,对于该文件权限修改的按钮都是灰色或者具有盾牌图标。我们将其与用户具有完全访问控制权的文件安全信息进行对比,就可以发现所有者信息起到了至关重要的作用,调用方必须为文件的安全主体或者权限比安全主体高,否则不能修改安全属性表。所以当正确获取权限后,我们首先需要修改文件的安全主体(所有者)。
首先我们简述一下通过资源管理器进行修改的步骤:
Step 1: 首先选中要修改的文件(夹),打开右键菜单,在菜单中找到“属性”项目,点击打开属性对话框;
Step 2: 在属性对话框中选中“安全”选项卡,右下角点击“高级(安全设置)”按钮;
Step 3: 可以看到该页面包含两个主要部分:1)所有者;2)成员及其权限
并且后面都有带有盾牌的“更改”按钮,再结合说明文档可知,如果当前进程代表的账户权限低于所有者所具有的权限,则低权限进程不具有修改高权限进程的权限能力;
这里我们需要先修改所有者,点击最上面的TrustedInstaller字样后面的更改按钮,此时可能弹出UAC对话框提示用户需要提升权限,也可能没有提示直接提升(取决于计算机配置的UAC警告级别)
Step 4: 此时会弹出选择所有者的对话框,点击“高级”按钮展开这个编辑器窗口:
Step 5: 在高级窗口中点击立即查找,在查找的列表中找到当前登陆用户的用户名,然后点击确定,或者选择everyone(字面意思,代表任何用户)。
Step 6: 逐级确定并应用,可以发现所有者被成功更改:
最后一步点击“应用”,然后再点击确定,应用更改,随后重新打开高级安全设置页面,选择要修改权限的成员,并发现编辑区域按钮被点亮,点击“添加/编辑”:
随后,在打开的窗口中,先点击蓝色字“选择主体”,类似于修改所有者的操作完成权限配置,并点击确定,最后点击应用即可完成整个修改操作:
最后,补充一下修改完成后改回TrustedInstaller的方法:在更改所有者时候,直接输入 NT SERVICE\TrustedInstaller 需要注意不能有拼写错误!然后其他步骤不变。
我们通过设置 SetNamedSecurityInfo 函数的 SecurityInfo 参数为 OWNER_SECURITY_INFORMATION 并指定 psidOwner 为指向新所有者的安全描述符 (SID) 结构的指针。
SID 就相当于所有者的身份证,那么我们如何获取到所需要的 SID 呢?
LookupAccountName 函数可供获取指定用户名对应的 SID 。
其参数如下:
BOOL LookupAccountNameW(
[in, optional] LPCWSTR lpSystemName,
[in] LPCWSTR lpAccountName,
[out, optional] PSID Sid,
[in, out] LPDWORD cbSid,
[out, optional] LPWSTR ReferencedDomainName,
[in, out] LPDWORD cchReferencedDomainName,
[out] PSID_NAME_USE peUse
);
其中,lpAccountName 指向指定帐户名称的以 NULL 结尾的字符串的指针。这里的本地账户名称可以通过输入已知的字符串如 "Administrators" 。注:采用 domain_name\user_name 格式的完全限定字符串来确保 LookupAccountName 函数在所需域中查找帐户,但本文不谈及域内操作,只谈本地操作。
关于账户名称的获取,有多种方法,如 GetUserName 可以获得创建当前调用线程所在帐户名称的字符串,环境变量函数 GetEnvironmentVariable 通过设置 lpName 参数为"USERNAME"可以获得当前所在帐户名称的字符串;使用 Windows Terminal Session API 获取当前会话对应的账户名称字符串,等等。这里通过 WTSQuerySessionInformation 函数并指定相关参数设置以获取当前登录账户的名称:
WTSQuerySessionInformationW(
WTS_CURRENT_SERVER_HANDLE,
WTS_CURRENT_SESSION,
WTS_INFO_CLASS::WTSUserName,
&userName,
&nameSize);
然后将得到的字符串传递给 LookupAccountName 函数,获取对应的 SID 。
随后,调用 SetNamedSecurityInfo 并设置 pObjectName, ObjectType, SecurityInfo 和 psidOwner 参数即可修改目标对象的所有者。
本文以在原有DACL表上添加新的对象权限为例进行讲解。有关于覆盖、删除、复制的方法需要自行调试。
一种通用的更新方法就是先获取到原始的DACL表,然后将其与要添加的内容合并为一张新的表。最后将新的表更新到文件对应的表上。
整个过程我们需要这几个步骤:
Step1: 调用 GetNamedSecurityInfo 并设置 DACL_SECURITY_INFORMATION 类型,获取旧的DACL表;
Step2: 调用 BuildExplicitAccessWithName 用于构建一个 ACE,包含需要的权限;
Step3: 调用 SetEntriesInAcl 合并 ACE 到 旧的DACL,返回合并后的新的 DACL;
Step4: 调用 SetNamedSecurityInfo 绑定新的 DACL 到文件对象。
一个典型的示例代码如下,其中 Step2 也可以直接为结构体成员赋值:
DWORD AddAceToObjectsSecurityDescriptor(
LPTSTR pszObjName, // name of object
SE_OBJECT_TYPE ObjectType, // type of object
LPWSTR pszTrustee, // trustee for new ACE
TRUSTEE_FORM TrusteeForm, // format of trustee structure
DWORD dwAccessRights, // access mask for new ACE
ACCESS_MODE AccessMode, // type of ACE
DWORD dwInheritance // inheritance flags for new ACE
)
{
DWORD dwRes = 0;
PACL pOldDACL = NULL, pNewDACL = NULL;
PSECURITY_DESCRIPTOR pSD = NULL;
EXPLICIT_ACCESS ea = {};
if (NULL == pszObjName)
return ERROR_INVALID_PARAMETER;
printf("|*|:为用户 %ws 获取文件:%ws 的访问权限。\n", pszTrustee, pszObjName);
// Get a pointer to the existing DACL.
dwRes = GetNamedSecurityInfoW(pszObjName, ObjectType,
DACL_SECURITY_INFORMATION,
NULL, NULL, &pOldDACL, NULL, &pSD);
if (ERROR_SUCCESS != dwRes) {
printf("GetNamedSecurityInfo Error %u\n", dwRes);
goto Cleanup;
}
// Initialize an EXPLICIT_ACCESS structure for the new ACE.
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
//ea.grfAccessPermissions = dwAccessRights;
//ea.grfAccessMode = AccessMode;
//ea.grfInheritance = dwInheritance;
//ea.Trustee.TrusteeForm = TrusteeForm;
//ea.Trustee.ptstrName = pszTrustee;
// 生成指定用户帐户的访问控制信息(这里指定赋予全部的访问权限)
BuildExplicitAccessWithNameW(&ea, pszTrustee, dwAccessRights, AccessMode, dwInheritance);
// Create a new ACL that merges the new ACE
// into the existing DACL.
dwRes = SetEntriesInAclW(1, &ea, pOldDACL, &pNewDACL);
if (ERROR_SUCCESS != dwRes) {
printf("SetEntriesInAcl Error %u\n", dwRes);
goto Cleanup;
}
// Attach the new ACL as the object's DACL.
dwRes = SetNamedSecurityInfoW(pszObjName, ObjectType,
DACL_SECURITY_INFORMATION,
NULL, NULL, pNewDACL, NULL);
if (ERROR_SUCCESS != dwRes) {
printf("SetNamedSecurityInfo Error %u\n", dwRes);
goto Cleanup;
}
else {
printf("成功:已经为用户 %ws 获取文件:%ws 的访问权限。\n", pszTrustee, pszObjName);
return dwRes;
}
Cleanup:
if (pSD != NULL)
LocalFree((HLOCAL)pSD);
if (pNewDACL != NULL)
LocalFree((HLOCAL)pNewDACL);
std::cout << "|*|:操作失败! " << std::endl;
return -1;
}
通过以上方法即可对指定路径文件进行安全访问权限的修改。
下面是按照上文思路编写的简单实现代码,需要将编译好的程序在提升的命令行中执行,命令行参数为 TestFile.exe <文件路径> 。(编译环境:Visual Studio C++)
#include
#include
#include
#include
#include
#include
#include
#include
#pragma comment(lib, "Advapi32.lib")
#pragma comment(lib, "Wtsapi32.lib")
#define MAX_NAME 260
BOOL SetPrivilege(
HANDLE hToken, // access token handle
LPCWSTR lpszPrivilege, // name of privilege to enable/disable
BOOL bEnablePrivilege // to enable or disable privilege
)
{
TOKEN_PRIVILEGES tp;
LUID luid;
if (!LookupPrivilegeValueW(
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;
}
BOOL SeTakeOwnershipToken()
{
printf("|*|操作:获取相关权限。该特权可以修改目标文件的 Ownership。\n");
HANDLE hToken;
BOOL bRet = OpenProcessToken(
GetCurrentProcess(), // 进程句柄(当前进程)
TOKEN_ALL_ACCESS, // 全权访问令牌
&hToken // 返回的参数 进程令牌句柄 (就是AdjustTokenPrivileges的第一个参数)
); // 获取进程的令牌句柄
if (bRet != TRUE) {
printf("获取令牌句柄失败!\nPlease wait...\n");
return FALSE;
}
// 提升权限
BOOL set_SECURITY = SetPrivilege(hToken, SE_SECURITY_NAME, TRUE);
if (!set_SECURITY || GetLastError() != ERROR_SUCCESS)
{
printf("提升SECURITY权限失败 Error: %u\n|*|:操作失败!\nPlease wait...\n", GetLastError());
Sleep(5000);
return FALSE;
}
// 提升获得所有者权限
BOOL set_OWNER = SetPrivilege(hToken, SE_TAKE_OWNERSHIP_NAME, TRUE);
if (!set_OWNER || GetLastError() != ERROR_SUCCESS)
{
printf("提升TAKE OWNERSHIP权限失败 Error: %u\n|*|:操作失败!\nPlease wait...\n", GetLastError());
Sleep(5000);
return FALSE;
}
printf("提升权限成功! \n");
return TRUE;
}
BOOL ChangeTrusteeViaObjectsSecurity(
LPTSTR pszObjName, // name of object
LPTSTR pszTrustee, // trustee for new ACE
SE_OBJECT_TYPE ObjectType, // type of object. value :SE_FILE_OBJECT, /* 注册表为:SE_REGISTRY_KEY */
SECURITY_INFORMATION SecurityInfo // Security Operational Information. value:OWNER_SECURITY_INFORMATION
)
{
DWORD dwRes = 0;
// LookupAccountName函数所需要的变量
wchar_t* userName = nullptr;
wchar_t sid[MAX_NAME]{ L'\0' };
DWORD nameSize = 0;
WTSQuerySessionInformationW(
WTS_CURRENT_SERVER_HANDLE,
WTS_CURRENT_SESSION,
WTS_INFO_CLASS::WTSUserName,
&userName,
&nameSize);
wchar_t userSID[MAX_NAME]{ L'\0' };
wchar_t userDomain[MAX_NAME]{ L'\0' };
DWORD sidSize = sizeof(userSID);
DWORD signSidSize = sizeof(userSID);
DWORD domainSize = sizeof(userDomain) / sizeof(WCHAR);
SID_NAME_USE snu;
dwRes = LookupAccountNameW(NULL,
userName,
userSID,
&sidSize,
userDomain,
&domainSize,
&snu);
WTSFreeMemory(userName);
//获取用户名SID
if (ERROR_SUCCESS != dwRes)// 调用成功返回值为0
{
PSID_IDENTIFIER_AUTHORITY psia = GetSidIdentifierAuthority(userSID);
signSidSize = swprintf_s(sid, sizeof(sid)/sizeof(wchar_t), L"S-%lu-", SID_REVISION);
signSidSize = (signSidSize + swprintf_s(sid + wcslen(sid), sizeof(sid), L"%-lu", psia->Value[5]));
int i = 0;
int subAuthorities = *GetSidSubAuthorityCount(userSID);
for (i = 0; i < subAuthorities; i++)
{
signSidSize += swprintf_s(sid + signSidSize, sizeof(sid), L"-%lu", *GetSidSubAuthority(userSID, i));
}
printf("Account SID: %ws\n", sid);
// 更改所有者
if (!SetNamedSecurityInfoW
(pszObjName,
ObjectType, /* 注册表为:SE_REGISTRY_KEY */
SecurityInfo, /* 更改所有者 */
&userSID, /* 需要更改所有者的SID */
NULL, NULL, NULL))
{
printf("成功更改所有者! \n");
return TRUE;
}
else {
printf("Security Info:OWNER_SECURITY_INFORMATION.\nSetNamedSecurityInfo Error %u\nPlease wait...\n", dwRes);
Sleep(5000);
}
}
else {
printf("LookupAccountName Error %u\nPlease wait...\n", dwRes);
Sleep(5000);
}
return FALSE;
}
DWORD AddAceToObjectsSecurityDescriptor(
LPTSTR pszObjName, // name of object
SE_OBJECT_TYPE ObjectType, // type of object
LPWSTR pszTrustee, // trustee for new ACE
TRUSTEE_FORM TrusteeForm, // format of trustee structure
DWORD dwAccessRights, // access mask for new ACE
ACCESS_MODE AccessMode, // type of ACE
DWORD dwInheritance // inheritance flags for new ACE
)
{
DWORD dwRes = 0;
PACL pOldDACL = NULL, pNewDACL = NULL;
PSECURITY_DESCRIPTOR pSD = NULL;
EXPLICIT_ACCESS ea = {};
if (NULL == pszObjName)
return ERROR_INVALID_PARAMETER;
printf("|*|:为用户 %ws 获取文件:%ws 的访问权限。\n", pszTrustee, pszObjName);
// Get a pointer to the existing DACL.
dwRes = GetNamedSecurityInfoW(pszObjName, ObjectType,
DACL_SECURITY_INFORMATION,
NULL, NULL, &pOldDACL, NULL, &pSD);
if (ERROR_SUCCESS != dwRes) {
printf("GetNamedSecurityInfo Error %u\n", dwRes);
goto Cleanup;
}
// Initialize an EXPLICIT_ACCESS structure for the new ACE.
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
//ea.grfAccessPermissions = dwAccessRights;
//ea.grfAccessMode = AccessMode;
//ea.grfInheritance = dwInheritance;
//ea.Trustee.TrusteeForm = TrusteeForm;
//ea.Trustee.ptstrName = pszTrustee;
// 生成指定用户帐户的访问控制信息(这里指定赋予全部的访问权限)
BuildExplicitAccessWithNameW(&ea, pszTrustee, dwAccessRights, AccessMode, dwInheritance);
// Create a new ACL that merges the new ACE
// into the existing DACL.
dwRes = SetEntriesInAclW(1, &ea, pOldDACL, &pNewDACL);
if (ERROR_SUCCESS != dwRes) {
printf("SetEntriesInAcl Error %u\n", dwRes);
goto Cleanup;
}
// Attach the new ACL as the object's DACL.
dwRes = SetNamedSecurityInfoW(pszObjName, ObjectType,
DACL_SECURITY_INFORMATION,
NULL, NULL, pNewDACL, NULL);
if (ERROR_SUCCESS != dwRes) {
printf("SetNamedSecurityInfo Error %u\n", dwRes);
goto Cleanup;
}
else {
printf("成功:已经为用户 %ws 获取文件:%ws 的访问权限。\n", pszTrustee, pszObjName);
return dwRes;
}
Cleanup:
if (pSD != NULL)
LocalFree((HLOCAL)pSD);
if (pNewDACL != NULL)
LocalFree((HLOCAL)pNewDACL);
std::cout << "|*|:操作失败! " << std::endl;
return -1;
}
int wmain(int argc, wchar_t* argv[])
{
setlocale(LC_CTYPE, "CHS");//让wprintf()支持中文
if (argc != 2)
{
std::cout << "Invalid Parameters!" << std::endl;
return 0;
}
wchar_t* path = argv[1];
wchar_t* pszObjName = path;
// 获取当前用户名
wchar_t* currentUser = nullptr;
DWORD dwSize_currentUser = 0;
WTSQuerySessionInformationW(
WTS_CURRENT_SERVER_HANDLE,
WTS_CURRENT_SESSION,
WTS_INFO_CLASS::WTSUserName,
¤tUser,
&dwSize_currentUser);
if (!SeTakeOwnershipToken()) {
std::cout << "|*|:在未获得SE_TAKE_OWNERSHIP_NAME特权时,继续更改文件所有者可能失败! " << std::endl;
}
// 更改ACL下的安全主体,以获得ACE控制权
if (ChangeTrusteeViaObjectsSecurity(pszObjName,
currentUser, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION))
{
// 覆盖ACL以获得完全控制的访问权限
if (AddAceToObjectsSecurityDescriptor(pszObjName,
SE_FILE_OBJECT,
currentUser,
TRUSTEE_IS_OBJECTS_AND_SID,
GENERIC_ALL,
GRANT_ACCESS,
SUB_CONTAINERS_AND_OBJECTS_INHERIT) == -1)
{
std::cout << "ACE操作已执行。" << std::endl;
}
}
else {
std::cout << "无法更改ACL下的安全主体,继续更改ACE控制权可能会失败。" << std::endl;
// 覆盖ACL以获得完全控制的访问权限
if (AddAceToObjectsSecurityDescriptor(pszObjName,
SE_FILE_OBJECT,
currentUser,
TRUSTEE_IS_OBJECTS_AND_SID,
GENERIC_ALL,
GRANT_ACCESS,
SUB_CONTAINERS_AND_OBJECTS_INHERIT) == -1)
{
std::cout << "ACE操作已执行。" << std::endl;
}
}
std::cout << "所有操作已完成。" << std::endl;
system("pause");
return 0;
}
下图是在提升的命令提示符中执行的效果:
从图中可见程序执行完毕,并且没有错误。
下图展示的是程序执行完毕后,目标对象的属性变化:
从图中可以看出目标对象的所有者发生变化,且为当前登陆账户添加了完全控制权限。
icacls
Intergrity Control Access Control List: 完整性权限控制列表
Windows系统下控制文件及文件夹的访问权限的命令行指令,相当于Linux中的chmod
原命令cacls已经被废弃。
这一篇博客园作者整理的命令详细信息比较全面清晰:Cacls和ICacls - 傩舞 - 博客园 (cnblogs.com)
就不详细列出了,这里给出一篇知乎,整理的也比较全面:渗透技巧——Windows下的Access Control List - 知乎
本文为原创文章,转载请注明出处。(修改于:2023/9/19)
本人博客:涟幽516-CSDN博客涟幽516擅长C++学习笔记,Win10动态壁纸开发,Windows上的编程,等方面的知识,涟幽516关注python,c++,c语言,系统安全领域.https://blog.csdn.net/qq_59075481