进程实例句柄
每一个运行的EXE或者DLL都有一个唯一的实例句柄,程序通常会将这个句柄作为wminmain的第一个参数hInstanceExe。
这个Handle在加载资源的时候使用,例如
HICON LoadIcon(
HINSTANCE hInstance,
PCTSTR pszIcon);
HhInstanceExe的实际值就是PE文件被装载在内存中的位置,这个位置由linker决定,windows默认加载在0x00400000,可以通过/BASE: address
来修改链接位置。
GetModuleHandle
返回程序的加载地址,如果传递的是NULL,返回当前程序加载地址。
如果是DLL有两种方法获得当前DLL的加载地址,一个是通过linker的预定义变量__ImageBase
,另外一个是通过函数GetModuleHandleEx
第一参数为GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS
,第二个参数为当前运行的函数地址,第三个参数就是获得地址的指针。 对应代码如下
extern "C" const IMAGE_DOS_HEADER __ImageBase;
void DumpModule() {
// Get the base address of the running application.
// Can be different from the running module if this code is in a DLL.
HMODULE hModule = GetModuleHandle(NULL);
_tprintf(TEXT("with GetModuleHandle(NULL) = 0x%x\r\n"), hModule);
// Use the pseudo-variable __ImageBase to get
// the address of the current module hModule/hInstance.
_tprintf(TEXT("with __ImageBase = 0x%x\r\n"), (HINSTANCE)&__ImageBase);
// Pass the address of the current method DumpModule
// as parameter to GetModuleHandleEx to get the address
// of the current module hModule/hInstance.
hModule = NULL;
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (PCTSTR)DumpModule,
&hModule);
_tprintf(TEXT("with GetModuleHandleEx = 0x%x\r\n"), hModule);
}
GetModuleHandle
需要注意,它被不会返回没有被加载的EXE或者DLL地址。二是它会返回的是运行的EXE地址,如果使用GetModuleHandle(NULL)
在DLL中,它返回的是EXE的加载地址。
环境变量
环境变量是一个内存块中包含一组字符串
=::=::\ ...
VarName1=VarValue1\0
VarName2=VarValue2\0
注意除了第一行之外,也可能有以=开头的行,这种不做环境变量使用
环境变量中空格,也被视为name或者value
系统环境变量存储在注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
中
用户环境变量存储在HKEY_CURRENT_USER\Environment
中
修改环境变量后一般需要重启来刷新,但是一些进程如explorer taskmanager control panel
可以通过WM_SETTINGCHANGE
message来刷新。
SendMessage(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM) TEXT("Environment"));
一般子进程的环境变量与父进程一样,但是父进程可以指定继承哪些环境变量。这里注意父子进程的环境变量是独立的
可以通过函数GetEnvironmentVariable
来获得对应的环境变量的值
环境变量中有一些\%\%字符表示可以被替换,可以通过ExpandEnvironmentStrings
来将它展开。
可以通过SetEnvironmentVariable
来对环境变量进行添加,删除、修改
进程的错误模式
所有进程都有一些flag来关联当遇到错误,例如找不到指定文件,磁盘错误、对齐错误时应该怎么做。通过SetErrorMode
来设置这些flag,UINT SetErrorMode(UINT fuErrorMode)
参数是下表中的一些组合
一盘来说子进程继承这些error flag,这也导致如果遇到一些问题,子进程可能直接失败然后退出。但是用户没有感受。
可以通过在CreateProcess设置CREATE_DEFAULT_ERROR_MODE
Flag不让子进程继承父进程error mode。
进程所在的目录和驱动
当未指定完成路径的时候,通过当前驱动器和当前目录开始寻找。
进程中的一个线程可以修改当前目录或者驱动,这对进程中所有线程有效
通过GetCurrentDirectory
获得当前目录,SetCurrentDirectory
设置当前目录
在windows中MAX_PATH=260
是默认的最长目录路径,所以在获得当前目录时,传递MAX_PATH是safe的
进程的当前目录
系统记录进程当前驱动器和目录,但是没有记录每个驱动器的当前目录。但是操作系统使用环境字符串来进行记录其他驱动的当前目录
=C:=C:\windows
=D:=D:\
创建子进程的时候不会默认继承父进程的各个驱动器的当前目录。想要子进程继承,父进程需要创建驱动器字母的环境变量,。
父进程可以获得指定驱动器的当前目录通过GetFullPathName
系统版本
通过GetVersinoEx
获得系统版本相关信息
BOOL GetVersionEx(POSVERSIONINFOEX pVersionInformation);
typedef struct {
DWORD dwOSVersionInfoSize;
DWORD dwMajorVersion;
DWORD dwMinorVersion;
DWORD dwBuildNumber;
DWORD dwPlatformId;
TCHAR szCSDVersion[128];
WORD wServicePackMajor;
WORD wServicePackMinor;
WORD wSuiteMask;
BYTE wProductType;
BYTE wReserved;
} OSVERSIONINFOEX, *POSVERSIONINFOEX;
可以通过VerifyVersionInfo
来比较系统版本是否符合
BOOL VerifyVersionInfo(
POSVERSIONINFOEX pVersionInformation,
DWORD dwTypeMask,
DWORDLONG dwlConditionMask)
使用这个函数必须初始化OSVERSIONINFOEX结构体,初始化它的dwOSVERSINOINFOSIZE成员为结构体的大小,之后初始化需要比较的成员。通过dwTypeMask指定初始化了哪一个版本编号。通过dwlConditionMask,描述如何进行比较
dwlConditionMask由复杂的bit位组成,可以通过VER_SET_CONDITION宏来设置。
VER_SET_CONDITION(
DWORDLONG dwlConditionMask,
ULONG dwTypeBitMask,
ULONG dwConditionMask)
dwlConditionMask是需要设置的变量,dwTypeBitMask是你想比较的结构体中的哪一个,如果比较多个成员需要多次使用VER_SET_CONDITION,每次dwTypeBitMask指向一个成员。dwConditionMask
指向如何比较。VER_EQUAL, VER_GREATER, VER_GREATER_EQUAL, VER_LESS, or VER_LESS_EQUAL。对于VER_NT_WORKSTATION成员也可以使用这个进行比较。但是对于VER_SUITENAME成员需要使用VER_AND VER_OR进行比较
设置完成之后可以使用VerifyVersionInfo进行比较,如果返回非0值符合要求,如果返回0版本不符合要求或者函数设置有错误。可以通过GetLastError,如果获得ERROR_OLD_WIN_VERSION,函数设置成功但是系统不符合要求。
下面这段程序判断是不是Windows Vista:
// Prepare the OSVERSIONINFOEX structure to indicate Windows Vista.
OSVERSIONINFOEX osver = {0};
osver.dwOSVersionInfoSize = sizeof(osver);
osver.dwMajorVersion = 6;
osver.dwMinorVersion = 0;
osver.dwPlatformId = VER_PLATFORM_WIN32_NT;
// Prepare the condition mask.
DWORDLONG dwlConditionMask = 0; // You MUST initialize this to 0.
VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_EQUAL);
VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_EQUAL);
VER_SET_CONDITION(dwlConditionMask, VER_PLATFORMID, VER_EQUAL);
// Perform the version test.
if (VerifyVersionInfo(&osver,
VER_MAJORVERSION | VER_MINORVERSION | VER_PLATFORMID,
dwlConditionMask)) {
// The host system is Windows Vista exactly.
} else {
// The host system is NOT Windows Vista.
}
CreateProcess
BOOL CreateProcess(
PCTSTR pszApplicationName,
PTSTR pszCommandLine,
PSECURITY_ATTRIBUTES psaProcess,
PSECURITY_ATTRIBUTES psaThread,
BOOL bInheritHandles,
DWORD fdwCreate,
PVOID pvEnvironment,
PCTSTR pszCurDir,
PSTARTUPINFO psiStartInfo,
PPROCESS_INFORMATION ppiProcInfo)
系统创建一个进程内核对象 with usage count 1,进程内核对象不是进程本身,而是一个小型的数据结构存储了进程相关信息。然后创建虚拟内存空间加载EXE和需要的DLL
系统然后创建一个Thread kernel object with usage count 1,作为进程的初始线程。线程执行linker设置的entry point 最终call main 函数。如果系统成功创建进程,和初始线程,返回TRUE。
CreateProcess 在进程完全初始化之前就会返回True.这个时候系统lader没有加载所有的所需DLL,如果DLL不能被加载的话,子进程会退出同时父进程不会发现问题
pszApplicationName and pszCommandLine
([[Windows Via C/C++,Fifth Edition .pdf#page=100&selection=82,0,91,74|Windows Via C/C++,Fifth Edition , p.100]]) The pszApplicationName and pszCommandLine parameters specify the name of the executable file the new process will use and the command-line string that will be passed to the new proces
ApplicationName和pszCommandLine分别是新建进程使用的名字,和传递的参数
pszCommandLine
注意到命令行是PTSTR,这意味者不是一个const值。
([[Windows Via C/C++,Fifth Edition .pdf#page=100&selection=111,0,118,7|Windows Via C/C++,Fifth Edition , p.100]]) CreateProcess actually does modify the command-line string that you pass to it. But before CreateProcess returns
如果传递一个const的值,可能会导致Acces 错误
[!PDF|] [[Windows Via C/C++,Fifth Edition .pdf#page=100&selection=124,0,130,32|Windows Via C/C++,Fifth Edition , p.100]] STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; CreateProcess(NULL, TEXT(“NOTEPAD”), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
最好的方法就是,复制一个copy出来。
[!PDF|] [[Windows Via C/C++,Fifth Edition .pdf#page=100&selection=150,0,154,40|Windows Via C/C++,Fifth Edition , p.100]] STARTUPINFO si = { sizeof(si) }; PROCESS_INFORMATION pi; TCHAR szCommandLine[] = TEXT(“NOTEPAD”); CreateProcess(NULL, szCommandLine, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
但是如果传递的String是ANSI,就不会有问题。因为在WINDOWS内部,已经帮你处理为Unicode。
CreateProcess 认为 pszCommandLine的 第一个TOken是EXE可执行文件的名字,如果不带.exe后缀,会自动加上EXE。
搜索EXE顺序
-
当前运行程序的目录
-
Current Dir
-
Windows 系统目录 = GetSystemDirectory 返回值
-
Windows Dir
-
PATH环境变量
如果使用的是绝对路径,不会去搜索目录
当找到EXE路径后,Windows创建一个新进程,将EXE映射到内存空间中。然后系统CALL Linker 设置的entry point。 像之前说的,将pszCmd在exe 名字后的地址,作为 WinMain’s pszCmdLine parameter
上述这些都发生在pszApplicationName为NULL。如果传递了pszApplicationName,除非使用绝对路径,系统会在current directory中寻找,同时系统不会帮你加上.exe后缀。 如果在current directory找不到,直接failed。
[!PDF|] [[Windows Via C/C++,Fifth Edition .pdf#page=100&selection=150,0,154,40|Windows Via C/C++,Fifth Edition , p.101]] Even if you specify a filename in the pszApplicationName parameter, however, CreateProcess passes the contents of the pszCommandLine parameter to the new process as its command line
如果使用了applicationName,系统始终会通过applicationName来作为执行的EXE,但是它仍然会传递pszCommandLine作为参数。 Check这里可能就有一个点,如果EDR通过argv[0]来查看进程,可能就会有问题,或者修改PEB?
[[Windows Via C/C++,Fifth Edition .pdf#page=101&selection=155,50,159,9&color=yellow|Windows Via C/C++,Fifth Edition , p.101]]
psaProcess, psaThread, and bInheritHandles
在新检查创建的时候,有一个Process Kernel Object , 和 Thread Kernel Object 创建。你可以设置它们两个的安全描述符。 psaProcess和PsaTHread就是的。如果传递NULL,系统给它们默认的安全描述符。
另外也可以通过psa,来决定这两个Handle,是否会被之后的创建的子进程继承。 详见KernelObject[[012 KernelObjet#^b88c9a]]
bInheritHandles绝对,创建的子进程能否继承父进程的句柄。
下面的代码,ProcessA 创建了ProcessB,其中psaProcess设置了可继承,psaThread设置了不可继承。 然后创建了ProcessC,设置了bInheritHandles为True。 这个时候,ProcessC就可以继承进程B的Process 句柄,不能继承B的Thread句柄。
fdwCreate
fdwCreate指定进程如何被创建,可以使用OR来进行组合。
-
DEBUG_PROCESS 告诉系统父进程想要调试子进程,以及子进程派生的所有进程。当子进程发生Events时,通知父进程
-
DEBUG_ONLY_THIS_PROCESS 与DEBUG_PROCESS 类似,但是只在最近的子进程发生Events时通知,孙进程不会进行通知
-
CREATE_SUSPENDED 新进程被创建但是,主线程被不会执行。可以在这是修改子进程内存,或者添加JOB、修改主线程优先级。当完成修改后,使用ResumeThread 函数恢复
-
DETACHED_PROCESS 阻止子进程将输出发送的父进程的命令行窗口。如果指定这个FALG,新进程需要发送输出到新的控制台窗口。,通过
AllocConsole
创建它自己的Console -
CREATE_NEW_CONSOLE 告诉系统为进进程创建一个新窗口。如果 CREATE_NEW_CONSOLE 和 DETACHED_PROCESS 同时声明,会产生错误
-
CREATE_NO_WINDOW 告诉系统不要为新进程创建任何控制台窗口,可以使用这个FLAG,创建一个进程没有用户界面。
-
CREATE_NEW_PROCESS_GROUP 修改当用户按 Ctrl + C 或者 CTRL + Break 时,通知的进程组。通过这个FLAG,可以创建一个新的进程LIST,当用户Ctrl + C时,只通知它一个
-
CREATE_DEFAULT_ERROR_MODE 子进程不继承父进程的[[#进程的错误模式 | ErrorMode]]
-
CREATE_SEPARATE_WOW_VDM 当运行16bite的程序时,新建一个VDM(Virtual DOS Machine)来运行。默认情况下所有16bit程序在一个虚拟环境中运行。优势是如果程序Crash,其他的VDM不会受到影响,劣势就是吃内存
-
CREATE_SHARED_WOW_VDM 默认情况下所有16bit程序在一个VDM中运行。但是如果修改 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\WOW的key DefaultSeparate 为YES,然后使用这个FALG,程序就会在 system’s shared VDM(??) 中运行。
如果想要检查一个32bit程序是否在64bit下运行,call IsWow64Process ,将Process Hanlde 作为第一个参数,如果返回Ture,就是的。
-
CREATE_UNICODE_ENVIRONMENT 子进程 environment block 包含 Unicode 字符,默认情况下 environment block 包含 ANSI 字符
-
CREATE_FORCEDOS 强制系统运行MS_DOS程序在16bits OS/2 程序中 ??
-
CREATE_BREAKAWAY_FROM_JOB JOB中的一个进程,spawn一个新进程 不关联JOB
-
EXTENDED_STARTUPINFO_PRESENT STARTUP-INFOEX 结构体,通过psiStartInfo 参宿传递
fdwCreate同样运行你定义进程的优先级,一般这件事系统自己做。
优先级影响进程中的线程与其他进程中的线程如何安排。
pvEnvironment
指向新进程使用的环境字符串,大部分情况下传递NULL,继承父进程的环境。 PVOID GetEnvironmentStrings();
返回调用进程的环境字符串,可以将它的返回值作为pvEnvironment参数。当pvEnvironment为NULL时,系统就是这么做的。 当不需要这块内存时,使用BOOL FreeEnvironmentStrings(PTSTR pszEnvironmentBlock);
pszCurDir
设置子进程的当前Drive和路径。如果为NULL,和父进程一样。
如果不为NULL,指向以0结尾的目录,目录必须包含Drive。
psiStartInfo
指向STARTUPINFO
或者STARTUPINFOEX
结构体
typedef struct _STARTUPINFOA {
DWORD cb;
LPSTR lpReserved;
LPSTR lpDesktop;
LPSTR lpTitle;
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
WORD cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFOA, *LPSTARTUPINFOA;
typedef struct _STARTUPINFOEXA {
STARTUPINFOA StartupInfo;
LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
} STARTUPINFOEXA, *LPSTARTUPINFOEXA;
在大部分情况下,使用默认的结构体来创建子进程。设置cb为当前结构体的size,然后其他的设置为0.
STARTUPINFO si = { sizeof(si) };
CreateProcess(..., &si, ...);
注意必须要设置为0 ,不然会将栈上的数据传给它,这就不能保证新进程一定创建成功了。
下表描述了所有的成员,有的成员只在创建窗口是有用,有的只在创建Console时有用。
成员 | Windows 、Console、Both | 作用 |
---|---|---|
cb | Both | 记录StartUPInnfo的字节数,起到版本识别的作用。当Windows在将来扩展了StartUPINFO时,通过size来确认是哪个结构体。目前值只能为 sizeof(STARTUPINFO) 或者 sizeof(STARTUPINFOEX) |
lpReserved | Both | 保留字段,必须从为NULL |
lpDesktop | Both | 指定新进程需要关联的桌面名称,如果名称不存在。使用默认的桌面属性和这个名称创建一个新桌面。 大多数情况下为NULL新检查关联当前桌面 |
lpTitle | Console | 控制台的Title名称,如果为NULL,使用EXE文件名称 |
dwX dwY | Both | 以像素为单位执行程序窗口放置在屏幕上的位置。 只用当子进程创建第一个个重叠的窗口使用CW_USEDEFAULT 作为 CreateWindow的x参数是才会使用? 如果是控制台,相对于父进程左上角的位置 |
dwXSize dwYSize | Both | 进程的宽和高,只有当子进程创建它第一个重叠窗口使用 CW_USEDEFAULT 作为 CreateWindow 的 nWidth时才被使用 对于console指定宽和高 |
dwXCountChars dwYCountChars | Console | 对于子进程控制台窗口,通过字符指定宽和高 |
dwFillAttribute | Console | 指定文字和背景颜色 |
dwFlags | Both | 一系列的Flag,标识startupinfo 那些成员会被使用 |
wShowWindow | Window | 指定进程的主窗口如何出现。在ShowWindows函数中将使用wShowWindow而不是nCmdShow. 在后续的调用中wShowWindow的值 只会在 SW_SHOW-DEFAULT 被传递给ShowWindow时使用。 注意:只有dwFlags 设置了 STARTF_USESHOWWINDOW ,wShowWindow 才会被使用 |
cbReserved2 | Both | 保留必须设置为0 |
lpReserved2 | Both | 保留必须设置为0 这两个参数在C中使用_dospawn来创建新进程时使用,详细看VC/crt/src\dospawn.c 和 ioinit.c |
hStdInput hStdOutput hStdError | Console | 指定Console输入和输出的Hanle。 默认情况下 hStdInput 指向键盘 hStdOutput和hStdError 指向Console 当你需要重定向输入输出时使用 |