Process and Thread

KPCR结构

CPU控制块结构,每一个逻辑核都有一个KPCR结构描述当前CPU的各种信息。全局变量“KeNumberProcessors”中保存了当前机器的CPU核数

Untitled

KPCR结构:

kd> dt _KPCR
ntdll!_KPCR
   +0x000 NtTib            : _NT_TIB
   +0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Used_StackBase   : Ptr32 Void
   +0x008 Spare2           : Ptr32 Void
   +0x00c TssCopy          : Ptr32 Void
   +0x010 ContextSwitches  : Uint4B
   +0x014 SetMemberCopy    : Uint4B
   +0x018 Used_Self        : Ptr32 Void
   +0x01c SelfPcr          : Ptr32 _KPCR
   +0x020 Prcb             : Ptr32 _KPRCB
   +0x024 Irql             : UChar
   +0x028 IRR              : Uint4B
   +0x02c IrrActive        : Uint4B
   +0x030 IDR              : Uint4B
   +0x034 KdVersionBlock   : Ptr32 Void
   +0x038 IDT              : Ptr32 _KIDTENTRY
   +0x03c GDT              : Ptr32 _KGDTENTRY
   +0x040 TSS              : Ptr32 _KTSS
   +0x044 MajorVersion     : Uint2B
   +0x046 MinorVersion     : Uint2B
   +0x048 SetMember        : Uint4B
   +0x04c StallScaleFactor : Uint4B
   +0x050 SpareUnused      : UChar
   +0x051 Number           : UChar
   +0x052 Spare0           : UChar
   +0x053 SecondLevelCacheAssociativity : UChar
   +0x054 VdmAlert         : Uint4B
   +0x058 KernelReserved   : [14] Uint4B
   +0x090 SecondLevelCacheSize : Uint4B
   +0x094 HalReserved      : [16] Uint4B
   +0x0d4 InterruptMode    : Uint4B
   +0x0d8 Spare1           : UChar
   +0x0dc KernelReserved2  : [17] Uint4B
   +0x120 PrcbData         : _KPRCB

kd> dt _NT_TIB
ntdll!_NT_TIB
   +0x000 ExceptionList    : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 StackBase        : Ptr32 Void
   +0x008 StackLimit       : Ptr32 Void
   +0x00c SubSystemTib     : Ptr32 Void
   +0x010 FiberData        : Ptr32 Void
   +0x010 Version          : Uint4B
   +0x014 ArbitraryUserPointer : Ptr32 Void
   +0x018 Self             : Ptr32 _NT_TIB

主要的结构为:

NtTib:

  • +0 Used_ExceptionList:异常处理程序链表
  • +4 Used_StackBase:当前线程的栈底
  • +8 Spare2/StackLimit:当前线程的堆栈大小
  • +18 Used_Self:指向NtTib自身,也是KPCR自身(fs:[0x18])
  • +1C SelfPcr:指向KPCR自身。与UsedSelf不同,SelfPcr必然指向KPCR自身,UsedSelf有时候会指向TEB
  • +20 Prcb:KPRCB结构指针
  • +38 IDT:当前线程的IDT表地址
  • +3C GDT:当前线程的GDT表地址
  • +40 TSS:指向当前线程的TSS表
  • +48 SetNumber:当前CPU编号,从1开始
  • +51 Number:当前CPU编号,从0开始
  • +120 PrcbData:KPRCB结构,扩展结构

KPRCB

CPU控制块扩展块。全局变量“KiProcessorBlock”中保存了KPRCB的地址

Untitled

  • +4 CurrentThread:当前CPU正在跑的线程
  • +8 NextThread:将要切换的线程
  • +C IdleThread:如果没有要切换的线程,CPU将要跑的空闲线程

