Windows编程_Lesson005_项目预备_初识进程

来源:互联网 发布:转发长视频软件 编辑:程序博客网 时间:2024/06/03 13:17

进程的组成

进程是“惰性”的,进程要做任何事情,都必须让线程来运行,线程执行进程地址空间中包含的代码。
一个进程可以拥有多个线程,所有的线程都能够在进程的地址空间中“同时”运行代码,每个线程都有它自己的一组CPU寄存器以及自己的堆栈。
每个进程必须有一个线程,当系统创建一个进程时,自动创建第一个线程,这个线程我们称为主线程,如果没有线程要执行进程地址空间中的代码,进程就失去了存在的意义,此时系统会自动销毁进程以及它的地址空间。操作系统会以轮训的方式为每个线程分配CPU时间片,从而营造出“并行”的假象!
如果计算机拥有多个CPU,操作系统会以更复杂的方式为线程分配CPU时间片。
线程切换和进程切换的时间成本是不一样的。

内核对象

操作系统用来管理进程

地址空间

包含了可执行文件及DLL模块的代码和数据。

应用程序类型

控制台用户界面

CUI用户控制台程序则是基于文本的,它们一般不会创建窗口或进程消息,而且不需要GUI。
当我们创建一个控制台应用程序时,操作系统会为我们自动的设置子系统为控制台,如下图 所示:
这里写图片描述
这是在编译时候的一个附加选项。当编译器看到这个附加选项时,在编译的时候就会去找main函数。

如果我们把这个附加选项设置为窗口时,编译器就会去找WinMain函数。
这里写图片描述

如果我们我们不设置附加选项,那么main和WinMain两个函数都能编译通过。
这里写图片描述

图形用户界面

GUI程序,是一个图形化的前端,它可以创建窗口,可以拥有菜单,能通过对话框与用户交互,还能使用所有标准的“视窗化”的东西,Windows几乎所有的附件应用程序都是GUI程序。

区别

  1. 在vs中,CUI程序的连接器开关为SUBSYSTEM:CONSOLE,GUI程序的连接器开关为SUBSYSTEM:WINDOWS,在加载时,编译器会获取此值,如果是一个文本控制台窗口,操作系统会使用命令提示符启动这个程序,否则它只是加载这个,由应用程序来管理自己的窗口;
  2. 入口函数不同;
int WINAPI _tWinMain(    _In_ HINSTANCE hInstance,    _In_ HINSTANCE hPrevInstance,    _In_ LPTSTR     lpCmdLine,    _In_ int       nCmdShow);int _tmain(    int argc,    TCHAR *argv[],    TCHAR *envp[]);
  1. 启动函数不同,GUI的启动函数为WinMainCRTStartup函数来加载,CUI的启动函数为mainCRTStartup函数来加载。
#include <Windows.h>#include <tchar.h>// Main     CUI// WinMain  GUI// 以上两种编译出来的都称为程序,// 区别是非常模糊的,在于两者的呈现方式不一样,其它的都一模一样// 哪怕是我们使用的是CUI模式,我们也可以使用CreateWindow函数来创建窗口。// 哪怕我们使用的是GUI,我们也可以使用Alloc函数来创建一个控制台。

进程实例句柄

加载到进程地址空间的每一个执行体或者DLL文件都被赋予了一个独一无二的实例句柄。
HMODULE和HINSTANCE作为同一类型,无区别。
我们可以使用GetModuleHandle函数来获取当前程序的实例句柄。

