从内核看进程与线程创建之间的关系

holic   ·   发表于 2020-10-30 09:17:39   ·   技术文章

什么是进程?

进程提供程序所需要的资源,如:数据、代码等等
进程扮演的角色仅仅是为当前程序提供资源,或者代码,这就是进程所提供的,当时程序运行的状态和进程没有关系,进程可以看做空间的概念

例子:
进程相当于一个房子,房子里面的东西,这些东西就是进程提供的;房子里面走来走去的人,和使用东西的人,就是线程

我们打开od,可以看到的这些东西就是进程

找到最后发现到 0x7FFEF000结束(这里是个小Tips了)

一、内核空间分布:
  在4GB内存的操作系统中,高2G的给内存空间操作系统(也就是内核)使用,这部分内存空间所有进程共享。
  低2G的内存给各个进程使用,每个进程占有独立的内存空间,相互进程其内存之间并不影响。

所谓的进程不是一个文件组成,而是多个文件组成

0x01.进程的创建过程

1)、任何进程都是别的进程创建的

系统中的程序都是通过explorer.exe创建的,这个程序调用了CreateProcess()函数

2)、进程的创建过程

  1. 1、映射EXE文件
  2. 2、创建内核对象`EPROCESS`
  3. 3、映射系统DLLntdll.dll
  4. 4、创建线程内核对象`ETHREAD`
  5. 5、系统启动线程
  6. 映射DLLntdll.LdrlnitializeThunk
  7. 线程开始执行

举个例子,我们点击 A.exe 就会调用 CreateProcess() 函数

第一步:映射EXE文件
通过上面的图片知道我们程序映射的话不能放前64k,也不能在后64k,但是其实PE中就有写到需要放到哪里

假设从这开始,然后我们画图,写入进程A

第二步:创建内核对象 EPROCESS

每个对象都有自己的 EPROCESS 进程对象,也就是结构体
(无需深究)

第三步:映射系统DLL(ntdll.dll)

也就是说,不管什么进程创建的时候,都会有个 ntdll.dll,然后继续把ntdll映射进去

第四步:创建线程内核对象 ETHREAD

每个进程中都会自动创建一个线程,跟进程一样,因为一个进程就得有一个线程,启动一个进程就有EPROCESS 结构体
然后进程自动创建一个线程对象 ETHREAD

第五步:系统启动线程

每一个进程都是由一堆PE来的,比如一个EXE带了一堆DLL,然而dll不是随便乱带的
因为PE创建中可能只使用到了 test.dll中的某个函数,但是 test.dll 可能又带了 love.dll中的东西

这时候发现进程只是一个空间的概念,真正用的人是线程

句柄表有什么用?

因为创建进程的时候提到了PROCESS_INFORMATION这个结构体
这个结构体里面存在两个值,一个是句柄一个是id
什么是句柄?什么是id?就得深入学习句柄表了

0x01. 什么是内核对象?

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

0x02. 如何管理内核对象

比如说我们创建了一个进程,如何内核层,也就是0环那边给了 EPROCESS 结构体
然后我们在这个进程内 再创建 进程,线程,事件,文件,然后又分别分配了各自的内核结构

0x03. 每个进程都有一个句柄表

在0环中有个EPROCESS结构体,结构体里面有个指针,指向了句柄表,我们创建了之后会在EPROCESS结构体中的句柄表写入地址
然后我们想用A的时候,就输入1,我们想用B的话就输入2,这就是句柄

注意:不是每个内核对象都有表的,只有内核对象有句柄表,简单的说,只有EPROCESS这个才有

0x04.多进程共享一个内核对象


A进程通过CreateProcess创建了一个进程,B进程通过OpenProcess打开别人创建好的进程
两个进程用的是同一个内核对象,A进程中有个句柄表,然后A内核对象索引为 1,就可以索引使用,C进程也是通过句柄表的值

如果再有创建,那么A内核对象计数器会 加 1;CloseHandle其实就是减计数器,当计数器为0的时候就减小了

注意:句柄表是私有的

0x05. 句柄是否“可以”被继承

微软除了使用上面那个方式共享内核对象,还可以使用另外一种方式进行共享

实际上句柄表有三列,而这还有一列是代表着是否可以被继承

从这边可以知道子进程可以继承父进程的句柄表,但并不是句柄表全部都能继承,而是允许继承的才会继承
这边就可以知道 索引为 2和4 的可以被继承,当不被继承的时候,就会在子进程填0

什么是线程?

什么是线程?

1、线程是附属在进程上的执行实体,是代码的执行流程
进程 本身是空间上的概念,代表4GB的虚拟内存,线程代表着时间概念,也就是说,线程是当前运行的代码

在某个时间点只能有一段代码执行,但是cpu切换的快,所以看着像同时运行,这是单核的,所以单核的情况下并不是真正的多线程
因为线程执行的话是需要一套寄存器的,比如eax,ecx,ebx…

2、一个进程可以保护多个线程,但一个进程至少要包含一个线程


我们现在想创建一个线程了,其实main函数这边就是一个线程了,但是我们需要再创建一个呢?
需要使用到新的api,CreateThread

0x02. 创建线程

  1. CreateThread(
  2. LPSECURITY_ATTRIBUTES lpThreadAttributes, //安全描述符,判断子进程是否可以继续父进程的句柄表,NULL就可以了
  3. IZE_T dwStackSize, //初始堆栈,如果不填写就会自动填写默认的
  4. LPTHREAD_START_ROUTINE lpStartAddress, //当前的线程要执行的代码
  5. __drv_aliasesMem LPVOID lpParameter, //要创建的线程的参数,这是个指针
  6. DWORD dwCreationFlags, //创建线程的标识
  7. LPDWORD lpThreadId //这个是OUT类型参数,这个参数说明是往外传递结果的,这个就是返回创建的线程ID,返回值是当前线程的句柄
  8. );

dwCreationFlags标识:

由于是返回值是句柄,所以我们要声明一个句柄变量进行接收,也可以不接收
CloseHandle()不想用的话可以用这个函数关闭,但是这里的关闭其实是减掉一个计数器
线程的内核对象计数器为0也不会关闭,关闭线程的两个必要条件
①、线程的内核对象计数器为 0
②、线程的执行代码执行完毕了

  1. #include <stdio.h>
  2. #include <windows.h>
  3. int main()
  4. {
  5. HANDLE hThread = CreateThread(NULL, 0, 0, NULL, 0, NULL);
  6. CloseHandle(hThread);
  7. for (int i = 0; i < 100; i++)
  8. {
  9. Sleep(500);
  10. printf("------- %d --------\n", i);
  11. }
  12. getchar();
  13. return 0;
  14. }

所以我们这边CloseHandle掉线程也是没有影响的,并不会影响到线程,因为当前代码没执行完后是没有人能关掉的

然后看向CreateThread的第三个参数:lpStartAddress
而这个参数是要执行的代码,然而这个代码是不能随便乱写的,是有格式的

  1. #include <stdio.h>
  2. #include <windows.h>
  3. DWORD WINAPI ThreadProc(LPVOID lpParameter)
  4. {
  5. return 0;
  6. }
  7. int main()
  8. {
  9. HANDLE hThread = CreateThread(NULL, 0, 0, NULL, 0, NULL);
  10. CloseHandle(hThread);
  11. for (int i = 0; i < 100; i++)
  12. {
  13. Sleep(500);
  14. printf("------- %d --------\n", i);
  15. }
  16. getchar();
  17. return 0;
  18. }

然后我们在这个ThreadProc函数中写上代码,我们依然写入for循环来

然后基本就类似是同时运行的,所以以后每创建一个线程就必须要使用 CreateThread函数来
这个函数就是告诉代码在哪里,返回值啥的就需要强转就可以了

这时候我们想指定线程循环的次数呢?

  1. DWORD WINAPI ThreadProc(LPVOID lpParameter)
  2. {
  3. int* p = (int*)lpParameter;
  4. for (int i = 0; i < *p; i++)
  5. {
  6. Sleep(500);
  7. printf("++++++++++++++ %d \n", i);
  8. }
  9. return 0;
  10. }

在ThreadProc() 函数内小改动一下,main函数中指定一下要几次

  1. int main()
  2. {
  3. int n;
  4. n = 10;
  5. HANDLE hThread = CreateThread(NULL, 0, ThreadProc, (LPVOID)&n, 0, NULL);
  6. CloseHandle(hThread);
  7. for (int i = 0; i < 100; i++)
  8. {
  9. Sleep(500);
  10. printf("------- %d \n", i);
  11. }
  12. getchar();
  13. return 0;
  14. }

线程参数可以传任何参数,传进去后转型一下就好了,然后运行一下,发现成功了

总结

因为昨天看到有人对这个进程创建有点迷茫,大家学到的东西都是基于有用的情况下,而不是扯到什么硬件(我也有点懵)

学python创建进程等等 都是这个意思,希望能帮助各位大表哥

用户名金币积分时间理由
veek 150.00 0 2020-10-30 10:10:03 一个受益终生的帖子~~

打赏我,让我更有动力~

0 条回复   |  直到 2020-10-30 | 4076 次浏览
登录后才可发表内容
返回顶部 投诉反馈

© 2016 - 2024 掌控者 All Rights Reserved.