专栏首页七夜安全博客linux无文件执行— fexecve 揭秘

linux无文件执行— fexecve 揭秘

前言

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

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

无文件执行

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

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

memfd_create 和 fexecve

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

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 功能。

#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;
}

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

  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系统调用,从创建文件开始关联:

大家可以看到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)。

#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来简单描述,作用是等同的:

总结

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

本文分享自微信公众号 - 七夜安全博客(qiye_safe),作者:七夜安全

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-02-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • IPProxyPool4月6号更新。。。

    七夜安全博客
  • 哈勃linux沙箱-源码剖析上篇

    今天说的哈勃沙箱是腾讯哈勃检测系统中,linux恶意文件检测部分的开源代码。github地址为:

    七夜安全博客
  • 1.4 Django基础篇--数据库模型设计

    七夜安全博客
  • Git入门及上传项目到github中

    最近需要将课设代码上传到Github上,之前只是用来fork别人的代码。 这篇文章写得是windows下的使用方法。 第一步:创建Github新账户 第二步:新...

    庞小明
  • Tikhonov正则化选取的方法

    最小二乘矩阵求解与正则化,最小二乘是最常用的线性参数估计方法,早在高斯的年代,就用开对平面上的点拟合线,对高维空间的点拟合超平面。

    于小勇
  • 7.11 Git 工具 - 子模块

    有种情况我们经常会遇到:某个工作中的项目需要包含并使用另一个项目。 也许是第三方库,或者你独立开发的,用于多个父项目的库。 现在问题来了:你想要把它们当做两个...

    shaonbean
  • Xcode 文件后状态标识

    ZY_FlyWay
  • JavaScript与ActionScript3 -- setTimeout方法的差异

    JavaScript中setTimeout方法接受的参数只有两个,而ActionScript3却可以有多个。

    meteoric
  • 调研:2015年SaaS运营大起底

    本文编译自《2015年Pacific Crest SaaS调查》报告,经纬创投(Matrix Partners)的David Skok联合美国投资银行太平洋皇冠...

    人称T客
  • 小朋友学C语言(2):安装Dev C++编译器

    (一)编译器 编译器是将“一种语言(通常为高级语言)”翻译为“另一种语言(通常为低级语言)”的程序。一个现代编译器的主要工作流程:源代码 (source cod...

    海天一树

扫码关注云+社区

领取腾讯云代金券