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