一.Why:为什么研究用户态execve why 做一件事情背后总有它根本的原因,不要被事情的表象迷惑。举个例子,老板让你给招聘会贴海报,那你直接去贴海报,其实有点单纯了。 研究用户态execve的实现,起初是从攻击的方向去思考的,在linux主机安全中,使用shell命令进行攻击是非常常见的场景,无论是横向移动,还是种马,很难不应用shell命令。 在之前的文章中,无"命令"反弹shell-逃逸基于execve的命令监控(上) 分享过关于shell命令的各种监控方式,其中最难绕过的是内核态的execve监控。 [eauldvj8mz.png] 我的选择是抛弃execve系统调用来执行命令,而是思考自己实现用户态execve,这样就可以彻底摆脱命令监控,如果再延展一下,还会有更深层次的操作。 二.How:如何设计linux elf loader how 用户态execve 是仿照linux内核中execve syscall的原理 ,在应用层实现程序的加载和运行,如果做过windows pe
在linux中,启动外部进程,是通过execve系统调用进行创建的,我们使用strace 打印一下在bash中启动ls的系统调用,第一句就是通过execve启动ls。 ? 但是我们在开发linux程序的时候,执行系统命令,并没有直接使用execve系统调用,这是因为libc/glibc库对execve系统调用封装成了函数,方便我们调用。 因此基于execve的系统命令监控方式,分成了用户态和内核态。用户态通过劫持libc/glibc的exec相关函数来实现,内核态则通过系统自身组件或者劫持execve syscall 来实现。 [root@VM_0_13_centos ~]# tcsh -c "echo hello" hello 3.绕过内核态execve syscall 只要你使用了execve执行了命令,就绝对逃不过内核态 大体的操作流程如下: 第一步:首先我们fork出来一个子进程,然后在子进程中先调用ptrace,接着执行execve("ls xxxxxx"),这个时候基于execve监控到的就是一个假参数。
文章目录 一、bionic/libc/include/unistd.h#execve 函数分析 二、使用自定义的 myexecve 函数替换 libc.so#execve 函数 在 【Android libc.so#execve 函数 ; 一、bionic/libc/include/unistd.h#execve 函数分析 ---- libc.so#execve 函数 定义在 Android 源码的 函数在 libc.so 的地址 , uint32_t new_addr 参数是自定义替换 execve 函数执行的函数地址 , uint32_t **proto_addr 参数是 execve 原函数的地址 , 就退出 ; 不需要拦截的 , 直接调用原函数执行 ; 源码示例 : // 用于接收 Android 自带的 execve 函数 int (*android_execve)(const char * __file, char *const *__argv, char *const *__envp); // 自定义的 execve 函数 , 用于替换 Android 自带的 execve 函数 //
躲避execve,是在原来的文章的基础上补充一个小思路,分析/proc/目录 是为了下一篇讲解内存中修改函数做准备,要让大家提前知道这回事。 躲避execve的小思路 在之前文章中,我讲解过各种躲避execve监控的方法,如果有朋友没看到过,可以点下面文章链接熟悉一下: 无"命令"反弹shell-逃逸基于execve的命令监控(上) linux 无文件执行— fexecve 揭秘 本文的思路,类似文件复制重命名,躲避execve监控的关键点在于创建软链接,通过创建软链接的方式来混淆命令执行的路径。 下面以whoami命令为例: ln -s /bin/whoami /tmp/whoami_tmp 接着使用strace调试执行/tmp/whoami_tmp,看一下execve的系统调用: strace 由于execve执行软链接,会自动执行目标文件,不会对软链接进行转换,从而实现了隐藏。 不要以为这就结束了!!!
上篇讲了程序的加载。然后设置了eip,这一篇分析一下开始执行第一条指令的时候。会发生什么。 我们先看一下这时候的内存布局。
在linux中,启动外部进程,是通过execve系统调用进行创建的,我们使用strace 打印一下在bash中启动ls的系统调用,第一句就是通过execve启动ls。 ? 但是我们在开发linux程序的时候,执行系统命令,并没有直接使用execve系统调用,这是因为libc/glibc库对execve系统调用封装成了函数,方便我们调用。 因此基于execve的系统命令监控方式,分成了用户态和内核态。用户态通过劫持libc/glibc的exec相关函数来实现,内核态则通过系统自身组件或者劫持execve syscall 来实现。 系统调用,下面是使用int 0x80调用execve syscall的简写代码: mov byte al, 0x0b # 好了,现在把execve()的系统调用号11号放入eax的最下位的 大体的操作流程如下: 第一步:首先我们fork出来一个子进程,然后在子进程中先调用ptrace,接着执行execve("ls xxxxxx"),这个时候基于execve监控到的就是一个假参数。
2.这两个函数没有本质区别,底层都是调用execve。他们就只有传参的不一样,execl调用execve后,传递的每个const char*会变成一个char*数组。 3.后面的几个函数都只有参数不同,底层都调用execve。设计这么多函数是为了满足不同场景的需求,有时候就是要一个一个传参,有时候就有char*数组。 <<std::endl; } return 0; } 4.execle和execve函数: 有e的是要进行自己进行组装环境变量的。 #include <stdlib.h> int putenv(char *string); 总结: 实际上,只有execve才是真正的系统调用,其他的函数都是调用execve。 所以execve在man的手册2,其他函数在手册3。
execve函数是操作系统非常重要的一个函数,他使得程序变成进程成为可能。下面我们通过do_execve的实现,了解一下程序变成进程的过程。首先do_execve是一个系统调用。 直接从sys_execve函数开始。 _sys_execve: lea EIP(%esp),%eax pushl %eax call _do_execve addl $4,%esp ret 执行_do_execve 在这里插入图片描述 下面开始分析do_execve的实现。 int do_execve(unsigned long * eip,long tmp,char * filename, char ** argv, char ** envp) { struct
文章目录 一、对 libc.so#execve 函数进行内联 HOOK 操作 在 【Android 逆向】ART 函数抽取加壳 ① ( ART 下的函数抽取恢复时机 | 禁用 dex2oat 机制源码分析 函数进行内联 HOOK 操作 ---- 要 HOOK libc 函数库 中的 exec_utils.cc#execve 函数 , 首先要查找到 libc 库的基地址 , 然后查找 exec_utils.cc #execve 函数的地址 ; execve 函数 定义在 bionic/libc/include/unistd.h 中 , 在 exec_utils.cc 中进行调用 ; 使用 在 【Android 在 libc.so 函数库的偏移地址 void *execve_addr = dlsym_compat(libc_addr, "execve"); // 地址不为空才能向后执行 if (execve_addr !
.>-59188 [000] d... 40067.137167: execsnoop_sys_execve: (SyS_execve+0x0/0x30) 59191 59188 /usr/local : (SyS_execve+0x0/0x30) 59192 28776 <...>-59192 [003] d... 40067.139103: execsnoop_sys_execve -t 1 -d 1 59198 28770 <...>-59198 [001] d... 40067.145500: execsnoop_sys_execve: (SyS_execve +0x0/0x30) 59199 28779 <...>-59199 [001] d... 40067.146228: execsnoop_sys_execve: (SyS_execve [003] d... 40067.158381: execsnoop_sys_execve: (SyS_execve+0x0/0x30) 59205 59204 /usr/local/bin/stress
接下来看第二种模式的使用(下面代码是execve-server.js)。 下面看环境变量和execve的逻辑。 (args[0], args, env); // execve会加载可执行文件,从新的入口开始执行,执行到这说明execve出错了 write /No execve-server.js)。 重点是execve函数会重新加载可执行文件,然后从新的地址(可执行文件中指定)开始执行,所以我们看到execve后是不需要return的,因为下面的代码不会执行了,除非execve执行出错了,这里我们打印错误信息然后退出进程
所以,如果我们将两个 Libc 函数的差值(execve和getuid)加到getuid的 GOT 条目,我们就得到了execve函数的地址。之后,调用getuid就会调用execve。 两个 Libc 函数(execve和getuid)的偏移差加到寄存器的内容。现在跳到寄存器的值就调用了execve。 seteuid@PLT | getuid@PLT | seteuid_arg | execve_arg1 | execve_arg2 | execve_arg3 其中: setuid@PLT:setuid #AC execve_arg1_oct4 = 0x804819a #60 #execve_arg2 0x804ac68 execve_arg2_oct1 = 0x8048143 #08 execve_arg2 execve_path "/bin/sh" execve_path_oct1 = 0x8048154 #/ execve_path_oct2 = 0x8048157 #b execve_path_oct3
execve系统调用 execve系统调用 我们前面提到了, fork, vfork等复制出来的进程是父进程的一个副本, 那么如何我们想加载新的程序, 可以通过execve来加载和启动新的程序。 execve加载可执行程序的过程 内核中实际执行execv()或execve()系统调用的程序是do_execve(),这个函数先打开目标映像文件,并从目标文件的头部(第一个字节开始)读入若干(当前Linux () > do_execve() > do_execveat_common > search_binary_handler() > load_elf_binary() execve的入口函数sys_execve 系统调用的的入口点是体系结构相关的sys_execve, 该函数很快将工作委托给系统无关的do_execve函数 SYSCALL_DEFINE3(execve, const sys_execve接受参数:1.可执行文件的路径 2.命令行参数字符串 3.环境变量字符串 sys_execve是调用do_execve实现的。
-name "trace_execve*" ./trace_execve.c ./progs/trace_execve.bpf.c . bpf目标文件trace_execve.bpf.o和probe_execve.bpf.o,仅仅依靠trace_execve和probe_execve两个文件即可成功执行。 从关键构建步骤中,我们可以了解到: probe_execve和trace_execve两个target都是all目标的下级目标,并且probe_execve和trace_execve是串行的。 这个里隐含的一个意思是,当trace_execve开始构建的时候,probe_execve已经完全构建完毕,probe_execve这个最终可执行文件已经生成完毕。 trace_execve ├── trace_execve.o │ ├── trace_execve.c │ ├── trace_execve.skel.h │ │ ├── trace_execve.hex
的栈帧 execve = SigreturnFrame() execve.rax = constants.SYS_execve execve.rdi = stack_addr + 0x120 execve.rsi '] = 0x6 execve['r9'] = 0x7 execve['r10'] = 0x8 execve['r11'] = 0x9 execve['r12'] = 0xa execve['r13'] = 0xb execve['r14'] = 0xc execve['r15'] = 0xd execve['rdi'] = 0xe execve['rsi'] = 0xf execve['rbp'] = 0x10 execve['rbx'] = 0x11 execve['rdx'] = 0x12 execve['rax'] = 0x13 execve['rcx'] = 0x14 execve['rsp '] = 0x15 execve['rip'] = 0x16 execve['eflags'] = 0x17 execve['csgsfs'] = 0x18 execve['err'] = 0x19 execve
./ 设置环境变量 unset PATH 删除环境变量 env 显示所有环境变量 execl函数 execve(执行文件)在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。 exec函数一共有六个,其中execve为内核级系统调用,其他(execl,execle,execlp,execv,execvp)都是调用execve的库函数。 execve int execve(const char * filename,char * const argv[ ],char * const envp[ ]); int execl(const char
c、execve()应用层调用脚本文件: 头文件:#include <unistd.h> 函数:int execve(const char * filename, char * const argv d、execve()测试代码: /************************** *功能:测试execve *时间:2016-4-15 *作者:Jack Cui ***************** void main(void) { char * args[] = {"/home/nfsroot/hwclock.sh", NULL}; if(-1 == (execve("/home /nfsroot/hwclock.sh",args,NULL))) { perror("execve"); exit(EXIT_FAILURE); } f、execve测试结果: ? 可以看出execve使用正常,我们将脚本内容改为hwclock –systohc就可以实现将系统时间同步到硬件时间了。
第二个函数,fexecve同样的功能很强大,它能使我们执行一个程序(同execve),但是传递给这个函数的是文件描述符,而不是文件的绝对路径,和memfd_create搭配使用非常完美! 大家可以看到shmopen 其实是在/dev/shm创建文件,而execve的执行文件为/proc/self/fd/3,为进程中打开的文件符号链接,这个指向的就是shm_open创建的文件,但是从监控execve 的角度来说, execve无法获取执行文件的路径,从而实现了混淆。 (buf, argv, envp); int save = errno; /* We come here only if the 'execve' call fails. (buf, argv, envp); fexecve本质上还是调用execve,只不过文件路径是在/proc中。
在上述整个调用流程串的最后一步是shell_execve。 该函数最终会调用系统函数execve,其声明如下: int execve(const char *filename, char *const argv [], char *const envp[]); 进入内核: execve系统调用 execve系统调用实现 该函数定义在fs/exec.c中,其声明如下: SYSCALL_DEFINE3(execve, const char __user *, filename (getname(filename), argv, envp); } execve的实现在这里非常简单,只调用了do_execve函数,其参数为execve的参数。 而do_execve函数的定义如下: int do_execve(struct filename *filename, const char __user *const __user *__argv,
在上述整个调用流程串的最后一步是shell_execve。 该函数最终会调用系统函数execve,其声明如下: int execve(const char *filename, char *const argv [], char *const envp[]); 进入内核: execve系统调用 execve系统调用实现 该函数定义在fs/exec.c中,其声明如下: SYSCALL_DEFINE3(execve, const char __user *, filename (getname(filename), argv, envp); } execve的实现在这里非常简单,只调用了do_execve函数,其参数为execve的参数。 而do_execve函数的定义如下: int do_execve(struct filename *filename, const char __user *const __user *__argv,