int WINAPI _tWinMain(    _In_ HINSTANCE hInstance,       // 进程句柄    _In_ HINSTANCE hPrevInstance,   // 前一个句柄,在我们自己的程序中,应该永远不要使用    _In_ LPTSTR     lpCmdLine,      // 命令行    _In_ int       nCmdShow         // 显示方式,SW_SHOW SW_HIDE){    HMODULE hmodule = GetModuleHandle(NULL);    if (hInstance == hmodule)    {        MessageBox(NULL, TEXT("相同"), TEXT("提示"), MB_OK);    }    return 0;}

执行新效果为:
这里写图片描述

在vs2015中,程序的入口地址是可变的,如图所示:
这里写图片描述
每次执行这个程序,获得的基地址是不一样的。

int main(){    HMODULE hmodule = GetModuleHandle(NULL);    printf("0X%p\r\n", hmodule);    // 程序的基地址,相当于车间的大门,一般的基地址是0X400000    system("pause");    return 0;}

但是我们也可以设置,让它的基地址是固定的,还可以设置指定的基地址,设置方法如图所示:
这里写图片描述

如果不放心的话,可以试,这里我就不多做演示了。
需要注意的是,当我们设置为固定的基地址时,那么我们启动多个这样的程序,它的基地址是一样的,因为我们所看到的都是vs给我们映射的一块虚拟地址,每一个应用程序都有一块独立的地址空间,所以能够保证每一个程序的基地址都是一样的,但是实际的地址是多少,那妞是vs或者Windows操作系统的事情了,以后再深入研究。

命令行参数

在vs中调试的时候,给程序加命令行的方法:
这里写图片描述

int main(){    HMODULE hmodule = GetModuleHandle(NULL);    printf("0X%p\r\n", hmodule);    // 程序的基地址,相当于车间的大门,一般的基地址是0X400000    TCHAR *strCommandLine = GetCommandLine();       // 使用函数来获取命令行参数, 这个函数返回的空间是一直存在的,也就是说不能被更改    printf("%ws\r\n", strCommandLine);    system("pause");    return 0;}

打印结果如下:
这里写图片描述

需要注意的是,使用printf(“%s\r\n”, strCommandLine);是打印不出来结果的,因为strCommandLine是宽字节,而%s打印的是窄字节。
我们也可以使用微软给我们提供的函数,_tprintf。打印结果如下:
这里写图片描述

从打印结果来看,它是一整行字符串,并不是一个一个的参数,我们自己去分割这些参数显得就有点费劲了,这是Windows给我们提供了另一个函数CommandLineToArgvW,可以帮我们分割参数。函数原型如下:

LPWSTR* CommandLineToArgvW(  _In_  LPCWSTR lpCmdLine,  _Out_ int     *pNumArgs);

从原型可以看出,CommandLineToArgvW函数的返回值是指针的指针。

int main(){    HMODULE hmodule = GetModuleHandle(NULL);    //printf("0X%p\r\n", hmodule);  // 程序的基地址,相当于车间的大门,一般的基地址是0X400000    TCHAR *strCommandLine = GetCommandLine();       // 使用函数来获取命令行参数, 这个函数返回的空间是一直存在的,也就是说不能被更改    //printf("%ws\r\n", strCommandLine);    //_tprintf(TEXT("%s\r\n"), strCommandLine);    int argc;    LPWSTR* ppArgv = CommandLineToArgvW(strCommandLine, &argc); // 返回的是已经分配好的内存,需要时LocalFree函数进行时释放    if (ppArgv != NULL)    {        for (int i=0; i<argc; ++i)        {            _tprintf(TEXT("%s\r\n"), ppArgv[i]);        }    }    system("pause");    return 0;}

打印结果如下:
这里写图片描述
当然,最标准的是从第二个参数开始打印。

环境变量

环境变量相当于系统中全局的命令行,它经常用作一些指标。

int main(){    // 环境变量    LPTCH strParth = GetEnvironmentStrings();   // 获取    _tprintf(TEXT("%s\r\n"), strParth);    FreeEnvironmentStrings(strParth);   // 释放    system("pause");    return 0;}

打印结果如下:
这里写图片描述
显然上面的返回结果是有问题的,问题在哪儿呢?让我们来看一下。

进程的环境变量

每个进程都有一个与它相关联的环境块(environment block),这是在进程地址空间内分配的一个内存块
函数GetEnvironmentStrings可以获得环境变量。
有些问题,稍后再解释!!!!!!!!!!

进程所在目录

GetCurrentDirectory()
SetCurrentDirectory()
下面是在调试的时候获取的进程所在目录:
这里写图片描述
下面是直接运行的.exe程序获取的所在目录:
这里写图片描述
所以我们在调试和直接运行exe的情况下,要注意所在进程目录的路径。

但是我们也可以使用SetCurrentDirectory()函数来更改进程所在目录,设置C盘,那么C盘的当前目录也会发生改变,如下图所示:
这里写图片描述

但是我们也可以使用SetCurrentDirectory()函数来更改进程所在目录,设置D盘,那么D盘的当前目录也会发生改变,如下图所示:
这里写图片描述

进程当前目录

我们可以使用GetFullPathName函数来获取进程的当前目录,如下图所示:
这里写图片描述

Module所在目录

调试状态下获取的Module目录:
这里写图片描述

运行.exe程序获得的Module目录:
这里写图片描述
从上面的结果可以看出,Module的目录并没有受到vs的调试与否的限制,得到的结果都是一样的,都是.exe程序所在的目录。

三个目录之间的关系总结:
1. 环境变量 分别对应的盘符: =C: =D: =E: 有几个盘符,就有几个这样的环境变量,这些都是进程启动的时候被默认设置的
2. 所在目录 进程启动的时候,默认分配给的.exe所在的目录,但是我们可以使用SetCurrentDirectory函数进行更改,在更改的同时,我们会将当前盘符下面的当前目录也更改为跟我一样的目录。这是这个函数比较坑的地方。
3. 当前目录 比如C:,它会直接存储在这个地方去,会默认给我们分配一个位置,这个默认位置会因为修改了所在目录而发生更改。

需要注意的是,当前目录是没有SetFullPathName这样的函数来修改当前目录的,我们只能修改环境变量或者通过修改所在目录来达到更改当前目录的效果!

CreateProcess

函数原型如下:

BOOL WINAPI CreateProcess(  _In_opt_    LPCTSTR               lpApplicationName,  _Inout_opt_ LPTSTR                lpCommandLine,  _In_opt_    LPSECURITY_ATTRIBUTES lpProcessAttributes,  _In_opt_    LPSECURITY_ATTRIBUTES lpThreadAttributes,  _In_        BOOL                  bInheritHandles,  _In_        DWORD                 dwCreationFlags,  _In_opt_    LPVOID                lpEnvironment,  _In_opt_    LPCTSTR               lpCurrentDirectory,  _In_        LPSTARTUPINFO         lpStartupInfo,  _Out_       LPPROCESS_INFORMATION lpProcessInformation);

当CreateProcess被一个线程调用时,系统会创建一个进程内核对象,进程内核对象并不代表进程本身,它是操作系统用来管理这个进程的数据结构,该结构中有一个使用计数,会在进程被创建时设置为1,然后系统为新进程创建一个虚拟地址空间,将需要的代码以及数据加载到进程的地址空间中。之后,系统会为新进程的主线程创建一个内核对象,主线程最终会调用你的main函数。
如果系统成功创建了新进程和主线程,函数会返回true。
需要注意的是,即便是父子进程,它们之间也是不能直接进行通信的,它们之间也是两个独立空间。

lpApplicationName

指定新进程要使用的执行体文件的名称。
当在lpApplicationName中传一个文件路径时,如果给的是一个相对路径,那么它只会在当前目录下进行查找当这个值不为NULL时,lpCommandLine会被作为命令行参数传递。
执行体文件实际上指的是.exe文件。也就是说本程序的exe文件所在目录下的其它.exe程序才能被CreateProcess函数打开,它不会去别的目录下找.exe文件。

lpCommandLine

传给新进程的命令行字符串
如果lpApplicationName为NULL时,我们可以在lpCommandLine中传一个文件的路径,如果我们传的是一个相对路径,它会按照以下顺序来搜索:
1. 所在目录
2. 当前目录
3. Windows系统目录(System32目录)
4. Windows目录
5. Path环境变量中列出的目录

lpCommandLine参数还有一个需要注意的地方,CreateProcess函数会修改这个参数,所以我们不应该传递一个常量指针。

lpProcessAttributes

设置进程内核对象的安全属性

lpThreadAttributes

设置主线程内核对象的安全属性

bInheritHandles

是否被继承

dwCreationFlags

进程创建方式标志
CREATE_NO_WINDOW:标志指示系统不要为应用程序创建任何控制台窗口。可以使用这个标志来执行没有用户界面的控制台应用程序。

lpEnvironment

lpEnvironment参数指向一个内存块,其中包含了新进程要使用的环境字符串。大多数时候,为这个参数传入的值都是NULL,这将导致子进程继承其父进程使用的一组环境字符串。另外,还可以使用GetEnvironmentStrings函数。

lpCurrentDirectory

lpCurrentDirectory参数允许父进程设置子进程的当前驱动器和目录。如果这个参数为NULL,则新进程的工作目录与生成新进程的应用程序一样。

lpProcessInformation

lpProcessInformation结构体一定要清零,否则成员间包含主调线程堆栈上的垃圾数据,使得偶尔创建成功,偶尔创建失败!!!

typedef struct _STARTUPINFO {    DWORD  cb;                  // 初始化为sizeof(STARTUPINFO)    LPTSTR lpReserved;          // 保留,必须初始化为NULL    LPTSTR lpDesktop;           // 启动应用程序的桌面名称    LPTSTR lpTitle;             // 控制台下可用,指定控制台的窗口标题    DWORD  dwX;                 // 屏幕的x坐标位置    DWORD  dwY;                 // 屏幕的y坐标位置    DWORD  dwXSize;             // 屏幕的高度    DWORD  dwYSize;             // 屏幕的宽度    DWORD  dwXCountChars;       // 控制台下可用,控制台的宽度    DWORD  dwYCountChars;       // 控制台下可用,控制台的高度    DWORD  dwFillAttribute;     // 控制台下可用,控制台的文本和背景色    DWORD  dwFlags;             // 组合值    WORD   wShowWindow;         // 窗口可用,窗口如何显示    WORD   cbReserved2;         // 保留    LPBYTE lpReserved2;         // 保留    HANDLE hStdInput;           // 设置标准输入    HANDLE hStdOutput;          // 设置标准输出    HANDLE hStdError;           // 设置标准错误输出} STARTUPINFO, *LPSTARTUPINFO;

dwFlags的取值:
STARTF_USESIZE 使用dwXSize和dwYSize成员
STARTF_USESHOWWINDOW 使用wShowWindow成员
STARTF_USEPOSITION 使用dwX和dwY
STARTF_USECOUNTCHARS 使用dwXCountChars和dwYCountChars成员
STARTF_USEFILLATTRIBUTE 使用dwFillAttribute成员
STARTF_USESTDHANDLES 使用hStdInput、hStdOutput和hStdError成员
STARTF_RUNFULLSCREEN 使x86计算机上运行的一个控制台应用程序以全屏的模式启动
STARTF_FORCEOFFFEEDBACK 改变鼠标为等待
STARTF_FORCEONFEEDBACK 不改变鼠标光标
STARTF_PREVENTPINNING 窗口无法被固定到任务栏上
STARTF_TITLEISLINKNAME 设置任务栏和开始菜单的状态

lpProcessInformation

typedef struct _PROCESS_INFORMATION {  HANDLE hProcess;  HANDLE hThread;  DWORD  dwProcessId;  DWORD  dwThreadId;} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;

终止进程

总共有4种方法可以终止进程,分别是:

  1. 入口函数返回;
  2. 进程中的一个线程调用ExitProcess;
  3. 另一个进程中的线程调用TerminateProcess;
  4. 进程中的所有线程都自然死亡。

1. 入口函数返回

最正确的方式,只有这样才能够保证主线程的所有资源都已经被正确清理。
入口函数在返回时,为确保以下几件事情已经完成:

  • 主线程所创建的任何对象都已经被正确的销毁;
  • 操作系统会正确释放线程的堆栈;
  • 将进程的退出代码设置成入口函数的返回值;
  • 递减内核对象的使用计数。

所以,如果我们使用了一个HANDLE,要及时的使用CloseHandle把它给关闭,这个内核对象会一直留在我们的操作系统中,导致内核对象泄漏,它比内存泄漏更加麻烦,因为内存泄漏可以用关闭进程的方法解决,而内核对象泄漏只能通过重启操作系统来解决。

2. 进程中的一个线程调用ExitProcess

不应该调用此函数来结束进程,当函数被调用的时候会强制结束进程,并将退出代码设置为uExitCose,但此时线程并未正确结束,会导致线程无法正确被清理。
函数原型如下:

VOID WINAPI ExitProcess(_In_ UINT uExitCode);

3. 另一个进程中的线程调用TerminateProcess

不应该调用此函数来结束进程,此函数能够结束其它进程。
函数原型如下:

BOOL WINAPI TerminateProcess(_In_ HANDLE hProcess, _In_ UINT   uExitCode);

4. 进程中的所有线程都自然死亡

很少碰到这种情况,但是理论上存在!

进程权限

在XP时代,系统中只存在一个操作权限,在Vista后,增加了筛选权限令牌(UAC),如果要求进程提升权限,那么必须在进程边界上进行提升,进程边界指的是在进程启动之时来提升权限,对进程进行提权操作都是单次的。

我们鼠标双击会打开一个进程,这个进程是由资源管理器运行CreateProcess打开一个进程,所以说资源管理器是所有进程的父进程,那么在XP时代,如果我们以管理员身份登录的系统,那么资源管理器就拥有了管理员的权限,那么双击打开的所有进程都就拥有了管理员的权限,这将是很危险的。到了UAC时代,即使资源管理器拥有了管理员权限,但是子进程只能在边界拥有这个权限,而且这个提升权限都是单次的,以后所有的提升操作都需要逐个的进行提升。那么有的人就会问,能不能把提升操作时的边界状态记录下来呢?也就是说我启动了一次子进程后,把这个状态记录下来,在电脑关机之前,我再启动一次,这样是无法完成的。因为但凡能够记录,那么就得存储在内存的某一段空间,这样黑客就能找到这块空间,把这个权限拷贝到黑客的软件上面,这就又变得不安全了。

DOS时代

在DOS时代,所有的程序都能操作物理内存,也就是说程序也好,操作系统也好,它们都处于同一等级,我们甚至可以写一个程序来篡改操作系统的代码,所以这种操作是非常不安全的。
1. 它会导致系统的不稳定;
2. 两个进程操作物理内存,并且物理内存之间没有加上权限管理,任何一个进程都能访问我需要保密的东西,那么就也没有保密性了。

XP时代

在XP时代,虚拟内存就出现了,它解决了系统不稳定的问题。杜绝了直接修改操作系统代码的可能,从而保证了系统的稳定性。我们现在使用的操作系统就很难再出现蓝屏的现象,因为只有经过了微软认证的软件才有可能和操作系统中的内核打交道,否则其余的所有软件都无法更改操作系统中任意一行的代码。
进程在设计的时候是完全封闭的,也就是说A进程和B进程是毫无关联的,但是这样毫无关联是不符合软件需求的,因为,比如说记事本程序需要和输入法程序进行关联才能达到良好的输入效果,所以进程与进程之间又开放了一些接口,但是这样又会失去保密性的功能,比如在A进程中输入了账号密码,这些账号和密码肯定是想要保密的,这就又事与愿违了。比如我们使用CreateProcess函数打开一个子进程,那么它的父进程将拥有子进程的访问权限(这样我就可以写一个盗号的木马,CreateProcess(“QQ”),那么我就能够访问到已经存储好的账号和密码)。正式因为开放了这些接口存在了风险,微软又提出了一个条件,你必须拥有了这个权限(用户赋予的权限,它会通知用户是否打开这个软件)件后才能打开这个程序,如果用户赋予了这个高危的权限,那么就能够以高危的权限打开这个程序。XP系统将这个权限划分为了管理员权限和用户权限,管理员权限可以做所有的高危的操作,而用户权限必须被赋予管理员权限后才能做这些高危操作。这个时候你就会发现这两种权限的区分并不是很明显的,所以就会导致XP系统上的所有权限都变成了管理员权限,那么 这些权限控制就变得可有可无了。比如说你以管理员的权限打开了一个进程,那么这个进程就拥有了管理员的权限,并且这个管理员权限就会被它的子进程所继承,它的子进程也就拥有了管理员的权限,这就是进程之间的权限继承。比如我们之前见到过的网页木马,网页木马如果不用管理员权限打开的话,它是没有危害的。网页木马的原理大概是这样的:IE浏览器拥有管理员权限,并且IE的插件功能能够被自动的下载和运行,所以这个管理员权限也就被继承到了这个插件上了,这就导致了这个插件能够操作你的操作系统。

Vista时代到现在

Vista之后就进入到了UAC(带过滤表的一种权限、带有局部的管理员权限)时代,UAC不再拥有管理员的所有权限,UAC权限可以访问C盘、更改系统时间、修改注册表等不是十分高危的操作,但是它不能打开进程、修改系统中的某些东西等十分高危的操作,UAC使得Windows权限得到了完善,这时网页木马等走向了没落。

以管理员身份运行

右键->以管理员身份运行可以使进程提升为管理员权限。

**通过RT_MANIFEST提升权限

自动提升程序的权限的两种方法:
1. 可以通过项目属性来修改UAC权限:
这里写图片描述

2.用户可以通过下面的方法提升权限
这里写图片描述

手动提升权限

BOOL ShellExecuteEx(_Inout_ SHELLEXECUTEINFO *pExecInfo);typedef struct _SHELLEXECUTEINFO {        DWORD     cbSize;           // 长度        ULONG     fMask;            // 标识        HWND      hwnd;             // 窗口句柄        LPCTSTR   lpVerb;           // 执行的操作        LPCTSTR   lpFile;           // 执行的文件路径        LPCTSTR   lpParameters;     // 参数        LPCTSTR   lpDirectory;      // 目录        int       nShow;            // 显示方式        HINSTANCE hInstApp;         // 返回的HINSTANCE        LPVOID    lpIDList;         // 特殊标识符        LPCTSTR   lpClass;          // 指明GUID或类名        HKEY      hkeyClass;        // 获得已在系统注册的文件类型        DWORD     dwHotKey;         // 热键        union {            HANDLE hIcon;           // 图标            HANDLE hMonitor;        // 显示器的HANDLE        } DUMMYUNIONNAME;        HANDLE    hProcess;         // 新启动程序的句柄    } SHELLEXECUTEINFO, *LPSHELLEXECUTEINFO;
#include <windows.h>#include <stdio.h>int main(int argc, TCHAR *argv[]){    SHELLEXECUTEINFO seInfo = { 0 };    seInfo.cbSize = sizeof(SHELLEXECUTEINFO);    seInfo.lpVerb = TEXT("runas");    seInfo.lpFile = TEXT("CMD");    seInfo.nShow = SW_SHOW;    if (!ShellExecuteEx(&seInfo))    {        DWORD dwErrorCode = GetLastError();        if (dwErrorCode == ERROR_CANCELLED)        {            printf("用户取消\r\n");        }        else if(dwErrorCode == D3D10_ERROR_FILE_NOT_FOUND)        {            printf("未找到文件!\r\n");        }    }    system("pause");    return 0;}

权限的继承(和UAC无关)
1. 如果是管理员权限打开的进程(管理员程序),当你再用这个进程CreateProcess另一个子进程的时候,此时的子进程会继承提升后的权限;
2. 如果这个进程是提权后执行的(UAC程序),当你再用这个进程CreateProcess另一个子进程的时候,此时将会失败。

所以在CreateProcess的时候,有可能因为权限的问题导致程序运行失败!所以这个时候就必须要判断这个错误类型,使用GetLastError函数获取错误码,如果是ERROR_ELEVATION_REQUIRED,说明请求的操作需要提升。

进程的总结及归纳

进程在启动之后后产生两个对象,这两个对象分别是进程内核对象(它是HANDLE,而不是HINSTANCE,HINSTANCE是在main函数中的一个参数,HINSTANCE可以代表一个进程的对象,但是它却不能代表进程内核的对象,HINSTANC的本质是当前进程的基地址,在进行CreateProcess的时候,我们有一个PROCESS_INFORMATION的结构体,这个结构体里面有一个hProcess的成员,它的类型是HANDLE,它代表进程内核对象的句柄,在进程内核对象中有一个非常重要的参数,叫做使用计数,使用计数会因为线程的启动而加1,线程的消亡而减1,当使用计数为0的时候,操作系统会回收这个进程内核对象的地址空间;如果使用计数不为0,那么当前这个进程内核对象一直存在操作系统中,此时我们称为内核对象泄漏。在进行CreateProcess的时候,因为我们新建了这样一个内核对象,所以这个内核对象中的使用计数会加1,而在CreateProcess函数中传递PROCESS_INFORMATION这个结构体,这个结构体里面需要使用到进程内核对象的HANDLE句柄,每当进程内核对象被使用或者引用一次,它的使用计数就会被加1,也就是说,CreateProcess执行成功后,这个进程内核对象的使用计数就会加2,如果我们想这个进程内核对象在进程消亡的时候能够被操作系统回收,并且我们也不再对我们的创建的进程进行操作的话,那么我们就应该关闭掉PROCESS_INFORMATION结构体中的hProcess以及hThread,这样才能被操作系统正确的释放内核对象资源)和线程内核对象。

我们平时在双击一个.exe程序的时候来打开这个程序,实际上是由资源管理器(操作系统中的一个软件,即explorer.exe,它会随着操作系统的启动而启动,随着操作系统的关闭而关闭,如果在操作系统还没有关闭的时候就把explorer.exe进程结束掉之后,那么就会导致桌面系统无法使用,必须使用cmd命令行打开这个进程才能使得桌面系统正常运行)来打开的这个进程。这时候,当前被打开的程序正在运行,资源管理器也在运行,但是资源管理器在打开这个.exe程序之后,就会关闭掉这个.exe程序的句柄,从而使得资源管理器和被打开的这个.exe程序变成了两个独立的程序,即使.exe程序在形式上是资源管理器的子进程,但是实际上已经变得毫无关联了。

权限管理系统:
Windows使用机制,分为超级管理员(Administrator)、管理员(大部分的操作)、用户(基础的操作)。

UAC(解决管理员权限继承的问题,目的就是不让完全管理员权限继承),并且UAC还考虑到了之前代码的兼容性。UAC的作用实际上跟不让进程继承管理员权限的作用是一模一样的,只是因为要兼容XP之前的程序,所以才设计了UAC这一套进程管理权限机制。

当我们以管理员的方式启动(CreateProcess)资源管理器,它所打开的程序将会分成两大类:
一大类是需要提权(提前到管理员权限)的程序,这一类程序将会拥有管理员的所有权限;
另一大类是正常(不需要提权的)的程序,这一类程序只拥有UAC过滤后的管理员权限。

这里写图片描述

进程补充

在Windows操作系统中有四类账户类型,分别是:超级管理员(administrator,这个名字不能被更改)、管理员、用户、临时用户(游客、guest)。
超级管理员:能在操作系统中做任何的事情,包括删除系统中的任何一个文件, 打开任何一个程序,执行任何一个程序;
管理员:可以增删改查系统中的任意东西,它与超级管理员的唯一一个区别是超级管理员可以增加或者删除一个管理员用户,其它的权限没有任何区别(当然了,这些区别是针对于Vista之后的,在Vista之前管理员和超级管理员还是有一些区别的);
用户:只能操作特定的东西。用户可以是多用户的,有A、B、C等用户,但是各个用户之间是隔离的,A用户不能操作B用户,B用户也不能操作A用户;

我们的程序写好后,比如杀毒软件,毫无疑问,我们的杀毒软件是要去C盘读取文件的,如果是管理员权限下运行的我们的杀毒软件,那么是无需提权的,而在用户权限下,就需要对程序进行提权了。但是我们一般是使用管理员进行登录操作系统的,这时运行一个程序,那么这个程序就可以悄悄的做一些恶意的事情,从而导致我们的操作系统又变得不安全了。Windows为了解决这个问题,但又符合一般人的使用习惯(以管理员权限登录),UAC(用户账户控制)权限管理机制出现了,它更多的是以通知的形式来管理权限,就是当你需要提权操作的时候,它会给你一个通知。我们可以按下Windows键,输入UAC并回车,就会打开这个页面,我们可以设置通知级别,如下图。

这里写图片描述
UAC主要是解决在管理员下面启动的程序,拥有了管理员权限之后,避免了这个程序做一些需要管理员权限才能做的一些事情。UAC就相当于一个权限令牌,来达到控制权限的继承问题。

这里写图片描述

当然了,我们也有办法绕过UAC,比如加载到程序中的dll就可以拥有管理员的权限,这样就可以让dll静默的绕过UAC了。

遍历进程

Windows API 在一开始设计的时候,并没有设计一个API来遍历系统中的进程、线程或者模块等。
Windows在操作系统里面写了一个数据库,这个数据库存放了所有进程的信息,通过注册表来访问系统中的所有进程,也就是说我们可以通过注册表来获取进程的信息,但是这样访问起来将是非常的事情,所以微软就写了一个ToolHelp32的库(从Win 95 操作系统开始有的),用来访问进程信息,但是这个库不属于Windows API,严格意义上的Windows API应该是Windows.h所包含的函数,其余的都不是Windows API。
再次需要强调的是,进程ID和HANDLE不是一回事,HANDLE代表的是当前进程内核对象的一个句柄,进程ID代表的是进程的一个编号,那么为什么要设计出这两个对象呢?因为HANDLE是给我们程序员来使用的,而进程ID是用来给用户看的。

Windows中所有的线程都是以抢占式的方式运行的,线程的优先级决定了线程被运行的可能性的大小。但是在Windows中线程的优先级是一件很坑的事情,我们不要在线程的优先级上面花太多的时间。

任何一个进程都是由多个模块构成的,因为任何一个程序在启动的时候都会加载ntdll.dll,任何一个界面在启动的时候都要加载user32.dll,因为这些.dll中存放了大量的Windows API,再加上我们自己程序的.exe,这三者(模块)加到一起才是一个完整的进程。

堆、模块和进程有点儿相似,进程代表了一大块空间,模块代表了一大块空间中的分块空间,堆更是代表了一块内存,并且这三者都有各自的内核对象,所以每个堆都有一个HANDLE句柄。

我们使用任务管理器就能看到PID,但是我们想获得当前进程的句柄是很难的,因为HANDLE是隐藏的,而PID是对外公开的,PID就相当于身份证号。

原创粉丝点击