首页
社区
课程
招聘
[原创]围观一下tp的游戏保护
发表于: 2011-9-20 11:43 98480

[原创]围观一下tp的游戏保护

2011-9-20 11:43
98480

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 6
支持
分享
最新回复 (83)
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
又是一种新思路,先回个贴表示认真围观。
2011-9-20 13:30
0
雪    币: 96
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
必火。。订楼主,楼上。。
2011-9-20 13:31
0
雪    币: 71
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
学习一下

3q
2011-9-20 13:38
0
雪    币: 220
活跃值: (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
呵呵 强势插入
2011-9-20 13:45
0
雪    币: 7354
活跃值: (4557)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
完全看不懂!!如果要看懂,需要什么条件?
2011-9-20 13:59
0
雪    币: 94
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
估计要精,顶一下,学习思路
2011-9-20 14:11
0
雪    币: 43
活跃值: (251)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
8
写得很棒,就连我这个不懂驱动的人都看得懂的。
能把核心技术写得这么浅显易懂的一定的高高手。
建议楼主能把使用的工具名写出来,我们好下载研究研究。

估计mj0011的这个进程用楼主这个方法可以结束了吧:
http://www.debugman.com/discussion/1122/%E8%BF%9B%E7%A8%8B%E4%BF%9D%E6%8A%A4%E6%8C%91%E6%88%98-%E6%97%A0hook%E6%97%A0kdom%E5%BE%AE%E8%BD%AF%E6%A0%87%E5%87%86%E5%87%BD%E6%95%B0
期待下文。
2011-9-20 14:28
0
雪    币: 343
活跃值: (40)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
9
强力帖子,支持,等下一帖
2011-9-20 14:30
0
雪    币: 71
活跃值: (40)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
学习了 不错
2011-9-20 14:34
0
雪    币: 27
活跃值: (127)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
11
我也围观一下 :)
2011-9-20 14:39
0
雪    币: 207
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
12
楼主是哪位牛人的马甲啊??前排围观...
2011-9-20 15:12
0
雪    币: 535
活跃值: (245)
能力值: ( LV12,RANK:400 )
在线值:
发帖
回帖
粉丝
13
这...初学者,见笑了
2011-9-20 15:32
0
雪    币: 204
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14

楼主为自己工作的道路上又多了一颗星,希望能最终成为五星上将
2011-9-20 15:42
0
雪    币: 415
活跃值: (34)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
15
支持下大神,看好你哦。
2011-9-20 16:22
0
雪    币: 388
活跃值: (10)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
16
求大牛的锁使用的神器??
2011-9-20 18:30
0
雪    币: 207
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
17
那你很不错哦...我现在还是小菜``希望可以多交流
2011-9-20 19:09
0
雪    币: 535
活跃值: (245)
能力值: ( LV12,RANK:400 )
在线值:
发帖
回帖
粉丝
18
以后会慢慢介绍它们的寻找方法的,呵呵 多谢捧场
2011-9-20 19:14
0
雪    币: 7651
活跃值: (523)
能力值: ( LV9,RANK:610 )
在线值:
发帖
回帖
粉丝
19
搞定了TP,还有各种P,合起来是NP,还是老V的AGP比较NB啊,搞定一切P~
2011-9-20 19:48
0
雪    币: 178
活跃值: (519)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
错过好贴的沙发了,围观楼主
2011-9-20 21:09
0
雪    币: 91
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
21
支持大牛,虽然说确实没懂多少
2011-9-20 22:56
0
雪    币: 1090
活跃值: (1288)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
拜帖  支持一下
2011-9-21 02:44
0
雪    币: 54
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
23
留名待查!~收藏了....楼主用的什么工具?
2011-9-21 09:45
0
雪    币: 414
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
标记下,有时间学习学习;
2011-9-21 09:59
0
雪    币: 535
活跃值: (245)
能力值: ( LV12,RANK:400 )
在线值:
发帖
回帖
粉丝
25

以下言论只当作技术探讨,请勿应用在非法领域

昨天与大家一起学习了关于遍历 PspCreateProcessNotifyRoutine 的一些知识,
今天我们来看一下关于线程方面

