huchen 发表于 2024-4-19 10:17

win32笔记四 句柄表

本帖最后由 huchen 于 2024-4-21 14:32 编辑

之前的索引
(https://www.52pojie.cn/thread-1914443-1-1.html)

# 句柄表

在上一章节中,我们用代码创建了一个进程,在CreateProcess函数中,最后一个参数

```c
                                 LPPROCESS_INFORMATION lpProcessInformation // process information
```

表示的是进程和线程相关的东西,进程句柄、线程句柄、进程ID、线程ID,要想了解其中的信息,要先学习学习下句柄表

在学句柄表之前,还要先认识一下什么是内核对象

像进程、线程、文件、互斥体、事件等在内核都有一个对应的结构体,这些结构体由内核负责管理。我们管这样的对象叫做内核对象



如图,这个就是内核对象,当然不止这么点,要想知道有哪些内核对象,可在MSDN里查,方法如下

在索引里搜索closehandle,这个是关闭句柄的意思,找到Remarks



这里列出的就是内核对象

## 如何管理内核对象



如图,假如有个进程,对应一个内核对象,即整个绿色的部分对应红色的内核对象,然后在这个进程里我又创建了四个内核对象,那么在内核中又产生了四个对应的结构体,了解了之后,这些怎么来管理呢,或者换句话说怎么使用呢?

以CreateProcess为例,在平常可能会想到不就是把内核里的结构体的地址返回给应用层,不就行了吗?

但是有个问题,如果应用层不小心把地址给改了,刚好又是内核的地址,是以0x80000000开始的地址,就会出现内存访问错误的问题,导致直接蓝屏,因为这是零环的内存。所以微软不会把内核层的地址直接暴露给应用层,那么怎么解决呢?

通过表的方式



如图右下角的表,这就是句柄表。

可以看到,这里只有一张句柄表,说明不是一个内核对象就有一张句柄表,而是一个进程就有一张句柄表

这张表就是存的结构体的地址,那应用层怎么用呢,就用编号,比如说,当应用层想用A,就返回1,想用B就返回2。

说白了,句柄表就是一种映射关系,用户层的索引就是内核层的地址

代码演示

继续用上次的代码

```c
//环境:win XP VC++ 6.0
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <windows.h>

//启动参数的相关信息
void GetStartInfo()
{
      STARTUPINFO si;
      GetStartupInfo(&si);
      printf("%X %X %X %X %X %X %X %X\n", si.dwX, si.dwY, si.dwXCountChars, si.dwYCountChars, si.dwFillAttribute, si.dwXSize, si.dwYSize, si.dwFlags);
}

BOOL CreateChildProcess(PTCHAR ChildProcessName, PTCHAR CommandLine)
{
      STARTUPINFO si;
      PROCESS_INFORMATION pi;

      ZeroMemory(&si, sizeof(si));
      ZeroMemory(&pi, sizeof(pi));
      si.cb = sizeof(si);

      //创建子进程 返回是否成功
      if (!CreateProcess(
                ChildProcessName,      //对象名称的完整路径
                CommandLine,                //命令行参数
                NULL,                              //不继承进程句柄
                NULL,                              //不继承线程句柄
                FALSE,                              //不继承句柄
                0,                                        //没有创建标志
                NULL,                              //使用父进程环境变量
                NULL,                              //使用父进程目录作为当前目录,可以自己设置目录
                &si,                              //STARTUPINFO结构体
                &pi)                              //PROCESS_INFORMATION结构体
                )
      {
                printf("创建进程错误 代码:%d\n", GetLastError());
                return FALSE;
      }
      //printf("进程句柄:%X\t进程ID:%X\n线程句柄:%X\t线程ID:%X\n", pi.hProcess,pi.dwProcessId, pi.hThread,pi.dwThreadId);
      SuspendThread(pi.hThread);      //挂起指定的线程(下断点)
      ResumeThread(pi.hThread);      //递减线程的挂起计数,当暂停计数减为零时,将恢复线程的执行

      //释放句柄
      CloseHandle(pi.hProcess);
      CloseHandle(pi.hThread);
      return TRUE;
}

int main(int argc,char* argv[])
{
      //C:\\Program Files\\Internet Explorer\\iexplore.exe
      //C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe
      TCHAR ApplicationName[] = TEXT("C:\\Program Files\\Internet Explorer\\iexplore.exe");
      TCHAR CmdLine[] = TEXT(" https://www.baidu.com");
      CreateChildProcess(ApplicationName, NULL);
      //GetStartInfo();

      //getchar();
      return 0;
}
```

如代码所示,执行会发现跟平常一样,可以操作,放大缩小等,当我们在单步执行一下,会发现不动了,点不开了等,想要恢复的话在执行一步

说明一下,SuspendThread()函数要传一个线程句柄,下面的函数也是一样,具体的话请自行查资料

我们了解了句柄的作用,和所谓的内核对象都在内核有一个结构在零环,还知道这个零环是所有进程共用的内存,既然这样,那意味着内核对象是可以跨进程共享的

## 多线程共享一个内核对象



如图,首先A进程有个内核对象,然后A进程里又创建一个内核对象即为A,B进程有个内核对象,如果B进程里是CreateProcess,那就跟A进程没半毛钱关系了,但是如果是OpenProcess函数的话,他的作用是打开一个别人创建好了的进程的内核对象,具体的参数可自行查阅资料了解

这样的话,AB两个进程共用了同一份的内核对象,那A进程怎么使用A呢,用句柄表,当然是A进程的,那B呢?用B进程的句柄表

由此可知,句柄表是一个私有的概念,仅对当前的相对应的进程有意义

A的句柄表的索引是1,而B的句柄表的索引是10,所以更能说明句柄表是私有的概念,如果都是1的索引的话,那B进程的原来的索引为1的对像,就冲突了,所以如果我把A句柄的值传给B,有意义吗?显然没有意义

接下来再说A对象里的2是什么意思,这个2代表一个计数器,比如,A进程创建了这个对象,那么这个计数器就+1,然后B进程又打开了这个对象,那么这个计数器在+1,如果再有人打开,就在+1,以此类推

如果这个句柄我不想用了,就可以关闭这个句柄,用CloseHandle()来执行

```c
CloseHandle(pi.hProcess);
```

看名字会觉得是直接把这个对象给关掉,实际上只是把这个计数器-1,比如说,我把A进程的句柄关闭了,计数器只是-1,还是1(原来是2),这个内核对象(注意我说的是内核对象)不会死掉,那怎么才死呢?

那我B进程在调用一个CloseHandle,此时这里的计数器变成了0,此时,这个内核对象变成了没有任何人指向的对象,那么就会死掉

说白了,只要计数器不为0,那这个内核对象就不会死掉

上面说的这些特征,适合于上面提到过的其他所有的内核对象,但是有一个例外,那就是线程

我们在回到MSDN里去看看

> Closing a thread handle does not terminate the associated thread. To remove athread object, you must terminate the thread, then close all handles to thethread.

翻译过来的意思是:关闭线程句柄不会终止相关联的线程。要移除线程对象,必须先终止线程,然后关闭所有指向该线程的句柄

什么意思呢,就是线程也是一个内核对象,所以在内核中也有一个结构体,必须要满足1.把线程关掉2.线程的内核对象的计数器为0,这两个条件必须同时成立,才能关掉线程的内核对象

### 实验

还是上面的代码,就不贴上去了

```c
SuspendThread(pi.hThread);      //挂起指定的线程(下断点)
ResumeThread(pi.hThread);      //递减线程的挂起计数,当暂停计数减为零时,将恢复线程的执行
```

这两个函数注释掉,执行,你会发现会打开一个网页,在回到代码里,发现

```c
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
```

我们已经执行了,却发现这个网页还能运作,就是因为里面的线程还没有死掉,所以能运作

这里CloseHandle(pi.hThread);只是让计数器-1,变成0了,但是线程没有关闭,所以线程还活着

换句话说,只要线程不死,那这个进程就不会死,进程的唯一线程死了,那这个进程也就死了

想让他死掉的话,点击关闭网页就可以了,线程死了,计数器清零,内核对象被销毁,噶了

## 句柄是否可以被继承

上面讲过了怎么识别是不是内核对象,还有个方法

以CreateEvent为例,要知道他是不是,在MSDN里看看

```c
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // SD
BOOL bManualReset,                     // reset type
BOOL bInitialState,                      // initial state
LPCTSTR lpName                           // object name
);

```

看到有LPSECURITY_ATTRIBUTES lpEventAttributes, // SD的,那就是内核对象

再看看CreateFile

```c
HANDLE CreateFile(
LPCTSTR lpFileName,                         // file name
DWORD dwDesiredAccess,                      // access mode
DWORD dwShareMode,                        // share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // SD
DWORD dwCreationDisposition,                // how to create
DWORD dwFlagsAndAttributes,               // file attributes
HANDLE hTemplateFile                        // handle to template file
);
```

一样的有

回到CreateEvent()的定义

```c
LPSECURITY_ATTRIBUTES lpEventAttributes
```



这里就只用知道这是个安全描述符(结构体指针),点击SECURITY_ATTRIBUTES (安全属性,也是个结构体指针),由于我的MSDN有点问题,就直接手写他的结构体

```c
typedef struct _SECURITY_ATTRIBUTES {
    DWORD nLength;
    LPVOID lpSecurityDescriptor;
    BOOL bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;
```

### nLength

*****

存储当前结构体大小的值

### lpSecurityDescriptor

******

指向安全描述符的指针,意思为不同用户被赋予了何种权限。在我们写代码的时候通常不用去特别关注他,他默认采用的安全设置就跟你的父进程是一样的

### bInheritHandle

*******

确定在创建新进程时是否继承返回的句柄。如果此字段设置为非零值,则新进程将继承句柄。如果为 0,则新进程不会继承句柄。

介绍完后,所以如果不继承的话那就

```C
CreateEvent(NULL,FALSE,FALSE,NULL)
```

(先不管其他参数的意思)

如果要继承,那就要创建SECURITY_ATTRIBUTES结构体

```c
SECURITY_ATTRIBUTES sa;
ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));//初始化操作
sa.nLength = sizeof(SECURITY_ATTRIBUTES);//结构体大小
sa.bInheritHandle = TRUE;//可以继承
CreateEvent(sa,FALSE,FALSE,NULL);
```

这样就能继承了,因此,为了彰显句柄表是否能够继承,那就应该还要在添加一个成员,就是能否被继承的标志



句柄表的0代表不继承,1代表继承

如果是父子进程的话,那么子进程是有机会直接继承过去的

比如说,在创建进程时

```c
if (!CreateProcess(
                ChildProcessName,      //对象名称的完整路径
                CommandLine,                //命令行参数
                NULL,                              //不继承进程句柄
                NULL,                              //不继承线程句柄
                FALSE,                              //不继承句柄
                0,                                        //没有创建标志
                NULL,                              //使用父进程环境变量
                NULL,                              //使用父进程目录作为当前目录,可以自己设置目录
                &si,                              //STARTUPINFO结构体
                &pi)                              //PROCESS_INFORMATION结构体
```

```
FALSE,                              //不继承句柄
```

这里我写的是FALSE,子进程是IE,父进程的句柄表跟子进程没有关系,也就是说子进程会有一个独立的句柄表,哪怕父进程里有可以的继承的,也没有任何关系

如果改成TRUE,那子进程就会得到父进程中允许继承的句柄表



这样子进程就可以直接使用父进程的句柄了

```c
NULL,                              //不继承进程句柄
NULL,                              //不继承线程句柄
```

再看进程与线程

```c
         LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD
         LPSECURITY_ATTRIBUTES lpThreadAttributes,// SD
```

如果那个需要继承,就把那个定义出来,写成TRUE

## 总结

*****

1. 每个进程对应一个内核对象
2. 有两种方式来共享内核

这里的知识有点绕,需要多多消化,查阅资料

Moinul 发表于 2024-4-19 13:39

厉害,学习了

starryskyhello 发表于 2024-4-19 20:42

厉害,学习到了

NINE09 发表于 2024-4-19 21:52


厉害,学习到了

z52pppjjj 发表于 2024-4-20 18:50

厉害,慢慢学习了

hjsen 发表于 2024-4-20 20:12

wjx1201 发表于 2024-4-20 22:20

这也太高级了叭

wdxdf 发表于 2024-4-21 08:27

学习了!!支持以下啊!!!

pai1233 发表于 2024-4-22 15:01

认真学习{:17_1062:}

sws323 发表于 2024-4-24 08:09

慢慢学习。
页: [1] 2
查看完整版本: win32笔记四 句柄表