吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2360|回复: 5
收起左侧

[系统底层] win32笔记五 进程相关API

  [复制链接]
huchen 发表于 2024-4-22 00:22
之前的索引

进程相关API

在前面的学习中,我们知道了,成功创建了一个进程,CreateProcess就会返回来四个数据,分别是:进程句柄、线程句柄、进程ID、线程ID

相信大家对句柄都有了深刻的理解,这次就来聊聊进程ID,也叫PID

还是以前的代码

//环境:虚拟机 win xp VC++ 6
#include <stdio.h>
#include <windows.h>

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);

    //释放句柄
    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);
    getchar();
    return 0;
}

运行后会直接打开一个网页,然后看输出的PID

image-20240417234437206.png

得到PID:0xEC4,换算成十进制为:3780,然后看看资源管理器,有没有3780的进程

image-20240417234605023.png

可以看到都一一对应上了,从这里就能感受到进程句柄与PID的略微差异

然后我们继续

回想句柄,句柄是每个进程私有的一张表,里面存储的所有创建的或打开的内核对象,除了每个进程以外,操作系统也有一张表,操作系统的这张表就不叫私有句柄表,而是全局句柄表,整个操作系统一份

那这个全局句柄表包含了啥呢?

下面画了个粗略的图,别的先不管,全局句柄表包含了所有正在运行中的进程和线程,这个表里结构跟私有表的结构没有多大的区别

image-20240417230825006.png

那为什么又要有PID的概念,和进程句柄的概念呢?

进程句柄是当前进程的索引,而PID就是全局的索引,意味着,你把进程句柄拿到其他地方没有任何意义,而PID不一样,他是全局的,拿到其他的进程,他仍然有意义。这就是两者最大的区别

进程ID和线程ID都是全局的索引,并且唯一

你绝对不可能在管理器中同时看见相同的进程ID或线程ID,但是唯一不代表不变,比如在某一时刻,一个进程死了,但又有新的进程进来,操作系统就有可能把这个编号给新的进程

