查看原文
其他

linux无文件执行— fexecve 揭秘

七夜安全 七夜安全博客 2021-01-04

微信公众号:七夜安全博客 关注信息安全技术、关注 系统底层原理。问题或建议,请公众号留言。


前言

良好的习惯是人生产生复利的有力助手。


继续2020年的flag,至少每周更一篇文章,今天讲linux无文件执行。

无文件执行

之前的文章中,我们讲到了无文件执行的方法以及混淆进程参数的方法,今天我们继续讲解一种linux无文件执行的技巧,是后台朋友给我的提醒,万分感谢,又学到了新的东西。

linux无文件执行,首先要提到两个函数:memfd_create 和 fexecve。

memfd_create 和 fexecve

1 . memfd_create:允许我们在内存中创建一个文件,但是它在内存中的存储并不会被映射到文件系统中,至少,如果映射了,我是没找到,因此不能简单的通过ls命令进行查看,现在看来这的确是相当隐蔽的。事实上,如果一个文件存在,那么我们还是可以去发现它的,谁会去调用这个文件呢?使用如下的命令:

  1. lsof | grep memfd

2 . 第二个函数,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 功能。

  1. #include <fcntl.h>

  2. #include <stdio.h>

  3. #include <stdlib.h>

  4. #include <sys/mman.h>

  5. #include <sys/stat.h>

  6. #include <unistd.h>


  7. static char *args[] = {

  8. "hic et nunc",

  9. "-l",

  10. "/dev/shm",

  11. NULL

  12. };


  13. extern char **environ;


  14. int main(void)

  15. {

  16. struct stat st;

  17. void *p;

  18. int fd, shm_fd, rc;


  19. shm_fd = shm_open("wurstverschwendung", O_RDWR | O_CREAT, 0777);

  20. if (shm_fd == -1) {

  21. perror("shm_open");

  22. exit(1);

  23. }


  24. rc = stat("/bin/ls", &st);

  25. if (rc == -1) {

  26. perror("stat");

  27. exit(1);

  28. }


  29. rc = ftruncate(shm_fd, st.st_size);

  30. if (rc == -1) {

  31. perror("ftruncate");

  32. exit(1);

  33. }


  34. p = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED,

  35. shm_fd, 0);

  36. if (p == MAP_FAILED) {

  37. perror("mmap");

  38. exit(1);

  39. }


  40. fd = open("/bin/ls", O_RDONLY, 0);

  41. if (fd == -1) {

  42. perror("openls");

  43. exit(1);

  44. }


  45. rc = read(fd, p, st.st_size);

  46. if (rc == -1) {

  47. perror("read");

  48. exit(1);

  49. }

  50. if (rc != st.st_size) {

  51. fputs("Strange situation!\n", stderr);

  52. exit(1);

  53. }


  54. munmap(p, st.st_size);

  55. close(shm_fd);


  56. shm_fd = shm_open("wurstverschwendung", O_RDONLY, 0);

  57. fexecve(shm_fd, args, environ);

  58. perror("fexecve");

  59. return 0;

  60. }

代码中主要是分为了三步:

  1. 首先通过shm_open函数在 /dev/shm中创建了wurstverschwendung文件

  2. 将ls 命令文件写入到wurstverschwendung文件

  3. 通过fexecve执行wurstverschwendung文件,因为/dev/shm在内存中,因此fexecve实际上是在内存中执行文件。

对fexecve_test.c 进行编译并执行,可以看到/dev/shm下面确实生成了wurstverschwendung文件。

调试角度

fexecve是如何执行内存中的文件呢?一般可以从调试和源码的角度来探究其中的原理。首先使用strace调试一下:

  1. strace -f -tt -T ./fexecve_test

从打印的日志中,找到open系统调用,从创建文件开始关联:

大家可以看到shmopen 其实是在/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)。

  1. #include <errno.h>

  2. #include <stddef.h>

  3. #include <stdio.h>

  4. #include <unistd.h>

  5. #include <sys/stat.h>



  6. /* Execute the file FD refers to, overlaying the running program image.

  7. ARGV and ENVP are passed to the new program, as for `execve'. */

  8. int

  9. fexecve (fd, argv, envp)

  10. int fd;

  11. char *const argv[];

  12. char *const envp[];

  13. {

  14. if (fd < 0 || argv == NULL || envp == NULL)

  15. {

  16. __set_errno (EINVAL);

  17. return -1;

  18. }


  19. /* We use the /proc filesystem to get the information. If it is not

  20. mounted we fail. */

  21. char buf[sizeof "/proc/self/fd/" + sizeof (int) * 3];

  22. __snprintf (buf, sizeof (buf), "/proc/self/fd/%d", fd);


  23. /* We do not need the return value. */

  24. __execve (buf, argv, envp);


  25. int save = errno;


  26. /* We come here only if the 'execve' call fails. Determine whether

  27. /proc is mounted. If not we return ENOSYS. */

  28. struct stat st;

  29. if (stat ("/proc/self/fd", &st) != 0 && errno == ENOENT)

  30. save = ENOSYS;


  31. __set_errno (save);


  32. return -1;

  33. }

关键部位代码:

  1. char buf[sizeof "/proc/self/fd/" + sizeof (int) * 3];

  2. __snprintf (buf, sizeof (buf), "/proc/self/fd/%d", fd);


  3. /* We do not need the return value. */

  4. __execve (buf, argv, envp);

fexecve本质上还是调用execve,只不过文件路径是在/proc中。fexecve_test中实现的功能,可以用bash来简单描述,作用是等同的: 


总结


写完快12点了,发了发了,睡觉睡觉。。。



推荐阅读:

沙盒syscall监控组件:strace and wtrace

无"命令"反弹shell-逃逸基于execve的命令监控(上)

APT组织武器:MuddyC3泄露代码分析

Python RASP 工程化:一次入侵的思考

教你学木马攻防 | 隧道木马 | 第一课

一个Python开源项目-哈勃沙箱源码剖析(下)


如果大家喜欢这篇文章的话,请不要吝啬分享到朋友圈,并置顶公众号。

关注公众号:七夜安全博客

回复【8】:领取 python神经网络 教程 

  • 回复【1】:领取 Python数据分析 教程大礼包

  • 回复【2】:领取 Python Flask 全套教程

  • 回复【3】:领取 某学院 机器学习 教程

  • 回复【4】:领取 爬虫 教程

  • 回复【5】:领取编译原理 教程

  • 回复【6】:领取渗透测试教程

  • 回复【7】:领取人工智能数学基础


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存