EPROCESS

  • +0 Pcb:Kprocess结构体

  • +98 ProcessLock:进程锁。修改EPROCESS结构存放锁结构,防止同时修改。改完了置0.

  • +A0 CreateTime:进程的创建时间。

  • +A8 ExitTime:进程的退出时间。

  • +B0 RundownProtect:进程锁。该字段置值后,进程无法被访问、打开、结束,相当于保护。但是会容易卡死。

  • +B4 UniqueProcessId:进程ID。任务管理器中显示的进程ID就是这个。

  • +B8 ActiveProcessLinks:双向链表。包括了windows中所有活动的进程。全局变量“PsActiveProcessHead”指向了这个链表的头部。通过该全局变量可以遍历整条链表。

  • +C0 ProcessQuotaUsage:进程物理页相关统计信息。

  • +C8 ProcessQuotaPeak:进程物理页相关统计信息。

  • +D0 CommitCharge:进程虚拟内存相关统计信息。

  • +D4 QuotaBlock:进程虚拟内存相关统计信息。

  • +D8 CpuQuotaBlock:进程虚拟内存相关统计信息。

  • +E4 SessionProcessLinks:会话进程链表。保存了当前登录的用户的所有进程。

  • +EC DebugPort:调试相关。如果该进程处于调试状态,这里会有值(一个结构体),该结构体用于进程与调试器之间通信。通过循环清0可以达到反调试效果。

  • +F0 ExceptionPortData:调试相关。

  • +F4 ObjectTable:进程的句柄表。句柄相关章节再学。

  • +F8 Token:进程Token。

  • +FC WorkingSetPage:表明当前进程用了多少个物理页。

  • +16C ImageFileName:当前进程的进程名。

  • +188 ThreadListHead:当前进程内所有线程的链表。

  • +198 ActiveThreads:当前进程内活动的线程数量。

  • +1A8 Peb。就是3环下该进程的PEB。(PEB结构此处不赘述了,网上有非常多的PEB结构说明。)

  • +1EC SeAuditProcessCreationInfo:当前进程完整路径。

  • +26C Flags2:一个联合体,每个位影响该进程的一些属性。

    • ProtectedProcess:进程保护位。该位置1后该进程被保护。CE看不到图片,打不开了进程。OD附加进程列表遍历不到。一个最简单的进程保护。
  • +270 Flags:一个联合体,每个位影响该进程的一些属性。

  • ProcessExiting:进程退出标志位。置1后表明该进程已退出,但实际还在运行。可以达到反调试的效果。同时进程无法使用任务管理器结束。

  • ProcessDelete:进程退出标志位。置1后表明该进程已退出,但实际还在运行。可以达到反调试的效果。同时进程无法使用任务管理器结束

  • BreakOnTermination:该位置1后,任务管理器结束进程时将提示“是否结束系统进程XXX”。结束后windbg将会断下

相关代码:

#include <ntifs.h>

PEPROCESS FindProcessByNameW(PWCH processName)
{
	PEPROCESS Process = NULL;

	//暴力枚举ProcessID
	for (ULONG_PTR i = 12; i < 0x100000; i += 4)
	{
		PEPROCESS TempProcess = NULL;
		//通过ID来查询EPROCCESS
		NTSTATUS st = PsLookupProcessByProcessId((HANDLE)i, &TempProcess);
		
		if (!NT_SUCCESS(st)) continue;

		//返回的状态码一定是STATUS_PENDING
		if (PsGetProcessExitStatus(TempProcess) != STATUS_PENDING)
		{
			ObReferenceObject(TempProcess);
			continue;
		}

		PUNICODE_STRING path = NULL;
		
		//查询全路径
		st = SeLocateProcessImageName(TempProcess, &path);

		if (!NT_SUCCESS(st))
		{
			ObReferenceObject(TempProcess);
			continue;
		}

		//向右查询
		PWCH baseName = wcsrchr(path->Buffer, '\\');

		if (baseName)
		{
			baseName += 1;
			if (_wcsicmp(baseName, processName) == 0)
			{
				Process = TempProcess;
			}
		}

		ExFreePool(path);

		if (Process) break;
	}
	return Process;
}

PEPROCESS FindProcessName(char* processName)
{
	//转换输入的char* -> wchar*
	ANSI_STRING aName;
	UNICODE_STRING uName;
	RtlInitAnsiString(&aName, processName);
	NTSTATUS st = RtlAnsiStringToUnicodeString(&uName, &aName, TRUE);

	if (!NT_SUCCESS(st)) return NULL;

	PEPROCESS Process = FindProcessByNameW(uName.Buffer);

	RtlFreeUnicodeString(&uName);

	return Process;
}