接下来代码论证

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <windows.h>

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[])
{

    HANDLE hProcess;
    hProcess = (HANDLE)0x7D0;
    //hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 0xB94);
    if (!TerminateProcess(hProcess, 1))
    {
        printf("终止进程失败:%d", GetLastError());
    }
    /*
    //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;
}

代码解释

HANDLE hProcess;
    hProcess = (HANDLE)0x7D0;
    //hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, 0xB94);
    if (!TerminateProcess(hProcess, 1))
    {
        printf("终止进程失败:%d", GetLastError());
    }

首先定义一个空句柄,然后在获得原来IE进程的句柄和ID,我这里是

进程句柄:0x7DO          PID:0xB94

(注意:运行出来的网页不要关哦)

TerminateProcess

然后是TerminateProcess函数,看看定义

Terminates the specified process and all of its threads.

意为终止指定的进程

BOOL TerminateProcess(
  HANDLE hProcess,
  UINT   uExitCode
);

这里有两个参数,第一个参数知道了,第二个参数的意思是,这个进程以什么原因退出的

If the function fails, the return value is zero. To get extended error information, call GetLastError.

退出的话,就可以用GetLastError来获取错误,这错误随便给个值,123都可以,你自己定

然后运行,会得到

image-20240418004023582.png

去查,说的是句柄无效,当然无效啊,我传的进程句柄,这是个私有的概念,拿到别的进程,注定失败,网页还生龙活虎着呢

然后注释掉当前代码,执行下一行代码,遇到了一个新API

OpenProcess

然后是OpenProcess,嘿嘿,老熟人了,这次具体来看看

HANDLE OpenProcess(
  DWORD dwDesiredAccess,
  BOOL  bInheritHandle,
  DWORD dwProcessId
);
dwDesiredAccess

你打开这个进程你希望拥有什么权力

image-20240418003726580.png

这些就是权力,这里我们就选拥有所有权利,就选第一个

bInheritHandle

是否被继承,不多说,是个熟人

dwProcessId

PID

开始运行,会发现,网页直接关闭了,所以验证了两者的区别

以挂起的形式创建进程

在说之前,还要介绍CreateProcess里的函数

[in]                  DWORD dwCreationFlags,                     // creation flags

第六个参数,不知道就查文档,即便是不认识的英语,那就去翻译,沉下心来

dwCreationFlags

The flags that control the priority class and the creation of the process. For a list of values, see Process Creation Flags.

意思为:控制优先级类别和进程创建的标志。有关数值列表,请参阅“进程创建标志”。

那我们再去看看Process Creation Flags

image-20240418181336366.png

这里的标志有点多,就不截完了,感兴趣的可自行去搜索

此标志的意思为创建新的控制台

来看实验

首先需要看一下实验程序

image-20240418181813628.png

他会读取当前的工作路径(后面会解释),并输出他的路径

代码如下

#include<stdio.h>
#include<direct.h>

int main()
{
    char path[100];
    getcwd(path,100);
    printf("%s\\A.exe",path);
    getchar();
    return 0;
}

(说明一下,刚开始没有弄懂这玩意,我又懒得重新截图了,各位就当他打印出12345一样,不用管输出的结果,这个不影响后面的内容,望各位大佬谅解)

然后我们在把路径写入以前的代码中

//环境:win10 VS2022
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <windows.h>

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", pi.hProcess,pi.dwProcessId);

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

int main(int argc,char* argv[])
{
    TCHAR ApplicationName[] = TEXT("C:\\c test\\A.exe");
    TCHAR CmdLine[] = TEXT("1 https://www.baidu.com");
    CreateChildProcess(ApplicationName, NULL);
    //GetStartInfo();

    getchar();
    return 0;
}
0,                  //没有创建标志

这里我设置为空,先看看效果

image-20240418182250857.png

可以看到这两个输出都在同一个控制台,如果我想把这两个分开,那么这个参数就要添上我刚刚说的那个标志

然后运行

image-20240418182459798.png

看到用了两个控制台

接下来是这个函数最重要的参数(我认为的,嘿嘿),就是

image-20240418212715582.png

以挂起的状态来起一个新的进程

还记得以前说的进程的创建过程吗,如果要以挂起的方式创建,那么这个流程就要发生变化了

创建后,系统不会启动线程,而是等着你去启动

所以流程图变为:

  1. 映射exe文件

  2. 创建内核对象EPROCESS

  3. 映射系统DLL(ntdll.dll)

  4. 创建线程内核对象ETHREAD

  5. 如果以挂起的方式创建进程

    …………………………

  6. 恢复后执行

    • 映射DLL(ntdll.LdrlnitializeThunk)
    • 线程开始执行

所以,在这恢复之前的地方就可以加些你想加的东西(嘿嘿嘿)

代码演示

//环境:win10 VS2022
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <windows.h>

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,              //不继承句柄
        CREATE_SUSPENDED,   //以挂起的方式创建
        NULL,               //使用父进程环境变量
        NULL,               //使用父进程目录作为当前目录,可以自己设置目录
        &si,                //STARTUPINFO结构体
        &pi)                //PROCESS_INFORMATION结构体
        )
    {
        printf("创建进程错误 代码:%d\n", GetLastError());
        return FALSE;
    }
    printf("进程句柄:%X\t进程ID:%X\n", pi.hProcess,pi.dwProcessId);

    for (int i = 0; i < 10; i++)
{
    Sleep(1000);
    printf("*************\n");
}
ResumeThread(pi.hThread);   //恢复执行

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

int main(int argc,char* argv[])
{
    TCHAR ApplicationName[] = TEXT("C:\\c test\\A.exe");
    TCHAR CmdLine[] = TEXT("1 https://www.baidu.com");
    CreateChildProcess(ApplicationName, NULL);
    //GetStartInfo();

    getchar();
    return 0;
}

运行结果

image-20240418214401262.png

会发现多了许多原本没有的东西,就是因为在恢复之前,添加了代码,然后再让他继续跑

模块路径与工作路径

容我吐槽一下,这么简单的东西,我竟然搞了半天,啊,心态炸了


在开始之前我要先介绍两位新API

GetModuleFileName

DWORD GetModuleFileName(
  HMODULE hModule,    // handle to module
  LPTSTR lpFilename,  // file name of module
  DWORD nSize         // size of buffer
);

大概意思就是获取当前运行的exe文件的路径,具体参数就自行搜索吧文档吧

GetCurrentDirectory

DWORD GetCurrentDirectory(
  DWORD nBufferLength,  // size of directory buffer
  LPTSTR lpBuffer       // directory buffer
);

大概意思就是获取这个exe文件的工作路径,那啥是工作路径?可以简单的理解为,谁创建这个进程的人添的,也就是他的父进程给他添的,比如说,我先运行一下

代码

#include<stdio.h>
#include<windows.h>

int main()
{
    CHAR fileModule[256];
    GetModuleFileNameA(NULL, fileModule, 256);//模块路径
    CHAR fileWork[1000];
    GetCurrentDirectoryA(1000, fileWork);//工作路径
    printf("模块路径:%s\n工作路径:%s\n", fileModule, fileWork);
    getchar();
    return 0;
}
//说明:我的VS2022是以宽字符为格式的,所以这里用ASCII编码模式的API,所以加了个A,

image-20240418234839418.png

得到这样的,模块路径没什么好解释的,工作路径,是VS2022这个进程帮我填的所以是这个路径,那我把这个exe文件放到桌面上

image-20240418235021789.png

这个父进程就是explore这个进程添的。

注意这个工作路径是可以改的

在CreateProcess中

[in, optional]    LPCTSTR lpCurrentDirectory,                // current directory name

这个参数就可以设置工作路径

用以前的代码来打开输出工作路径的exe文件

就不贴全代码了,只用改里面的参数和文件路径就行了

TCHAR Direc[] = TEXT("C:\\");
//创建子进程 返回是否成功
if (!CreateProcess(
    ChildProcessName,   //对象名称的完整路径
    CommandLine,        //命令行参数
    NULL,               //不继承进程句柄
    NULL,               //不继承线程句柄
    FALSE,              //不继承句柄
    0,                  //没有创建标志
    NULL,               //使用父进程环境变量
    Direc,              //使用父进程目录作为当前目录,可以自己设置目录
    &si,                //STARTUPINFO结构体
    &pi)                //PROCESS_INFORMATION结构体
    )

由于我的VS2022是宽字符格式的,所以难免要类型转换的,在win xp就不用了,可以直接在参数位置写上"C:\"

运行结果

image-20240419000354043.png

可以看到更改成功

这个参数不设置的就默认跟他的父进程一样

那这个有什么用呢?

在以前,我们写文件操作类代码基本上都是

FILE* fp = fopen("A.exe","r");

相信大家对这个代码不陌生,通常情况下,我们会写文件的绝对路径,但如果不写呢?,那他就是找的工作路径

实验

FILE* fp = fopen("A.exe", "r");
if (fp)
{
    printf("成功\n");
}
else
{
    printf("失败\n");
}

首先这个没有在工作路径

image-20240419001804751.png

运行一下,失败

image-20240419001850502.png

那把文件放入到工作路径

image-20240419001950722.png

在运行,成功

image-20240419002015599.png

其他进程相关API

获取进程PID:GetCurrentProcessId

获取进程句柄:GetCurrentProcess

获取命令行:GetCommandLine

获取启动信息:GetStartupInfo

遍历进程ID:EnumProcesses

快照:CreateToolhelp32Snapshot  获取进程的一些相关信息和模块,像拍照一样记录下来

这些API感兴趣的也可去了解一下,还是比较常用的

总结

  1. 全局句柄表的结构与解析
  2. 知道挂起方式创建进程,可以在里面嘿嘿嘿
  3. 工作路径与模块路径

免费评分

参与人数 3吾爱币 +9 热心值 +2 收起 理由
rzdezhu + 1 + 1 用心讨论,共获提升!
asciibase64 + 1 谢谢@Thanks!
willJ + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

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

GuoTingRui 发表于 2024-4-22 09:57
非常不错的学习方法,这个正好需要学习!
wapjdyw680 发表于 2024-4-22 23:03
QinHanHan 发表于 2024-4-23 08:30
静静想我1970 发表于 2024-4-23 09:43
教科书,mark备用
Yifan2007 发表于 2024-4-23 11:15
那么有没有进程相关的api可以限制某一进程的帧数呢
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-12-14 12:05

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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