操作系统中有些资源是不能由用户代码直接访问的,比如线程进程,文件等等,这些资源必须由系统级代码由RING3层进入到RING0层操作,并且返回一些标识供用户程序使用,一般调用某个函数陷入到内核,这样的函数叫做系统调用,而有些不直接陷入到内核,一般叫做系统API,linux中使用系统调用,而windows中封装了一系列的API。

windows对象与句柄

windows对象

操作系统为了安全,提供了一种保护机制,这种机制会禁止用户操作某些资源,避免用户过于关注细节,或者由于操作不当而造成系统崩溃。windows中将所有这些资源封装成了一个个对象。对象就是操作系统为了维护这些资源而定义的一系列的数据结构。对象这个词我们很容易联想到面向对象编程语言中的对象,对象其实就是对一些数据以及操作这些数据的一个封装,而windows是采用面向对象思想开发的,所以我们可以将windows中的对象想象成面向对象中的对象,而windows针对每种对象都提供了操作函数,这些函数一般都会以句柄作为第一个参数,这就有点像类函数中传入的this指针。而这操作对象的函数就是类函数。

windows中总共有三种对象:GUI对象、GDI对象、内核对象。

windows中的句柄

windows中对象的操作是由系统提供的一系列的API函数来完成,这些函数有一个共同特点,就是以HANDLE 句柄作为第一个参数,windows中采用句柄来唯一标识每个内核对象。在windows中句柄的定义如下:

typedef void * HANDLE

从上面的定义可以看出,这个句柄应该是指向这个对象的结构体的指针。由于每次在程序启动时内存都是随机分配的,所以句柄不要使用硬编码的方式,同时在复制内核对像的时候,并不是简单的复制它的句柄,对象的复制有专门的函数,DuplicateHandle,该函数原型如下:

BOOL DuplicateHandle(
HANDLE hSourceProcessHandle, //源对象所在进程句柄
HANDLE hSourceHandle, //源对象句柄
HANDLE hTargetProcessHandle, //目标对象所在进程句柄
LPHANDLE lpTargetHandle, //返回目标对象的句柄
DWORD dwDesiredAccess, //以何种权限复制
BOOL bInheritHandle, //复制的对象句柄是否可继承
DWORD dwOptions //标记一些特殊的动作
);

下面是一个例子代码:

HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
HANDLE hMutexDup, hThread;
DWORD dwThreadId; DuplicateHandle(GetCurrentProcess(),hMutex,GetCurrentProcess(),&hMutexDup,
0,FALSE,DUPLICATE_SAME_ACCESS);

上述代码在本进程中复制了一个互斥对象对象的句柄。

windows 安全对象模型



windows中的内核对象由进程和线程进行操作,而对象就好像一个被锁上的房间,进程想要访问对象,并对对象进程某种操作,就必须获取这个对象的钥匙,而线程就好像拥有钥匙的人,只有钥匙是对的,才可以访问。这把锁能够由不同的钥匙打开,这些钥匙信息存储在ACE中,而只有当ACE中的信息与访问字串这个钥匙匹配才可以打开。我们一般把这个钥匙称为访问字串(Token)

访问字串

访问字串主要包括:用户标识、组标识、优先权信息、以及其他访问信息。

用户标识:用于唯一标识每个用户,就好像为每个用户都分配了一个唯一的用户ID

组标识:用户所属组的唯一标识ID

优先权:一般系统对每个用户以及它所属组分配了一些权限,而有的时候这些权限并不够,这个时候需要通过这个优先权信息额外新增一些权限

当用户登录windows系统时,系统就为这个用户分配了一个带有该用户信息的访问字串,该用户创建的每个安全对象都有这个访问字串的拷贝,当用户打开的进程试图访问某个安全对象时系统就在对象的ACL中查找该用户是否有某项权限。有这个权限才能对对象进行这项操作。

