Windows必须做一些事情来parsingPE头,在内存中加载可执行文件,并将命令行parameter passing给main()
。
使用OllyDbg我已经设置debugging器打破在main(),所以我可以查看调用堆栈:
好像符号不见了,所以我们不能得到函数的名字,就像看到的内存地址一样。 但是我们可以看到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
BaseThreadStartThunk
或BaseProcessStartThunk
(从CreateProcess
调用的情况下)。
但是现在系统从ntdll.dll
指定RtlUserThreadStart
。 RtlUserThreadStart
通常调用kernel32.dll
BaseThreadInitThunk
(本机(boot执行)应用程序除外,如smss.exe
和chkdsk.exe
,它们本身没有kernel32.dll
)。 BaseThreadInitThunk
已经调用了你的原始线程入口点,并在(if)之后返回 – 调用了RtlExitUserThread
。
这个通用线程启动包装器的主要目标 – 设置最高级别的SEH
过滤器。 只是因为这个我们可以调用SetUnhandledExceptionFilter
函数。 如果线程从入口点直接启动,而没有包装 – 顶级异常过滤器的功能变得不可用。
但无论线程入口点 – 线程在用户空间 – 永远不要从这一点开始执行!
当用户模式线程开始执行时 – 系统插入APC
以LdrInitializeThunk
作为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.dll
和ntdll.dll
。 LdrInitializeThunk
为此作业调用LdrpInitializeProcess
。 如果很简单:
LdrpDoDebuggerBreak
– 这个函数看起来 – 是调试器附加到进程,如果是 – int 3
调用 – 所以调试器接收异常消息 – STATUS_BREAKPOINT
– 大多数调试器只能从这一点开始UI调试。 但是,存在调试器(S),让我们从LdrInitializeThunk
调试过程 – 从这种调试器的所有我的屏幕截图 ntdll.dll
(可能来自kernel32.dll
) – 代码来自另一个DLL,任何第三方代码在执行过程中还没有。 DLL_PROCESS_DETACH
调用其EP TLS初始化和TLS回调调用(如果存在)
ZwTestAlert
被调用 – 这个调用检查在线程队列中是否存在APC,并执行它。 这一点存在于从NT4到10的所有版本中。例如,在暂停状态下创建进程,然后向其线程( PROCESS_INFORMATION.hThread
)插入APC调用( QueueUserAPC
) – 结果这个调用将在进程结束后执行完全初始化,全部DLL_PROCESS_DETACH
,但在EXE入口点之前。 在第一个进程线程的上下文中。
请阅读CreateProcess的流程