简体   繁体   English

线程进入和线程启动之间的确切区别和关系是什么?

[英]What is the exact difference and the relation between thread entry and thread start?

  1. What is the exact difference between thread entry and thread start ? 线程进入和线程启动之间的确切区别是什么? and
  2. does RIP (where the execution front is, in a dynamic analysis) always reaches them in the same predictable order? RIP(在动态分析中执行前沿在哪里)总是以相同的可预测顺序到达它们吗?
  3. is thread entry changing dynamically (in dynamic analysis I think I saw it being reported in registers and stack)?线程条目是否动态变化(在动态分析中我想我看到它在寄存器和堆栈中报告)?

I understand so far that thread start is defined from a point of view, eg., in Windows, it's always ntdll.RtlUserThreadStart+21 (User) but at the program library level, it can be any function.到目前为止,我了解thread start是从一个角度定义的,例如,在 Windows 中,它始终是ntdll.RtlUserThreadStart+21 (用户),但在程序库级别,它可以是任何 function。 But the thread start is not called before the thread is created ntdll.NtCreateThreadEx+14 (System).但是在创建线程之前不会调用thread start ntdll.NtCreateThreadEx+14 (系统)。

The thread entry is the (library ie., exported, or private) function given as argument to the thread create function. thread entry是(库,即导出的或私有的)function,作为thread create function 的参数给出。

An example of a callstack with threads (threadID, Address, to, from, size, comment, party) made with x64dbg:使用 x64dbg 制作的带有线程(threadID、Address、to、from、size、comment、party)的调用堆栈示例:

4200                                                                                                 
      00000076EBDFF9A8 00007FFEC900A34E 00007FFECB4EC034 A0  ntdll.NtWaitForSingleObject+14          System
      00000076EBDFFA48 00007FF7987B48A1 00007FFEC900A34E 30  kernelbase.WaitForSingleObjectEx+8E     User
      00000076EBDFFA78 00007FF7988961A0 00007FF7987B48A1 30  mylibrarydll0.00007FF7987B48A1          User
      00000076EBDFFAA8 00007FF7987B13DF 00007FF7988961A0 30  mylibrarydll0.00007FF7988961A0          User
      00000076EBDFFAD8 00007FF798B4A175 00007FF7987B13DF 30  mylibrarydll0.00007FF7987B13DF          User
      00000076EBDFFB08 00007FFECA637034 00007FF798B4A175 30  mylibrarydll0.sub_7FF798B4A0B4+C1       System
      00000076EBDFFB38 00007FFECB49D0D1 00007FFECA637034 80  kernel32.BaseThreadInitThunk+14         System
      00000076EBDFFBB8 0000000000000000 00007FFECB49D0D1     ntdll.RtlUserThreadStart+21             User
2736                                                                                                 
      00000076EB5FF648 00007FFECB4623D7 00007FFECB4EFA04 300 ntdll.NtWaitForWorkViaWorkerFactory+14  System
      00000076EB5FF948 00007FFECA637034 00007FFECB4623D7 30  ntdll.TppWorkerThread+2F7               System
      00000076EB5FF978 00007FFECB49D0D1 00007FFECA637034 80  kernel32.BaseThreadInitThunk+14         System
      00000076EB5FF9F8 0000000000000000 00007FFECB49D0D1     ntdll.RtlUserThreadStart+21             User
2468                                                                                                 
      00000076EBBFFB78 00007FFEC900A34E 00007FFECB4EC034 A0  ntdll.NtWaitForSingleObject+14          System
      00000076EBBFFC18 00007FF7987B48A1 00007FFEC900A34E 30  kernelbase.WaitForSingleObjectEx+8E     User
      00000076EBBFFC48 00007FF7988961A0 00007FF7987B48A1 30  mylibrarydll0.00007FF7987B48A1          User
      00000076EBBFFC78 00007FF7987B13DF 00007FF7988961A0 30  mylibrarydll0.00007FF7988961A0          User
      00000076EBBFFCA8 00007FF798B4A175 00007FF7987B13DF 30  mylibrarydll0.00007FF7987B13DF          User
      00000076EBBFFCD8 00007FFECA637034 00007FF798B4A175 30  mylibrarydll0.sub_7FF798B4A0B4+C1       System
      00000076EBBFFD08 00007FFECB49D0D1 00007FFECA637034 80  kernel32.BaseThreadInitThunk+14         System
      00000076EBBFFD88 0000000000000000 00007FFECB49D0D1     ntdll.RtlUserThreadStart+21             User
