《GDB调试之ptrace实现原理》

《C语言程序调用栈:backtrace+backtrace_symbols+backtrace_symbols_fd》

《strace实现原理:ptrace系统调用》

《Linux环境无文件渗透执行ELF:memfd_create、ptrace》

《利用ptrace和memfd_create混淆程序名和参数》

目录

说明

源代码

运行结果

原理分析

fork创建子进程

父进程调试子进程

修改子进程

总结

参考


说明


在linux环境下无文件执行elf(本站地址《Linux环境无文件渗透执行ELF:memfd_create、ptrace》)这篇文章中对ptrace中的memfd_create中的原理进行了说明,但是这个并不是ptrace的全部.在ptrace中还利用了ptrace这个系统调用对进程进行了修改,从而躲过了execve的检测.本文章就是对ptrace这个工具更加详细具体的分析.

在ptrace中除了使用到memfd_create()创建匿名的位于内存中的文件,之后还利用了ptrace这个系统调用.

PS:由于此款工具叫ptrace,同时ptrace也是一个系统调用.为了便于说明,工具就叫做ptrace工具,ptrace就称为ptrace系统调用.

源代码


ptrace工具的核心代码位于ptrace.c文件中.代码如下:

#include "ptrace.h"
#include "anonyexec.h"
#include "elfreader.h"
#include "common.h"int main(int argc, char *argv[], char *envp[])
{pid_t  child = 0;long   addr  = 0, argaddr = 0;int    status = 0, i = 0, arc = 0;struct user_regs_struct regs;union{long val;char chars[sizeof(long)];} data;char *args[] = { "/bin/ls", "-a", "-l", NULL };uint64_t entry = elfentry(args[0]);    //_start: entry pointchild = fork();IFMSG(child == -1, 0, "fork");IF(child == 0, proc_child(args[0], args));MSG("child pid = %d\r\n", child);while(1){wait(&status);if(WIFEXITED(status))break;// 获取寄存器中的值,并将其保存在regs中ptrace(PTRACE_GETREGS, child, NULL, ®s);if(regs.rip == entry){MSG("EIP: _start %llx \r\n", regs.rip);MSG("RSP: %llx\r\n", regs.rsp);MSG("RSP + 8 => RDX(char **ubp_av) to __libc_start_main\r\n");//解析堆栈数据,栈顶为int argcaddr = regs.rsp;arc = ptrace(PTRACE_PEEKTEXT, child, addr, NULL);MSG("argc: %d\r\n", arc);//POP ESI后栈顶为char **ubp_av, 同时可见此指针数组存储在堆栈之上addr += 8;//开始解析和修改参数for(i = 1;i < arc;i ++){//ptrace(PTRACE_PEEKDATA, pid, addr, data)//从内存地址中读取一个字节,pid表示被跟踪的子进程,内存地址由addr给出,data为用户变量地址用于返回读到的数据argaddr = ptrace(PTRACE_PEEKTEXT, child, addr + (i * sizeof(void*)), NULL);data.val = ptrace(PTRACE_PEEKTEXT, child, argaddr, NULL);MSG("src: ubp_av[%d]: %s\r\n", i, data.chars);MSG("dst: upb_av[%d]: %s\r\n", i, args[i]);//修改参数指针指向的内容,demo暂时不支持超过7个字符的参数strncpy(data.chars, args[i], sizeof(long) - 1);ptrace(PTRACE_POKETEXT, child, argaddr, data.val);}ptrace(PTRACE_CONT, child, NULL, NULL);ptrace(PTRACE_DETACH, child, NULL, NULL);break;}//调用一下 ptrace(PTRACE_SINGLESTEP) 就能完成这样的事情,这个调用会告诉内核,在子进程每执行完一条子令之后,就停一下ptrace(PTRACE_SINGLESTEP, child, NULL, NULL);}return 0;
}static char *encryptedarg = "3abb6677af34ac57c0ca5828fd94f9d886c"
"26ce59a8ce60ecf6778079423dccff1d6f19cb655805d56098e6d38a1a710dee59523"
"eed7511e5a9e4b8ccb3a4686";int proc_child(const char *path, char *argv[])
{int i = 1;ptrace(PTRACE_TRACEME, 0, NULL, NULL);for(i = 1;argv[i] != NULL;i ++)argv[i] = encryptedarg;anonyexec(path, argv);return 0;
}

运行结果


之前在kernel5.0 上面测试运行时,出现了如下的问题:

