windows启动以及exe文件的加载简介

先来看这样一个过程(非VISTA启动):CPU复位->传统BIOS->MBR->引导扇区->NTLDR->NTOS->SMSS->1:CSRSS 2:WinLogon->1:LSRSS 2:Services 3:LogonUI->2:Services->Svchost 3:LogonUI->USERINT->1:StartUp App 2:Shell

这是非 VISTA 的启动过程,是传统 Windows 的启动,这里有几处必要提到,首先 NTLDR 是一个非常重要的启动文件,在 C 盘下,相关的还有 boot.ini, pagefile.sys, NTDETECT.COM,我们按 F8 或者 F5 时进入安全模式的界面也是它产生的。然后是 NTOSKEL 这就是产生滚动条界面的,这是启动最漫长的过程!我曾经看到过有“高手”说过可以改掉滚动条滚动次数的注册表位置,其实这是不能改的,在这个界面下的过程非常漫长非常复杂,不提。然后是 SMSS 进程,这是第一个真正意义上由 .EXE 产生的进程。然后建出 CSRSS 子系统服务器进程和 WinLogon 进程。最后建出 Shell:Explorer.exe 进程。和本文最相关的就是这个 Shell 了,Shell “命令解释器”它的经典就是 Explorer.exe 。每一个窗口其实就是它创建的一个线程!这是一个非常厉害的多线程设计了!比如我们点击桌面上的图标启动一个程序,就是由它来加载创建的,好了,有了这些就开始来讲本文的其中一个内容:.EXE 的加载过程,然后再由此扩充开。

双击一个 .EXE 图标(操作系统怎么定位哪一个图标不是本文的内容),Shell 调用 CreateProcess 函数,系统找出在调用 CreateProcess 函数时的 .exe 文件,如果文件不在则返回 FALSE 进程创建失败。成功的话则为该进程创建一个新进程内核对象,这个内核对象是由系统内核管理的,关于内核对象也是一个很长的话题,详细叙述的话我写到明天恐怕都写不完这篇文章了,只要知道这个时候如果 CreateProcess 成功的话系统为这个进程创建了一个使用记数为1的进程内核对象,值得一提的是有一个进程内核对象句柄表,这里纪录了所有在该进程中所有创建出来的进程内核对象,但这个东西 Microsoft 没有提供相关文档,我的偶像 Jeffrey Richter 也只是简单的提了一提,接下来系统为这个新进程创建了一个私有地址空间,这就是我们很清楚的 4GB 地址空间! 写到这里先停住,我发现了一个比较大的问题,Shell 调用 CreateProcess 这个函数时其中的参数是哪里来的?我根据 Jeffrey Richter 的一句话也就是系统找出 EXE 文件,如果存在的话则创建成功,那么我进一步推论,那其中的参数很有可能是来自 PE 格式中的信息,可以继续推论下去,研究一下 CreateProcess 的参数再对照一下 PE 格式各个段的包含信息,那应该可以研究出某些东西来,这个细节我是还没有深入对比过,如果有哪位看官知道的话还请赐教!在这里先谢过了!接下来系统会保留一个足够大的区域来存放.exe 文件,默认的话是保存在从OXOO4OOOOO开始的位置,而这个数值是在 exe 这个 PE 结构中的信息,用 /BASE 是可以改的。这里就牵涉到内存映射文件了,不提。我们来研究一下 0X00400000 在内存中的什么区域,看一下 Win32 内存结构中4GB的划分(不再提 98 那破烂了)0x00000000 - 0x0000FFFF 这64KB用于 NULL 指针分配,举一个 Richter 的例子:int *pnSomeInteger = (int*)malloc(sizeof(int)); *pnSomeInteger = 5; 如果 malloc 不能找到足够的内存来分配的话那它就返回 NULL 就是保存在这里了,接下来 0x00010000 - 0x7FFEFFFF 这一段就是 Win32 进程私有的地址空间,刚才的.exe 加载就在这一段空间中,接下来继续加载程序执行所必须的 DLL 文件也是在这个段里,当然在这里还有一些数据需要加载,和为线程开辟的堆栈区,所以进程的这个用户区其实分成了3个部分:文本区包括指令和可读的数据,通常这个段是标记为可读,数据区,这个段对应 PE 中的 DATA_BASS 包含以初始化和未初始化的数据,静态变量也存放在这个区中,最后就是堆栈区,这是为线程分配的默认为1MB,当然是可以改的,在这里包含了函数参数和局部变量,当然也有现场保护时的信息,很关键的一点是 IP 指针也在这里,缓冲区溢出攻击就是在这个区域做文章,俗称:践踏堆栈。好!因为 Win32 中进程是一个惰性的,它什么也不执行,除了 EXE 和 DLL 还有它们所需的数据加载到进程地址空间外每个进程还拥有别的资源,这里只要记住:窗口和钩子是属于线程的外其他都是进程的,所以进程必须有一个线程来执行它的代码,这时候系统就为进程创建一个主线程,但它是调用 C 运行时库函数,这里又有区分 CUI 和 GUI,ANSI 和 UNIODE 的差别,如果以 GUI 和 UNICODE 来讲,它调用的是 wWinMainCTRStartup 这个可执行文件的启动函数。这个函数的功能不多讲,它会进行一系列初始化工作,完了后它将调用我们编写的主函数入口函数比如 wWinMain();最后 exit 调用调用操作系统的ExitProcess 函数,将 nMainRetVal 传递给它,这使得操作系统能够撤销进程并设置它的 exit 代码。到这里启动过程结束!

回顾上面的内容,就这一个启动过程实在复杂!复杂!牵涉到进程,线程,PE 结构,Win32 内存结构!而这些我看其实就是操作系统原理的核心部分了!上面的分析只是非常简单的提了个大概,里面的每一个内容我看都可以写成一本书了...再回顾一下 Windows 的启动过程,这两者之间的复杂程度何其的相似!有兴趣的可以去深入研究 Windows 的启动过程,单单就那个滚动条界面的时候, Windows 做了一大堆大堆的工作!想要用一篇短短的文章是不可能写完这些的。比如 PE 格式的有效性,Win32 内存管理,当然,很精彩的是我刚才提到的缓冲区溢出攻击,真佩服这个创始人!好了,写代码去了。



文章来自: 本站原创
Tags:
评论: 0 | 查看次数: 11456