ULONG GetProcessIDOffset()
{
	UNICODE_STRING FuncNamePsGetProcessId = { 0 };
	//浅拷贝函数
	RtlInitUnicodeString(&FuncNamePsGetProcessId, L"PsGetProcessId");
	PUCHAR funcPsGetProcessId =  MmGetSystemRoutineAddress(&FuncNamePsGetProcessId);

#if (_X86_ == 1)
	ULONG offset = 0;
	for (int i = 0; i < 100; i++)
	{
		if (funcPsGetProcessId[i] == 0x8B && funcPsGetProcessId[i + 1] == 0x80)
		{
			offset = *(PULONG)(funcPsGetProcessId + i + 2);
			//RtlFreeUnicodeString(&FuncNamePsGetProcessId);
			break;
		}
	}
#else
	ULONG offset = *(PULONG)(funcPsGetProcessId + 3);
#endif // (_X86_ == 1)

	return offset;
}

//保护1 通过修改PID进行隐藏
BOOLEAN ProtectOne(PEPROCESS Process, ULONG_PTR Pid)
{
	ULONG offset = GetProcessIDOffset();

	if (offset == 0)
	{
		return FALSE;
	}

	*(PULONG)((PUCHAR)Process + offset) = Pid;

	return TRUE;
}

//保护2 通过修改 FLAGS值进行隐藏 分别可以修改的值有:ProcessInserted ProtectedProcess
BOOLEAN ProtectTwo(PEPROCESS Process)
{
	//测了一下可以这里改成通过ProcessExiting来获取0x270这个偏移的值
	*(PULONG)((PUCHAR)Process + 0x270) &= 0xfBffffff;
}

