一、内核对象简介
1.1 内核对象有哪些
令牌对象 token、事件对象 Event、文件对象 File、文件映射对象 Mapping_File、线程对象 Thread、时钟对象 Timer、线程池对象 ThreadPool、I/O完成端口对象 Completion port、工作对象 job、邮槽对象 mailslot、互斥对象 Mutex、管道对象 pipe、进程对象 process、信号灯对象 semaphore
1.2 内核对象简介
每个进程创建的内核对象是一个索引,通过这个索引会在每个进程的内核对象表中找到这个内核对象的内存块,同一个内核对象在不同进程中的句柄是不一样的
内核对象用一个句柄来标识。
内核对象的内存块位于操作系统的内核空间,应用程序不能直接操作内核对象,需要系统给定的函数来操作
内核对象的结构:公用部分(安全描述符、计数)和个性部分
1.3 内核对象计数的作用
如果一个内核对象能被多个进程共用。每当一个进程使用这个内核对象的时候,内核对象计数值就加1;每当有一个进程不使用这个内核对象的时候,内核对象计数值就减1。当计数值为0的时候,系统会释放这个内核对象资源
1.4 区分内核对象和用户对象
区分应用层对象和内核对象是看创建对象的函数,如果创建对象的函数有安全描述符,那么这个函数创建的对象就是内核对象
内核对象:
HANDLE CreateFileMapping(
HANDLE hFile, //物理文件句柄
LPSECURITY_ATTRIBUTES lpAttributes, //安全设置
DWORD flProtect, //保护设置
DWORD dwMaximumSizeHigh, //高位文件大小
DWORD dwMaximumSizeLow, //低位文件大小
LPCTSTR lpName //共享内存名称
);
应用层对象:
BOOL CreateBitmap(
int nWidth, // 指定位图的宽度(以像素为单位)
int nHeight, // 指定位图的高度(以像素为单位)
UINT nPlanes, // 指定位图中的颜色平面的数量
UINT nBitcount, // 指定每个显示像素的颜色位的数量
const void* lpBits // 指向包含的出师未土值的字节的数组。如果为NULL,则不初始化新图
);
1.5 继承选项的含义
创建进程内核
BOOL CreateProcess(
LPCTSTR lpApplicationName, // 应用程序名称
LPTSTR lpCommandLine, // 命令行字符串
LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程的安全属性
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性
BOOL bInheritHandles, // 是否继承父进程的属性
DWORD dwCreationFlags, // 创建标志
LPVOID lpEnvironment, // 指向新的环境块的指针
LPCTSTR lpCurrentDirectory, // 指向当前目录名的指针
LPSTARTUPINFO lpStartupInfo, // 传递给新进程的信息
LPPROCESS_INFORMATION lpProcessInformation // 新进程返回的信息
);
如果上面创建进程内核函数中的继承选项传入了TRUE,父进程在创建子进程的时候会把内核对象继承给他。也就是子进程也有父进程的内核对象
二、内核对象操作
2.1 关闭内核对象
CloseHandle(HANDLE);
2.2 内核对象信息获取与修改
1、修改内核对象信息
BOOL SetHandleInformation(
[in] HANDLE hObject,
[in] DWORD dwMask,
[in] DWORD dwFlags
);
[in] dwMask
一个掩码,指定要更改的位标志。 使用 dwFlags 说明中显示的相同常量。
[in] dwFlags
指定对象句柄的属性的位标志集。 此参数可以是 0 或以下一个或多个值。
值 | 含义 |
---|---|
HANDLE_FLAG_INHERIT 0x00000001 | 如果设置了此标志,则使用 CreateProcess 设置为 TRUE 的 bInheritHandles 参数创建的子进程将继承对象句柄。 |
HANDLE_FLAG_PROTECT_FROM_CLOSE 0x00000002 | 如果设置了此标志,则调用 CloseHandle 函数不会关闭对象句柄。 |
2、获取内核信息
BOOL GetHandleInformation(
[in] HANDLE hObject,
[out] LPDWORD lpdwFlags
);
函数的使用
GetHandleInformation(handle, &dw);
if (dw & HANDLE_FLAG_INHERIT)
{
该对象可以被继承
}
三、防止多开的程序
3.1 命名内核对象
命名内核对象主要是为了进程间共享内核对象。创建内核对象的函数中,如果有pszName参数,说明这个内核对象可以被命名,也就是可以创建命名内核对象。命名内核对象可以在不同进程中使用
3.2 用互斥的内核对象实现防止多开程序
#include <Windows.h>
#include <stdio.h>
#include <fcntl.h>
#include <Windows.h>
#include <tchar.h>
int _tmain()
{
HANDLE hMutex = CreateMutex(NULL, FALSE, TEXT("huan"));
// 创建了一个名字叫huan的互斥内核对象
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
_tprintf(TEXT("Can't open this program.\n"));
_gettchar();
return 0;
}
DWORD sessionid = 0;
ProcessIdToSessionId(GetProcessId(NULL), &sessionid);
_tprintf(TEXT("%s,\n"), TEXT("this is the first instance! and session id is"));
_gettchar();
return 0;
}
3.3 破解防止软件多开
方法1:反汇编,修改判定 if (GetLastError() == ERROR_ALREADY_EXISTS) 这个判定条件,让这个条件不成立
方法2:使用Process explorer(进程资源管理器)删除内核
官方下载链接:进程资源管理器 - Sysinternals | Microsoft Learn
1、以管理员权限打开软件
2、显示进程内核对象
3、关闭内核对象句柄
注意:我们编写的软件可以轻松列举其他进程用到的所有内核对象,但是想要实现杀死内核对象很困难,首先要注入到所有用到内核对象的程序,然后关闭内核对象,最终杀死内核对象。而微软内部员工有源码,所以编写上面的程序相比比较轻松
四、session会话
4.1 简介
windows还没人登录的时候就会创建一个session0会话,windows服务程序在session0中。每当有一个用户登录就会创建一个session
4.2 查看当前程序运行在哪个session中
获取当前进程句柄,根据句柄获取当前进程id,根据id获取程序运行在哪个会话中,获取成功返回非0
#include <Windows.h>
#include <stdio.h>
#include <fcntl.h>
#include <Windows.h>
#include <tchar.h>
int _tmain()
{
HANDLE hMutex = CreateMutex(NULL, FALSE, TEXT("huan")); // 创建了一个名字叫huan的互斥内核对象
if (GetLastError() == ERROR_ALREADY_EXISTS)
{
_tprintf(TEXT("Can't open this program.\n"));
_gettchar();
return 0;
}
DWORD sessionid = 0;
DWORD processid = 0;
processid = GetProcessId(GetCurrentProcess());
ProcessIdToSessionId(processid, &sessionid);
if (ProcessIdToSessionId(processid, &sessionid)) // 获取当前程序运行在哪个session会话中
_tprintf(TEXT("%s, %d\n"), TEXT("this is the first instance! and session id is"), sessionid);
_gettchar();
return 0;
}
4.2 会话和命名空间
第1部分 全局命名空间
一个远程桌面的服务可以为自己的命名内核对象设置多个命名空间,这些内核对象包括:event、sernaphores、waitable timers、file-mapping、job object。在client/server类型的应用程序中,服务都有一个全局命名空间global namespace。除此之外,每个客户会话都可以有自己单独的命名空间,来防止自己独有的内核对象。
独立的客户会话命名空间能够让多个客户运行同一个应用程序而不互相干扰。在一个话中使用使用会话名称作为内核对象的缺省命名空间, 另外,进程还可以使用全局命名空间,使用的方法是在内核对象的名字前,加上"Global\" 前缀
通俗说就是程序命名前面不加global前缀,其他的会话就访问不到你会话中的内核对象,全局命名空间内核对象创建方法如下:
CreateEvent(NULL, FALSE, FALSE, "Global\\CSAPP");
第2部分 本地命名空间
如果要在本会话下创建一个命名对象,可以内核对象前加上“Local\”前缀,注意关键字是大小写敏感的。"Session\"前缀被系统保留,不要使用这个前缀来创建命名内核对象
第3部分 私有命名空间