./ptrace
child pid = 7392
/proc/self/fd/3: cannot access '3abb6677af34ac57c0ca5828fd94f9d886c26ce59a8ce60ecf6778079423dccff1d6f19cb655805d56098e6d38a1a710dee59523eed7511e5a9e4b8ccb3a4686': No such file or directory
/proc/self/fd/3: cannot access '3abb6677af34ac57c0ca5828fd94f9d886c26ce59a8ce60ecf6778079423dccff1d6f19cb655805d56098e6d38a1a710dee59523eed7511e5a9e4b8ccb3a4686': No such file or directory

但是在kernel4.18及其以下的内核都能够运行成功,成功运行的结果如下:

$ ./ptrace
child pid = 58894
EIP: _start 4042d4
RSP: 7ffe6a464980
RSP + 8 => RDX(char **ubp_av) to __libc_start_main
argc: 3
src: ubp_av[1]: 3abb6677
dst: upb_av[1]: -a
src: ubp_av[2]: 3abb6677
dst: upb_av[2]: -l
total 72
drwxrwxr-x.  4 spoock spoock   268 Aug 22 21:55 .
drwxr-xr-x. 10 spoock spoock   189 Aug 22 21:54 ..
drwxrwxr-x.  8 spoock spoock   163 Aug 22 21:54 .git
-rw-rw-r--.  1 spoock spoock   803 Aug 22 21:54 1.c
-rw-rw-r--.  1 spoock spoock   361 Aug 22 21:54 Makefile
-rw-rw-r--.  1 spoock spoock  2842 Aug 22 21:54 README
-rw-rw-r--.  1 spoock spoock   681 Aug 22 21:54 anonyexec.c
-rw-rw-r--.  1 spoock spoock   226 Aug 22 21:54 anonyexec.h
-rw-rw-r--.  1 spoock spoock  2488 Aug 22 21:55 anonyexec.o
-rw-rw-r--.  1 spoock spoock   527 Aug 22 21:54 common.h
-rw-rw-r--.  1 spoock spoock   230 Aug 22 21:54 elfreader.c
-rw-rw-r--.  1 spoock spoock   142 Aug 22 21:54 elfreader.h
-rw-rw-r--.  1 spoock spoock  1544 Aug 22 21:55 elfreader.o
drwxrwxr-x.  2 spoock spoock   174 Aug 22 21:54 libptrace
-rwxrwxr-x.  1 spoock spoock 13768 Aug 22 21:55 ptrace
-rw-rw-r--.  1 spoock spoock  2123 Aug 22 21:54 ptrace.c
-rw-rw-r--.  1 spoock spoock   328 Aug 22 21:54 ptrace.h
-rw-rw-r--.  1 spoock spoock  4568 Aug 22 21:55 ptrace.o

最终输出的total 72…..之后的信息,说明成功执行了ls -a -l

使用auditd监控,得到的结果如下:

type=SYSCALL msg=audit(1566540263.416:2144): arch=c000003e syscall=59 success=yes exit=0 a0=7fff5c378750 a1=7fff5c3788d0 a2=0 a3=7fff5c3781a0 items=2 ppid=58893 pid=58894 auid=1000 uid=1000 gid=1000 euid=1000 suid=1000 fsuid=1000 egid=1000 sgid=1000 fsgid=1000 tty=pts3 ses=1 comm="3" exe=2F6D656D66643A656C66202864656C6574656429 subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key="procmon"
type=EXECVE msg=audit(1566540263.416:2144): argc=3 a0="/proc/self/fd/3" a1="3abb6677af34ac57c0ca5828fd94f9d886c26ce59a8ce60ecf6778079423dccff1d6f19cb655805d56098e6d38a1a710dee59523eed7511e5a9e4b8ccb3a4686" a2="3abb6677af34ac57c0ca5828fd94f9d886c26ce59a8ce60ecf6778079423dccff1d6f19cb655805d56098e6d38a1a710dee59523eed7511e5a9e4b8ccb3a4686"
type=CWD msg=audit(1566540263.416:2144):  cwd="/home/centos/Desktop/ptrace"
type=PATH msg=audit(1566540263.416:2144): item=0 name="/proc/self/fd/3" inode=264888 dev=00:04 mode=0100777 ouid=1000 ogid=1000 rdev=00:00 obj=unconfined_u:object_r:user_tmp_t:s0 objtype=NORMAL cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0
type=PATH msg=audit(1566540263.416:2144): item=1 name="/lib64/ld-linux-x86-64.so.2" inode=1415463 dev=fd:00 mode=0100755 ouid=0 ogid=0 rdev=00:00 obj=system_u:object_r:ld_so_t:s0 objtype=NORMAL cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0
type=PROCTITLE msg=audit(1566540263.416:2144): proctitle=2F70726F632F73656C662F66642F330033616262363637376166333461633537633063613538323866643934663964383836633236636535396138636536306563663637373830373934323364636366663164366631396362363535383035643536303938653664333861316137313064656535393532336565643735313165