3784                                                                                                 
      00000076EB6FFB88 00007FFECB4623D7 00007FFECB4EFA04 300 ntdll.NtWaitForWorkViaWorkerFactory+14  System
      00000076EB6FFE88 00007FFECA637034 00007FFECB4623D7 30  ntdll.TppWorkerThread+2F7               System
      00000076EB6FFEB8 00007FFECB49D0D1 00007FFECA637034 80  kernel32.BaseThreadInitThunk+14         System
      00000076EB6FFF38 0000000000000000 00007FFECB49D0D1     ntdll.RtlUserThreadStart+21             User
1928                                                                                                 
      00000076EB7FFA48 00007FFEC900A34E 00007FFECB4EC034 A0  ntdll.NtWaitForSingleObject+14          System
      00000076EB7FFAE8 00007FF7987B48A1 00007FFEC900A34E 30  kernelbase.WaitForSingleObjectEx+8E     User
      00000076EB7FFB18 00007FF7988961A0 00007FF7987B48A1 30  mylibrarydll0.00007FF7987B48A1          User
      00000076EB7FFB48 00007FF7987B13DF 00007FF7988961A0 30  mylibrarydll0.00007FF7988961A0          User
      00000076EB7FFB78 00007FF798B4A175 00007FF7987B13DF 30  mylibrarydll0.00007FF7987B13DF          User
      00000076EB7FFBA8 00007FFECA637034 00007FF798B4A175 30  mylibrarydll0.sub_7FF798B4A0B4+C1       System
      00000076EB7FFBD8 00007FFECB49D0D1 00007FFECA637034 80  kernel32.BaseThreadInitThunk+14         System
      00000076EB7FFC58 0000000000000000 00007FFECB49D0D1     ntdll.RtlUserThreadStart+21             User
2276                                                                                                 
      00000076EB8FF7C8 00007FFEC900A34E 00007FFECB4EC034 A0  ntdll.NtWaitForSingleObject+14          System
      00000076EB8FF868 00007FF7987B48A1 00007FFEC900A34E 30  kernelbase.WaitForSingleObjectEx+8E     User
      00000076EB8FF898 00007FF7988961A0 00007FF7987B48A1 30  mylibrarydll0.00007FF7987B48A1          User
      00000076EB8FF8C8 00007FF7987B13DF 00007FF7988961A0 30  mylibrarydll0.00007FF7988961A0          User
      00000076EB8FF8F8 00007FF798B4A175 00007FF7987B13DF 30  mylibrarydll0.00007FF7987B13DF          User
      00000076EB8FF928 00007FFECA637034 00007FF798B4A175 30  mylibrarydll0.sub_7FF798B4A0B4+C1       System
      00000076EB8FF958 00007FFECB49D0D1 00007FFECA637034 80  kernel32.BaseThreadInitThunk+14         System
      00000076EB8FF9D8 0000000000000000 00007FFECB49D0D1     ntdll.RtlUserThreadStart+21             User
12168                                                                                                
      00000076EB9FF6E8 00007FFECB4623D7 00007FFECB4EFA04 300 ntdll.NtWaitForWorkViaWorkerFactory+14  System
      00000076EB9FF9E8 00007FFECA637034 00007FFECB4623D7 30  ntdll.TppWorkerThread+2F7               System
      00000076EB9FFA18 00007FFECB49D0D1 00007FFECA637034 80  kernel32.BaseThreadInitThunk+14         System
      00000076EB9FFA98 0000000000000000 00007FFECB49D0D1     ntdll.RtlUserThreadStart+21             User
2428                                                                                                 
      00000076EBAFF5D8 00007FFECB4623D7 00007FFECB4EFA04 300 ntdll.NtWaitForWorkViaWorkerFactory+14  System
      00000076EBAFF8D8 00007FFECA637034 00007FFECB4623D7 30  ntdll.TppWorkerThread+2F7               System
      00000076EBAFF908 00007FFECB49D0D1 00007FFECA637034 80  kernel32.BaseThreadInitThunk+14         System
      00000076EBAFF988 0000000000000000 00007FFECB49D0D1     ntdll.RtlUserThreadStart+21             User

Windows sends the debugger a specific set of events, you can find them in the documentation of WaitForDebugEvent . Windows 向调试器发送一组特定的事件,您可以在WaitForDebugEvent的文档中找到它们。
One of these events is CREATE_THREAD_DEBUG_INFO , which is sent when Windows has created but not yet started the thread .其中一个事件是CREATE_THREAD_DEBUG_INFO它在 Windows 已创建但尚未启动线程时发送
In Windows, process and thread creation happens in the kernel but their final initialization steps happen in userspace (unless it's a picoprocess, which we won't address here).在 Windows 中,进程和线程的创建发生在 kernel 中,但它们的最终初始化步骤发生在用户空间中(除非它是一个 pico 进程,我们不会在这里讨论)。 The DLL ntdll.dll is mapped in the thread just after it's been created and the thread context's RIP is set to point to one of this DLL's functions. DLL ntdll.dll在线程被创建之后被映射到线程中,并且线程上下文的RIP被设置为指向这个 DLL 的函数之一。 This function will perform the necessary initializations and then jump to the address given in CreateThread or similar.这个 function 将执行必要的初始化,然后跳转到CreateThread或类似的地址。 This function is kind of a wrapper for threads.这个 function 是一种线程包装器。

