Uroburos Rootkit样本简单分析

 1.首先拿到样本,载入ida发现有一个类似于压缩壳解码的函数,跳过后dump出pe,修复对齐和section header。

 2.该样本会检查是否运行在wow64环境,检测名为Ultra3的服务和一系列事件来确保是第一次启动。

 

 

3.随后该样本会内存加载资源段的一个pe文件尝试利用CVE-2009-1123执行特权代码

 

 4.如果该漏洞利用成功将直接内存加载驱动文件,然后退出该样本。否则将会尝试利用CVE-2010-0232进行权限提升,该样本通过尝试改写注册表currentversion字段来判断是否权限提升成功。如果提权失败则返回错误代码退出样本。如果该样本运行在vista以上版本并且是64位系统,则会尝试通过virtualbox的驱动漏洞关闭dse。然后打开加载驱动权限,手动填写注册表的服务,通过ZwLoadDriver加载驱动。

 

5.该驱动会解密自身的代码,然后重新改写pe的入口,我们可以在解码完后用windbg dump出来然后修复。

 

 5.修复完后该样本就是安全客那篇文章最后dump出来的样本(https://www.anquanke.com/post/id/189549)

 6.该样本在驱动入口首先创建了一个全局的结构体储存一些信息,随后创建之前在应用层检测的全局事件。

struct
{
    int  unknown; //0xffffffff
    int* DriverStart;
    int  DriverSize;
    int* ntoskrl_base;
    int  ntoskrl_size;
    int  process_id;
}

 7.然后把自身的整个pe映像复制到了一块新申请的内存并做了重定位后重新执行驱动入口,然后执行关键代码。

 1 unsigned int __stdcall fix_reloc(int a1, int a2)
 2 {
 3   unsigned int result; // eax
 4   int TypeOffset; // [esp+4h] [ebp-34h]
 5   _DWORD *i; // [esp+Ch] [ebp-2Ch]
 6   int v5; // [esp+14h] [ebp-24h]
 7   int v6; // [esp+1Ch] [ebp-1Ch]
 8   int v7; // [esp+20h] [ebp-18h]
 9   unsigned __int16 v8; // [esp+24h] [ebp-14h]
10   unsigned int v9; // [esp+28h] [ebp-10h]
11   int v10; // [esp+2Ch] [ebp-Ch]
12   int offset; // [esp+34h] [ebp-4h]
13 
14   v6 = *(_DWORD *)(a1 + 0x3C) + a1;
15   if ( *(_WORD *)(v6 + 0x18) == 0x10B )
16   {
17     v5 = *(_DWORD *)(v6 + 0xA0);                // image_directory_entry_basereloc.VirtualAddress
18     v7 = a2 - *(_DWORD *)(v6 + 0x34);
19     v10 = *(_DWORD *)(v6 + 0x70);
20   }
21   else
22   {
23     v5 = *(_DWORD *)(v6 + 0xB0);
24     v7 = a2 - *(_DWORD *)(v6 + 0x30);
25     v10 = *(_DWORD *)(v6 + 0x80);
26   }
27   if ( !v7 )
28     return 0;
29   if ( v5 )
30   {
31     for ( i = (_DWORD *)(v5 + a1); i[1]; i = (_DWORD *)(v5 + a1) )// 修复重定位
32     {
33       TypeOffset = v5 + a1 + 8;
34       v8 = (unsigned int)(i[1] - 8) >> 1;       // (IMAGE_BASE_RELOCATION.SizeOfBlock 
35                                                 // -sizeof(IMAGE_BASE_RELOCATION.SizeOfBlock))
36                                                 // /sizeof(TypeOffset)
37       v9 = 0;                                   // 计算重定位条目的个数
38       while ( v9 < v8 )
39       {
40         offset = *i + (*(_WORD *)(TypeOffset + 2 * v9) & 0xFFF);// 待修复地址
41         switch ( (signed int)*(unsigned __int16 *)(TypeOffset + 2 * v9) >> 0xC )// Based relocation types
42         {
43           case 0:
44             goto LABEL_13;
45           case 1:
46             *(_WORD *)(offset + a1) += HIWORD(v7);
47             goto LABEL_13;
48           case 2:
49             *(_WORD *)(offset + a1) += v7;
50             goto LABEL_13;
51           case 3:
52             *(_DWORD *)(offset + a1) += v7;     // 重定位之后的值 = 需要进行重定位的地址值 - IMAGE_OPTINAL_HEADER中的基址 + 实际基址
53             goto LABEL_13;
54           case 4:
55             return 0x21590064;
56           case 5:
57           case 6:
58           case 7:
59           case 8:
60           case 9:
61             return 0x21590064;
62           case 0xA:
63             *(_QWORD *)(offset + a1) += (unsigned int)v7;
64 LABEL_13:
65             ++v9;
66             break;
67         }
68       }
69       v5 += i[1];
70     }
71     result = 0;
72   }
73   else if ( v10 & 1 )
74   {
75     result = 0xFFFFFFFF;
76   }
77   else
78   {
79     result = 0;
80   }
81   return result;
82 }

8.内核重载,然后根据内核加载基址重定位全局变量。然后将自己加载内核中的ssdt内容复制到申请的内存池中确保后面hook api时取到的是正确的地址。

  1 int __stdcall memLoad_fixRelocate(int a1, wchar_t *ntoskrl_path, int ntoskrl_base, int a4, int a5)
  2 {
  3   unsigned int v6; // eax
  4   char v7[5]; // [esp+37h] [ebp-19h]
  5   HANDLE ProcessHandle; // [esp+3Ch] [ebp-14h]
  6   PVOID Buffer; // [esp+40h] [ebp-10h]
  7   ULONG Length; // [esp+44h] [ebp-Ch]
  8   HANDLE Handle; // [esp+48h] [ebp-8h]
  9   PVOID P; // [esp+4Ch] [ebp-4h]
 10 
 11   P = 0;
 12   ProcessHandle = 0;
 13   Buffer = 0;
 14   *(_DWORD *)a4 = 0;
 15   Handle = IoCreateFile_(ntoskrl_path, 0x8000); // open ntoskrnl
 16   if ( Handle != (HANDLE)0xFFFFFFFF )
 17     goto LABEL_15;
 18   P = alloc_pool(0x208u);
 19   if ( !P )
 20     return 0x21590004;
 21   *(_DWORD *)&v7[1] = sub_86911830((wchar_t *)P, 0x104u);
 22   if ( !*(_DWORD *)&v7[1] )
 23   {
 24     if ( a1 )
 25     {
 26       *(_DWORD *)&v7[1] = sub_8693A3E0(&ProcessHandle, 0x400u, a1);
 27       if ( *(_DWORD *)&v7[1] )
 28         goto LABEL_30;
 29       *(_DWORD *)&v7[1] = sub_8693A100((HANDLE)0xFFFFFFFF, (int)v7);
 30       if ( *(_DWORD *)&v7[1] )
 31         goto LABEL_30;
 32     }
 33     else
 34     {
 35       v7[0] = 0;
 36     }
 37     if ( v7[0] )
 38       wcsncat((wchar_t *)P, L"\SysWOW64\", 0x104 - wcslen((const unsigned __int16 *)P));
 39     else
 40       wcsncat((wchar_t *)P, L"\System32\", 0x104 - wcslen((const unsigned __int16 *)P));
 41     wcsncat((wchar_t *)P, ntoskrl_path, 0x104 - wcslen((const unsigned __int16 *)P));
 42     *((_WORD *)P + 0x103) = 0;
 43     Handle = IoCreateFile_((wchar_t *)P, 0);
 44     if ( Handle == (HANDLE)0xFFFFFFFF )
 45     {
 46       *(_DWORD *)&v7[1] = 0x21590005;
 47       goto LABEL_30;
 48     }
 49 LABEL_15:
 50     Length = sub_86912690(Handle);              // 查询文件长度
 51     if ( Length == 0xFFFFFFFF )
 52     {
 53       *(_DWORD *)&v7[1] = 0xFFFFFFFF;
 54     }
 55     else
 56     {
 57       Buffer = alloc_pool(Length);
 58       if ( Buffer )
 59       {
 60         if ( readfile(Handle, Buffer, Length) == 0xFFFFFFFF )
 61         {
 62           *(_DWORD *)&v7[1] = 0xFFFFFFFF;
 63         }
 64         else
 65         {
 66           Length = calc_pe_size_in_memory((int)Buffer);// 计算在内存中展开大小
 67           *(_DWORD *)a4 = alloc_pool(Length);
 68           if ( *(_DWORD *)a4 )
 69           {
 70             *(_DWORD *)&v7[1] = load_pe_in_memory(*(_DWORD *)a4, (int)Buffer);// 内存加载ntoskrnl
 71             if ( !*(_DWORD *)&v7[1] )
 72             {
 73               v6 = ntoskrl_base ? fix_reloc(*(_DWORD *)a4, ntoskrl_base) : fix_reloc(*(_DWORD *)a4, *(_DWORD *)a4);// 用nt内核文件的加载基址来进行重定位修复,应该是准备内核重载
 74               *(_DWORD *)&v7[1] = v6;           // 修正新加载内核中的全局变量
 75               if ( !v6 )
 76               {
 77                 if ( a5 )
 78                   *(_DWORD *)a5 = Length;
 79               }
 80             }
 81           }
 82           else
 83           {
 84             *(_DWORD *)&v7[1] = 0x21590004;
 85           }
 86         }
 87       }
 88       else
 89       {
 90         *(_DWORD *)&v7[1] = 0x21590004;
 91       }
 92     }
 93   }
 94 LABEL_30:
 95   if ( *(_DWORD *)&v7[1] )
 96   {
 97     free_pool(*(PVOID *)a4);
 98     *(_DWORD *)a4 = 0;
 99   }
100   free_pool(Buffer);
101   sub_8693A460(ProcessHandle);
102   free_pool(P);
103   if ( Handle != (HANDLE)0xFFFFFFFF )
104     sub_86912590(Handle);
105   return *(_DWORD *)&v7[1];
106 }

9.hook 0xC3号中断,后面inline hook 时用中断进行服务分发,利用dpc将线程跑在指定cpu上来读取多核idtr。

 

 

 

 

 9.接着有类似于修复无模块seh的处理,这块我不太懂,如果有大佬知道的,可以告诉我。

 

 0xA.接着保存了一个要hook函数信息的结构体,这块没有仔细阅读代码,不过猜测应该是利用了反汇编引擎动态分析的函数头部需要hook的字节数,同时生成了要hook代码。

struct
{
  void* api_info; --->
                    {
                     void*  new_api;
                     int    hook_length;
                     void*  original_api;
                    }
}

 

0xB.这些api具体hook的函数代码没有继续分析了,这个样本一直零零散散搞了这么久,感觉没必要在分析下去了。需要花点时间看看书了。

这里附上这个样本的下载地址。https://files.cnblogs.com/files/DreamoneOnly/Uroburos.7z

原文地址:https://www.cnblogs.com/DreamoneOnly/p/11995391.html