avatar

Catalog
线程结构体

Windows中每个进程会包含一个或多个线程,每个线程在0环都有一个对应的结构体:ETHREAD,这个结构体包含了线程所有重要的信息,下面来简单了解一下。

ETHREAD

Windbg中,执行dt _ETHREAD可以看到这个完成的结构:

+0x000 Tcb

  • 成员名:Tcb
  • 数据类型:_KTHREAD
  • Windbg查询指令:dt _KTHREAD
  • 结构图:
  • 说明:KTHREAD这个结构应该已经比较眼熟了,在API函数的调用过程(保存现场),就多次用到了KTHREAD结构里的成员。

+0x000 Header

  • 成员名:Header
  • 数据类型:_DISPATCHER_HEADER
  • 说明:结构体内若包含_DISPATCHER_HEADER这个数据类型,说明这是一个可等待对象
  • 可等待对象:Mutex,Event都是可等待对象,可被作用于WaitForSingleObject这类函数

+0x018 InitialStack/+0x01c StackLimit/+0x028 KernelStack

  • 成员名:InitialStack/StackLimit/KernelStack
  • 数据类型:Ptr32 Void
  • 说明:这三个成员与线程切换有关。有印象的话,在分析KiFastCallEntry函数保存现场的过程中,曾有一行代码获取了InitialStack的值,并存到了ebp中。 此外,线程切换发生时,会根据KernelStack修改TSS的ESP0。更多关于这三个成员的用法,会在后面线程切换的地方再提到

+0x020 Teb

  • 成员名:Teb(Thread Environment Block),线程环境块
  • 数据类型:Ptr32 Void
  • 大小:4KB
  • 结构图:
  • 说明:
    • 0x20位置存着一个指向Teb结构的指针
    • Teb是在3环用来描述线程的一个结构。
    • 0环时,FS:[0]指向KPCR;3环时,FS:[0]指向TEB

+0x02c DebugActive

  • 成员名:DebugActive
  • 数据类型:UChar
  • 说明:在分析KiSystemService进行保存现场的过中遇到过,若这个位置的值不是-1,说明处于调试状态,程序会跳转到执行一个将Dr0~Dr7保存到_Trap_Frame里面的操作。从而衍生出了一个反调试手段,将这个位置的值置为-1,从而不能使用8个调试寄存器

+0x02d State

  • 成员名:State
  • 数据类型:UChar
  • 说明:线程状态-就绪/等待/运行

+0x060 WaitListEntry/SwapListEntry

  • 成员名:WaitListEntry/SwapListEntry
  • 数据类型:WaitListEntry为_LIST_ENTRY / SwapListEntry为 _SINGLE_LIST_ENTRY
  • 说明:此处为Wait链表或Ready链表。Windows线程总是处于Wait/Running/Ready这三种状态之一

+0x06c BasePrioirty

  • 成员名:BasePriority
  • 数据类型:Char
  • 说明:其初始值是所属进程的BasePrioirty值(KPROCESS->BasePriority),以后可以通过KeSetBasePriorityThread()函数重新设定

+0x070 WaitBlock

  • 成员名:WaitBlock
  • 数据类型:[4]_KWAIT_BLOCK
  • 说明:当前线程,正在等待哪个可等待对象(WaitForSingleObject)这个可等待对象的信息就会被写入这个_KWAIT_BLOCK结构的数组里。

+0x0E0 ServiceTable

  • 成员名:ServiceTable
  • 数据类型:Ptr32 Void
  • 说明:这个应该很熟悉了,指向系统服务表基址;通过系统服务表,可以找到函数地址表,根据系统服务号提供的偏移,就可以在函数地址表中找到3环API接口对应的0环内核函数

+0x134 TrapFrame

  • 成员名:TrapFrame
  • 数据类型:Ptr32 _KTRAP_FRAME
  • 说明:这个也很熟悉了,进0环时,保存现场的原理就是填充寄存器及相关数据到TrapFrame结构中,最后更新TrapFrame位置的值,使其指向新保存的TrapFrame

+0x140 PerviousMode

  • 成员名:PerviousMode
  • 数据类型:Char
  • 说明:在调用0环函数,保存现场的过程时,会将先前模式保存到TrapFrame结构中,以便根据先前模式,能够正确的返回到调用它的函数。

+0x1b0 ThreadListEntry

  • 成员名:ThreadListEntry
  • 数据类型:_LIST_ENTRY
  • 说明:
    • 双向链表,一个进程所有的线程,都挂在一个链表中,挂的就是这个位置
    • 链表头位于KPROCESS+0x50的位置以及EPROCESS+0x190的位置,相当于ThreadListEntry的PsThreadListHead
    • 一共有两个这样的链表

APC相关(位于KTHREAD内)

成员位置 成员名 数据类型
+0x034 ApcState _KAPC_STATE
+0x0e8 ApcQueueLock Uint4B
+0x138 ApcStatePointer [2] Ptr32 _KAPC_STATE
+0x14c SavedApcState _KAPC_STATE
+0x165 ApcStateIndex UChar
+0x166 ApcQueueable UChar
  • 说明:这些均为与APC相关的结构,具体到APC章节再做分析,这里仅作了解

+0x1ec Cid

  • 成员名:Cid
  • 数据类型:_CLIENT_ID
  • 说明:共八字节,包含两个值,当前进程的PID和当前线程的CID
  • 结构图:

+0x220 ThreadProcess

  • 成员名:ThreadProcess
  • 数据类型:Ptr32 _EPROCESS
  • 说明:指向自己所属进程

+0x22c ThreadListEntry

  • 成员名:ThreadListEntry
  • 数据类型:_LIST_ENTRY
  • 说明:
    • 双向链表,一个进程所有的线程,都挂在一个链表中,挂的就是这个位置
    • 链表头位于KPROCESS+0x50的位置以及EPROCESS+0x194的位置,相当于ThreadListEntry的PsThreadListHead
    • 一共有两个这样的链表
    • 这个双向链表的内容和0x1b0位置的完全一样,构建2个双向链表主要是为了方便,一个位于KTHREAD内,一个位于ETHREAD内
  • 结构图:

关于断链

在进程结构体中,我们通过断链实现了简单的进程隐藏,其原理在于,任务管理器在查询进程便是通过遍历ActiveProcessLink实现的。尽管进程已经不在活动进程链表上,但是仍然可以运行,原因是Windows调度的基本单位是线程,而不是进程,所以才有从进程链表上摘除自身进程的隐藏方法,这虽然从进程链表上摘除了自身,但不会影响操作系统的调度,所以不影响程序运行。

不过这里没能完成线程断链的实验,原因可能在于VMware虚拟机的指令问题,Windbg无法中断操作系统时间过长,所以经常实验到一半,就无法继续执行指令了,只能重启虚拟机(应该是VMware 15的问题,升级到VMware 16就没问题了)。

参考教程:https://www.bilibili.com/video/BV1NJ411M7aE?p=44

参考文章:https://blog.csdn.net/emaste_r/article/details/8916786

参考笔记:张嘉杰的笔记

Author: cataLoc
Link: http://cata1oc.github.io/2020/03/29/%E7%BA%BF%E7%A8%8B%E7%BB%93%E6%9E%84%E4%BD%93/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