还是那句话我们这里讨论的目的不是如何杀死tp的两条线程,而是对于Windows系统的理解,从而能更加熟练地驾驭系统

驱动程序创建线程一般使用 PsCreateSystemThread 函数
NTSTATUS 
  PsCreateSystemThread(
    OUT PHANDLE  ThreadHandle,
    IN ULONG  DesiredAccess,
    IN POBJECT_ATTRIBUTES  ObjectAttributes  OPTIONAL,
    IN HANDLE  ProcessHandle  OPTIONAL,
    OUT PCLIENT_ID  ClientId  OPTIONAL,
    IN PKSTART_ROUTINE  StartRoutine,
    IN PVOID  StartContext
    );


这个函数很简单,由它创建的线程将运行在system进程中,
那么既然我们要杀死一条由驱动创建的线程,我们遍历一下system进程中的线程,
然后判断一下该线程函数是否位于目标模块的地址空间中就可以找到它了

那么我们接下来的工作就是首先要遍历system进程中的线程得到所有的线程函数地址,
遍历系统空间所有的模块得到目标模块的加载基址和模块的大小,
剩下的就是进行匹配了,
杀死线程我用的插APC的方法,所以必须设法得到线程的ETHREAD域,
因为插APC需要用到 KeInitializeApc 函数,这里面需要使用线程的KTHREAD结构地址,而KTHREAD就是ETHREAD的起始

NTKERNELAPI VOID
KeInitializeApc (PRKAPC Apc,
				 PRKTHREAD Thread,					//目标线程对象
				 KAPC_ENVIRONMENT Environment,
				 PKKERNEL_ROUTINE KernelRoutine,
				 PKRUNDOWN_ROUTINE RundownRoutine,
				 PKNORMAL_ROUTINE NormalRoutine,
				 KPROCESSOR_MODE ProcessorMode,
				 PVOID NormalContext);


我们这里使用 ZwQuerySystemInformation 进行进线程信息和模块信息的查询,
这个函数想必大家都再熟悉不过了,声明一下函数原型就能直接使用
不过相关的结构需要注意一下:

typedef struct _SYSTEM_PROCESSES_INFORMATION { // Information Class 5
	ULONG NextEntryDelta;          //由此结构开始距离下一个结构的偏移,如果为0表示没有下一个结构了
	ULONG ThreadCount;             //进程中的线程数
	ULONG Reserved1[6];            //保留
	LARGE_INTEGER CreateTime;      //进程的创建时间(从1601 1 1)
	LARGE_INTEGER UserTime;        //用户模式下运行的时间
	LARGE_INTEGER KernelTime;      //内核模式下运行的时间
	UNICODE_STRING ProcessName;    //进程名
	KPRIORITY BasePriority;
	ULONG ProcessId;			   //进程ID
	ULONG InheritedFromProcessId;
	ULONG HandleCount;
	ULONG Reserved2[2];
	VM_COUNTERS VmCounters;
	IO_COUNTERS IoCounters;
	SYSTEM_THREADS_INFORMATION Threads[1];     //进程的线程结构首个数组地址
} SYSTEM_PROCESSES_INFORMATION, *PSYSTEM_PROCESSES_INFORMATION;


typedef struct _SYSTEM_THREADS_INFORMATION {  // Information Class 5
	LARGE_INTEGER	KernelTime;
	LARGE_INTEGER	UserTime;
	LARGE_INTEGER	CreateTime;
	ULONG			WaitTime;
	PVOID			StartAddress;       //线程起始地址
	CLIENT_ID		ClientId;           //线程标识符,包含线程标识符和进程标识符
	KPRIORITY		Priority;           //线程优先级
	KPRIORITY		BasePriority; 
	ULONG			ContextSwitchCount; //线程的切换次数
	THREAD_STATE	State;				//线程的运行状态
	KWAIT_REASON	WaitReason;
	ULONG			Unknow;
} SYSTEM_THREADS_INFORMATION, *PSYSTEM_THREADS_INFORMATION;


我们一般都是从 Windows Nt 2000 Nttive Api Reference这本书中获取这些结构信息,
但是实际使用中我发现 SYSTEM_THREADS_INFORMATION 这个结构的长度是不准确的,少了四个字节,
所以结构最后我追加了一个双字域,我只测试了xp,win7两个系统,在这两个系统下该结构能正确匹配

