前言
良好的習慣是人生產生復利的有力助手。
繼續2020年的flag,至少每周更一篇文章。
無文件執行
之前的文章中,我們講到了無文件執行的方法以及混淆進程參數的方法,今天我們繼續講解一種linux上無文件執行的技巧,是后台朋友給我的提醒,萬分感謝,又學到了新的東西。
linux無文件執行,首先要提到兩個函數:memfd_create 和 fexecve。
memfd_create 和 fexecve
- memfd_create:第一個允許我們在內存中創建一個文件,但是它在內存中的存儲並不會被映射到文件系統中,至少,如果映射了,我是沒找到,因此不能簡單的通過ls命令進行查看,現在看來這的確是相當隱蔽的。事實上,如果一個文件存在,那么我們還是可以去發現它的,誰會去調用這個文件呢?使用如下的命令:
lsof | grep memfd
- 第二個函數,fexecve同樣的功能很強大,它能使我們執行一個程序(同execve),但是傳遞給這個函數的是文件描述符,而不是文件的絕對路徑,和memfd_create搭配使用非常完美!
但是這里有一個需要注意的地方就是,因為這兩個函數相對的比較新,memfd_create 是在kernel3.17才被引進來,fexecve是glibc的一個函數,是在版本2.3.2之后才有的, 沒有fexecve的時候, 可以使用其它方式去取代它,而memfd_create只能用在相對較新的linux內核系統上。
fexecve的實現
今天不談memfd_create,這是linux的新特性,沒有什么好玩的,本人對fexecve 的實現很有興趣,因為fexecve是glibc中的函數,而不是linux的系統調用。先看一下fexecve的用法,下面的fexecve_test.c 代碼是實現ls -l /dev/shm 功能。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
static char *args[] = {
"hic et nunc",
"-l",
"/dev/shm",
NULL
};
extern char **environ;
int main(void)
{
struct stat st;
void *p;
int fd, shm_fd, rc;
shm_fd = shm_open("wurstverschwendung", O_RDWR | O_CREAT, 0777);
if (shm_fd == -1) {
perror("shm_open");
exit(1);
}
rc = stat("/bin/ls", &st);
if (rc == -1) {
perror("stat");
exit(1);
}
rc = ftruncate(shm_fd, st.st_size);
if (rc == -1) {
perror("ftruncate");
exit(1);
}
p = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED,
shm_fd, 0);
if (p == MAP_FAILED) {
perror("mmap");
exit(1);
}
fd = open("/bin/ls", O_RDONLY, 0);
if (fd == -1) {
perror("openls");
exit(1);
}
rc = read(fd, p, st.st_size);
if (rc == -1) {
perror("read");
exit(1);
}
if (rc != st.st_size) {
fputs("Strange situation!\n", stderr);
exit(1);
}
munmap(p, st.st_size);
close(shm_fd);
shm_fd = shm_open("wurstverschwendung", O_RDONLY, 0);
fexecve(shm_fd, args, environ);
perror("fexecve");
return 0;
}
代碼中主要是分為了三步:
- 首先通過shm_open函數在 /dev/shm中創建了wurstverschwendung文件
- 將ls 命令文件寫入到wurstverschwendung文件
- 通過fexecve執行wurstverschwendung文件,因為/dev/shm在內存中,因此fexecve實際上是在內存中執行文件。
對fexecve_test.c 進行編譯並執行,可以看到/dev/shm下面確實生成了wurstverschwendung文件。
調試角度
fexecve是如何執行內存中的文件的呢?一般可以從調試和源碼的角度來探究其中的原理。首先使用strace調試一下:
strace -f -tt -T ./fexecve_test
從打印的日志中,找到open系統調用,從創建文件開始關聯:
大家可以看到shm_open 其實是在/dev/shm創建文件,而execve的執行文件為/proc/self/fd/3,為進程中打開的文件符號鏈接,這個指向的就是shm_open創建的文件,但是從監控execve的角度來說, execve無法獲取執行文件的路徑,從而實現了混淆。
源碼角度
從上文中,我們大致知道了原理。具體細節還是要看源碼:glibc中的代碼庫中(https://github.com/jeremie-koenig/glibc/blob/master-beware-rebase/sysdeps/unix/sysv/linux/fexecve.c)。
#include <errno.h>
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.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 (fd, argv, envp)
int fd;
char *const argv[];
char *const envp[];
{
if (fd < 0 || argv == NULL || envp == NULL)
{
__set_errno (EINVAL);
return -1;
}
/* We use the /proc filesystem to get the information. If it is not
mounted we fail. */
char buf[sizeof "/proc/self/fd/" + sizeof (int) * 3];
__snprintf (buf, sizeof (buf), "/proc/self/fd/%d", fd);
/* We do not need the return value. */
__execve (buf, 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 stat st;
if (stat ("/proc/self/fd", &st) != 0 && errno == ENOENT)
save = ENOSYS;
__set_errno (save);
return -1;
}
關鍵部位代碼:
char buf[sizeof "/proc/self/fd/" + sizeof (int) * 3];
__snprintf (buf, sizeof (buf), "/proc/self/fd/%d", fd);
/* We do not need the return value. */
__execve (buf, argv, envp);
fexecve本質上還是調用execve,只不過文件路徑是在/proc中。fexecve_test中實現的功能,可以用bash來簡單描述,作用是等同的:
最后
關注公眾號:七夜安全博客
回復【1】:領取 Python數據分析 教程大禮包
回復【2】:領取 Python Flask 全套教程
回復【3】:領取 某學院 機器學習 教程
回復【4】:領取 爬蟲 教程
回復【5】:領取 編譯原理 教程
回復【6】:領取 滲透測試 教程
回復【7】:領取 人工智能數學基礎 教程
本文章屬於原創作品,歡迎大家轉載分享,禁止修改文章的內容。尊重原創,轉載請注明來自:七夜的故事 http://www.cnblogs.com/qiyeboy/