zoukankan      html  css  js  c++  java
  • linux无文件执行— fexecve 揭秘

    前言

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

    继续2020年的flag,至少每周更一篇文章。

    无文件执行

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

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

    memfd_create 和 fexecve

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

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

    1. 首先通过shm_open函数在 /dev/shm中创建了wurstverschwendung文件
    2. 将ls 命令文件写入到wurstverschwendung文件
    3. 通过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/

  • 相关阅读:
    数据库范式
    RobotFrameWork自动化系列:安装配置
    jenkins安装
    jenkins+ant+ssh远程部署服务glassfish
    wordpress登录、修改、删除、查看代码记录
    wordpress 登录实例(一)
    BZOJ2806:[CTSC2012]Cheat(广义SAM,二分,DP)
    BZOJ4180:字符串计数(SAM,二分,矩阵乘法)
    BZOJ2780:[SPOJ8093]Sevenk Love Oimaster(广义SAM)
    BZOJ4032:[HEOI2015]最短不公共子串(SAM)
  • 原文地址:https://www.cnblogs.com/qiyeboy/p/12547204.html
Copyright © 2011-2022 走看看