9.6 KiB
论文精读1
Behavior Nets: Context-Aware Behavior Modeling for Code Injection-Based Windows Malware
问题分析
摘要翻译
尽管人们在防御机制的研发上投入了大量精力,但新的恶意软件仍在不断快速发展, 使其成为互联网上的主要威胁之一。 恶意软件要想成功,开发者的最想要的就是尽可能长时间地逃避检测。 实现这一目标的方法之一是使用代码注入,即将恶意代码注入另一个良性进程,使其执行一些原本不该执行的操作。
自动检测和表征代码注入非常困难。 许多注入技术仅依赖于系统调用,而这些系统调用单独来看似乎是良性的,很容易与其他后台系统活动混淆。 因此,需要能够考虑单个系统事件所处上下文的模型,以便轻松区分相关活动。 在之前的工作中,我们首次对代码注入进行了系统性研究,以深入了解 Windows 平台上恶意软件开发人员可用的各种技术。 本文通过引入并形式化行为网络 (Behavior Nets) 扩展了这项工作:这是一种新颖的、可重用的、上下文感知的建模语言, 它通过可观察的事件及其一般的相互依赖关系来表达恶意软件行为。 这使得系统调用能够进行匹配,即使这些系统调用通常在良性环境中使用。 我们对行为网络进行了评估,并通过实验证实,将事件上下文引入行为特征在表征恶意行为方面比现有技术能取得更好的效果。 最后,我们针对未来如何基于动态分析开展恶意软件研究提出了宝贵的见解。
结果复现
作者Git仓库: https://github.com/utwente-scs/behavior-net
问题扩展 - Linux
在Linux平台上,文章提到的问题是否同样存在?
Step.1 文献搜索
Barabosch, T., Eschweiler, S., Gerhards-Padilla, E. (2014). Bee Master: Detecting Host-Based Code Injection Attacks. In: Dietrich, S. (eds) Detection of Intrusions and Malware, and Vulnerability Assessment. DIMVA 2014. Lecture Notes in Computer Science, vol 8550. Springer, Cham. https://doi.org/10.1007/978-3-319-08509-8_13
T. Barabosch and E. Gerhards-Padilla, "Host-based code injection attacks: A popular technique used by malware," 2014 9th International Conference on Malicious and Unwanted Software: The Americas (MALWARE), Fajardo, PR, USA, 2014, pp. 8-17, doi: 10.1109/MALWARE.2014.6999410. keywords: {Malware;Payloads;Complexity theory;Operating systems;Debugging}
从搜索结果可知,作者所提到的问题在Linux上同样存在,并且早已被广泛关注,但并不存在一个研究使用了作者的方法的“Linux版本”。
Step.2 扩展分析
一个很重要的问题是,自上面的两篇文章发布至今, Linux上的跨进程内存读写手段(Host-Based Code Injection行为的本质)是否有显著的变化?
答案是肯定的。
具体分析见第三节。
Step.3 扩展方向
- 基于最新版本的Linux生态,重新对现在Linux上真正广泛存在的“Host-Based Code Injection”问题进行建模。
- 尝试将本篇文献中提到的缓解方法迁移到Linux上,并对Linux平台的差异做适配。
- 研究行为图能否被结构化并使用深度学习技术进行分析建模。
今天Linux中的跨进程内存读写技术
近十年以来,Linux平台中的跨进程内存读写手段发生了较大的变化,这些变化主要并不集中于新增或删除,而是集中于收紧和沙箱化。 这主要是源于Linux社区的一个重要的开发、维护理念:“We do not break userspace”,这也是Linus的一句名言。
为了保持内核的向后兼容性,Linux不会轻易删除一个用户空间接口,除非他们有着一个明确的,非设计预期的“Bug”。 这意味着对于大多数的“符合设计预期的、合法的恶意利用”,Linux不得不保持接口原来的行为。
但这并不是说我们只能被动的接受,为了改善上述的问题,社区通常使用LSM(Linux Security Modules)。在我们的讨论主题中, 对Linux平台中的跨进程内存读写手段影响最大的LSM是Yama LSM。
什么是Yama LSM?
Yama 是一个轻量的 Linux Security Module (LSM),用于收紧 ptrace(和与之相关的 /proc/*/mem)权限, 避免任意同 UID 进程随意 attach/读写别人进程的内存,从而降低凭证窃取与进程注入风险。
历史上,任何同一 UID 的进程都能互相 ptrace,这给多用户主机、共享宿主机与容器环境带来了明显攻击面。 Yama 的目标是在不破坏现有调试工作流的前提下增加默认防护,防止横向滥用调试接口。
Yama会创建如上图所示的控制点:
/proc/sys/kernel/yama/ptrace_scope
默认情况下,这个控制点的值为“1”。其合法取值包括:
- 0:传统情况,同UID的进程可随意互相ptrace
- 1:限制模式,通常只允许父进程 ptrace 自己的子进程。
Yama的具体影响体现在ptrace的鉴权过程中,一个名为ptrace_may_access,位于linux/kernel/ptrace.c文件的函数中。
static int __ptrace_may_access(struct task_struct *task, unsigned int mode)
{
const struct cred *cred = current_cred(), *tcred;
struct mm_struct *mm;
kuid_t caller_uid;
kgid_t caller_gid;
if (!(mode & PTRACE_MODE_FSCREDS) == !(mode & PTRACE_MODE_REALCREDS)) {
WARN(1, "denying ptrace access check without PTRACE_MODE_*CREDS\n");
return -EPERM;
}
/* Don't let security modules deny introspection */
if (same_thread_group(task, current))
return 0;
rcu_read_lock();
if (mode & PTRACE_MODE_FSCREDS) {
caller_uid = cred->fsuid;
caller_gid = cred->fsgid;
} else {
caller_uid = cred->uid;
caller_gid = cred->gid;
}
tcred = __task_cred(task);
if (uid_eq(caller_uid, tcred->euid) &&
uid_eq(caller_uid, tcred->suid) &&
uid_eq(caller_uid, tcred->uid) &&
gid_eq(caller_gid, tcred->egid) &&
gid_eq(caller_gid, tcred->sgid) &&
gid_eq(caller_gid, tcred->gid))
goto ok;
if (ptrace_has_cap(tcred->user_ns, mode))
goto ok;
rcu_read_unlock();
return -EPERM;
ok:
rcu_read_unlock();
smp_rmb();
mm = task->mm;
if (mm &&
((get_dumpable(mm) != SUID_DUMP_USER) &&
!ptrace_has_cap(mm->user_ns, mode)))
return -EPERM;
return security_ptrace_access_check(task, mode);
}
bool ptrace_may_access(struct task_struct *task, unsigned int mode)
{
int err;
task_lock(task);
err = __ptrace_may_access(task, mode);
task_unlock(task);
return !err;
}
Yama会在security_ptrace_access_check这个层次介入,但具体的介入方式在这里没有必要深入讨论, 只需要知道Yama会影响ptrace_may_access的结果即可。
需要进一步指出的是,Yama这个LSM的影响范围绝不局限于ptrace调用,而是更加全面,下面将逐个说明。
/proc/<pid>/mem
/proc/<pid>/mem是Linux在Vfs中创建的一个重要的控制节点,其在Linux源码的linux/fs/proc/base.c文件中实现。
static const struct file_operations proc_mem_operations = {
.llseek = mem_lseek,
.read = mem_read,
.write = mem_write,
.open = mem_open,
.release = mem_release,
.fop_flags = FOP_UNSIGNED_OFFSET,
};
重点关注mem_open函数,file_operations中定义的open接口通常决定了访问是否被允许:
struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode)
{
struct task_struct *task = get_proc_task(inode);
struct mm_struct *mm;
if (!task)
return ERR_PTR(-ESRCH);
mm = mm_access(task, mode | PTRACE_MODE_FSCREDS);
put_task_struct(task);
if (IS_ERR(mm))
return mm == ERR_PTR(-ESRCH) ? NULL : mm;
/* ensure this mm_struct can't be freed */
mmgrab(mm);
/* but do not pin its memory */
mmput(mm);
return mm;
}
static int __mem_open(struct inode *inode, struct file *file, unsigned int mode)
{
struct mm_struct *mm = proc_mem_open(inode, mode);
if (IS_ERR_OR_NULL(mm))
return mm ? PTR_ERR(mm) : -ESRCH;
file->private_data = mm;
return 0;
}
static int mem_open(struct inode *inode, struct file *file)
{
if (WARN_ON_ONCE(!(file->f_op->fop_flags & FOP_UNSIGNED_OFFSET)))
return -EINVAL;
return __mem_open(inode, file, PTRACE_MODE_ATTACH);
}
通过追踪调用链, 不难注意到鉴权的核心逻辑位于函数 mm_access (mem_open -> __mem_open -> proc_mem_open -> mm_access) 中。
沿着 mm_access 继续追踪调用链:
struct mm_struct *mm_access(struct task_struct *task, unsigned int mode)
{
struct mm_struct *mm;
int err;
err = down_read_killable(&task->signal->exec_update_lock);
if (err)
return ERR_PTR(err);
mm = get_task_mm(task);
if (!mm) {
mm = ERR_PTR(-ESRCH);
} else if (!may_access_mm(mm, task, mode)) { // <- 从此处进一步深入
mmput(mm);
mm = ERR_PTR(-EACCES);
}
up_read(&task->signal->exec_update_lock);
return mm;
}
static bool may_access_mm(struct mm_struct *mm, struct task_struct *task, unsigned int mode)
{
if (mm == current->mm)
return true;
if (ptrace_may_access(task, mode)) // <- 兜兜转转,殊途同归
return true;
if ((mode & PTRACE_MODE_READ) && perfmon_capable())
return true;
return false;
}
最终我们会发现,经过调用链 mm_access -> may_access_mm -> ptrace_may_access, mem_open 接口最终通过 ptrace_may_access 检查了调用者是否有权访问 /proc/<pid>/mem文件
故而,毫无疑问地,Yama同样影响了 /proc/<pid>/mem 的访问逻辑,使其受到了:
只允许父进程 ptrace 自己的子进程
的限制。