封装了一下对于 ZwQuerySystemInformation 查询进线程信息的操作
BOOLEAN GetSystemProcessInformationByZwQuerySystemInformation(PVOID* pvBuf)
{
	
	ULONG			ulBufLen = 0x2000;
	NTSTATUS		status;

	do{
		*pvBuf = ExAllocatePoolWithTag(PagedPool,ulBufLen,1542);
		if (*pvBuf == NULL) 
			return FALSE;

		status = ZwQuerySystemInformation(5,*pvBuf,ulBufLen,NULL);

		if (status == STATUS_INFO_LENGTH_MISMATCH)
		{
			if (*pvBuf!=NULL)
			{
				ExFreePool(*pvBuf);
				*pvBuf = NULL;
			}
			ulBufLen += 0x2000;
		}
		else if (!NT_SUCCESS(status))
		{
			if (*pvBuf!=NULL)
			{
				ExFreePool(*pvBuf);
				*pvBuf = NULL;
			}
			return FALSE;
		}

	}while(status == STATUS_INFO_LENGTH_MISMATCH);

	return TRUE;
}


函数成功返回后 pvBuf 将包含一系列相关结构信息,并由调用者释放缓冲
SYSTEM_PROCESSES_INFORMATION的ProcessName域包含了进程的名称,
不过我选择了使用 PsLookupProcessByProcessId, 这个函数执行成功将返回进程的EPROCESS地址,
我们可以通过比较返回结果和 PsInitialSystemProcess 是否相同来判断是否为系统进程,
PsInitialSystemProcess 为系统导出变量,可以直接使用,它指向了 system 进程的EPROCESS
注意调用 PsLookupProcessByProcessId 成功之后要调用 ObDereferenceObject,具体原因在WDK上已明确指出

得到system进程的 SYSTEM_PROCESSES_INFORMATION 之后遍历一下其下的线程结构,
线程数量由 SYSTEM_PROCESSES_INFORMATION.ThreadCount 给出,
将线程结构数组中的 StartAddress 域和 ClientId 保存出来

接下来我们取得目标模块的相关信息,因为需要经常用到,我封装了一下这一操作,
不过我是通过遍历 PsLoadedModuleList 链来取得所有模块信息的,大家可以换成更稳妥的方式:

//遍历PsLoadedModuleList寻找指定模块的基址及大小<如果指定的话>
//执行成功返回指定模块基址,否则为0
ULONG GetSpecialModuleBase(PWCHAR pModuleName, PULONG pModuleSize)
{
	PVOID	pModuleList = NULL;
	PLDR_DATA_TABLE_ENTRY  pTableEntry = NULL;
	ULONG	ulTemp = 0;
	ULONG	ulReturnBytes = 0;
	ULONG	ulCount = 0;

	UNICODE_STRING inputModuleName = {0};
	UNICODE_STRING indexModuleName = {0};
	UNICODE_STRING ntoskrnl = RTL_CONSTANT_STRING(L"ntoskrnl.exe");
	UNICODE_STRING ntkrnlpa = RTL_CONSTANT_STRING(L"ntkrnlpa.exe");

	RtlInitUnicodeString(&inputModuleName,pModuleName);
	if ( inputModuleName.Length == 0 ) return 0;

	//是否是内核ntoskrnl<没有考虑多处理器>
	if ( (RtlCompareUnicodeString(&inputModuleName,&ntoskrnl,TRUE)==0) || (RtlCompareUnicodeString(&inputModuleName,&ntkrnlpa,TRUE)==0) )
		RtlInitUnicodeString(&inputModuleName,L"ntoskrnl.exe");

	ulReturnBytes = GetSystemModuleInformationByPsLoadedModuleList(&pModuleList);
	if ( (ulReturnBytes == 0)||(pModuleList == NULL) ) return 0;

	for (ulCount = 0; ulCount < (ulReturnBytes / sizeof(LDR_DATA_TABLE_ENTRY)); ulCount++ )
	{
		pTableEntry = (PLDR_DATA_TABLE_ENTRY)((ULONG)pModuleList+ulCount*sizeof(LDR_DATA_TABLE_ENTRY));

		if ( pTableEntry->BaseName[0]!=(WCHAR)L"\0" )
			RtlInitUnicodeString(&indexModuleName,(PWCHAR)pTableEntry->BaseName);
		else
			continue;

		//比较字串如果发现了则break出去,此时pTableEntry包含了对应的模块信息
		if ( RtlCompareUnicodeString(&inputModuleName,&indexModuleName,TRUE)==0 ) break;
	}

	//如果遍历完毕没有找到对应的模块,返回失败
	if ( ulCount == (ulReturnBytes / sizeof(LDR_DATA_TABLE_ENTRY)) )
	{
		if ( (pModuleList!=NULL) )
		{
			ExFreePool(pModuleList);
			pModuleList = NULL;
		}
		KdPrint(("GetSpecialModuleBase:未发现指定模块"));
		return 0;
	}

	ulTemp = (ULONG)pTableEntry->DllBase;
	if ( pModuleSize != NULL )
	{
		*pModuleSize = pTableEntry->SizeOfImage;
	}

	if ( (pModuleList!=NULL) )
	{
		ExFreePool(pModuleList);
		pModuleList = NULL;
	}

	return ulTemp;
}



