欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]重载内核之四蓝屏与崩溃处理

[原创]重载内核之四蓝屏与崩溃处理

  重载内核的相关文章实在是太多了,鉴于还是有很多初学者研究这一块,本文仅作为一个引导作用,文笔不好,见谅。
  我的博客:http://blog.csdn.net/sidyhe
  开发环境:VS2010 + WinDDK
  测试环境:VirtualDDK + VMware + Win7 sp1 x86
  第一部分http://bbs.pediy.com/showthread.php?t=187863
  第二部分http://bbs.pediy.com/showthread.php?t=187919
  第三部分http://bbs.pediy.com/showthread.php?t=187982
第四部分:最后的修正(蓝屏与崩溃)
  终于走到了这里,对于新的NT内核只剩下了两个问题了(其他尚未发现),第一个就是蓝屏,最麻烦的BUG,起初在我遇到的时候也是疯掉了(当时用的还是Windows 7 x86),最后发现是因为内核异常得不到处理造成的,即POOL代码决不能异常。而造成这种现象的就是SafeSEH机制。简要说明就是在内核代码中发生了异常会多一些处理,判断SEH Handler是否有效,再执行。
  现在普遍的解决思路有两个:1.废除SafeSEH机制。2.使POOL合法化。
  先来研究一下这个SafeSEH到底是怎么回事,在发生异常时经过一些处理后会调用RtlDispatchException,进而调用RtlIsValidHandler来判断Handler是否有效,如果无效,你懂得。


BOOLEAN RtlIsValidHandler(IN PEXCEPTION_ROUTINE Handler)

{

    PULONG FunctionTable;

    ULONG FunctionTableLength;

    PVOID Base;

    FunctionTable = RtlLookupFunctionTable(Handler, &Base, &FunctionTableLength);

    if (FunctionTable && FunctionTableLength) {

        PEXCEPTION_ROUTINE FunctionEntry;

        LONG High, Middle, Low;

        if ((FunctionTable == LongToPtr(-1)) && (FunctionTableLength == (ULONG)-1)) {

            // Address is in an image that shouldn't have any handlers (like a resource only dll).

            RtlInvalidHandlerDetected((PVOID)((ULONG)Handler+(ULONG)Base), LongToPtr(-1), -1);

            return FALSE;

        }

    

        // Bias the handler value down by the image base and see if the result

        // is in the table

        (ULONG)Handler -= (ULONG)Base;

        Low = 0;

        High = FunctionTableLength;

        while (High >= Low) {

            Middle = (Low + High) >> 1;

            FunctionEntry = (PEXCEPTION_ROUTINE)FunctionTable[Middle];

            if (Handler < FunctionEntry) {

                High = Middle - 1;

            } else if (Handler > FunctionEntry) {

                Low = Middle + 1;

            } else {

                // found it

                return TRUE;

            }

        }

        // Didn't find it

        RtlInvalidHandlerDetected((PVOID)((ULONG)Handler+(ULONG)Base), FunctionTable, FunctionTableLength);

        return FALSE;

    }

    // Can't verify

    return TRUE;

}


  上面代码中的RtlLookupFunctionTable是取得一个类似函数表的东西,在PE信息中的体现则是IMAGE_NT_HEADERS.OptionalHeader[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG],用我手里的内核文件做例子,通过IDA观察到这个函数表,放着一堆RVA:

  这些地址就是SEH Handler了,一共有0x12=18个,再来看KLDR的定义:

typedef struct _KLDR_DATA_TABLE_ENTRY {

    LIST_ENTRY InLoadOrderLinks;

    PVOID ExceptionTable;

    ULONG ExceptionTableSize;

    // ULONG padding on IA64

    PVOID GpValue;

    PNON_PAGED_DEBUG_INFO NonPagedDebugInfo;

    PVOID DllBase;

    PVOID EntryPoint;

    ULONG SizeOfImage;

    UNICODE_STRING FullDllName;

    UNICODE_STRING BaseDllName;

    ULONG Flags;

    USHORT LoadCount;

    USHORT __Unused5;

    PVOID SectionPointer;

    ULONG CheckSum;

    // ULONG padding on IA64

    PVOID LoadedImports;

    PVOID PatchInformation;

} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;


  很明显KLDR中的ExceptionTable应该指向IDA图示中的首地址。注意KLDR结构只能在WRK中查看,用WinDBG也看不到(dt),KLDR和LDR并不一样。还有,这个函数表里存放的就是RVA,并不会被重定位修复为VA。
  那么第一种方法就是HOOK RtlIsValidHandler,直接返回TRUE,则废掉SafeSEH。这个函数没导出,隐藏的很深,字节搜索或者重定位搜索很麻烦,但也是一种方法,不过我不用这种方式,虽然处理好之后能够很好的隐藏自己的NT内核。
  第二种方式就简单多了,虽然会被检测到存在异常模块,但又不影响什么,毕竟做的不是Rootkit。方法就是直接在PsLoadedModuleList插入一个KLDR,就那么简单。