VOID
DriverUnload(
	_In_ struct _DRIVER_OBJECT* DriverObject
)
{

}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pSeg)
{
	DbgBreakPoint();
	PEPROCESS Process = FindProcessName("驱动管理.exe");
	if (Process)
	{
		
		ProtectTwo(Process);

		DbgPrintEx(77, 0, "[db]:%x\r\n", Process);

		ObReferenceObject(Process);
	}
	pDriver->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

KPROCESS

  • +0 Header:可等待对象头部。所有0环结构体只要以_DISPATCHER_HEADER结构开头的,都可以使用WaitForSingleObject等待。如互斥体、事件。

  • +10 ProfileListHead:性能分析相关,任务管理器,性能栏那些数据。

  • +18 DirectoryTableBase:页目录表基址。物理地址,指向页目录表,CR3中的值就从这里获取。

  • +2C ThreadListHead:当前进程的所有线程结构体链表。

  • +38 Affinity:亲核性。规定了当前进程内的所有线程可以在哪些CPU上跑,4字节,共32位,每一位对应一个CPU核。如000000A1,转换为二进制为1010 0001,则该进程中的线程只能在0、5、7号CPU上运行。因此32位系统最多支持32核CPU,64位系统支持64核CPU。该值仅为线程结构中的亲核性做初始化赋值使用,没有实际的限制功能。

    如果只有1个CPU,但此处值为2(0010),则该进程为一个“死”了的进程。

  • +44 ReadyListHead:当前进程内的就绪线程链表。

  • +4C SwapListEntry:交换到文件磁盘上时使用该链表。记录了哪些内存被交换到文件里。

  • +50 ActiveProcessors:当前进程内正在运行的线程运行在哪些CPU上。

  • +5C AutoAlignment:强制内存对齐。一般为0。

  • +5C DisableBoost:置1为关闭当前进程内所有线程的时间碎片。(置1后,不会由于时间中断触发线程切换)

  • +60 BasePriority:基础优先级。该进程内所有线程最初的优先级。

  • +61 QuantumReset:当前进程内线程的初始时间碎片。每一次时钟中断会将线程中的时间碎片减6,为0时,切换线程。线程从就绪变为运行时,会从这个值中取到初始的时间碎片。改大这个值会让该进程内的线程跑的更久。

  • +78 ProcessListEntry:系统内所有进程的链表。win7及以上此处为空,已弃用。

  • +80 CycleTime:当前进程执行了多少个指令周期。当进程结束时才会被赋值,指明了该进程存活了多久。

  • +88 KernelTime:(统计信息)当前进程在0环的运行时间。当进程结束时才会被赋值,指明了该进程存活了多久。

  • +8C UserTime:(统计信息)当前进程在3环的运行时间。当进程结束时才会被赋值,指明了该进程存活了多久。

  • +90 VdmTrapcHandler:虚拟8086模式时使用。

ETHREAD

  • +0 Tcb:KTHREAD成员
  • +218 StartAddress:线程函数起始地址。
  • +22C Cid:当前线程的线程ID。为_CLIENT_ID结构,包含了线程ID和所属的进程ID。
  • +268 ThreadListEntry:当前进程内所有线程的双向链表。

KTHREAD

  • +0 Header:可等待对象头部。
  • +28 InitialStack:线程切换相关。当前线程的栈底。栈底-29C是TrapFrame结构首地址。
  • +2C StackLimit:线程切换相关。当前线程的最大栈顶。ESP不能小于这个值。
  • +30 KernelStack:线程切换相关。线程切换时,存储当前线程切换时的ESP,被切换回来时,从这里恢复ESP。
  • +39 Running:线程状态,正在运行中为1,否则为0。
  • +3A Alerted:可警惕性。APC相关,后续APC章节学习。
  • +3C MiscFlags:
    • KernelStackResident:堆栈可扩展位。为1时,线程内核堆栈可以被扩大。0时无法扩大。
    • SystemThread:为1时,该线程为内核线程,否则为用户线程。
  • +40 ApcState:APC相关,后续APC章节学习。
  • +57 Priority:当前线程的优先级。如存储11,则优先级为11,当前线程存储在第11个就绪链表中。优先级数字越大,优先级越低。默认线程优先级为8,存储在第8个就绪链表中。
  • +60 ApcQueueLock:APC相关,后续APC章节学习。
  • +64 ContextSwitches:当前线程切换了多少次。
  • +68 State:线程状态。就绪、等待、运行等。
  • +88 Teb:3环的TEB。
  • +BC ServiceTable:系统服务表地址,系统调用章节已学过。
  • +C0 WaitBlock:当前线程正在等待的对象。
  • +128 TrapFrame:TrapFrame结构,进0环时保存3环寄存器的值,系统调用章节已学过。
  • +135 BasePriority:线程基础优先级。这个值就是所属进程的BasePriority值。
  • +13A PreviousMode:先前模式。一些内核函数会判断这个值。
  • +150 Process:该线程的父进程(创建该线程的进程)。
  • +168 ApcStatePointer:APC相关,后续APC章节学习。
  • +170 SavedApcState:APC相关,后续APC章节学习。
  • +18C Win32Thread:win32线程,如果该线程是UI图形线程,就会多一个win32线程结构体。
  • +1E0 ThreadListEntry:当前进程所有线程的双向链表。

对于线程断链隐藏也写了相关的代码,要对两个链表进行相对的断,才能成功

#include <ntifs.h>

PEPROCESS FindProcessByNameW(PWCH processName)
{
	PEPROCESS Process = NULL;

	for (ULONG_PTR i = 12; i < 0x100000; i += 4)
	{
		PEPROCESS TempProcess = NULL;

		NTSTATUS st = PsLookupProcessByProcessId((HANDLE)i, &TempProcess);

		if (!NT_SUCCESS(st)) continue;

		if (PsGetProcessExitStatus(TempProcess) != STATUS_PENDING)
		{
			ObDereferenceObject(TempProcess);
			continue;
		}

		PUNICODE_STRING unPath = NULL;

		st = SeLocateProcessImageName(TempProcess, &unPath);

		if (!NT_SUCCESS(st))
		{
			ObDereferenceObject(TempProcess);
			continue;
		}

		PWCH baseName = wcsrchr(unPath->Buffer, L'\\');

		if (baseName)
		{
			baseName += 1;

			if (_wcsicmp(baseName, processName) == 0)
			{
				Process = TempProcess;
			}
		}

		ExFreePool(unPath);

		if (Process) break;
	}

	return Process;
}

PEPROCESS FindProcessByNameA(char* processName)
{
	ANSI_STRING aName;
	UNICODE_STRING uName;
	RtlInitAnsiString(&aName, processName);
	NTSTATUS st = RtlAnsiStringToUnicodeString(&uName, &aName, TRUE);

	if (!NT_SUCCESS(st)) return NULL;

	PEPROCESS Process = FindProcessByNameW(uName.Buffer);

	RtlFreeUnicodeString(&uName);

	return Process;
}

//kprocess 2c  kthread 1e0
//		   188         268	
VOID KillThread(PEPROCESS process)
{
	PETHREAD pThread = *(PULONG)((PUCHAR)process + 0x2c) - 0x1e0;
	
	PLIST_ENTRY pThreadHeader_1 = (PUCHAR)process + 0x2c;
	PLIST_ENTRY pThreadHeader_2 = (PUCHAR)process + 0x188;

	PLIST_ENTRY pEThreadCur, pEThreadNext;

	//线程遍历
	pEThreadCur = pThreadHeader_1;

	DbgBreakPoint();

	do
	{
		pEThreadNext = pEThreadCur->Flink;

		if (pEThreadNext == pThreadHeader_1)
		{
			break;
		}

		DbgPrintEx(77, 0, "[DB]: ETHREAD 0x%08x\r\n", pThread);

		pThread = *(PULONG)pEThreadNext - 0x1e0;

		pEThreadCur = pEThreadNext;

		//RemoveEntryList(pEThreadNext);

	} while (1);

	// 线程断链
	//pEThreadCur = pThreadHeader_2;

	//do
	//{
	//	pEThreadNext = pEThreadCur->Flink;

	//	if (pEThreadNext == pThreadHeader_2)
	//	{
	//		break;
	//	}

	//	pEThreadCur = pEThreadNext;

	//	RemoveEntryList(pEThreadNext);

	//} while (1);

	//线程断链
	pThreadHeader_1->Blink = pThreadHeader_1;
	pThreadHeader_1->Flink = pThreadHeader_1;
	pThreadHeader_2->Blink = pThreadHeader_2;
	pThreadHeader_2->Flink = pThreadHeader_2;
}

VOID
DriverUnload(
	_In_ struct _DRIVER_OBJECT* DriverObject
)
{

}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pSeg)
{

	//找到我们想要的进程结构 并且返回EPROCESS 结构
	PEPROCESS Process = FindProcessByNameA("驱动管理.exe");

	if (Process)
	{
		DbgPrintEx(77, 0, "[db] Find EPROCESS Struct :%x\r\n", Process);
		
		//根据EPROCESS结构 来找到ETHREAD 然后进行线程的遍历
		
		KillThread(Process);

		ObReferenceObject(Process);
	}

	pDriver->DriverUnload = DriverUnload;

	return STATUS_SUCCESS;
}