发现通过1execve获取到的结果是:

a0="/proc/self/fd/3" a1="3abb6677af34ac57c0ca5828fd94f9d886c26ce59a8ce60ecf6778079423dccff1d6f19cb655805d56098e6d38a1a710dee59523eed7511e5a9e4b8ccb3a4686" a2="3abb6677af34ac57c0ca5828fd94f9d886c26ce59a8ce60ecf6778079423dccff1d6f19cb655805d56098e6d38a1a710dee59523eed7511e5a9e4b8ccb3a4686"

并没有捕获到ls -a -l的命令

直接观察/proc下面的进程信息,得到的结果如下:

{"pid": "58894","ppid": "58893","uid": "1000","cmdline": "/proc/self/fd/3 3abb6677af34ac57c0ca5828fd94f9d886c26ce59a8ce60ecf6778079423dccff1d6f19cb655805d56098e6d38a1a710dee59523eed7511e5a9e4b8ccb3a4686 3abb6677af34ac57c0ca5828fd94f9d886c26ce59a8ce60ecf6778079423dccff1d6f19cb655805d56098e6d38a1a710dee59523eed7511e5a9e4b8ccb3a4686 ","exe": "/memfd:elf (deleted)","cwd": "/home/centos/Desktop/ptrace"
}

发现cmdline的结果与audit监控到的结果一样,但exe(/memfd:elf(deleted))却暴露了其文件是由memfd_create()创建的.

其实程序执行的是ls -a -l,但是最终监控到的只有/proc/self/fd/3 3abb6677af34ac57c0ca5828fd94f9d886c26ce59a8ce60ecf6778079423dccff1d6f19cb655805d56098e6d38a1a710dee59523eed7511e 3abb6677af34ac57c0ca5828fd94f9d886c26ce59a8ce60ecf6778079423dccff1d6f19cb655805d56098e6d38a1a710dee59523eed7511e5a9e4b8ccb3a4686完全隐藏了执行的进程名和参数,完全无法检测到.这也就是ptrace这个工具说的Linux低权限模糊化执行的程序名和参数,避开基于execve系统调用监控的命令日志.

原理分析


ptrace首先定义了自己需要执行的实际的命令:

char *args[] = { "/bin/ls", "-a", "-l", NULL };

整个工具就是围绕实际执行/bin/ls -a -l却不会被检测出来展开的.

fork创建子进程


child = fork();
IFMSG(child == -1, 0, "fork");
IF(child == 0, proc_child(args[0], args));
MSG("child pid = %d\r\n", child);

通过fork()创建一个子进程,创建成功,子进程执行proc_child(args[0], args).

static char *encryptedarg = "3abb6677af34ac57c0ca5828fd94f9d886c"
"26ce59a8ce60ecf6778079423dccff1d6f19cb655805d56098e6d38a1a710dee59523"
"eed7511e5a9e4b8ccb3a4686";int proc_child(const char *path, char *argv[])
{int i = 1;ptrace(PTRACE_TRACEME, 0, NULL, NULL);for(i = 1;argv[i] != NULL;i ++)argv[i] = encryptedarg;anonyexec(path, argv);return 0;
}

首先分析for()循环,当子进程实际执行proc_child()时:

  • path:/bin/ls
  • argv[]:char args[] = { “/bin/ls”, “-a”, “-l”, NULL };
    经过for循环之后,path和argv变为了:
  • path:/bin/ls
  • argv[]:char args[] = { “/bin/ls”, “3abb6677af34ac5………”, “3abb6677af34ac5………”, NULL };
    此时再调用anonyexec(path, argv);,按照linux环境下无文件执行elf分析,最终执行的就是/proc/self/fd/3 3abb6677....... 3abb6677....... 其中的/proc/self/fd/3就是/bin/ls.到这里,实际上我们只是执行了ls,并不是/bin/ls -a -l.

父进程调试子进程


在proc_child()中存在如下代码:ptrace(PTRACE_TRACEME, 0, NULL, NULL); 借用玩转ptrace (一)这篇文章中的说法:

