如果你调用CreateProcess系统内部调用ZwCreateThread[Ex]来创建进程中的第一个线程
当你创建线程时——你(如果你直接调用ZwCreateThread)或系统为新线程初始化CONTEXT记录——这里Eip(i386)或Rip(amd64)是线程的入口点。如果你这样做 - 你可以指定任何地址。但是当你打电话说Create[Remote]Thread[Ex] - 我怎么说 - 系统填充CONTEXT 并将自身例程设置为线程入口点。您的原始入口点保存在Eax(i386) 或Rcx(amd64) 寄存器中。
此例程的名称取决于 Windows 版本。
早期这是来自kernel32.dll 的BaseThreadStartThunk 或BaseProcessStartThunk(以防CreateProcess 调用)。
但现在系统从 ntdll.dll 指定 RtlUserThreadStart 。 RtlUserThreadStart 通常从kernel32.dll 调用BaseThreadInitThunk(除了本机(启动执行)应用程序,如smss.exe 和chkdsk.exe,它们在自身地址空间中根本没有kernel32.dll)。 BaseThreadInitThunk 已经调用了您的原始线程入口点,并且在(如果)它返回之后 - 调用了 RtlExitUserThread。
这个公共线程启动包装器的主要目标 - 设置顶级SEH 过滤器。仅仅因为这个我们可以调用SetUnhandledExceptionFilter函数。如果线程直接从您的入口点开始,没有包装器 - Top level Exception Filter 的功能将不可用。
但无论线程入口点是什么——用户空间中的线程——绝不从这一点开始执行!
当用户模式线程开始执行的早期-系统将APC插入到线程中,LdrInitializeThunk作为Apc例程-这是通过将线程CONTEXT复制(保存)到用户堆栈然后调用KiUserApcDispatcher来完成的LdrInitializeThunk。当LdrInitializeThunk 完成时——我们返回到KiUserApcDispatcher,它调用了NtContinue,并保存了线程CONTEXT——只有在这个已经线程入口点开始执行之后。
但是现在系统在这个过程中做了一些优化——它将线程CONTEXT复制(保存)到用户堆栈并直接调用LdrInitializeThunk。在这个函数的末尾NtContinue 被调用 - 并且正在执行线程入口点。
所以 EVERY 线程从 LdrInitializeThunk 开始在用户模式下执行。 (这个名字完全正确的函数存在并在从 nt4 到 win10 的所有 windows 版本中调用)
这个功能是做什么的?这是为了什么?您可能正在收听DLL_THREAD_ATTACH 通知?当进程中的新线程开始执行时(特殊系统工作线程除外,如LdrpWorkCallback)-他通过加载的DLL列表,并使用DLL_THREAD_ATTACH通知调用DLL入口点(当然如果DLL有入口点和@987654337 @ 未调用此 DLL)。但是这是如何实现的?感谢LdrInitializeThunk 调用LdrpInitialize -> LdrpInitializeThread -> LdrpCallInitRoutine(用于 DLL EP)
当进程中的第一个线程开始时 - 这是特殊情况。需要为进程初始化做许多额外的工作。此时只有两个模块正在加载 - EXE 和 ntdll.dll 。 LdrInitializeThunk
致电LdrpInitializeProcess 获取这份工作。如果非常简短:
- 不同的进程结构被初始化
- 静态加载 EXE 的所有 DLL(及其依赖项)
链接 - 但不称他们为 EP!
- 调用
LdrpDoDebuggerBreak - 这个函数看起来 - 是调试器
附加到进程,如果是 - int 3 调用 - 所以调试器
接收异常消息 - STATUS_BREAKPOINT - 大多数调试器都可以
开始 UI 调试只能从这一点开始。然而存在
从LdrInitializeThunk 作为调试进程的调试器 -
我所有来自这种调试器的屏幕截图
- 重要一点 - 直到在进程中执行的代码仅来自
ntdll.dll(可能来自kernel32.dll) - 来自另一个的代码
DLL,任何尚未在进程中执行的第三方代码。
- 可选加载的垫片 dll 来处理 - 垫片引擎已初始化。但
这是可选的
- 遍历加载的 DLL 列表并调用其 EP
DLL_PROCESS_DETACH
TLS 调用的初始化和 TLS 回调(如果存在)
ZwTestAlert 被调用 - 此调用检查是否存在线程中的 APC
队列,并执行它。这点存在于从NT4到所有版本
win 10. 这让例如创建处于挂起状态的进程
然后将 APC 调用 (QueueUserAPC) 插入到它的线程
(PROCESS_INFORMATION.hThread) - 结果这个电话将是
进程将完全初始化后执行,所有
DLL_PROCESS_DETACH 调用,但在 EXE 入口点之前。在上下文中
第一个进程线程。
- 最后调用了 NtContinue - 这恢复了保存的线程上下文
最后我们跳到线程 EP
另请阅读Flow of CreateProcess