子进程的访问字串一般继承与父进程,但是子进程也可以自行创建访问字串来覆盖原来的访问字串。

操作访问字串所使用的API主要有以下几个:

OpenProcessToken //打开进程的访问令牌

OpenThreadToken //打开线程的访问令牌

AdjustTokenGroups //改变用户组的访问令牌

AdjustTokenPrivileges //改变令牌的访问特权

GetTokenInformation //获取访问令牌的信息

SetTokenInformation //设置访问令牌信息

下面主要介绍一下函数GetTokenInformation 的用法:

BOOL WINAPI GetTokenInformation(
__in HANDLE TokenHandle, //访问字串的句柄
__in TOKEN_INFORMATION_CLASS TokenInformationClass, //需要返回访问字串哪方面的信息
__out_opt LPVOID TokenInformation, //用于接收返回信息的缓冲
__in DWORD TokenInformationLength, //缓冲的长度
__out PDWORD ReturnLength //实际所需的缓冲的长度
);

这个函数可以返回访问令牌多方面的信息,具体返回哪个方面的信息,需要通过第二个参数来指定,这个参数是一个枚举类型,表示需要获取的信息,每个方面的信息都定义了对应的结构体,这些信息可以由MSDN中查到。另外这个函数支持两次调用,第一次传入NULL指针和0长度,这样通过最后一个参数可以得到具体所需要的缓冲的大小。

SID

访问字串中用户与用户组采用安全标识符的方式唯一标识(Security Indentifer SID),系统中的SID是唯一的。它主要用来标识下面的这些内容:

1. 安全描述符中的所有者和用户组

2. 被ACE认可的访问者

3. 访问字串的用户和组

SID的长度是可变的,在使用时不应该使用SID这个数据类型,因为这个时候还不知道需要的长度是多少,应该由系统来创建并返回它的指针,所以在使用时需要使用SID的指针。下面是一些操作SID的API

AllocateAndInitializeSid //初始化一个SID

FreeSid //释放一个SID

CopySid //拷贝一个SID

EqualSid //判断两个SID是否相等

GetLengthSid //获取SID的长度

IsValidSid //是否是有效的SID

ConvertSidToStringSid //将SID转化为字符串的方式

下面是一个利用这些API获取系统用户组的SID和当前登录用户的SID的例子:

BOOL GetLoginSid(HANDLE hToken, PSID *ppsid);
//SID本身大小是不可知的,所以在传入参数时应该传入2级指针,由函数自己决定大小
void FreeSid(PSID *ppsid);
int _tmain(int argc, TCHAR* argv[])
{
setlocale(LC_ALL, "chs");
HANDLE hProcess = GetCurrentProcess();
HANDLE hToken = NULL;
OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken);
PSID pSid = NULL;
GetLoginSid(hToken, &pSid);
LPTSTR pStringSid = NULL;
ConvertSidToStringSid(pSid, &pStringSid);
_tprintf(_T("当前登录的用户sid = %s\n"), pStringSid);
FreeSid(&pSid);
return 0;
} BOOL GetLoginSid(HANDLE hToken, PSID *ppsid)
{
TOKEN_GROUPS *ptg = NULL;
BOOL bSuccess = FALSE;
DWORD dwLength = 0; if(!GetTokenInformation(hToken, TokenGroups, ptg, 0, &dwLength))
{
if (ERROR_INSUFFICIENT_BUFFER != GetLastError())
{
goto __CLEAN_UP;
} ptg = (TOKEN_GROUPS*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength); if (!GetTokenInformation(hToken, TokenGroups, ptg, dwLength, &dwLength))
{
goto __CLEAN_UP;
}
} _tprintf(_T("共找到%d个组SID\n"), ptg->GroupCount);
LPTSTR pStringSid = NULL;
for (int i = 0; i < ptg->GroupCount; i++)
{
ConvertSidToStringSid(ptg->Groups[i].Sid, &pStringSid);
_tprintf(_T("\t id = %d sid = %s"), i, pStringSid);
if ((ptg->Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID)
{
_tprintf(_T("此用户为当前登录的用户"));
*ppsid = (PSID)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwLength);
CopySid(dwLength, *ppsid, ptg->Groups[i].Sid);
} _tprintf(_T("\n"));
} __CLEAN_UP:
if (ptg != NULL)
{
HeapFree(GetProcessHeap(), 0, ptg);
}
return bSuccess;
} void FreeSid(PSID *ppsid)
{
HeapFree(GetProcessHeap(), 0, *ppsid);
*ppsid = NULL;
}

