吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 857|回复: 10
收起左侧

[系统底层] win32笔记四 句柄表

  [复制链接]
huchen 发表于 2024-4-19 10:17
本帖最后由 huchen 于 2024-4-21 14:32 编辑

之前的索引

句柄表

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

[out]                                 LPPROCESS_INFORMATION lpProcessInformation // process information

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

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

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

image-20240416193354696.png

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

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

image-20240416194016962.png

这里列出的就是内核对象

如何管理内核对象

image-20240416195338853.png

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

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

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

通过表的方式

image-20240416201141332.png

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

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

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

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

代码演示

继续用上次的代码

//环境: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()函数要传一个线程句柄,下面的函数也是一样,具体的话请自行查资料

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

多线程共享一个内核对象

image-20240417110802810.png

如图,首先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()来执行

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 a  thread object, you must terminate the thread, then close all handles to the  thread.

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

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

实验

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

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

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

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

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

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

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

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

句柄是否可以被继承

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

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

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

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

再看看CreateFile

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()的定义

LPSECURITY_ATTRIBUTES lpEventAttributes

image-20240417192445712.png

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

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

nLength


存储当前结构体大小的值

lpSecurityDescriptor


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

bInheritHandle


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

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

CreateEvent(NULL,FALSE,FALSE,NULL)

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

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

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

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

image-20240417213135725.png

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

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

比如说,在创建进程时

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

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

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

image-20240417215144489.png

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

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

再看进程与线程

[in, optional]           LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD
[in, optional]           LPSECURITY_ATTRIBUTES lpThreadAttributes,  // SD

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

总结


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

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

免费评分

参与人数 5吾爱币 +10 热心值 +4 收起 理由
willJ + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
MJ_B + 1 热心回复!
为之奈何? + 1 + 1 我很赞同!
小朋友呢 + 1 热心回复!
lccccccc + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

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
认真学习
sws323 发表于 2024-4-24 08:09
慢慢学习。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-5-2 20:39

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表