It is quite granted that thread start happens when the first instruction of the initialization function is about to execute (think of it as if Windows had set a breakpoint there).当初始化 function 的第一条指令即将执行时,线程启动是理所当然的(认为它好像 Windows 在那里设置了一个断点)。
The thread entry is, instead, just the address given to the thread creation API.相反,线程条目只是给线程创建 API 的地址。 It is important because it is the actual code the caller intended to be executed.这很重要,因为它是调用者打算执行的实际代码。 In fact, for debugging or RE purposes, you can almost (if not always) ignore the thread start event.事实上,出于调试或 RE 的目的,您几乎可以(如果不是总是)忽略线程启动事件。


Let's do an example.让我们做一个例子。 Consider this simple 64-bit program.考虑这个简单的 64 位程序。

BITS 64

EXTERN CreateThread 
GLOBAL _start 

SECTION .text 

_start:
    and rsp, -16 
    
    push 0
    push 0
    sub rsp, 20h
    xor r9, r9 
    lea r8, [REL _thread1]
    xor edx, edx 
    xor ecx, ecx 
    call CreateThread

.loop:
    TIMES 1000 pause 
jmp .loop

_thread1:
    TIMES 1000 pause
jmp _thread1 

All it does is create a thread pointing to a sled of pause instructions executed in a loop.它所做的只是创建一个线程,指向在循环中执行的一组pause指令。 The main thread will also execute a similar, but different, loop.主线程也将执行一个类似但不同的循环。
The purpose of the loop is to have the RIP of the threads change but still not being inside a Windows API.循环的目的是让线程的RIP发生变化,但仍然不在 Windows API 内。 Any instruction in the loop, granted it doesn't fault, will be fine.循环中的任何指令,只要它没有错误,都可以。 I picked pause , because:)我选择了pause ,因为:)

Assemble and link the program.组装和链接程序。
Open x64dbg, open the program, and then set the Thread start and Thread entry events.打开x64dbg,打开程序,然后设置Thread startThread entry事件。 x96dbg 的调试事件

Now press F9 to reach the program entry point and press F9 again to let it go.现在按 F9 到达程序入口点,再次按 F9 让它 go。 The debugger will be notified of the new thread creation.调试器将收到新线程创建的通知。

创建了新线程

Note that the execution stopped at the beginning of RtlUserThreadStart .请注意,执行在RtlUserThreadStart的开头停止。 This is always the case for my version of Windows (Windows 7 something).我的 Windows 版本(Windows 7 的东西)总是这种情况。 It makes sense, given the introduction at the beginning of this answer.鉴于此答案开头的介绍,这是有道理的。
Also note that the thread entry point is in rcx , meaning it is the first parameter for RtlUserThreadStart .另请注意,线程入口点位于rcx中,这意味着它是RtlUserThreadStart的第一个参数。

Now, this was the event that Windows sent to the debugger, so it's natural the execution stopped here.现在,这是 Windows 发送到调试器的事件,所以执行在这里停止是很自然的。
But the thread entry event doesn't exist , what is x64dbg doing here?但是线程进入事件不存在,x64dbg在这里做什么?
You can unveil this mystery by looking at the breakpoint tab.您可以通过查看断点选项卡来揭开这个谜团。

断点

You see that the debugger set a one-time (ie it will be removed automatically by the debugger itself) breakpoint at the thread entry point.您会看到调试器在线程入口点设置了一次性(即调试器本身会自动删除)断点。
So, while Windows doesn't offer support for generating a debug event when a thread first starts executing its user code, a debugger can emulate it easily by putting a breakpoint there before the thread actually start.因此,虽然 Windows 不支持在线程第一次开始执行其用户代码时生成调试事件,但调试器可以通过在线程实际启动之前放置断点来轻松模拟它。
Note that this means the debugger always react to the thread start events, when disabled in the options it will simply not stop, show and wait for you to do something.请注意,这意味着调试器总是对线程启动事件做出反应,当在选项中禁用时,它根本不会停止、显示并等待你做某事。