这个例子是MSDN中的一个例子,上述代码中首先获取进程的访问令牌,然后通过函数GetTokenInformation 获取访问令牌的信息。通过传入TokenGroups这个值获取当前用户所在用户组的访问字串。并将信息保存到结构体TOKEN_GROUPS中最后通过(ptg->Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID这样一个表达式来判断当前的SID是否是登录用户的。

优先权

优先权是由字符串标识的局部唯一的标识符(LUID)

优先权是由系统管理员分配给对应的用户,一般不能通过编程的方式提升用户的优先权,但是有时候即使用户具有某个优先权,但是它启动的程序并不具有相关的优先权。这个时候可以通过编程的方式提升用户进程特权。

系统有3个值代表一个优先权:

字符串名,整个系统上都有意义,称为全局名,这个字符串并不是一个可读的字符串,显示出来的信息不一定能看得懂。

显示给用户的可读名称;如:改变系统时间(可以在组策略中查看)

每个计算机都不同的局部值;

下面有几个常用的优先权:

#define SE_DEBUG_NAME  TEXT("SeDebugPrivilege") //调试进程
#define SE_LOAD_DRIVER_NAME TEXT("SeLoadDriverPrivilege") //装载驱动
#define SE_LOCK_MEMORY_NAME TEXT("SeLockMemoryPrivilege") //锁定内存页面
#define SE_SHUTDOWN_NAME TEXT("SeShutdownPrivilege") //关机

下面是几个优先权函数

LookupPrivilegeValue //查询优先权的值

LookupPrivilegeDisplayName //查询优先权的输出名

LookupPrivilegeName //查询优先权的名称

PrivilegeCheck //优先权信息检查

下面是一个获取用户特权信息的代码:

int _tmain(int argc, TCHAR *argv[])
{
setlocale(LC_ALL, "chs");
HANDLE hToken = NULL;
HANDLE hProcess = GetCurrentProcess();
OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
TOKEN_PRIVILEGES* ptg = NULL;
DWORD dwTgSize = 0;
TCHAR szName[255] = _T("");
TCHAR szDisplay[255] = _T("");
DWORD languageID = GetUserDefaultLangID();
if (!GetTokenInformation(hToken, TokenPrivileges, ptg, 0, &dwTgSize))
{
if (ERROR_INSUFFICIENT_BUFFER != GetLastError())
{
_tprintf(_T("获取令牌失败\n"));
return 0;
} ptg = (TOKEN_PRIVILEGES*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwTgSize);
if (!GetTokenInformation(hToken, TokenPrivileges, ptg, dwTgSize, &dwTgSize))
{
_tprintf(_T("读取令牌信息失败\n"));
return 0;
}
} _tprintf(_T("用户的特权信息:\n"));
for(int i = 0; i < ptg->PrivilegeCount; i++)
{
DWORD dwName = sizeof(szName) / sizeof(TCHAR);
DWORD dwDisplay = sizeof(szDisplay) / sizeof(TCHAR); LookupPrivilegeName(NULL, &ptg->Privileges[i].Luid, szName, &dwName);
LookupPrivilegeDisplayName(NULL, szName, szDisplay, &dwDisplay, &languageID); _tprintf(_T("\t 特权名%s %s"), szName, szDisplay);
if (ptg->Privileges[i].Attributes & ((SE_PRIVILEGE_ENABLED | SE_PRIVILEGE_ENABLED_BY_DEFAULT)))
{
_tprintf(_T("特权开放\n"));
}else
{
_tprintf(_T("特权关闭\n"));
}
} HeapFree(GetProcessHeap(), 0, ptg);
return 0;
}

下面是进程提权的代码:

int _tmain(int argc, TCHAR *argv[])
{
HANDLE hToken = NULL;
HANDLE hProcess = GetCurrentProcess(); SetPrivileges(hToken, SE_TCB_NAME , TRUE);
return 0;
} BOOL SetPrivileges(HANDLE hToken, LPTSTR lpPrivilegesName, BOOL bEnablePrivilege)
{
//获取Token的特权信息
LUID uid = {0};
LookupPrivilegeValue(NULL, lpPrivilegesName, &uid);
TOKEN_PRIVILEGES tp = {0};
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = uid;
if (bEnablePrivilege)
{
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
}else
{
tp.Privileges[0].Attributes = 0;
} AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL); if ( GetLastError() == ERROR_NOT_ALL_ASSIGNED )
{
_tprintf(_T("分配指定的特权(%s)失败. \n"),lpPrivilegesName);
return FALSE;
}
return TRUE;
}

