chroot 详解

2022-06-08

在做某次关于chroot 的jailbreak实验时,深受某老师迫害(啥都不会瞎讲),做得非常痛苦,你说你不会吧,那你布置这实验干啥呢,抄的还是别的学习2014年的版本,2022年用seedubuntu14:04,哈哈,笑不活喽

他啥都不会,但是实验我们还是得做(哭),这里记录下有关chroot的知识

什么是chroot

容器的起点呢,可以追溯到 1979 年Version 7 UNIX系统中提供的 chroot 命令,这个命令是英文单词“Change Root”的缩写,它所具备的功能是当某个进程经过 chroot 操作之后,它的根目录就会被锁定在命令参数所指定的位置,以后它或者它的子进程就不能再访问和操作该目录之外的其他文件。

人们使用容器的最初目的,并不是为了部署软件,而是为了隔离计算机中的各类资源,以便降低软件开发、测试阶段可能产生的误操作风险,或者是专门充当蜜罐,吸引黑客的攻击,以便监视黑客的行为

1991 年,世界上第一个监控黑客行动的蜜罐程序就是使用 chroot 来实现的,那个参数指定的根目录当时被作者被戏称为“Chroot 监狱”(Chroot Jail),而黑客突破 chroot 限制的方法就叫做 Jailbreak。后来,FreeBSD 4.0 系统重新实现了 chroot 命令,把它作为系统中进程沙箱隔离的基础,并将其命名为FreeBSD jail。

后面人们发现,使用容器技术来部署软件也非常方便,不仅可以解决不同软件依赖库的冲突问题,还能即插即用(把容器打包放到另一个机器中实现快速部署),后来的docker就诞生了。

chroot原理

回到正题,所谓的改变root目录,其实就是 改变进程的taskstruct中fs结构体中的root字段,实现不同的根目录查询。从实现角度看, chroot的确不能实现安全的环境隔离

chroot 用法

用法一 shell执行

chroot只能由高贵的root来执行

sudo chroot /path/to/your/new/root /bin/bash

这里有一点要提的是,因为chroot会切换根目录,所以/bin/bash的依赖你需要事先移动到 new root里。(可以通过ldd /bin/bash查看依赖关系)

我们可以跟踪一下chroot的执行流程

strace chroot . /bin/sh

execve("/usr/sbin/chroot", ["chroot", ".", "sh"], [/* 28 vars */]) = 0
brk(NULL)                               = 0x60a000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=111669, ...}) = 0
mmap(NULL, 111669, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ffff7fdb000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`\t\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffff7fda000
mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7ffff7a0d000
mprotect(0x7ffff7bcd000, 2097152, PROT_NONE) = 0
mmap(0x7ffff7dcd000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7ffff7dcd000
mmap(0x7ffff7dd3000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7ffff7dd3000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffff7fd9000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7ffff7fd8000
arch_prctl(ARCH_SET_FS, 0x7ffff7fd9700) = 0
mprotect(0x7ffff7dcd000, 16384, PROT_READ) = 0
mprotect(0x608000, 4096, PROT_READ)     = 0
mprotect(0x7ffff7ffc000, 4096, PROT_READ) = 0
munmap(0x7ffff7fdb000, 111669)          = 0
brk(NULL)                               = 0x60a000
brk(0x62b000)                           = 0x62b000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=2981280, ...}) = 0
mmap(NULL, 2981280, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ffff7735000
close(3)                                = 0
getcwd("/home/wsxk/Desktop/information_security/lab2", 4096) = 45
chroot(".")                             = 0
chdir("/")                              = 0
execve("/usr/local/sbin/sh", ["sh"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)
execve("/usr/local/bin/sh", ["sh"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)
execve("/usr/sbin/sh", ["sh"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)
execve("/usr/bin/sh", ["sh"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)
execve("/sbin/sh", ["sh"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)
execve("/bin/sh", ["sh"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)
execve("/usr/games/sh", ["sh"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)
execve("/usr/local/games/sh", ["sh"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)
execve("/snap/bin/sh", ["sh"], [/* 28 vars */]) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en_US/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/en_US/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/en/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/lib/x86_64-linux-gnu/charset.alias", O_RDONLY|O_NOFOLLOW) = -1 ENOENT (No such file or directory)
write(2, "chroot: ", 8chroot: )                 = 8
write(2, "failed to run command \342\200\230sh\342\200\231", 30failed to run command ‘sh’) = 30
open("/usr/share/locale/en_US/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/en_US/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale-langpack/en/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
write(2, ": No such file or directory", 27: No such file or directory) = 27
write(2, "\n", 1
)                       = 1
close(1)                                = 0
close(2)                                = 0
exit_group(127)                         = ?
+++ exited with 127 +++

这里面要关注的点是 chroot(“.”) 和 chdir(“/”)

其中chroot主要作用是修改根目录,而chdir是切换当前工作目录

用法二 c语言自动执行

可执行程序同样需要具有chroot权限。

chdir("/path/to/your/new/root")
chroot("/path/to/your/new/root");

这个函数是有返回值的,可以通过查看返回值判断是否限制成功。

不过上面那个程序是有缺陷的,一般情况下,这个程序如果有root权限,被劫持后还是可以自己突破jail

chdir("/path/to/your/new/root")
chroot("/path/to/your/new/root");
setuid(1000);

这个程序在改变目录后,还进行setuid(取消root权限),之后它只能以普通用户的身份运行。还是比较安全的(当然也是有特殊情况存在的,比如说被提权了)

chroot jailbreak

chroot有个很明显的缺陷,就是执行它的程序必须拥有root的权限,这给了我们突破的机会。

如果我们可以控制该程序的执行流程(必须有前提,有root权限)

一般情况下,我们可以通过执行

mkdir(“foo”) chroot(“foo”)

chdir(“../”)若干次+ chroot(“./”)一次来实现越狱。

chdir的次数取决于你的可执行程序当前工作目录的深度。(我也不知道为什么,应该只是一个大概的值,实际上还需要你自己试试)

举个例子,你的程序chroot在

/tmp/test 目录

那么你需要执行2-3次 chdir(“../”)才行。

reference

linux 命令分析之 chroot 的原理

Chroot隔离文件

http://www.unixwiz.net/techtips/mirror/chroot-break.html

https://man7.org/linux/man-pages/man2/chroot.2.html