我如何进入线程函数(以便我可以逐步跟踪),只知道线程的句柄。我使用 OllyDbg 进行跟踪,线程是通过 API 创建的ZwCreateProcess()。但是,我看到的有关此 API 的文档不包含创建标志和指向它将执行的已定义函数的指针,而我都需要这些。
有没有办法进入线程函数,只知道线程句柄?另外,除了CreateThread()and之外,还有其他方法可以创建挂起的线程CreateRemoteThread()吗?
我如何进入线程函数(以便我可以逐步跟踪),只知道线程的句柄。我使用 OllyDbg 进行跟踪,线程是通过 API 创建的ZwCreateProcess()。但是,我看到的有关此 API 的文档不包含创建标志和指向它将执行的已定义函数的指针,而我都需要这些。
有没有办法进入线程函数,只知道线程句柄?另外,除了CreateThread()and之外,还有其他方法可以创建挂起的线程CreateRemoteThread()吗?
有没有办法进入线程函数,只知道线程句柄?
是的,这是一个 2 步过程。
步骤 1 - 将线程句柄转换为线程 ID
在Process Explorer的菜单栏中,检查以下内容:
接下来,在 Process Explorer 的进程列表中选择您的目标进程。然后,您将在下方窗格中看到该进程的句柄列表,包括线程句柄。找到与您的目标句柄关联的线程 ID。对于下面的示例,线程句柄0x228与线程 ID 相关联3000:

尽管在 Process Explorer 中句柄值以十六进制显示,但线程 ID 以十进制显示。因此3000,十进制的线程 ID等于0xBB8十六进制的线程 ID 。
第 2 步 - 查找线程 ID 的 EIP
在 OllyDbg 的菜单栏中,选择View → Threads。右键单击Ident与您在步骤 1 中找到的线程 ID 对应的线程(0xBB8在下面的示例中),然后选择Show registers:

这将显示EIP该线程的当前值,即该线程恢复后要执行的下一条指令:

替代步骤 2 - 查找线程 ID 的 EIP
如果目标线程在挂起状态下创建并且尚未恢复,则该线程将不会出现在 OllyDbg 的线程窗口中。在这种情况下,您可以使用LiveKd通过发出 LiveKd 命令来查找线程的起始地址!thread -t <thread ID in hexadecimal>
kd> !thread -t BB8
Cid handle table at 88e01108 with 944 entries in use
THREAD 86B4E548 Cid 169c.0bb8 Teb: 7ffdb000 Win32Thread: 00000000 WAIT: (Suspended) KernelMode Non-Alertable
SuspendCount 1
FreezeCount 1
86b4ec28 Semaphore Limit 0x2
Not impersonating
DeviceMap 9a70f9e8
Owning Process 86b4cd40 Image: wordpad.exe
Attached Process N/A Image: N/A
Wait Start TickCount 21829348 Ticks: 1299 (0:00:00:20.264)
Context Switch Count 1 IdealProcessor: 0
UserTime 00:00:00.000
KernelTime 00:00:00.000
Win32 Start Address 0x002cb23d
Stack Init 8b777ed0 Current 8b777a40 Base 8b778000 Limit 8b775000 Call 0
Priority 8 BasePriority 8 UnusualBoost 0 ForegroundBoost 0 IoPriority 2 PagePriority 5
ChildEBP RetAddr Args to Child
8b777a58 82a88d3d 85807a60 00000000 82b35d20 nt!KiSwapContext+0x26 (FPO: [Uses EBP] [0,0,4])
8b777a90 82a87b9b 85807b20 85807a60 85807c28 nt!KiSwapThread+0x266
8b777ab8 82a8158f 85807a60 85807b20 00000000 nt!KiCommitThreadWait+0x1df
8b777b34 82abbfd9 85807c28 00000005 00000000 nt!KeWaitForSingleObject+0x393
8b777b4c 82abbaf4 00000000 00000000 00000000 nt!KiSuspendThread+0x18 (FPO: [3,0,0])
8b777b90 82e2390f 00000000 00000000 00000000 nt!KiDeliverApc+0x17f
8b777bb0 82e23b29 00000001 00000000 00000000 hal!HalpDispatchSoftwareInterrupt+0x49 (FPO: [Non-Fpo])
8b777bc8 82e23ba9 00000000 00000000 8b777c20 hal!HalpCheckForSoftwareInterrupt+0x83 (FPO: [Non-Fpo])
8b777bd8 82c6450d b553bcc6 00000000 00000000 hal!KfLowerIrql+0x61 (FPO: [Non-Fpo])
8b777c20 82abb559 00000000 778870d8 00000001 nt!PspUserThreadStartup+0x14
00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x19
您可以Win32 Start Address 0x002cb23d在上面的输出中看到,这是挂起线程的起始地址。
另外,除了
CreateThread()and之外,还有其他方法可以创建挂起的线程CreateRemoteThread()吗?
是的,您可以致电ntdll!NtCreateThread()或ntdll!NtCreateThreadEx()。
根据提供的信息,我怀疑这里使用的方法是进程的动态分叉,或者因为它也知道进程挖空。这个想法是在挂起状态下执行一些任意进程,并用另一个可执行映像的内容替换它的“胆量”。一般来说,这个想法可以实现如下(基于Tan Chew Keong 的动态分叉,2004):
CreateProcess带有CREATE_SUSPENDED参数的API从任何 EXE 文件创建挂起的进程。(称之为第一个 EXE)。GetThreadContextAPI 获取挂起进程的寄存器值(线程上下文)。挂起进程的EBX 寄存器指向进程的PEB。EAX 寄存器包含进程的入口点(第一个 EXE)。WriteProcessMemory函数将第二个EXE的image写入挂起进程的内存空间暂停进程,从基地址开始。ZwUnmapViewOfSection(由 ntdll.dll 导出)取消映射第一个 EXE 的映像,并使用 VirtualAllocEx 为挂起进程的内存空间内的第二个 EXE 分配足够的内存。该VirtualAllocExAPI必须与第二EXE的基地址提供给确保Windows会给我们记忆中所需要的区域。接下来,将第二个 EXE 的映像复制到挂起进程的内存空间中,从分配的地址开始(使用WriteProcessMemory)。WriteProcessMemory)。所以,为了回答你的问题,我首先建议验证这确实发生了。其次,如果是这样,您可以执行以下操作来中断新创建的线程入口点。建议的方法不是唯一的方法,但考虑到您在 RE/可执行分析方面的经验相对较少,恕我直言很容易做到:
检查PoC以真正全面了解整个过程。
CTX变量,它保存线程上下文的各个方面,也是线程函数的地址。CTX.eax以获取线程函数的地址。例如0x00402030,您在那里找到的地址。0x00400000。0x2030。0x2030用0xEBFE(记住前面的字节)在偏移处修补内存,这会使线程无限循环 - jmp 0x00402030。Olly到现在已经运行的挂起进程。转到 EP 并修补回原始字节。我希望这是可以理解的,否则请询问,我会澄清。
如果线程被创建挂起并第一次调用恢复线程,ollydbg 将不会在其中显示该线程,thread window也不会在堆栈窗口中提供该线程的堆栈跟踪(ALT+K)->Right Click->Thread ->Checkmark
在这种情况下,您可以在以下 apis 上设置断点
跟进基于 xp sp3 布局(您可能需要在较新的操作系统中进行一些调整)
1) ntdll!NtContinue / ZwContinue (can be found without symbol )
2) ntdll!ZwRegisterThreadTerminateport (can find this too without symbol)
3) CsrNewThread (needs symbol)
4) BaseThreadStartThunk ditto
5) BaseThreadStart ditto
恢复线程将在这些 api 中的任何一个上中断
如果上破度Zw / NT继续遵循context->eip从堆栈[ESP + 4] + 0xb8] ,并设置在EIP断点发现(该地址通常会BaseThreadStartThunk which is directly identifiable if you have public symbols loaded),并打去 ,一旦你在BaseThreadStartThunk单步F8几次,直到您进行间接调用, call dword ptr ds:[R32 + CONST] 这是您的 ThreadProc
如果它在ZwRegisterTerminatePort上损坏,您只需执行单步操作(您将返回 csrNewThread,您可以在其中找到符号是否可用)并保持 f8ing,直到您到达上述间接调用以登陆 ThreadProc
其他api都包含在上面,可以减少单步的数量
ntdll!ZwRegisterThreadTerminatePort 是 ThreadProc 之前最接近的中断
需要一个 ctrl+f9 (execute until return) and 3 f8 (single step)
如果 windbg / livekd 是一个选项,则此脚本可以在 ResumeThread 调用中断时检索 eip,无论是首次恢复还是后续恢复
.foreach /ps 8 /pS 0n19 (place { !process 0 4 ${$arg1} } ) {.printf "ETHREAD = %x\n", place ; r? $t0 = (((nt!_ETHREAD *) @@masm( place ))->Tcb) ; r? $t1 = @$t0.StackLimit;r? $t2 = @$t0.InitialStack;.foreach (vlace {s -[1]d @$t1 @$t2 0x23 0x23 } ) {dt nt!_KTRAP_FRAME DbgEip Eip @@masm(${vlace}-34) }}