安全描述符

安全描述符中通常包含信息:所有者、主组、任选访问控制列表(DACL)、系统访问控制列表(SACL)

安全描述符是以SECURITE_DESCRIPTOR结构开始,后面连续跟着安全描述符的其它信息

访问控制列表

访问控制列表(Access Control List ACL)主要由多个访问访问控制入口(Access Control Entries ACE)组成。ACE用于标识一个用户、组或局部组以及它们中每一个允许的访问权;

安全描述符的创建

在创建安全访问对象的函数中一般都需要填入一个SECURITY_ATTRIBUTES结构体的指针,我们要么给定一个NULL值使其具有默认的安全属性,或者自己创建一个安全描述符并将他的指针传入。

创建一个安全描述符主要有下面几步:

1. 用函数 AllocateAndInitializeSid 创建用户的SID。函数的定义如下:

BOOL WINAPI AllocateAndInitializeSid(
__in PSID_IDENTIFIER_AUTHORITY pIdentifierAuthority, //用于表示该SID标识的颁发机构
__in BYTE nSubAuthorityCount, //SID有多少个子部分
__in DWORD dwSubAuthority0, //第0个子部分
__in DWORD dwSubAuthority1,
__in DWORD dwSubAuthority2,
__in DWORD dwSubAuthority3,
__in DWORD dwSubAuthority4,
__in DWORD dwSubAuthority5,
__in DWORD dwSubAuthority6,
__in DWORD dwSubAuthority7,
__out PSID* pSid //返回一个PSID的指针
);

SID主要由一个颁发机构以及一个或者多个32位的唯一的RID组成这些RID通过参数dwSubAuthority0到dwSubAuthority7生成。对应的用户SID都有固定的组合。这个我还没找到具体的用户与SID是如何定义的。

2. 为该用户SID分配访问控制权限,主要通过填充结构体EXPLICIT_ACCESS成员来实现,这个结构体的定义如下:

typedef struct _EXPLICIT_ACCESS {
DWORD grfAccessPermissions; //制定用户权限
ACCESS_MODE grfAccessMode; //用于表示允许、拒绝、审查特定用户的权限
DWORD grfInheritance; //当前的权限是否可以继承
TRUSTEE Trustee; //这是一个访问托管
} EXPLICIT_ACCESS, *PEXPLICIT_ACCESS;

3.用API SetEntriesInAcl将上述结构体放入到ACL中

4. 分配并初始化SECURITY_DESCRIPTOR结构体,初始化该结构体所用到的API 是InitializeSecurityDescriptor

5. 将SECURITY_DESCRIPTOR结构加入到安全描述符中,安全描述符的结构体是:SECURITY_ATTRIBUTES

