微信小程序从零快速开发,如何利用FinClip技术确保合规与数据安全
552
2022-09-12
计算机成熟思路回顾(三)
这次聊聊关于Windows 内核对象的一些相关内容:
1.什么是内核对象
Kernel Object
还记得在学校图书馆抱着一本操作系统相关的书籍,虽然看不懂,但是觉得介绍的还是挺条理。
有Kernel oBject 就有 Handle(虽然我也想调侃一下这个单词的翻译,简直是奇葩的典范,不知道中文到底有没有这个词)
基本作用:Kernel Object 管理着进程,线程,文件以及诸多种类的资源
常见类型:access token Object,事件对象,文件对象,文件映射对象,I/O完成端口对象
作业对象,mailslot Object,mutex(互斥量)对象,pipe Object,进程对象
semaphore(信号量) Object,线程对象,waitable timer Object,Thread pool woker factory Object...
常用Windows工具包 :SysinternalsSuite
常见Kernel Object 列表截图
不过确实没有啥定义,就是这些Object 就属于Kernel Object
对象创建思路:Kernel Object 通过不同的名称函数创建不同的对应具体某一个的Kernel Object
(注:但是函数名称可能可能不是和内核级别的对象类型对应)
例子:CreateFileMapping,将创建一个Section对象的文件映射
本质:一般对象都是一个内存块,所以内核对象也只是一个内存块。
权限:由操作系统内核分配,并且只能由操作系统内核访问
内容:对象都是一个数据结构,其成员维护着与对象相关的信息,内核对象也是如此。其中一部分成员是所有对象共有的
例子:一个(进程对象)有自己的进程ID,一个基本的优先级,一个推出代码,这些应该就是进程对象特有的
一个文件对象有一个字节偏移量,一个共享模式,一个打开模式
限制:一般的应用程序时不能在内核对象中定位数据结构并且直接修改,因为本身只能由内核本身操作
Micorsoft有意强化这个限制,所以Microsoft就可以自由的添加和删除,修改这些结构的成员,高内聚,不会影响使用的应用程序
操纵:使用Windows提供的一组函数来操作
操作思路:蛤蟆数据范围标识对象的handle,使用这个值就可以继续使用函数对它操作。32位系统,handle是32位值;64位系统是一个64位值
操作限制:handle是和进程相关的;所以handle传给别的进程就会发生调用失败
经典思路:垃圾回收,(计数法,php 也是使用这个思路)
内核对象的所有者是操作系统内核,而非进程。虽然是进程创建了内核对象,进程终止进行,内核对象不一定销毁。
大多数情况下会进入 销毁垃圾回收阶段。
但是如果有别的进程在使用,就不会重复创建,而是使用这一个,复用一下。
所以内核对象的生命周期可能超过创建它的进程。
Usage count
操作系统使用计数器来记录多少个正在使用一个特定的内核对象
该Usage Count 是所有内核对象类型都有的一个数据成员;默认值为1
另一个进程获得该内核对象的访问后,Usage Count会加一
Usage Count变成0,操作系统内核就会销毁该对象。(是的,PHP Zend引擎的垃圾回收就是这样标记对象引用的)
经典的设计思路总是会不自觉的运行在各处。
常规关于内核对象的基本知识,创建,销毁,生命周期就是这些东西。
以下都是相关细节。可能就不是通用的了
****************************************************************************************************************
2.内核对象的安全性
Security Description(SD)
这个变量来保护内核对象
含义:
定义了谁拥有对象,哪些用户和用户被允许访问或使用该对象;
哪些组和用户被拒绝访问此对象。
例子:用于创建内核对象的函数几乎都有一个指向SECURITY_ATTRIBUTES 结构的指针作为参数
Handle CreateFileMapping(
Handle handle,
PSECURITY_ATTRIBUTES psa,
DWORD flProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName)
该 psa默认接收一个NULL,代表具有默认的安全性
默认值和当前的security token有关系
当然,也可以分配一个非NULL的SECURITY_ATTRIBUTES结构,
初始化后,将这个变量的地址传给psa
SECUTIRY_ATTRIBUTES结构如下:(C语言的结构体)
typedef struct _SECURITY_ATTRIBUTES{
DWORD nLength;
LPVOLD lpSecurityDescriptor;
BOOL bInheritHandle;
} SECURITY_ATTRIBUTES
这样来看,只有一个与安全性相关的变量:lpSecurityDescriptor,
初始化例子:
SECURITY_ATTRIBUTES sa;sa.nLength = sizeof(sa);sa.lpSecurityDescriptor = pSD;sa.bInheritHandle = FALSE;Handle MyFileMapping = CreateFileMapping(InValid_HANGLE_VALUE,&sa,PAGE_READWRITE,0,1024,TEXT("HELLO"));
获取已有内核对象的访问:必须指定打算对这个对象执行哪些操作
例子:
HANDLE MyFileMapping = OpenFileMapping(FILE_MAP_READ,FALSE,TEXT("HEllo"));
解释:其中第一个参数就是表明我对这个对象进行读取操作
该函数返回一个handle之前会进行一个安全检查:允许则返回有效handle,否则就是null(调用getLastError返回5(ERROR_ACCESS_DENIED))
第二步,安全检查还会对权限内容判断,是否包括执行的操作,如果没有FILE_MAP_READ,也会拒绝访问
注:忽视正确的安全访问标志是很多开发人员最大的失误之一。
注:只有使用了正确的安全访问标志,我i们的程序就可以容易在不同的windows版本中移植
(苹果就认为开发人员的行为无法控制,所以强制将最佳实践固定化,不给你机会自己操作)
对于优秀程序员,这是一种侮辱和挑衅。但是大部分都是普通用户,这就没啥问题,毕竟产品与系统还是两回事;
可能只是因为开源是一种信仰,对于思想,应该可以自由获取
3:Handle 进程内核对象的handle Table
这里就更加具体化,是为了讨论进程对象的handle Table.
初始化:一个进程在初始化时候,系统会分配一个handle Table
限制:该Handle Table 仅仅供内核对象使用,用户或者GDI是不可以
内容:这一条死胡同!结构如何?如何管理Handle Table,暂时没有文档开源描述
一般情况下该Table 结构如下
索引 | 指针(内存块地址) | 访问掩码(一个DWORD) | 标志(地址的一个16进制值) |
使用:进程初始化时候,该表一定是空的;
注:当其中的一个线程调用一个创建Kernel Object 的函数时候,就会初始化该对象,
并且会在进程的handle Table 找一个空白位置,然后初始化记录:
指针会设置为对象的内部内存地址,访问掩码会设置为拥有完全访问权限,标志后续再说
例子:
handle CreateThread( PDWORD pdwThreadID ...)handle CreateFile( HANDLE hTemplateFile ...)handle CreateFileMapping( HANDLE hFile ...)handle CreateSemaphore( PSECURITY_ATTRIBUTES psa ...)
从上面来看,创建内核对象的任何函数都会返回一个与进程相关的handle
这个handle 可以由同一个进程中运行的所有线程来使用
注:系统使用索引来标识内核对象的信息保存在进程 Handle Table 的具体位置
想要获得实际的索引值,Handle 实际上来说需要除以4(右移两位)
所以实际值一般都是比较小,4,8啥的,但是这是推测,没有文档
创建内核对象失败后,返回的handle一般是0/null,但是也有INVALID_HANDLE_VALUE
所以这一块不统一,处理需要仔细、
4.关闭内核对象:
CloseHandle,
Bool CloseHandle(HANDLE hobject)
流程:
在内部先检查主调进程的 Handle Table
验证传递给函数的handle 标识的是 进程确实有权访问 的一个对象
如果有效,获得内核对象的内存地址,并且将Usage Count减一
如果无效,进程正常的话,则返回false,(getLastError,返回ERROR_INVALID_HANDLE)
如果进程在调试,则抛出 0x C0000008 异常;
此流程中,会清理掉Handle Table中该记录,所以这个handle一定就不可用了:
不关闭的话,也不一定会发生Memory Leak:
进程终止,操作系统会保证所有该进程使用的资源全部释放,扫描handle Table ,释放所有有效的记录
在进程还在运行,那么这些没有销毁的内存对象,可能会遗留;
如何在应用程序运行的时候检测内核对象遗留。
使用之前的小工具,结合windows任务管理器
5.共享内核对象
主要是跨越进程的共享
为何需要共享:这个思路应该在很多多线程 或者多进程的编程中类似思考
文件映射对象,可以在多个进程和之间共享同一个文件借助pipe ,mailslot可以在进程之间发送消息互斥量,信号量,事件允许在不同进程中的线程同步执行(也就是一个应用在完成某个任务后,就会向另一个应用发消息)
设计考虑:
将内核对象的handle 设计成 进程相关,最重要的原因就是健壮性 安全性
共享的方式:
使用对象句柄集成为对象命名复制对象句柄
第一种:
为了创建一个继承的句柄,
第一步,父进程必须分配并且初始化一个SECURITY_ATTRIBUTES结构
并且将这个地址传给具体的Create函数
SERCURITY_ATTRIBUTES sa;sa.nLength = sizeof(sa);sa.lpSecurityDescription = NULL;sa.hInherithandle = true;handle hMutex = CreateMutex(&sa,False,NULL);
从上面可以看出,就是其中一个hInherithandle 的布尔值设置
下一步:
使用CreateProcess
CreateProcess( PCTSTR pszApplicationName, PTSTR pszCommandLine, PSECURITY_ATTRIBUTES psaProcess, PSECURITY_ATTRIBUTES psaThread, BOOL bInheritHandle, DWORD dwCreationFlags, PVOID pvEnvironment, PCTSTR pszCurrentDirectory, LPSTARTUPINFO pStartupInfo, PROCESS_INFORMATION pProcessInfomation)
从上面可以知道就是 bInheritHandle
如果是TRUE,那么子进程就会继承父进程的值bInheritHandle
创建子进程的时候,会遍历父进程的Handle Table ,对于其中的每一个记录值都会检查,
凡是包含bInheritHandle的项,都会被完整的复制到子进程自己的Handle Table
在子进程的表中,复制项的位置与父进程的一模一样,这是一个非常重要的设计
在父进程和子进程中,对于一个内核对象进行标识的handle 是完全一样的。
这个时候同一个内核对象就会因为父子加2
所以关闭的时候,就要父子都要调用CloseHandle
这里和之前说的有点不一样。父进程关掉内核对象handle,子进程还是可以操纵这个对象
注:对象handle的继承只会在子进程的时候发生;如果事后父进程又创建了可继承的handle,也不会到子进程
注:子进程并不知道自己继承了啥handle
控制哪个子进程可以继承:
使用 SetHandleInfomation函数
Bool SethandleInfomation( handle hObject, dword dwMask, DWORD dwFlags)
第二种方法:
为对象(共享对象)命名
使用 PCTSTR pszName成员变量
大部分内核对象都可以,有一部分不可以
第三种方法:终端服务命名空间Terminal Service:
一个是全局命名空间
还有就是客户端自己的命名空间client session
作用:多个会话正在运行同一个应用程序,或多个远程桌面使用同一个,这样的安排可以避免彼此之间的干扰
注:当没有任何用户登录的时候
服务会在第一个会话,Session 0中启动
这个会话不会交互
第三种方法:复制对象handle
Bool DuplicateHandle( handle hSourceProcessHandle, handle hSOurceHandle, handle hTargetProcessHandle, phandle phTargetHandle, dword dwDesiredAccess, bool bInheritHandle, dword dwOptions)
流程就是获取一个进程的一个记录值,然后在另一个进程中的Handle Table中创建
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~