逆向 KiFindReadyThread ( 如何查找一个线程 )

补充一下知识点:

KPRCB中ReadySummary成员(0x31EC)为就绪位图,4字节32位。每一位对应一条就绪链表。某一位为1则说明对应的就绪链表中有等待执行的线程。32条就绪链表存储在KPRCB中DispatcherReadyListHead成员

就绪链表(调度链表)

所有就绪的线程都会存储在就绪链表中,共有32个就绪链表(32位),所有核共享这32个链表,每个链表对应一个优先级(0~31)。全局变量KiDispatcherReadyListHead存储了这32个链表的起始位置

Untitled

如上图:ReadySummary值为0x300,二进制1100000000,说明第8号、第9号就绪链表中存在待执行线程。在线程切换时就会从这两个链表中取待切换的线程

追参数 首先得到edx 为kprcb ( 发现原来fs:[0x0] 是KPCR的首结构体 )

Untitled

通过逆向可以发现kiFindReadyThread有三个参数 分别是 eax→ ReadySummary的值,edi → 数组中取到的kprcb, esi是由上面图的edx所得来的 为当前核的kprcb

Untitled

深入来看一看各个参数嘛:

EDI:

edi首先看是由KiProcessorBlock来的,里面装有了核的地址也就是kprcb的地址,所以看到和eax有关,我们来深入理解一下这个eax,kprcb.group代表了 我们cpu的组的编号,GroupSetMember代表了CPU中核的编号,因为一般个人电脑是有一个cpu,一些工作站服务器可能有多个cpu,edx = 当前cpu的核序号后,又赋值了当前核的亲核性,当前核的亲核性与当前所在的核进行xor,来看当前核是否可以跑,所以edx = 0 为不行,把这个亲核性的结果保存后,eax = 当前的cpu组,通过获取当前空闲的位置进行获取到ecx,对ecx进行取反后核ecx进行想与,这里判定的可不可以,可以的话通过获取当前可以用的核进行获取最高位的位置通过bsr指令,获取后通过kimask32arrary的表来获取当前的值,来把可使用的核的最高位去掉,暂时保存到ecx中,那么根据逆向可以知道后面三句话中的ecx是哪个核,eax为索引,从而获取kprcb的地址