PKLDR_DATA_TABLE_ENTRY KeGetImageLdrPointer(PKLDR_DATA_TABLE_ENTRY PsLoadedModuleList, PVOID lpImageAddress)

{

  PKLDR_DATA_TABLE_ENTRY lpTableEntry = PsLoadedModuleList;

  PKLDR_DATA_TABLE_ENTRY lpTablePointer = lpTableEntry;

  do 

  {

    if (lpTablePointer->DllBase == lpImageAddress)

    {

      return lpTablePointer;

    }

    lpTablePointer = (PKLDR_DATA_TABLE_ENTRY)(lpTablePointer->InLoadOrderLinks.Flink);

  } while (lpTableEntry != lpTablePointer);

  return NULL;

}

BOOLEAN KeInsertPsLoadedModuleList(PKLDR_DATA_TABLE_ENTRY PsLoadedModuleList, PVOID NewImage, PVOID OldImage)

{

  PKLDR_DATA_TABLE_ENTRY lpNewLdr, lpSimLdr;

  if (lpSimLdr = KeGetImageLdrPointer(PsLoadedModuleList, OldImage))

  {

    if (lpNewLdr = ExAllocatePool(NonPagedPool, sizeof(KLDR_DATA_TABLE_ENTRY)))

    {

      RtlCopyMemory(lpNewLdr, lpSimLdr, sizeof(KLDR_DATA_TABLE_ENTRY));

      lpNewLdr->DllBase = NewImage;

      InsertTailList(&PsLoadedModuleList->InLoadOrderLinks, &lpNewLdr->InLoadOrderLinks);

      DbgPrint("KeInsertPsLoadedModuleList:0x%p\n", lpNewLdr);

      return TRUE;

    }

  }

  return FALSE;

}


  在曾想过是否要修正KLDR中的ExceptionTable,但感觉没必要,毕竟指向的函数表里面都是RVA,又不是VA,反正SEH Handler都一样,不改也罢。
  好了这样就解决了SafeSEH所带来的蓝屏。基本上这个驱动就可以拿来在本机测试了。
崩溃问题
  这份代码能够很好的在大部分CPU上进行工作,但有些CPU仍会发生程序崩溃的问题,这与第三部分的文章中提到的打不开程序是不同的问题。我的CPU就是如此,否则我都不会发现这个问题。
  后面的东西绝对原创,网上没有哦。
  一开始遇到这个问题同样使我疯掉了一段时间,通过各种途径找问题,最后奇妙的发现了这里:

  在干净的系统中,当我使用ARK恢复所有的钩子后,居然出现了我所说的崩溃问题,这就说明了在系统初始化时自己Patch了自己,很明显的大家能够观察到有一个巨大的补丁,那就是一个修改了22字节的东西,这是啥玩意?WinDBG会告诉你的:

0: kd> uf 83e7dd50

nt!KeFlushCurrentTb:

83e7dd50 0f20e0          mov     eax,cr4

83e7dd53 0fbaf007        btr     eax,7

83e7dd57 7309            jae     nt!KeFlushCurrentTb+0x12 (83e7dd62)

nt!KeFlushCurrentTb+0x9:

83e7dd59 0f22e0          mov     cr4,eax

83e7dd5c 0c80            or      al,80h

83e7dd5e 0f22e0          mov     cr4,eax

83e7dd61 c3              ret

nt!KeFlushCurrentTb+0x12:

83e7dd62 0f20d9          mov     ecx,cr3

83e7dd65 0f22d9          mov     cr3,ecx

83e7dd68 c3              ret


  从字面上不难理解KeFlushCurrentTb是刷新了TB,TB是什么我就不知道了,个人猜测是CPU中TLB与TIB的统称,看代码也知道是刷新了页表。你也可以通过IDA来查看这部分的代码,简言之就是判断了CPU的类型,如果满足了什么条件,则Patch了这个函数。再来看恢复之后的样子:

1: kd> uf 83e7dd50

nt!KeFlushCurrentTb:

83e7dd50 0f20d8          mov     eax,cr3

83e7dd53 0f22d8          mov     cr3,eax

83e7dd56 c3              ret


  只能说这个函数在不同的CPU上可能会有不同的代码,那如何解决?难道像它一样来判断CPU进而修改这个代码?大可没有必要,我的做法就是定位到这里,然后JMP到原模块的这个地方。我想这里不会被HOOK,没有那么逆天。定位方法我也是利用的重定位,通过找到负责Patch的代码定位KeFlushCurrentTb,再JMP过去:

VOID PatchKeFlushCurrentTb(PVOID lpNewNtoskrnlAddress, PVOID lpNtoskrnlAddress)