下面是一个具体的例子(这个例子来自MSDN):

    SID_IDENTIFIER_AUTHORITY SIDAuthorityNT = SECURITY_WORLD_SID_AUTHORITY;
SID_IDENTIFIER_AUTHORITY SIDAuthorityWord = SECURITY_NT_AUTHORITY;
PSID pEveryOneSid = NULL;
PSID pAdminSid = NULL;
EXPLICIT_ACCESS ea[2] = {0};
PACL pAcl = NULL;
//创建everyone用户的SID
AllocateAndInitializeSid(&SIDAuthorityWord, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &pEveryOneSid);
PSECURITY_DESCRIPTOR pSD = NULL;
//定义everyone 的访问控制信息
ea[0].grfAccessMode = SET_ACCESS; //用于表示允许、拒绝、审查特定用户的权限
ea[0].grfAccessPermissions = KEY_READ; //制定用户权限
ea[0].grfInheritance = FALSE;
ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[0].Trustee.ptstrName = (LPTSTR)pEveryOneSid; //创建administor用户组的SID
AllocateAndInitializeSid(&SIDAuthorityNT, 2, SECURITY_BUILTIN_DOMAIN_RID,DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &pAdminSid);
ea[1].grfAccessMode = SET_ACCESS;
ea[1].grfAccessPermissions = KEY_ALL_ACCESS;
ea[1].grfInheritance = FALSE;
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
ea[1].Trustee.ptstrName = (LPTSTR)pAdminSid; //将以上两个SID加入到ACL中
SetEntriesInAcl(2, ea, NULL, &pAcl);
pSD = (PSECURITY_DESCRIPTOR) HeapAlloc(GetProcessHeap(), 0, SECURITY_DESCRIPTOR_MIN_LENGTH); //初始化一个SECURITY_DESCRIPTOR结构
InitializeSecurityDescriptor(&pSD, SECURITY_DESCRIPTOR_REVISION); //将SECURITY_DESCRIPTOR结构加入到安全描述符中
SECURITY_ATTRIBUTES sa = {0};
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = pSD;
sa.nLength = sizeof(SECURITY_ATTRIBUTES); HKEY hkSub = NULL;
DWORD dwDisposition = 0;
RegCreateKeyEx(HKEY_CURRENT_USER, _T("mykey"), 0, _T(""), 0, KEY_READ | KEY_WRITE, &sa, &hkSub, &dwDisposition); if (pEveryOneSid)
{
FreeSid(pEveryOneSid);
}
if (pAdminSid)
{
FreeSid(pAdminSid);
}
if (pAcl)
{
LocalFree(pAcl);
}
HeapFree(GetProcessHeap(), 0, pSD);
if (hkSub)
{
RegCloseKey(hkSub);
}

