进程线程

基础基础~~~

进程线程

进程

测试环境:Winxp

关于进程,最初始的概念就是提供了程序所能访问的空间,如x86下进程就是4gb虚拟内存空间,进程唯一提供有用的东西就是cr3寄存器,每当进程创建在0环就有一个结构体EPROCESS

image-20200720101950692

image-20200720102133521

其中第一个成员为KPROCESS

+0x000 Header : _DISPATCHER_HEADER //表示为可等待对象(WaitForSingleObject)

+0x018 DirectoryTableBase : [2] 0x2ff04000 //为页目录表基址

+0x038 KernelTime : 6
+0x03c UserTime : 1 //进程在用户态和内核态所花费的时间

+0x05c Affinity : 1 //规定只能在那几个cpu上跑 转换成二进制即可

+0x062 BasePriority : 8 ‘’ //基础优先级

+0x070 CreateTime : _LARGE_INTEGER 0x01d65e3b`60784231
+0x078 ExitTime : _LARGE_INTEGER 0x0 //进程创建和退出的时间

+0x084 UniqueProcessId : 0x0000075c Void //PID

+0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x80562358 - 0x89b7e818 ] //双向链表 连接的所有活动的进程

//这边可以通过遍历这个双向链表来得到所有活动中的进程,如 dt _eprocess 0x80562358 -88

//也可以通过断链来实现隐藏,当然只能不让某些api找到

+0x11c VadRoot : 0x89543e80 Void //标志了0-2G中哪写内存被使用了

+0x0bc DebugPort : (null) //调试相关

+0x0c4 ObjectTable : 0xe185e178 _HANDLE_TABLE //句柄表

+0x174 ImageFileName : [16] “notepad.exe” //进程镜像文件名 最多16字节

+0x1a0 ActiveThreads : 1 //活动线程的数量

+0x1b0 Peb : 0x7ffde000 _PEB //进程环境块

进程隐藏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <ntddk.h>

enum WIN10
{
g_uNextProcess = 0x2f0,
g_uPrevProcess = 0x2f8,
g_uImageFileName = 0x450
};

PLIST_ENTRY g_pListEntryTemp = NULL;
PLIST_ENTRY g_pHeadEntry = NULL;

VOID MyDriverUnload(PDRIVER_OBJECT pDriverObject)
{
DbgPrint("unload;");
if(g_pListEntryTemp != NULL)
{
InsertHeadList(g_pHeadEntry, g_pListEntryTemp);
}
}

VOID HideProcessor()
{
PEPROCESS pCurrrentProcess = NULL;
PEPROCESS pPreviousProcess = NULL;

pCurrrentProcess = PsGetCurrentProcess();
pPreviousProcess = (PEPROCESS)(*((PULONG_PTR)((ULONG_PTR)pCurrrentProcess + g_uPrevProcess)) - g_uNextProcess);

while (pCurrrentProcess != pPreviousProcess)
{
if (strcmp(((PCHAR)((ULONG_PTR)pCurrrentProcess + g_uImageFileName)), "temp.exe") == 0)
{
g_pListEntryTemp = (PLIST_ENTRY)(((ULONG_PTR)pCurrrentProcess + g_uNextProcess));
RemoveEntryList(g_pListEntryTemp);
break;
}
pCurrrentProcess = (PEPROCESS)(*((PULONG_PTR)((ULONG_PTR)pCurrrentProcess + g_uNextProcess)) - g_uNextProcess);
}
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pStrRegPath)
{
g_pHeadEntry = (PLIST_ENTRY)((ULONG_PTR)PsGetCurrentProcess() + g_uNextProcess);
DbgPrint("start");
__try
{
HideProcessor();
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
DbgPrint("something wrong!");
}
pDriverObject->DriverUnload = MyDriverUnload;
return STATUS_SUCCESS;
}

image-20200720150207163

查看id 7352,加载驱动隐藏进程

image-20200720150244169

可以发现ida已经无法找到进程,但是火绒剑还是可以,看来是没有使用api

image-20200720150327358

同样的任务管理器也无法找到进程

unload恢复一波

image-20200720150400202

image-20200720150412684

image-20200720150437551

可以发先ida与任务管理器都成功查找到了进程

线程

与进程相对应得是线程得ethread

image-20200721100730610

具体查看方法是kprocess得0x50处,可以发现一个双向链表链接了进程内的所有线程

因为我打开的是记事本所以俩个一样只有一个线程

image-20200721100919135

对应的在0x190处还能看到一个副本

分析一波具体结构,首先从kthread开始看起

+0x000 Header : _DISPATCHER_HEADER //和eprocess一模一样,是用来表示是否可等待

+0x010 MutantListHead : _LIST_ENTRY [ 0x5ae36ce0 - 0xeb2f81a ]
+0x018 InitialStack : 0x8994da98 Void
+0x01c StackLimit : 0x8994da98 Void //与线程切换相关

+0x020 Teb : (null) //线程环境块,在三环时就是fs:[0],在0环时fs指向的KPCR

+0x02c DebugActive : 0xe8 ‘’ //如果值为-1 则表示不能使用调试寄存器

+0x034 ApcState : _KAPC_STATE

+0x0e8 ApcQueueLock : 0

+0x138 ApcStatePointer : [2] (null)

+0x14c SavedApcState : _KAPC_STATE //这边是与APC相关的应用

+0x02d State : 0x98 ‘’ //线程运行状态

+0x06c BasePriority : 0 ‘’ //基础优先值

+0x070 WaitBlock : [4] _KWAIT_BLOCK //表示等待哪个对象中

+0x0e0 ServiceTable : 0x8994db30 Void //指向了系统服务表的基地址

+0x134 TrapFrame : 0xe380d1f0 _KTRAP_FRAME //保存现场环境

+0x140 PreviousMode : 0 ‘’ //表示先前模式是0环还是3环

+0x1ec Cid : _CLIENT_ID //表示进程和线程id

+0x220 ThreadsProcess : 0x89a86240 _EPROCESS //指向自己所属的进程

+0x22c ThreadListEntry : _LIST_ENTRY [ 0x0 - 0x0 ] //所有线程得位置

KPCR

image-20200721103855314

+0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD //异常处理链表
+0x004 StackBase : Ptr32 Void
+0x008 StackLimit : Ptr32 Void //当前线程内核栈的基地址和大小

+0x018 Self : Ptr32 _NT_TIB //指向自己

+0x01c SelfPcr : Ptr32 _KPCR //同上

+0x020 Prcb : Ptr32 _KPRCB //指向扩展结构体

+0x038 IDT : Ptr32 _KIDTENTRY //指向IDT表基地址
+0x03c GDT : Ptr32 _KGDTENTRY//GDT表基地址
+0x040 TSS : Ptr32 _KTSS //指向TSS任务段

+0x051 Number : UChar //CPU编号

+0x120 PrcbData : _KPRCB //指向扩展结构体

image-20200721104412142

查看_KPRCB结构体

+0x004 CurrentThread : Ptr32 _KTHREAD //当前线程
+0x008 NextThread : Ptr32 _KTHREAD //即将切换线程
+0x00c IdleThread : Ptr32 _KTHREAD //空闲线程

等待链表-调度链表

image-20200721110137217

主要是kthread +0x60处 //等待链表是当线程调用了Sleep() 或者 WaitForSingleObject()等函数时,就挂到这个链表

调度链表有32个圈,就是优先级:0 - 31 0最低 31最高 默认优先级一般是8

改变优先级就是从一个圈里面卸下来挂到另外一个圈上

这32个圈是正在调度中的线程:包括正在运行的和准备运行的

比如:只有一个CPU但有10个线程在运行,那么某一时刻,正在运行的线程在KPCR中,其他9个在这32个圈中。

image-20200721110401655

查看一波调度链表

XP只有一个33个圈,也就是说上面这个数组只有一个,多核也只有一个.

Win7也是一样的只有一个圈,如果是64位的,那就有64个圈.

服务器版本:KiWaitListHead整个系统只有一个,但KiDispatcherReadyListHead这个数组有几个CPU就有几组

模拟线程切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#define _CRT_SECURE_NO_DEPRECATE
#include <stdio.h>
#include <windows.h>

//最大支持的线程数
#define MAXGMTHREAD 100

//线程信息的结构
typedef struct
{
char* name; //线程名 相当于线程ID
int Flags; //线程状态
int SleepMillsecondDot; //休眠时间

void* initialStack; //线程堆栈起始位置
void* StackLimit; //线程堆栈界限
void* KernelStack; //线程堆栈当前位置,也就是ESP

void* lpParameter; //线程函数的参数
void(*func)(void* lpParameter); //线程函数
}GMThread_t;

void GMThreadStartup(GMThread_t* GMThreadp);
void IdleGMThread(void* lpParameter);
void PushStack(unsigned int** Stackpp, unsigned int v);
void initGMThread(GMThread_t* GMThreadp, char* name, void(*func)(void* lpParameter), void* lpParameter);
int RegisterGMThread(char* name, void(*func)(void* lpParameter), void* lpParameter);
void SwitchContext(GMThread_t* SrcGMThreadp, GMThread_t* DstGMThreadp);
void Scheduling();
void GMSleep(int MilliSeconds);
void Thread1(void*);
void Thread2(void*);
void Thread3(void*);
void Thread4(void*);
//定义线程栈的大小
#define GMTHREADSTACKSIZE 0x80000

//当前线程的索引
int CurrentThreadIndex = 0;

//线程的列表
GMThread_t GMThreadList[MAXGMTHREAD] = { NULL, 0 };

//线程状态的标志
enum FLAGS
{
GMTHREAD_CREATE = 0x1,
GMTHREAD_READY = 0x2,
GMTHREAD_SLEEP = 0x4,
GMTHREAD_EXIT = 0x8,
};



int main()
{
RegisterGMThread((char*)"Thread1", Thread1, NULL);
RegisterGMThread((char*)"Thread2", Thread2, NULL);
RegisterGMThread((char*)"Thread3", Thread3, NULL);
RegisterGMThread((char*)"Thread4", Thread4, NULL);

for (;;)
{
Sleep(20);
Scheduling();
}

return 0;
}

//启动线程的函数
void GMThreadStartup(GMThread_t* GMThreadp)
{
GMThreadp->func(GMThreadp->lpParameter);
GMThreadp->Flags = GMTHREAD_EXIT;
Scheduling();

return;
}

//空闲线程的函数
void IdleGMThread(void* lpParameter)
{
printf("IdleGMThread---------------\n");
Scheduling();
return;
}

//向栈中压入一个uint值
void PushStack(unsigned int** Stackpp, unsigned int v)
{
*Stackpp -= 1;//esp - 4
**Stackpp = v;//

return;
}

//初始化线程的信息
void initGMThread(GMThread_t* GMThreadp, char* name, void(*func)(void* lpParameter), void* lpParameter)
{
unsigned char* StackPages;
unsigned int* StackDWordParam;

//结构初始化赋值
GMThreadp->Flags = GMTHREAD_CREATE;
GMThreadp->name = name;
GMThreadp->func = func;
GMThreadp->lpParameter = lpParameter;

//申请空间
StackPages = (unsigned char*)VirtualAlloc(NULL, GMTHREADSTACKSIZE, MEM_COMMIT, PAGE_READWRITE);
//初始化
ZeroMemory(StackPages, GMTHREADSTACKSIZE);
//初始化地址地址
GMThreadp->initialStack = StackPages + GMTHREADSTACKSIZE;
//堆栈限制
GMThreadp->StackLimit = StackPages;
//堆栈地址
StackDWordParam = (unsigned int*)GMThreadp->initialStack;

//入栈
PushStack(&StackDWordParam, (unsigned int)GMThreadp); //通过这个指针来找到线程函数,线程参数
PushStack(&StackDWordParam, (unsigned int)9); //平衡堆栈的(不用管)
PushStack(&StackDWordParam, (unsigned int)GMThreadStartup); //线程入口函数 这个函数负责调用线程函数
PushStack(&StackDWordParam, (unsigned int)5); //push ebp
PushStack(&StackDWordParam, (unsigned int)7); //push edi
PushStack(&StackDWordParam, (unsigned int)6); //push esi
PushStack(&StackDWordParam, (unsigned int)3); //push ebx
PushStack(&StackDWordParam, (unsigned int)2); //push ecx
PushStack(&StackDWordParam, (unsigned int)1); //push edx
PushStack(&StackDWordParam, (unsigned int)0); //push eax

//当前线程的栈顶
GMThreadp->KernelStack = StackDWordParam;

//当前线程状态
GMThreadp->Flags = GMTHREAD_READY;

return;
}

//将一个函数注册为单独线程执行
int RegisterGMThread(char* name, void(*func)(void* lpParameter), void* lpParameter)
{
int i;
for (i = 1; GMThreadList[i].name; i++)
{
if (0 == _stricmp(GMThreadList[i].name, name))
{
break;
}
}
initGMThread(&GMThreadList[i], name, func, lpParameter);

return (i & 0x55AA0000);
}

//切换线程 1:当前线程结构体指针 2:要切换的线程结构体指针
__declspec(naked) void SwitchContext(GMThread_t* SrcGMThreadp, GMThread_t* DstGMThreadp)
{
__asm
{
//提升堆栈
push ebp
mov ebp, esp

//保存当前线程寄存器
push edi
push esi
push ebx
push ecx
push edx
push eax

mov esi, SrcGMThreadp
mov edi, DstGMThreadp

mov[esi + GMThread_t.KernelStack], esp

//经典线程切换,另外一个线程复活
mov esp, [edi + GMThread_t.KernelStack]

pop eax
pop edx
pop ecx
pop ebx
pop esi
pop edi

pop ebp
ret //把startup(线程函数入口)弹到eip 执行的就是线程函数了
}
}

//这个函数会让出cpu,从队列里重新选择一个线程执行
void Scheduling()
{
int TickCount = GetTickCount();

GMThread_t* SrcGMThreadp = &GMThreadList[CurrentThreadIndex];
GMThread_t* DstGMThreadp = &GMThreadList[0];


for (int i = 1; GMThreadList[i].name; i++)
{
if (GMThreadList[i].Flags & GMTHREAD_SLEEP)
{
if (TickCount > GMThreadList[i].SleepMillsecondDot)
{
GMThreadList[i].Flags = GMTHREAD_READY;
}
}
if (GMThreadList[i].Flags & GMTHREAD_READY)
{
DstGMThreadp = &GMThreadList[i];
break;
}
}

CurrentThreadIndex = DstGMThreadp - GMThreadList;

SwitchContext(SrcGMThreadp, DstGMThreadp);

return;
}
void GMSleep(int MilliSeconds)
{
GMThread_t* GMThreadp;
GMThreadp = &GMThreadList[CurrentThreadIndex];

if (GMThreadp->Flags != 0)
{
GMThreadp->Flags = GMTHREAD_SLEEP;
GMThreadp->SleepMillsecondDot = GetTickCount() + MilliSeconds;
}

Scheduling();
return;
}

void Thread1(void*)
{
while (1)
{
printf("Thread1\n");
GMSleep(500);
}
}

void Thread2(void*)
{
while (1)
{
printf("Thread2\n");
GMSleep(200);
}
}

void Thread3(void*)
{
while (1)
{
printf("Thread3\n");
GMSleep(10);
}
}

void Thread4(void*)
{
while (1)
{
printf("Thread4\n");
GMSleep(1000);
}
}

代码是于海东老师的,直接拿过来编译即可使用,主要是逆向的过程

image-20200722105812335

前面函数单纯负责注册线程,真正切换线程在Scheduling函数中

image-20200722105938429

判断状态,然后当前线程主动call SwitchContext来让出cpu

具体切换过程就在SwitchContext中

image-20200722110025824

保存现场环境,然后堆栈切换后恢复环境

image-20200722110118078

返回时会回到GMThreadStartup,这边call了线程处理函数

函数call完后设置一波状态然后继续判断

image-20200722110212411

KiSwapContext

来逆向一波内核函数,总体和上面模拟线程切换差不多

image-20200729102000046

来看看swapcontext

image-20200729105325211

总结一波线程切换知识

1.api调用//绝大部分api调用时都会判断是否要线程切换

2.时钟中断

3.异常

image-20200729102025644

这边还能看到tss的esp0

image-20200729103533151

具体如上图,eax是InitialStack

-0x210 -0x10后保存在tss中

这是因为内核堆栈结构所导致,0x210存放浮点寄存器值,然后紧跟着trap frame

可以看之前的笔记,trapframe一开始4个4字节是给虚拟8086用的所以是0x10

这就是为啥tss中esp0永远是当前线程的esp0

后面还往tss中存放cr3

image-20200729104020382

edi是kprocess

+18就是cr3了放到tss中然后切换cr3

继续往下看

image-20200729104756803

可以看到这个TEB相关

这也解释了为什么不管是什么线程fs都一样,他会把当前线程peb放到gdt表中,具体可以看之前段描述符结构,发现正好是吧地址填满了base addr

现在回去看一波线程优先级

image-20200729111828055

KiFindReadyThread后比较返回值是否为空,KiFindReadyThread其实就是遍历32个链表来根据优先级查找

如果链表为空那么开始跑idlethread

查看跳转处

image-20200729111927533

取出idlethread放到eax中然后跳转

image-20200729111947770

这边_KiIdleSummary是个dword每个位用来表示每个优先级的链表是否为空

windbg查看一波链表

image-20200729112134138

文章目录
  1. 1. 进程线程
    1. 1.1. 进程
      1. 1.1.1. 进程隐藏
    2. 1.2. 线程
    3. 1.3. KPCR
    4. 1.4. 等待链表-调度链表
    5. 1.5. 模拟线程切换
    6. 1.6. KiSwapContext
|