Pausing and resuming the thread doesn't change the thread entry point, which is fixed at thread creation.暂停和恢复线程不会更改线程入口点,该入口点在线程创建时固定。
x64dbg has a threads tab that allows the user to suspend and resume the threads. x64dbg 有一个线程选项卡,允许用户暂停和恢复线程。 Playing with it doesn't change the thread entry point, just the RIP s that still point somewhere in the two loops (that exists for easing this test).使用它不会改变线程入口点,只是仍然指向两个循环中某处的RIP (存在用于简化此测试)。


If the thread is created with the suspend flag, the thread start event won't fire until the thread is resumed.如果线程是使用挂起标志创建的,则在线程恢复之前不会触发线程启动事件。
But if, before resuming the thread, a pair of calls to Get/SetThreadContext is done to change the thread's RIP , then RtlUserStartThread will never be executed (IDK what this function does exactly, but a thread can do without it) and the thread start event will never fire .但是,如果在恢复线程之前,对Get/SetThreadContext进行了一对调用以更改线程的RIP ,那么RtlUserStartThread将永远不会被执行(IDK 这个 function 确实做了什么,但是一个线程可以没有它)并且线程启动事件永远不会触发
The execution will go straight to the altered RIP .执行将 go 直接到更改后的RIP
I'm not sure if this is a legacy bug of Windows' debugging interface, the thread start event could be generated by setting the TF before the first schedule of the thread (and immediately removing it upon catching the relevant exception).我不确定这是否是 Windows 调试界面的遗留错误,可以通过在线程的第一个调度之前设置TF来生成线程启动事件(并在捕获相关异常时立即将其删除)。
When debugging/REing thread, what I usually do is putting a breakpoint in the thread entry point (which is easy to get) or in the hijacked RIP (which is also easy to get, since this kind of threads are created suspended, so you know something is fishy).在调试/REing线程时,我通常做的是在线程入口点(这很容易获得)或被劫持的RIP (这也容易获得,因为这种线程被创建为挂起,所以你知道某事是可疑的)。
If the program is being nasty and the code at the thread's RIP is not yet in clear (eg is still obfuscated), use a hardware breakpoint.如果程序很糟糕并且线程RIP中的代码还不清楚(例如仍然被混淆),请使用硬件断点。

Note This same whole thing happens for process creation too, exactly the same (only with the PE entry point instead of a thread entry point).注意进程创建也会发生同样的事情,完全相同(仅使用 PE 入口点而不是线程入口点)。

  1. The terms in question do not necessarily have precise definitions in common jargon.所讨论的术语不一定在常用术语中有精确的定义。 The x64dbg docs you linked give these definitions:您链接的 x64dbg 文档给出了以下定义:

Thread Entry线程条目

Set a single-shoot breakpoint on the entry of the thread when a thread is about to run.当线程即将运行时,在线程的入口处设置一个单射断点。

and

Thread Start线程开始

Pause when a new thread is about to run.当新线程即将运行时暂停。

These are that debugger's chosen labels for what are apparently different kinds of events to which it can alert you.这些是调试器为它可以提醒您的明显不同类型的事件选择的标签。 My interpretation, which is consistent with what you describe in the question, is that "thread start" is about the creation of a new thread, apparently in the context of the thread that does the creating, whereas "thread entry" is about execution of the code that will run in the new thread, presumably in the context of that thread.我的解释与您在问题中描述的一致,是“线程启动”是关于创建一个新线程,显然是在创建线程的上下文中,而“线程入口”是关于执行将在新线程中运行的代码,大概是在该线程的上下文中。

  1. I would be inclined to think that, in these terms, thread start must always happen before thread entry.我倾向于认为,在这些术语中,线程启动必须始终发生在线程进入之前。 Execution cannot enter the code of a thread before that thread has started.执行不能在线程启动之前输入线程的代码。 Indeed, I would be inclined to guess that a thread start event is the very last one the debugger can signal that definitely comes before thread entry for the thread in question.实际上,我倾向于猜测线程启动事件是调试器可以发出信号的最后一个事件,该事件肯定发生在相关线程的线程进入之前。

  2. In a general sense, I would expect a thread entry address to be the address of the thread's entry point function, or perhaps of the first instruction in its body (not necessarily the same thing).在一般意义上,我希望线程入口地址是线程入口点 function 的地址,或者可能是其主体中的第一条指令的地址(不一定是同一件事)。 This cannot be expected to be consistent for different entry-point functions, and it might not be the same for the same function on different runs of the program.不能期望这对于不同的入口点函数是一致的,并且对于相同的 function 在程序的不同运行中可能不一样。 If you think you see something else then consult the tool's documentation.如果您认为您看到了其他内容,请查阅该工具的文档。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM