• win32笔记-进程

    了解进程

    在win32中,要想把exe程序运行起来,就必须将程序加载到内存中,当程序被双击运行起来以后,这时才能被叫做进程。现在的计算机是多线程操作系统,可以在计算机的任务管理器看到当前的所有进程。

    • 进程,可以为当前程序提供所需的资源,如:数据、代码等等

    每一个进程都有自己的4GB的虚拟地址空间

    进程内存空间地址的划分

    分区 x86 32位windows
    空指针赋值区 0x00000000 - 0x0000FFFF
    用户模式区 0x00010000 - 0x7FFEFFFF
    64KB禁用区 0x7FFF0000 - 0x7FFFFFFF
    内核 0x80000000 - 0xFFFFFFFF
    • 在空指针赋值区和64KB禁用区,可以简单的理解为从来没有被使用的区域,包括操作系统和应用软件都不会使用它。

    进程的创建

    任何进程都是别的进程创建的,而第一个进程起源于操作系统,比如双击运行一个exe文件,进程explorer.exe通过调用CreateProcess()创建了一个新的进程

    进程创建过程

    • 映射EXE文件
    • 创建内核对象EPROCESS
    • 映射系统DLL(ntdll.dll)
    • 创建线程内核对象ETHREAD
    • 系统启动线程(映射DLL(ntdll.LdrlnitializeThunk),线程开始执行)

    image-20220403151422129

    一个有趣的事情就是如果运行程序A.exe,但是等他在系统启动线程时,用B程序里面的数据和代码在进程里面替换A程序的数据和代码,然后你会惊奇的发现,B程序成功的跑起来了,但是在任务管理器界面只能看到A程序的进程,而看不到B程序的进程,实际上,A程序的进程就是替换后的B程序的进程,这非常好玩。试想一下,如果将B程序换成病毒,A程序伪装成系统程序(如service.exe),那么就无法手动结束A程序,因为其受到系统的保护,实际运行的是病毒程序,并且可能还判断不出A程序就是病毒换了的程序。这就是狸猫换太子。

    另一个就是游戏外挂的运行原理,就是系统启动线程之前,注入自己的外挂dll,但是游戏肯定会不有一个xx.dll用来检测,防止dll注入,事实上,只要在这个xx.dll检测文件映射之前,先行注入外挂dll,就可以达到防止检测注入的目的,归根到底,xx.dll用来检测注入,其实其本身也是代码和数据。

    win32笔记-创建进程

    前言

    之前已经学习过什么是进程,知道一个进程是别的程序创建出来的,那么我们自己如何创建一个进程呢?下面就用代码来详细演示。

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    #include <windows.h>
    #include <stdio.h>
    void createp(LPCWSTR appname,LPWSTR pram)
    {
    //创建结构体
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    //初始化,这里就是置零
    ZeroMemory(&pi, sizeof(pi));
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);//对si结构体的大小赋值
    if (!CreateProcess(
    appname, //对象名称
    pram, //传入参数
    NULL,
    NULL,
    FALSE,
    0,
    NULL,
    NULL,
    &si, //传入类型
    &pi //传出线程和进程信息
    ))
    {
    printf("fail:%d",GetLastError());
    }
    else {
    printf("success");
    }
    printf("%s",pram);
    }


    int main() {
    wchar_t sr[64] = L"a http://kuangtant.gitee.io";
    createp(L"C:\\Users\\admin\\AppData\\Local\\Google\\Chrome\\Application\\chrome.exe", (LPWSTR)L"a http://kuangtant.gitee.io");

    return 0;
    }

    实例代码演示创建了一个chrome.exe的进程,浏览器一般传入参数是网站网址。

    win32笔记-句柄

    什么是内核对象

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

    image-20220404182630799

    比如,当应用层创建创建进程时,相应在内核层会有一个EPROCESS的结构体与之对应,线程、文件、互斥体、事件等也是同理。

    如何管理内核对象

    如果,现在有一个进程,在内核层一定有一个与之对应的E-PROCESS结构体内核对象。现在,分别依次调用createProcess,CreateThread,createEvent,createFile等函数,在内核里面肯定会有与之相对应的结构体

    image-20220404184459269

    为了保证内核结构和系统的安全性,系统不会直接提供内核结构的地址给用户层直接操作。但是,为了操作,系统会提供一张表,使得内核结构和一个唯一的编号对应。每一个进程都有一个句柄表。

    image-20220404185521963

    这个编号,也就是句柄,通过对句柄的操作间接对内核结构操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    #include <windows.h>
    #include <stdio.h>
    void createp(LPCWSTR appname,LPWSTR pram)
    {
    //创建结构体
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    //初始化,这里就是置零
    ZeroMemory(&pi, sizeof(pi));
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);//对si的大小赋值
    if (!CreateProcess(
    appname, //对象名称
    pram, //传入参数
    NULL,
    NULL,
    FALSE,
    0,
    NULL,
    NULL,
    &si, //传入类型
    &pi //传出线程和进程信息
    ))
    {
    printf("fail:%d",GetLastError());
    }
    else {
    printf("success");
    }
    //获取进程信息
    SuspendThread(pi.hThread);
    ResumeThread(pi.hThread);
    //释放句柄
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    }

    多进程共享一个内核对象

    前面,知道了CreateProcess可以创建一个进程,如果这个行为在A进程里面发生的,也就是说,在A进程里面调用了CreateProcess创建了一个A的子进程,那么,这个子进程能否被另一个进程B共享呢?答案是肯定的。

    image-20220405152243603

    如果,在B进程里面也CreateProcess,也会创建一个进程,当然,与A的子进程没有一点关系,因为这是它自己创建的进程;要想有关系,就必须通过OpenProcess,这样,就有了关系。但需要注意的是,这时,A、B进程都有一个句柄表,都有一个句柄同时指向A创建的子进程A,不过,这两个编号不同,这两个句柄并不相同,只是指向结果一样,能够同时操作同一个内核对象。

    其次,这个子进程的内核对象自己有一个计数器,这个计数器是用来记录当前一共有多少进程对其进行掌控,操作。如上图,当A进程CreateProcess创建了一个进程的内核对象,这个计数器会记录1,当B进程OpenProcess,这个计数器会变成2,依次类推。这样做的意义,就得当A进程CloseHandle,计数器会减1,但是这个进程不会死亡,当B进程也CloseHandle,计数器变为0,没有进程把控对应它,这时,才会死亡。所有内核对象都一样,但是有一个内核对象除外,就是线程。

    每一个进程都有一个线程,进程是靠线程支持的,如果仅仅释放进程句柄,释放线程句柄,计时器为0了,但达不到使得进程死亡的目的,因为线程还没有被终止。

    句柄是否可以被继承

    简单来说,是可以的。前提是,父进程调用createProcess,CreateThread,createEvent,createFile等函数必须将将其是否可以被继承关系描述为允许继承,那么,当父进程创建一个子进程时,这个子进程,就会继承得到父进程允许继承的句柄。

    上一篇:
    简单使用gdb
    下一篇:
    Win32笔记-基础
    本文目录
    本文目录