{

  /*

  mov     edi, offset KeFlushCurrentTb

  mov     esi, offset byte_477D57

  mov     ecx, XXX

  rep movsb

  */

  IMAGE_DOS_HEADER *lpDosHeader = (IMAGE_DOS_HEADER*)lpNewNtoskrnlAddress;

  IMAGE_NT_HEADERS *lpNtHeader = (IMAGE_NT_HEADERS *)((PCHAR)lpDosHeader + lpDosHeader->e_lfanew);

  IMAGE_BASE_RELOCATION *lpRelocateTable = (IMAGE_BASE_RELOCATION*)((PCHAR)lpDosHeader + lpNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);

  while (lpRelocateTable->SizeOfBlock)

  {

    ULONG NumberOfItems = (lpRelocateTable->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);

    USHORT  *lpItem = (USHORT*)((PCHAR)lpRelocateTable + sizeof(IMAGE_BASE_RELOCATION));

    ULONG i;

    for (i = 0; i < NumberOfItems - 1; i++)

    {

      if ((lpItem[i] >> 12) == IMAGE_REL_BASED_HIGHLOW && (lpItem[i + 1] >> 12) == IMAGE_REL_BASED_HIGHLOW)

      {

        ULONG *lpFixAddress1 = (ULONG*)((PCHAR)lpDosHeader + lpRelocateTable->VirtualAddress + (lpItem[i] & 0x0FFF));

        ULONG *lpFixAddress2 = (ULONG*)((PCHAR)lpDosHeader + lpRelocateTable->VirtualAddress + (lpItem[i + 1] & 0x0FFF));

        if ((ULONG)lpFixAddress2 - (ULONG)lpFixAddress1 == 5)

        {

          if (*((PUCHAR)lpFixAddress1 - 1) == 0xBF && *((PUCHAR)lpFixAddress2 - 1) == 0xBE)

          {

            PUCHAR lpCheckBytes = (PUCHAR)lpFixAddress2 + sizeof(PVOID);

            if (lpCheckBytes[0] == 0xB9 && lpCheckBytes[5] == 0xF3 && lpCheckBytes[6] == 0xA4)

            {

              PUCHAR lpPatchAddress = (PUCHAR)*lpFixAddress1;

              lpPatchAddress[0] = 0xE9;

              *(ULONG*)&lpPatchAddress[1] = (ULONG)lpNtoskrnlAddress - (ULONG)lpNewNtoskrnlAddress - 5;

              return;

            }

          }

        }

      }

    }

    lpRelocateTable = (IMAGE_BASE_RELOCATION *)((PCHAR)lpRelocateTable + lpRelocateTable->SizeOfBlock);

  }

  return;

}


  好了,没事儿了,新的NT内核没问题了,全部搞定,如果仍发现自己解决不了的问题可以联系我,最后的DriverEntry代码:

NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING  RegistryPath)

{

  PVOID lpHookKiFastCallEntryAddress;

  UCHAR HookCode[7];

  DbgPrint("Driver Load.\n");

  InitializePsLoadedModuleList(DriverObject);

  g_lpNtoskrnlAddress = KeGetModuleHandle(PsLoadedModuleList, "ntoskrnl.exe");

  g_lpNewNtoskrnlAddress = ReloadNtModule(PsLoadedModuleList);

  g_KeServiceTable = (PVOID*)BuildKeServiceTable(g_lpNewNtoskrnlAddress, g_lpNtoskrnlAddress);

  KeFixReloc2(g_lpNewNtoskrnlAddress, g_lpNtoskrnlAddress);

  PatchKeFlushCurrentTb(g_lpNewNtoskrnlAddress, g_lpNtoskrnlAddress);

  KeInsertPsLoadedModuleList(PsLoadedModuleList, g_lpNewNtoskrnlAddress, g_lpNtoskrnlAddress);

  lpHookKiFastCallEntryAddress = FindHookKiFastCallEntryAddress(GetKiFastCallEntryAddress());

  HookCode[0] = 0xE8;

  *(ULONG*)&HookCode[1] = (ULONG_PTR)_MyKiFastCallEntryFrame - (ULONG_PTR)lpHookKiFastCallEntryAddress - 5;

  HookCode[5] = 0x90;

  HookCode[6] = 0x90;

  RtlCopyMemoryEx(lpHookKiFastCallEntryAddress, HookCode, sizeof(HookCode));

  DriverObject->DriverUnload = DriverUnload;

  return STATUS_SUCCESS;

}


  这一部分告一段落,接下来继续重载win32k,搞定SHADOW SSDT。
  最后,为啥PCHunter不支持Wwindows 8.1 update 1呢?现在的最新版1.32运行不到一分钟就蓝屏,原因是触发了PatchGuard,望修复,没有ARK用真心难受。

上传的附件 00.jpg
01.jpg
4.重载内核之蓝屏与崩溃处理.doc

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功