|
[讨论]MmGetSystemRoutineAddress 得不到 NtReadVirtualMemory 地址
非导出函数 一般做法就是根据系统版本硬编码 |
|
|
|
[原创]为你的驱动挂上外挂 - 嵌入反汇编引擎
自己占楼备用 |
|
[原创]围观一下tp的游戏保护
tp中创建了一个IO TIMER,所以我们来看一下如何遍历系统中的所有IO TIMER 至于基本概念,大家有不明白的就去参考一下WDK文档吧 PT中提供了这个功能: 最终我山寨的效果: 多了一个,应该是PT没有显示无效的IO TIMER 我们最终的目的是遍历IO TIMER链然后取消指定模块中的IO TIMER, 所以理想中我会最终封装出来一个这样的函数: BOOLEAN DeleteIoTimerInSpecialModule(PWCHAR pModuleName); 关于遍历IO TIMER,网上我搜了下貌似没什么具体的介绍,那么我们就看一下WRK中有没有相关线索, 还是从已导出的函数入手,相关函数: IoInitializeTimer初始化IO TIMER IoStartTimer启动IO TIMER IoStopTimer停止IO TIMER 先看一下IoInitializeTimer: 代码很简单,如果传入的设备对象没有关联IO TIMER就分配一个IO TIMER,并与设备对象相关联, 也就是保存到设备对象的Timer域, 最后将IO TIMER的TimerList域追加到了IopTimerQueueHead的尾部,来看一下 IO TIMER 结构: 这个结构在xp,2k3,win7中是相同的 TimerList是一个ListEntry,这样我们只要找到了IopTimerQueueHead就能遍历系统中的所有IO TIMER了 搜索IopTimerQueueHead过程就不列出了,无非就是定位 接下来将IO TIMER中对我们有用的信息保存出来,这里我自定义了一个结构用来保存信息: typedef struct _MyIoTimer{ ULONG TimerObjectAddress; ULONG TimerState; ULONG TimerRoutine; ULONG DeviceObject; }MyIoTimer,*PMyIoTimer; #define MAX_IOTIMER_COUNT 150 //遍历IopTimerQueueHead取得IO TIMER信息 //执行成功返回MyIoTimer结构的数量,否则为0 ULONG GetIoTimerInformationByIopTimerQueueHead(PVOID* pvBuf) { ULONG IopTimerQueueHeadAddress; ULONG ulBufSize; ULONG ulTemp; ULONG ulCount = 0; PLIST_ENTRY pIopTimerQueueHead = NULL; PLIST_ENTRY pNextTimer = NULL; PIoTimer pTimer = NULL; MyIoTimer myTimer; IopTimerQueueHeadAddress = GetIopTimerQueueHeadAddress(); if ( MmIsAddressValidEx((PVOID)IopTimerQueueHeadAddress) == VCS_INVALID ) return 0; pIopTimerQueueHead = (PLIST_ENTRY)IopTimerQueueHeadAddress; pNextTimer = pIopTimerQueueHead->Blink; //分配足够大的空间 ulBufSize = MAX_IOTIMER_COUNT * sizeof(MyIoTimer); *pvBuf = ExAllocatePoolWithTag(PagedPool,ulBufSize,123); if (*pvBuf == NULL) return 0; while ( TRUE ) { if ( MmIsAddressValidEx(pNextTimer) == VCS_INVALID ) { KdPrint(("检测到IopTimerQueueHead中存在非法地址")); ExFreePool(*pvBuf); *pvBuf = NULL; return 0; } if ( pNextTimer == pIopTimerQueueHead ) break; pTimer = CONTAINING_RECORD(pNextTimer,IoTimer,TimerList); RtlZeroMemory(&myTimer,sizeof(MyIoTimer)); myTimer.TimerObjectAddress = (ULONG)pTimer; //IO TIMER对象地址 myTimer.TimerState = (ULONG)pTimer->TimerFlag; myTimer.TimerRoutine = (ULONG)pTimer->TimerRoutine; //TIMER例程地址 myTimer.DeviceObject = (ULONG)pTimer->DeviceObject; //设备对象 RtlMoveMemory( (PVOID)((ULONG)*pvBuf+ulCount*sizeof(MyIoTimer)),&myTimer,sizeof(MyIoTimer) ); pNextTimer = pNextTimer->Blink; ulCount++; if ( ulCount > MAX_IOTIMER_COUNT ) { KdPrint(("Io Timer 数量超过了150")); //这里简单退出了,可以更改为分配更大的缓冲 ExFreePool(*pvBuf); *pvBuf = NULL; return 0; } } return (ulCount*sizeof(MyIoTimer)); } 通过封装的这个函数就能得到系统中的所有IO TIMER了 最后判断一下 TimerRoutine 是否在指定的模块地址范围之内,如果检测到了的话, 调用IoStopTimer取消定时器 BOOLEAN DeleteIoTimerInSpecialModule(PWCHAR pModuleName) { PVOID pvTimerBuf = NULL; ULONG ulTimerReturn; ULONG ulModuleBase; ULONG ulModuleSize; ULONG i; ULONG ulCount = 0; PMyIoTimer pMyTimer = NULL; BOOLEAN Result = FALSE; if (pModuleName == NULL) goto __exit1; ulTimerReturn = GetDriverObjectByIopTimerQueueHead(&pvTimerBuf); if (ulTimerReturn == 0) goto __exit1; ulModuleBase = GetSpecialModuleBase(pModuleName,&ulModuleSize); if ( (ulModuleBase < (ULONG)MmSystemRangeStart)||(ulModuleSize == 0) ) goto __exit1; pMyTimer = (PMyIoTimer)pvTimerBuf; //遍历所有IO TIMER while (TRUE) { if ( (pMyTimer->TimerObjectAddress == 0)||(ulCount == MAX_IOTIMER_COUNT - 1) ) break; if ( (pMyTimer->TimerRoutine > ulModuleBase)&&(pMyTimer->TimerRoutine < ulModuleBase + ulModuleSize) ) IoStopTimer((PDEVICE_OBJECT)pMyTimer->DeviceObject); pMyTimer++; ulCount++; } Result = TRUE; __exit1: if ( pvTimerBuf != NULL ) ExFreePool(pvTimerBuf); pvTimerBuf = NULL; return Result; } |
|
[原创]围观一下tp的游戏保护
以下言论只当作技术探讨,请勿应用在非法领域 昨天与大家一起学习了关于遍历 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检测 要解决这些还要做很多额外的工作. 如果大家有什么好的方法,也要共享出来啊~~ |
|
[原创]围观一下tp的游戏保护
以后会慢慢介绍它们的寻找方法的,呵呵 多谢捧场 |
|
[原创]围观一下tp的游戏保护
这...初学者,见笑了 |
|
[讨论]X游戏保护,过了很多 还有问题
我得到的结论不是它的线程负责清零debug port,而是其他更猥琐的东西.. |
操作理由
RANk
{{ user_info.golds == '' ? 0 : user_info.golds }}
雪币
{{ experience }}
课程经验
{{ score }}
学习收益
{{study_duration_fmt}}
学习时长
基本信息
荣誉称号:
{{ honorary_title }}
能力排名:
No.{{ rank_num }}
等 级:
LV{{ rank_lv-100 }}
活跃值:
在线值:
浏览人数:{{ visits }}
最近活跃:{{ last_active_time }}
注册时间:{{ user_info.create_date_jsonfmt }}
勋章
兑换勋章
证书
证书查询 >
能力值