使用OllyDbg我已将调试器设置为在main()上中断,因此我可以查看调用堆栈:
好像符号缺失所以我们无法获得函数名称,只能看到它的内存地址.但是我们可以看到main的调用者是kernel32.767262C4,它是ntdll.77A90FD9的被调用者.在堆栈的底部,我们看到RETURN到ntdll.77A90FA4,我认为它是第一个被调用来运行可执行文件的函数.看起来传递给该函数的值得注意的参数是Windows的结构化异常处理程序地址和可执行文件的入口点.
那么这些函数究竟是如何最终将程序加载到内存中并为入口点执行做好准备呢?调试器是否在main()之前显示操作系统执行的整个过程?
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已经调用了原始的线程入口点,并在(if)之后返回 – 调用了RtlExitUserThread
.
这个常见线程启动包装器的主要目标 – 设置顶级SEH
过滤器.只因为这个我们可以调用SetUnhandledExceptionFilter
功能.如果线程从您的入口点直接开始,没有包装器 – Top level Exception Filter的功能变得不可用.
但无论线程入口点 – 用户空间中的线程 – 从这一点开始执行!
用户模式线程开始执行时的早期 – 系统将APC连接到带有LdrInitializeThunk的线程作为Apc例程 – 这是通过将(保存)线程CONTEXT复制到用户堆栈然后调用调用LdrInitializeThunk的KiUserApcDispatcher来完成的.当LdrInitializeThunk完成时 – 我们返回KiUserApcDispatcher,它使用保存的线程CONTEXT调用NtContinue – 只有在此线程入口点开始执行之后.
但是现在系统在这个过程中做了一些优化 – 它将线程CONTEXT复制(保存)到用户堆栈并直接调用LdrInitializeThunk.在这个函数的末尾,NtContinue调用了 – 并且正在执行线程入口点.
所以每个线程都从LdrInitializeThunk开始在用户模式下执行. (具有确切名称的此函数存在并在从nt4到win10的所有Windows版本中调用)
这个功能是做什么的?这是什么?你可能会听到DLL_THREAD_ATTACH
的通知?当进程中的新线程开始执行时(特殊系统工作线程除外,如LdrpWorkCallback) – 他按加载的DLL列表行走,并用DLL_THREAD_ATTACH
通知调用DLL入口点(当然,如果DLL有入口点而DisableThreadLibraryCalls
没有为此DLL调用) ).但是如何实现呢?感谢LdrInitializeThunk调用LdrpInitialize – > LdrpInitializeThread – > LdrpCallInitRoutine(用于DLL EP)
当进程中的第一个线程开始时 – 这是特例.需要为流程初始化做许多额外的工作.此时只有两个模块加载进程–EXE和ntdll.dll. LdrInitializeThunk
为此作业调用LdrpInitializeProcess.如果非常简短:
>初始化不同的流程结构
>静态加载所有DLL(及其依赖项)到EXE
链接 – 但不称他们为EP!
>调用LdrpDoDebuggerBreak – 这个函数看 – 是调试器
附加到进程,如果是 – int 3调用 – 所以调试器
接收异常消息 – STATUS_BREAKPOINT – 大多数调试器都可以
开始UI调试只从这一点开始.但是存在
调试器,它允许从LdrInitializeThunk进行调试过程 –
我从这种调试器的截图
>重要的一点 – 直到进程中只执行代码
ntdll.dll(可能来自kernel32.dll) – 来自另一个的代码
DLL,尚未在进程中执行的任何第三方代码.
>可选加载的垫片dll进行处理 – Shim Engine初始化.但
这是可选的
>按加载的DLL列表行走并调用其EPDLL_PROCESS_DETACH
> TLS调用初始化和TLS回调(如果存在)
>调用ZwTestAlert – 此调用检查在线程中存在APC
队列,并执行它.这一点存在于从NT4到的所有版本中
win 10.这样就可以创建处于挂起状态的进程
然后将APC调用(QueueUserAPC
)插入其线程
(PROCESS_INFORMATION.hThread) – 结果这个调用将是
全部初始化后,执行完毕
调用DLL_PROCESS_DETACH,但在EXE入口点之前.在上下文中
第一个进程线程.
>和NtContinue最后调用 – 这个恢复保存的线程上下文
我们终于跳到EP了