Untitled

函数逆向:

Untitled

这样看会好一点

Untitled

总结:

根据就绪位图,先获取最高位,获取后暂时把最高位取消,获取线程链表中的项根据就绪位图刚才拿到的最高位,获取后要判断亲核性是不是一个组内的,如果找到相同的组那就把这个线程移除等待链表,如果链表不是空的,就绪位图不需要清空,空的话就把之前的就绪位图清空最高位的写回到就绪位图中

线程切换

Untitled

  • Initialized → 我们创建线程的时候
  • Ready → 创建完线程(在切换掉后)准备运行
  • Running → 运行状态
  • Standy → 先准备 备用状态 然后运行
  • Terminated → 线程结束
  • Waiting →线程等待 ( KeWaitForsingleObject / Sleep )
  • Transition → 转化 线程的堆栈内存,是否在磁盘上
  • DeferredReady → 延时就绪
  • GateWait → KeWaitForGate

在内核的时候,状态除了 Initialized和Terminated的状态 其他的状态都是可以随意切换的,并不像说在3环的时候 什么从A→B→C状态的那种

线程的切换有两种姿势

  • 时钟切换
  • 主动切换

主动切换 SwapContext :

首先来到KiSwapThread函数:

参数分别为 edx → KPRCB

  • edx → KPRCB
  • ecx → current thread

Untitled

性能统计相关,不需要关注,直接跳过去

Untitled

通过API KiSearchForNewThread函数,再搜索一个新的线程,把就绪线程放到eax寄存器

Untitled

由上面搜索完新的就绪线程存在后,首先要比较取出来的线程是不是闲置线程,然后判断取出来的线程是不是和旧的线程是同一个线程,也要比较取出来的线程的状态是不是运行状态,因为备用线程不能是运行状态,所以如果是运行状态需要改成备用状态

Untitled

这些都做完后,相当于找到了新的线程,也有旧的线程,调用KiSwapContext进行切换

Untitled

将非易变的寄存器放进栈中进行保存,然后调用SwapContext函数

Untitled

判断新线程的运行状态是不是0,如果是运行状态,就自旋,直到新线程不是运行状态为止

Untitled

想调试的话 直接对running的状态那里下个写断点,就可以啦

Untitled

记录了线程的交换次数+1,保存老线程的异常,根据NpxState的数值来判断是否要保存浮点,保存esp后进行esp切换来切换线程的栈

Untitled

判断新旧两个线程是不是同一个父进程,从而来判断切不切换cr3,也要通过eflag来判断是不是vm 8086模式,这些都判断完后清空GS位

Untitled

在逆向 线程切换的时候 出现的问题就是 不知道 当时系统调用中的29c和函数中的210的问题 现在知道了

内核堆栈的结构:

内核堆栈有2部分组成,即 InitialStack + 0x210字节 和 _Trap_Frame结构组成

Untitled

总结来看:线程就是:EIP,EIP在哪哪就是线程,线程切换就是:ESp,进程就是:CR3,线程主动切换,根据搜索的线程,从而得到新线程,如果新线程如果想和旧线程切换那么就是切换esp,如果父进程不一样那么就是切换cr3

分析滴水模拟线程切换源码

主要是当年滴水逆向唐老师写的那份模拟线程切换的源码,感觉写的还不错,于是乎看了一下,感觉大部分的原理和上面的swapcontext的原理差不多

注册线程基础结构,初始化线程环境

Untitled

定义了线程的基本的结构,然后指针数组0,为NULL,

typedef struct
{
	char *name;						//线程名 相当于线程TID
	int Flags;						//线程状态
	int SleepMillisecondDot;		//休眠时间
	
	void *InitialStack;				//线程堆栈起始位置
	void *StackLimit;				//线程堆栈界限
	void *KernelStack;				//线程堆栈当前位置,也就是ESP
	
	void *lpParameter;				//线程函数的参数
	void (*func)(void *lpParameter);//线程函数
	
} GMThread_t;

GMThread_t GMThreadList[MAXGMTHREAD] = { NULL,0 };
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);
}