ptrace 的使用流程一般是这样的:父进程 fork() 出子进程,子进程中执行我们所想要 trace 的程序,在子进程调用 exec() 之前,子进程需要先调用一次 ptrace,以 PTRACE_TRACEME 为参数。这个调用是为了告诉内核,当前进程已经正在被 traced,当子进程执行 execve() 之后,子进程会进入暂停状态,把控制权转给它的父进程(SIG_CHLD信号), 而父进程在fork()之后,就调用 wait() 等子进程停下来,当 wait() 返回后,父进程就可以去查看子进程的寄存器或者对子进程做其它的事情了

所以在父进程的while(1)循环中有wait(&status);就是用来处理子进程的.

修改子进程


确认执行ls

uint64_t entry = elfentry(args[0]);    //_start: entry point
....
ptrace(PTRACE_GETREGS, child, NULL, &regs);
if(regs.rip == entry)
{.....
  • uint64_t entry = elfentry(args[0]); 其中的args[0]就是/bin/ls,所以entry其实就是得到/bin/ls的entry
  • ptrace(PTRACE_GETREGS, child, NULL, &regs); 获取child进程的寄存器的值,并将其保存到&regs中,&regs是一个user_regs_struct类型的结构体
  • if(regs.rip == entry) 这个的含义就是判断如果判断当前的执行的进程如果是正在执行/bin/ls,则进入到下面的处理流程中

获取参数个数

//解析堆栈数据,栈顶为int argc
addr = regs.rsp;
arc = ptrace(PTRACE_PEEKTEXT, child, addr, NULL);

ptrace(PTRACE_PEEKDATA, pid, addr, data),从内存地址中读取一个字节,pid表示被跟踪的子进程,内存地址由addr给出,所以上面就是获取栈顶数据,就是参数个数.

修改参数值

for(i = 1;i < arc;i ++)
{//ptrace(PTRACE_PEEKDATA, pid, addr, data)//从内存地址中读取一个字节,pid表示被跟踪的子进程,内存地址由addr给出,data为用户变量地址用于返回读到的数据argaddr = ptrace(PTRACE_PEEKTEXT, child, addr + (i * sizeof(void*)), NULL);data.val = ptrace(PTRACE_PEEKTEXT, child, argaddr, NULL);MSG("src: ubp_av[%d]: %s\r\n", i, data.chars);MSG("dst: upb_av[%d]: %s\r\n", i, args[i]);//修改参数指针指向的内容,demo暂时不支持超过7个字符的参数strncpy(data.chars, args[i], sizeof(long) - 1);ptrace(PTRACE_POKETEXT, child, argaddr, data.val);
}

由于第一个参数args[0]的值是/bin/ls,并不需要进行修改.在前面fork创建子进程的这一章节中,args[1]和args[2]的参数都是3abb6677……. .

  1. 通过ptrace()获取到寄存器中的值,实际就是3abb6677…….
  2. 利用strncpy(data.chars, args[i], sizeof(long) - 1); 进行修改
  3. ptrace(PTRACE_POKETEXT, child, argaddr, data.val); 写回寄存器

以上三步就修改了寄存器中的值,由原来的3abb6677……. 分别修改为了-a 和-l
最后调用ptrace(PTRACE_CONT/PTRACE_DETACH/PTRACE_SINGLESTEP, child, NULL, NULL);结束整个ptrace的操作.
所以父进程通过ptrace的方式,修改了位于寄存器中的参数值,而可执行的binary通过过memfd_create()的方式最终也变为了/proc/self/fd/3,所以通过execve和/proc的cmdline观察并不能看到真实执行的命令.

总结


对ptrace的分析整体下来十分有趣.通过对ptrace的分析,其实也告诉了我们,进程的cmdline并不可靠,execve获取执行命令不一定是实际执行的命令.那么在execve和cmdline都不一定完全可靠的情况下,我们有如何能够检测到这种行为呢?当然通过syscall hook ptrace当然是可以捕获到通过ptrace来修改进程的参数的行为,但是syscall hook是不是一个唯一解呢?

参考


  • [译] 玩转ptrace (一)
  • ptrace
  • 本文标题: 利用ptrace和memfd_create混淆程序名和参数
  • 本文作者: Spoock
  • 创建时间: 2019年08月31日
  • 修改时间: 2019年08月31日
  • 引用链接: http://blog.spoock.com/2019/08/31/escape-comm-and-parameter-from-audit/
  • 版权声明:  署名-非商业性使用-禁止演绎 4.0 国际 。未经授权,不得转载。转载请保留原文链接及作者

利用ptrace和memfd_create混淆程序名和参数相关推荐

  1. Linux ptrace系统调用详解:利用 ptrace 设置硬件断点

    <GDB调试之ptrace实现原理> <C语言程序调用栈:backtrace+backtrace_symbols+backtrace_symbols_fd> <strac ...

  2. 利用ptrace hook 系统调用

    我们知道gdb的实现原理就是利用ptrace,关于ptrace我就不多介绍了,主要看怎么利用它去hook 首先,利用ptrace可以给被调试进程下断点,也可以改其寄存器,和opcode,我们利用这点可 ...

  3. linux ptrace,如何利用Ptrace拦截和模拟Linux系统调用

    写在前面的话 ptrace(2)这个系统调用一般都跟调试离不开关系,它不仅是类Unix系统中本地调试器监控实现的主要机制,而且它还是strace系统调用常用的实现方法.ptrace()系统调用函数提供 ...

  4. Keras之DNN:利用DNN算法【Input(8)→12+8(relu)→O(sigmoid)】利用糖尿病数据集训练、评估模型(利用糖尿病数据集中的八个参数特征预测一个0或1结果)

    Keras之DNN:利用DNN算法[Input(8)→12+8(relu)→O(sigmoid)]利用糖尿病数据集训练.评估模型(利用糖尿病数据集中的八个参数特征预测一个0或1结果) 目录 输出结果 ...

  5. 将投影矩阵P利用QR分解分解出摄像机内外参数(Opencv)

    将投影矩阵P利用QR分解分解出摄像机内外参数(Opencv) /***************************************************************     ...

  6. EKLAVYA -- 利用神经网络推断二进制文件中函数的参数

    EKLAVYA – 利用神经网络推断二进制文件中函数的参数 文章目录 EKLAVYA -- 利用神经网络推断二进制文件中函数的参数 问题介绍以及形式化定义 方法设计 数据准备 实验结果 这一次介绍一篇 ...

  7. (转)利用Ant与Proguard混淆引用的子工程项目jar包及打war包

    当前的web项目有引用到子工程项目,而且多个子工程项目也有引用到其它的工程项目,现要求利用Ant自动将web项目打包成war包,其中引用到的子工程项目需打成jar包,而且必须是混淆后的jar包.其中混 ...

  8. 代码混淆android.mk,利用ollvm进行代码混淆

    OLLVM简介 OLLVM(Obfuscator-LLVM)是瑞士西北应用科技大学于2010年6月份发起的一个项目,该项目旨在提供一套开源的针对LLVM的代码混淆工具,以增加对逆向工程的难度. OLL ...

  9. 利用VisualEsxtop工具图形化查看esxtop参数

    怎样使用VisualEsxtop工具 众多VMware的工程师都知道esxtop/rextop在进行故障排查.性能检测的场景中的重要性:一般而言我们都可以通过各种shell工具连接到ESXI主机,默认 ...

最新文章

  1. ZOJ 3781 最短路(想法好题目)
  2. 大数据 智能交通调度_大数据技术在智能交通中的应用
  3. Java黑皮书课后题第4章:*4.10(猜测生日)改写程序清单4-3,提示用户输入字符Y代表“是”N代表“否”,代替之前输入1表示“是”,0表示“否
  4. WebSphere Application Server 5.0在Linux平台上中文界面乱码问题的解决
  5. lda进行图片分类_基于SIFT+Kmeans+LDA的图片分类器的实现
  6. mysql中两列拼接_python之Pandas读写操作mysql数据库
  7. 教你做炫酷的蜂巢式图片墙
  8. 弯道超车时机已来 百度:中国有机会定义AI时代的用户体验标准
  9. TextView输入文字改变输入框大小
  10. Java基础篇:什么是异常,异常处理的基础是什么?
  11. Go语言:数组练习—数组逆置
  12. Matlab图像线条绘制
  13. 软件常见的各种版本英文缩写
  14. Vue3动态加载图片
  15. 移动硬盘未知usb设备(设定地址失败)
  16. DSR 和AODV的对比
  17. 非暴力沟通简易思维导图
  18. Mean Average Precision(MAP):平均精度均值
  19. ‘完成下面程序:取圆周率为3.14 ,从键盘中输入半径r和高h,计算并输出圆柱体的体积。‘
  20. 织梦php如何完全卸载,织梦DEDECMS后台精简删除不需要的文件

热门文章

  1. 带撤销贪心——cf1148F好题
  2. Python学习笔记(八)随机数的处理
  3. JDK1.7的HashMap的put(key, value)源码剖析
  4. 搭建了Pycharm对话平台
  5. 十一、多线程——5-线程同步
  6. Android非常好用的组件或者框架
  7. WGS84坐标和UTM坐标的转换
  8. mysql巡检常用命令_mysql 常用命令
  9. python存储大量数据_如何在文件中密集地存储大量数据?
  10. python自学笔记_Python 自学笔记