Main()被调用之前Windows做了什么?

Windows必须做一些事情来parsingPE头,在内存中加载可执行文件,并将命令行parameter passing给main()

使用OllyDbg我已经设置debugging器打破在main(),所以我可以查看调用堆栈: http://puu.sh/t5vxB/3d52089d22.png

好像符号不见了,所以我们不能得到函数的名字,就像看到的内存地址一样。 但是我们可以看到main的调用者是kernel32.767262C4 ,它是ntdll.77A90FD9的被调用者。 ntdll.77A90FA4堆栈底部,我们看到返回到ntdll.77A90FA4 ,我认为这是第一个运行可执行程序的函数。 看起来像传递给该函数的值得注意的参数是Windows的“结构化exception处理程序”地址和可执行文件的入口点。

那么这些函数究竟如何将程序加载到内存中并准备好执行入口点呢? debugging器在main()之前显示OS执行的整个进程的内容?

如果调用CreateProcess系统内部调用ZwCreateThread[Ex]来创建进程中的第一个线程

当你创建线程 – 你(如果你直接调用ZwCreateThread )或者系统初始化新线程的CONTEXT记录 – 这里Eip(i386)Rip(amd64)是线程的入口点。 如果你这样做 – 你可以指定任何地址。 但是当你打电话说Create[Remote]Thread[Ex] – 我怎么说 – 系统填充CONTEXT ,它设置自我例程作为线程入口点。 您的原始入口点保存在Rcx(amd64) Eax(i386)Rcx(amd64)寄存器中。

这个例程的名字取决于Windows版本。

早期,这是从kernel32.dll BaseThreadStartThunkBaseProcessStartThunk (从CreateProcess调用的情况下)。

但是现在系统从ntdll.dll指定RtlUserThreadStartRtlUserThreadStart通常调用kernel32.dll BaseThreadInitThunk (本机(boot执行)应用程序除外,如smss.exechkdsk.exe ,它们本身没有kernel32.dll )。 BaseThreadInitThunk已经调用了你的原始线程入口点,并在(if)之后返回 – 调用了RtlExitUserThread

在这里输入图像说明 在这里输入图像说明 这个通用线程启动包装器的主要目标 – 设置最高级别的SEH过滤器。 只是因为这个我们可以调用SetUnhandledExceptionFilter函数。 如果线程从入口点直接启动,而没有包装 – 顶级异常过滤器的功能变得不可用。

但无论线程入口点 – 线程在用户空间 – 永远不要从这一点开始执行!

当用户模式线程开始执行时 – 系统插入APCLdrInitializeThunk作为Apc-routine进行线程化 – 这是通过将线程CONTEXT复制(保存)到用户栈,然后调用KiUserApcDispatcher来调用LdrInitializeThunk 。 当LdrInitializeThunk完成时 – 我们返回KiUserApcDispatcher ,它用保存的线程CONTEXT调用NtContinue – 只有在这个已经线程入口点开始执行之后。

但现在系统在这个过程中做了一些优化 – 将线程CONTEXT复制(保存)到用户堆栈并直接调用LdrInitializeThunk 。 在这个函数的NtContinue调用NtContinue – 并且正在执行线程入口点。

所以每个线程都从LdrInitializeThunk用户模式开始执行。 ( 这个函数具有名称存在,并在从nt4到win10的所有Windows版本中调用

在这里输入图像说明 在这里输入图像说明 这个功能是做什么的? 这是什么? 你可能正在听DLL_THREAD_ATTACH通知? 当进程中的新线程开始执行(除了特殊系统工作的线程,例如LdrpWorkCallback ) – 他通过加载的DLL列表,并使用DLL_THREAD_ATTACH通知调用DLL入口点(当然,如果DLL有入口点和DisableThreadLibraryCalls不调用此DLL )。 但是这是如何实施的? 感谢LdrInitializeThunk调用LdrpInitialize – > LdrpInitializeThread – > LdrpCallInitRoutine (用于DLL EP)

在这里输入图像说明 当第一个线程正在进行时 – 这是特例。 需要为流程初始化做许多额外的工作。 此时只有两个模块在加载ntdll.dllntdll.dllLdrInitializeThunk为此作业调用LdrpInitializeProcess 。 如果很简单:

  1. 初始化不同的进程结构
  2. 加载所有DLL(和他们的家属)的EXE静态链接 – 但不能叫他们EPs!
  3. 称为LdrpDoDebuggerBreak – 这个函数看起来 – 是调试器附加到进程,如果是 – int 3调用 – 所以调试器接收异常消息 – STATUS_BREAKPOINT – 大多数调试器只能从这一点开始UI调试。 但是,存在调试器(S),让我们从LdrInitializeThunk调试过程 – 从这种调试器的所有我的屏幕截图
  4. 重要的一点 – 直到在进程中执行的代码只能从ntdll.dll (可能来自kernel32.dll ) – 代码来自另一个DLL,任何第三方代码在执行过程中还没有。
  5. 可选加载的shim dll来处理 – Shim引擎初始化。 但是这是可选的
  6. 通过加载的DLL列表,并使用DLL_PROCESS_DETACH调用其EP
  7. TLS初始化和TLS回调调用(如果存在)

  8. ZwTestAlert被调用 – 这个调用检查在线程队列中是否存在APC,并执行它。 这一点存在于从NT4到10的所有版本中。例如,在暂停状态下创建进程,然后向其线程( PROCESS_INFORMATION.hThread )插入APC调用( QueueUserAPC ) – 结果这个调用将在进程结束后执行完全初始化,全部DLL_PROCESS_DETACH ,但在EXE入口点之前。 在第一个进程线程的上下文中。

  9. NtContinue最后调用 – 这个恢复保存了线程上下文,最后我们跳到线程EP

在这里输入图像说明 在这里输入图像说明 请阅读CreateProcess的流程