一、进程
从内核的角度,进程可以理解成为一个内核对象,该内核对象保存了进程运行的所有数据。
从二进制的角度,也可以认为进程是一个地址空间,包含了可执行文件和dll的代码和数据,以及内存的分配。
从线程的角度,进程是数据容器,进程中所有的线程都共享进程的数据。
二、进程相关的数据结构
(1)EPROCESS ( 内核态)
typedef struct _EPROCESS {
KPROCESS Pcb; //本进程的KPROCESS结构
......
PHANDLE_TABLE ObjectTable; //本进程的句柄表
PVOID VadRoot; //指向代表本进程用户空间的数据结构
HANDLE SectionHandle; //指向为可执行程序映像创建的文件映射区句柄
PPEB Peb; //指向用户空间的PEB
ACCESS_MASK GrantedAccess; //所允许的访问方式
UCHAR ImageFileName[ 16 ]; //所执行程序映像的文件名
UCHAR PriorityClass; //本进程的优先级类别
PVOID Win32Process; //指向本进程的W32PROCESS结构
LIST_ENTRY ThreadListHead; //本进程的ETHREAD队列
......
} EPROCESS;</span></span>
(2)KPROCESS(内核态)
typedef struct _KPROCESS {
DISPATCHER_HEADER Header;
ULONG_PTR DirectoryTableBase[2]; //本进程页面映射表的物理地址
... ...
LIST_ENTRY ReadyListHead; // 本进程的就绪线程队列
SCHAR BasePriority; //本进程的基本优先级
LIST_ENTRY ProcessListEntry; //挂入内核的进程队列
... ...
ULONG KernelTime;
ULONG UserTime;
SINGLE_LIST_ENTRY SwapListEntry;
LIST_ENTRY ThreadListHead;
ULONG_PTR StackCount;
} KPROCESS, *PKPROCESS, *PRKPROCESS;</span></span>
(3)PEB(用户态)
typedef struct _PEB {
......
HINSTANCE ImageBaseAddress; // 程序映像的起点
VOID *DllList;
PPROCESS_PARAMETERS *ProcessParameters; //参数块
ULONG KernelCallbackTable; //用于从内核“回调”用户空间的函数
ULONG TlsBitmap; //TLS位图
LARGE_INTEGER TlsBitmapBits;
ULONG NumberOfProcessors;
ULONG NumberOfHeaps;
ULONG MaximumNumberOfHeaps;
ULONG ProcessHeaps;
ULONG OSMajorVersion;
ULONG OSMinorVersion;
USHORT OSBuildNumber;
USHORT OSCSDVersion;
ULONG OSPlatformId;
ULONG GdiHandleBuffer[0x22];
ULONG PostProcessInitRoutine;
ULONG SessionId;
} PEB, *PPEB; </span></span>
用户态windbg调试,查看这些数据结构,有很多种方法:
(a)通过!peb直接查看peb结构
(b)FS段寄存器指向当前的TEB结构,在TEB偏移0x30处是PEB指针,再用dt命令查看peb结构
三、进程入口函数和退出函数
(1)入口函数
windows程序分为GUI(图形界面)和CUI(控制台)程序,GUI的入口函数为_tWinMain(......),CUI的入口函数为_tmain(......),在编译链接的过程中,会根据链接选项SubSystem的取值,来决定链接哪个入口函数,如果设置为/SUBSYSTEM:CONSOLE则会寻找_tmain, /SUBSYSTEM:WINDOWS则会寻找_tWinMain。
值得注意的是,操作系统并不直接调用程序的入口函数,而是调用c/c++运行时库提供的运行时启动函数,如果是GUI程序,则调用_tWinMainCRTStartup,CUI则调用_tmainCRTStartup。c/c++提供的启动函数,会初始化c/c++运行时库,初始化c/c++运行时库的全局变量、内存分配函数和其他底层I/O例程使用的heap,调用所有全局和静态C++类对象的构造函数。
(2)退出函数
入口函数退出后,启动函数将调用c运行时库函数exit,向其传递返回值。exit将完成一下任务:
(a)调用_onexit函数调用所注册的任何一个函数;
(b)调用所有全局和静态c++类对象的析构函数;
(c)调用操作系统的ExitProcess函数,向其传入nMainRetVal。这会导致操作系统“杀死”我们的进程,并设置它的退出码。
(d)在DEBUG生成中,如果定义了内存泄漏检查宏标志,则会生成内存泄漏报告。
四、进程的创建
BOOL CreateProcess (
LPCTSTR lpApplicationName,
LPTSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCTSTR lpCurrentDirectory,
LPSTARTUPINFO lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation);</span>
首先,创建内核对象。系统为新进程创建虚拟地址空间,将可执行文件或者dll的代码和数据加载到进程的地址空间。然后,主线程执行c/c++运行时的启动例程,最终会调用main系列函数。如果,系统成功创建了进程和主线程,createprocess则返回true。
(1)pszApplicationName, pszCommandline
这两个参数,不能是常量字符串
(2)psaProcess, psaThread, bInheritHandles
进程安全描述符和线程安全描述符
(3)fdwCreate
标识了影响进程创建方式的标志
(4)pvEnvironment
(5)pszCurDir
(6)psiStartInfo(STARTUPINFO)
(7)ppiProcInfo(PROCESS_INFORMATION)
五、进程的退出
四种方式:
(1)主线程入口点函数返回
该线程创建的任何C++对象都由析构函数正确的销毁;
操作系统将正确的释放线程栈使用的内存;
系统将进程退出码设为入口点的返回值;
系统递减进程内核对象的使用计数;
(2)进程中另一个线程调用exitProcess函数
会导致进程或者线程直接终止运行——再也不会返回当前函数调用。对于C、C++应用程序而言,会导致C/C++运行时库不会执行正确的清理操作;
(3)另一个进程中的线程调用TerminateProcess函数
进程没有任何机会做清理操作,不过操作系统会接管,保证进程终止后绝对不泄漏任何东西。 TerminateProcess是异步的,需要调用WaitFor...Object来等待完成。
(4)进程中的所有线程都“自然死亡”
六、其他
(1)一些API
进程实例句柄:GetModuleHandle/GetModuleHandleEx
进程命令行:GetCommandLine,CommandLineToArgvW
进程环境变量:
GetEnvironmentStrings ,FreeEnvironmentStrings,GetEnvironmentVariable,
SetEnvironmentVariable,ExpandEnvironmentVariable
进程当前所在目录和驱动器:GetCurrentDirectory,GetFullPathName
系统版本:GetVersionEx
进程错误模式:SetErrorMode(默认情况下,子进程会继承父进程的错误模式)
进程终止码:GetExitCodeProcess
(2)进程提权(UAC)
ShellExcuteEx
(3)当前权限上下文
GetTokenInformation
(4)枚举正在运行的进程
ToolHelp API中有Process32First, Process32Next, EnumPorcess等函数