目录
-
操作系统特征检测方法
-
1. 检查调试权限
-
2. 使用不平衡的堆栈
-
反制措施
-
归功于
操作系统特征检测方法
该组中的规避技术利用了操作系统的工作方式的特征性。
1. 检查调试权限
如果恶意软件在调试器或像Cuckoo这样的沙盒中运行,其进程令牌将有一个调试权限处于启用状态。发生这种情况是因为该权限在父进程中启用,并由恶意软件进程继承。
恶意软件试图用PROCESS_ALL_ACCESS访问权打开关键的系统进程,如csrss.exe、smss.exe、lsass.exe,然后试图终止它们。在正常情况下,当恶意软件从资源管理器或命令行执行时,这将失败,因为即使是管理员用户也不能终止这些进程。但是,如果进程令牌在启用状态下具有调试权限,这将取得成功。终止关键的系统进程会导致操作系统崩溃,出现0x000000F4的错误,所以伪装过程会被中止。
获取正在运行进程快照的函数:
-
CreateToolhelp32Snapshot
-
psapi.EnumProcesses (WinXP, Vista)
-
kernel32.EnumProcesses (Win7+)
用来启动进程的函数:
-
OpenProcess(PROCESS_ALL_ACCESS, …, pid) // track for PIDs of ‘csrss.exe’, ‘smss.exe’, ‘lsass.exe’
代码样本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
/* 中文: 如果我们正在被调试,并且该进程有SeDebugPrivileges 权限,那么OpenProcess的调用将是成功的。 这需要管理员的权限!在Windows XP、Vista和7中,在调用OpenProcess时有 PROCESS_ALL_ACCESS调用OpenProcess会失败,即使启用了SeDebugPrivilege。 这就是为什么我使用PROCESS_QUERY_LIMITED_INFORMATION。 English: If we're being debugged and the process has SeDebugPrivileges privileges then OpenProcess call will be successful. This requires administrator privilege! In Windows XP, Vista and 7, calling OpenProcess with PROCESS_ALL_ACCESS will fait even with SeDebugPrivilege enabled, That's why I used PROCESS_QUERY_LIMITED_INFORMATION */ DWORD GetCsrssProcessId() { if (API::IsAvailable(API_IDENTIFIER::API_CsrGetProcessId)) { auto CsrGetProcessId = static_cast <pCsrGetId>(API::GetAPI(API_IDENTIFIER::API_CsrGetProcessId)); return CsrGetProcessId(); } else return GetProcessIdFromName(_T( "csrss.exe" )); } BOOL CanOpenCsrss() { HANDLE hCsrss = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, GetCsrssProcessId()); if (hCsrss != NULL) { CloseHandle(hCsrss); return TRUE; } else return FALSE; } |
该代码样本的作者:al-khaser project
识别标志
如果OpenProcess在打开一个关键的系统进程时要求所有可能的权限–这是恶意软件试图应用这种规避技术的强烈迹象。
2. 使用不平衡的堆栈
这项技术是由Check Point恶意软件逆向工程团队在2016年病毒公告上提出的。它是由这个链接描述的。
为了跟踪进程行为,CuckooMon/Cuckoo Monitor模块钩住了相关功能。在这种类型的架构中,钩子在原始函数之前被调用。被钩住的函数除了使用原始函数的空间外,还可能使用堆栈上的一些空间。因此,被钩住的函数在堆栈上使用的总空间可能大于仅被原始函数使用的空间。
问题:恶意软件有关于被调用函数在堆栈上使用多少空间的信息。因此,它可以将堆栈指针移向较低的地址,其偏移量足以存储函数参数、局部变量和返回地址,为它们保留空间。恶意软件用一些相关数据填充堆栈指针下面的空间。然后,它将堆栈指针移到原来的位置并调用库函数。如果该函数没有被钩住,恶意软件会在相关数据之前填入保留的空间(见图1)。如果函数被钩住,恶意软件就会重叠相关数据,因为为原始函数的局部变量保留的空间小于钩子和原始函数的局部变量加起来的空间。因此,相关数据被破坏了(见图2)。如果它存储了一些函数的指针,这些指针在以后的执行过程中被使用,恶意软件就会跳转到任意代码,偶尔会使应用程序崩溃。
在非拦截函数调用中的堆栈:
在拦截函数调用中的堆栈:
解决方案:为了避免这种行为,Cuckoo Monitor/CuckooMon模块可以使用一个两阶段的钩子过程。在第一阶段,代替钩子的代码执行,它可以将堆栈指针移到一个特定大小的较低的地址,足以容纳恶意软件的相关数据。然后,函数的参数被复制到新的堆栈指针下。只有在这些准备工作完成后,第二阶段的钩子(执行真正的钩子)才被调用。恶意软件输入的相关数据驻留在上层堆栈地址,因此它不会以任何方式受到调用函数的影响。
代码样本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
bool Cuckoo::CheckUnbalancedStack() const { usf_t f = { { lib_name_t(L "ntdll" ), { { sizeof ( void *), NULL, "ZwDelayExecution" , ARG_ITEM(kZwDelayExecutionArgs) } } } }; const uint8_t canary[8] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF }; uint32_t args_size; const void *args_buff; uint32_t reserved_size; uint32_t reserved_size_after_call; uint32_t canary_size; FARPROC func; bool us_detected; void *canary_addr = ( void *)&canary[0]; static_assert(( sizeof (canary) % sizeof ( void *)) == 0, "Invalid canary alignement" ); for ( auto it = f.begin(), end = f.end(); it != end; ++it) { for ( auto &vi : it->second) { vi.func_addr = GetProcAddress(GetModuleHandleW(it->first.c_str()), vi.func_name.c_str()); // call to Unbalanced Stack args_size = vi.args_size; args_buff = vi.args_buff; canary_size = sizeof (canary); reserved_size = sizeof ( void *) + vi.local_vars_size + canary_size; reserved_size_after_call = reserved_size + args_size; func = vi.func_addr; us_detected = false ; __asm { pusha mov ecx, args_size sub esp, ecx mov esi, args_buff mov edi, esp cld rep movsb sub esp, reserved_size mov ecx, canary_size mov esi, canary_addr mov edi, esp rep movsb add esp, reserved_size mov eax, func call eax sub esp, reserved_size_after_call mov ecx, canary_size mov esi, canary_addr mov edi, esp repz cmpsb cmp ecx, 0 setnz us_detected add esp, reserved_size_after_call popa } if (us_detected) return true ; } } return false ; } |
识别标志
我们不提供识别标志,因为在恶意软件方面跟踪这种行为是非常棘手的。
反制措施
-
与检查调试权限相比:拦截OpenProcess并跟踪关键系统进程的PID – 然后返回一个错误。
-
与使用不平衡堆栈相比。1)在函数调用前调整堆栈;2)在内核模式下拦截。
暂无评论内容