windows 安全模型简介的更多相关文章

  1. Docker for Windows使用简介

    在上一篇文章中,通过演练指导的方式,介绍了在Docker中运行ASP.NET Core Web API应用程序的过程.本文将介绍Docker for Windows的使用. 先决条件 前两周时间,Do ...

  2. Platform SDK、Windows SDK简介

    Platform SDK及Windows SDK是由微软公司出品的一个软件开发包,向在微软的Windows操作系统和.NET框架上开发软件和网站的程序员提供头文件.库文件.示例代码.开发文档和开发工具 ...

  3. Windows Phone 简介

    中文官网 https://dev.windowsphone.com/zh-cn Windows Phone SDK 7.1 http://www.microsoft.com/zh-cn/downloa ...

  4. windows 勾子简介

    近段时间因朋友催促让试着写一个监控系统,主要是用来管理孩子使用电脑,帮助孩子合理使用电脑.在网上查询了相关内容发现没有这方面的资料,所以只有自已来试试,要用到钩子来对windows应用程序进行监控,也 ...

  5. windows程序设计简介

    大家好,非常高兴和大家一起分享Windows开发心得,Windows已经诞生很多年了,一直因为它的简单易用而深受欢迎,相信很多人在使用Windows的时候,一定有这样一个想法:希望自己将来可以写一个很 ...

  6. Windows PowerShell 简介

    Powershell 是运行在windows机器上实现系统和应用程序管理自动化的命令行脚本环境.微软之所以将Powershell 定位为Power,并不是夸大其词,因为它完全支持对象.其可读性,易用性 ...

  7. Windows API 简介

    操作系统的作用之一就是屏蔽一些复杂的直接对硬件操作,并提供给用户一个简单明确的应用接口,类外对于一些基本的或常用的操作也以API的形式提供给用户,比如内存管理.文件管理等. 消息传递机制 消息循环是一 ...

  8. Windows的安全模型

    1. 安全身份 Windows的安全模型是以用户为线索的,用户的身份是在登录系统时验证的. 除了用户外,还可以有一些特殊实体需要拥有安全的身份,以便进行验证,比如groups, domain等等. W ...

  9. Linux C++ 开发简介

    主要介绍将Windows程序迁移到Linux系统相关知识 简介 Windows程序迁移到Linux系统可能需要修改很多代码, 既需要了解Linux平台的开发知识, 也需要了解Windows平台代码如何 ...

随机推荐

  1. caffe在windows编译project及执行mnist数据集測试

    caffe在windows上的配置和编译能够參考例如以下的博客: http://blog.csdn.net/joshua_1988/article/details/45036993 http://bl ...

  2. java 可变參数列表

    Java SE5加入了可变參数列表特性 參数能够这样定义.(Object-args).可变參数用"..."来定义,args是可变參数的数组.举个样例: package sample ...

  3. --------------Hibernate学习(四) 多对一映射 和 一对多映射

    现实中有很多场景需要用到多对一或者一对多,比如上面这两个类图所展现出来的,一般情况下,一个部门会有多名员工,一名员工只在一个部门任职. 多对一关联映射 在上面的场景中,对于Employee来说,它跟D ...

  4. HTML5 Canvas:初始Canvas

    Canvas ,HTML 5中引入它,可以做很多事情:画图.动画.游戏开发等等. Canvas 元素 Canvas 中文翻译为:画布. <canvas id=”yourCanvasId” wid ...

  5. 分布式:2PC,3PC,Paxos,Raft,ISR [转]

    本文主要讲述2PC及3PC,以及Paxos以及Raft协议. 两类一致性(操作原子性与副本一致性) 2PC协议用于保证属于多个数据分片上的操作的原子性.这些数据分片可能分布在不同的服务器上,2PC协议 ...

  6. springboot+shiro

    作者:纯洁的微笑 出处:http://www.ityouknow.com/ 这篇文章我们来学习如何使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公 ...

  7. Intellij idea 复制粘贴查找快捷键失效

    遇到此问题,竟不能复制, 发现原因,是因为勾选了Vim模式, Tools,Vim Emulator,前面会有一个√,取消即可,如图: 我的是这个原因,复制粘贴快捷键失效,也有可能历史粘贴板的深度不够 ...

  8. Spring 链接数据库

    一.前言 Spring 现在是我们在做 JavaWeb 开发中,用的最主流的框架.以后是不是我们暂时不知道,但现在是.废话不多我就介绍 Spring 中.链接数据库的三种方式: git源码地址 需要的 ...

  9. The ResourceConfig instance does not contain any root resource classes

    问题描述 当我们在使用 myeclipse 创建 Web Service Projects 项目后,运行项目然后就会出现这个问题. 解决方案 通过这个错误描述,我们项目没有找到这个资源.报错的原因在于 ...

  10. Sphinx学习笔记(一)

    最近负责一个项目,需要用到全文检索,我的环境大体如下:       1.数据保存在MySQL中     2.需要支持中文检索     3.尽可能的简单       选择了Sphinx,至于solr和E ...