结构体进行初始化,首先把名字当作线程的pid,线程的函数指向了func,参数,申请的内存空间被当作了栈的空间,根据入栈来对当作栈空间的位置,当我们设置完后,把当前的线程的状态设置位就绪态,

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);
	//清零
	memset(StackPages,0,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,5);								//push ebp
	PushStack(&StackDWORDParam,7);								//push edi
	PushStack(&StackDWORDParam,6);								//push esi
	PushStack(&StackDWORDParam,3);								//push ebx
	PushStack(&StackDWORDParam,2);								//push ecx
	PushStack(&StackDWORDParam,1);								//push edx
	PushStack(&StackDWORDParam,0);								//push eax
    
	GMThreadp->KernelStack = StackDWORDParam;
	

	GMThreadp->Flags = GMTHREAD_READAY;

	return ;
}

仿制线程切换:

Untitled

首先把SrcGMThreadp,线程结构的数组定义为0,然后进入循环,来搜索第一个GMThreadList结构体,因为第一个初始化的是一定是Ready状态,所以DstGMThreadp变成了要切换新的线程的结构体,然后进行SwitchContext

void Scheduling(void)
{
	int i;
	int TickCount;
	GMThread_t *SrcGMThreadp;
	GMThread_t *DstGMThreadp;

	TickCount = GetTickCount();

	SrcGMThreadp = &GMThreadList[CurrentThreadindex];
	
	DstGMThreadp = &GMThreadList[0];
	for (i=1;GMThreadList[i].name;i++)
	{
		if (GMThreadList[i].Flags&GMTHREAD_SLEEP)
		{
			if (TickCount>GMThreadList[i].SleepMillisecondDot)
			{
				GMThreadList[i].Flags = GMTHREAD_READAY;
			}
		}

		if ((GMThreadList[i].Flags&GMTHREAD_READAY))
		{
			DstGMThreadp = &GMThreadList[i];
			break;
		}
	}
	
	CurrentThreadindex = DstGMThreadp-GMThreadList;
	SwitchContext(SrcGMThreadp,DstGMThreadp);
	
	return ;
}

切换线程

__declspec(naked) void SwitchContext(GMThread_t *SrcGMThreadp,GMThread_t *DstGMThreadp)
{
	__asm
	{
		push ebp
		mov ebp,esp
		//sub esp,__LOCAL_SIZE
		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
		//add esp,__LOCAL_SIZE
		pop ebp
		ret
	}
}

第一次ret的地方就是GMThreadStartup

void GMThreadStartup(GMThread_t *GMThreadp)
{
	GMThreadp->func(GMThreadp->lpParameter);
	GMThreadp->Flags = GMTHREAD_EXIT;
	Scheduling();

	return ;
}

GMThreadp→func()

void Thread1(void *lpParameter)
{
	int i;
	for (i=0;i<3;i++)
	{
		vmmprint(_SELF,__LINE__,"Thread1 \n");
		GMSleep(1000);
	}

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

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

	Scheduling();
	return ;
}

大概平衡堆栈应该就是这个意思了,esp是我们的函数的一般的返回的地址,一般函数的参数是esp +4 这就是push 9当时的一个妙处,所以为什么我们要压入一个参数来让我们的堆栈进行平衡

Untitled

具体的看这个汇编我们首先把push ebp 提升到 push 9的那个之前的栈+4 然后进行了mov ebp, esp 把当前的位置给了ebp,然后ebp+8正好是我们GMThread的结构体的位置

Untitled

总结看来大概没啥!感觉把swapcontext逆明白就牛逼的!其实

时钟中断

Untitled

  • 时钟中断发生到切换线程的函数调用顺序
    • HalpHpetClockInterrupt
    • KeUpdateSystemTimeAssist
    • KeUpdateSystemTime
    • KeUpdateRunTime
    • HalRequestSoftwareInterrupt
    • KfLowerIrql
    • HalpCheckForSoftwareInterrupt
    • HalpDispatchSoftwareInterrupt
    • KiQuantumEnd
  • 时钟中断后主要做的几件事情
    • 刷新硬件状态
    • 时间累加
    • 滴答更新
    • DPC回调派发
    • 定时器回调派发
    • 线程切换
    • APC派发
    • DR7检查
    • 双机断点检查

WRK里面的代码太多了!就不写了!