取到目标模块的地址空间范围之后,我们就可以遍历一下之前取得的线程 StartAddress 信息,
如果 StartAddress 位于模块的地址空间范围就说明该线程由此模块创建,
这时我们取出对应的 ClientId.UniqueThread,
通过 PsLookupThreadByThreadId 就能取得线程的ETHREAD结构地址了
与 PsLookupProcessByProcessId 一样, 成功调用 PsLookupThreadByThreadId 之后也要调用 ObDereferenceObject解除引用

封装了一下对于插APC的操作
VOID
ApcKernelRoutine(PKAPC pApc,PKNORMAL_ROUTINE *NormalRoutine,PVOID *NormalContext,PVOID *SystemArgument1,PVOID *SystemArgument2)
{
	ExFreePool(pApc);
	PsTerminateSystemThread(STATUS_SUCCESS);
}

BOOLEAN TerminateSpecialThreadByAPC(PETHREAD threadEThread)
{
	PRKAPC	pApc = NULL;
	
	if ( MmIsAddressValidEx((PVOID)threadEThread) == VCS_INVALID ) return FALSE;
	
	pApc = ExAllocatePoolWithTag(NonPagedPool,sizeof(KAPC),12345);
	if ( pApc == NULL ) return FALSE;

	KeInitializeApc(pApc,(PRKTHREAD)threadEThread,OriginalApcEnvironment,&ApcKernelRoutine,NULL,NULL,KernelMode,NULL);

	return KeInsertQueueApc(pApc,NULL,NULL,1);
}


相对于我们现在的需要这貌似够用了,但是我想让它能再通用一些,
因为这个操作目前只能杀死system进程中的线程,
我想让它能杀死system进程中以外的线程,但是我们看一下WRK,
PsTerminateSystemThread 只能终结掉system进程中的线程:


看来是通过 IS_SYSTEM_THREAD 进行了判断,再看一下 IS_SYSTEM_THREAD:


嘿嘿,看来只是简单得检测了一个标志位,这样就简单了,我们手动添加这个标志到KTHREAD中不就行了吗?
不过这个标志位我看了一下在xp,2k3,win7 7600,win7 7601,下的偏移都不一样,这里就不做解释了

另外想直接利用PspTerminateThreadByPointer也是可以的,
从PsTerminateSystemThread开始搜索,可以得到PspTerminateThreadByPointer的地址
不过在xp中这个函数有两个参数,在2k3之后追加了一个参数,使用的时候需要注意

其实上面的所有操作都可以封装一下
BOOLEAN KillThreadsInSpecialModuleByApc(PWCHAR pModuleName);


以后自己使用就简单多了,这个比网上流传的那份特征码靠谱的多了
啊?什么特征码?好吧,当我没说..

当然这些操作对于断链的模块来说就无能为力了,而且主要的函数在调用前也没有进行hook检测
要解决这些还要做很多额外的工作.
如果大家有什么好的方法,也要共享出来啊~~

上传的附件:
2011-9-21 10:14
0
游客
登录 | 注册 方可回帖
返回
//