---------------- /------| ---------------- | Roll your | / | |-------| | 8d 35 fa ff | | own chroot | / /----| ------\ /----|--+ +--| | ff ff | | container | | / | ---\ \/----\| /\ | | | | 83 c6 11 | | | | | |-| || | | || /\ || || | | | | 31 c9 | | | | \ | | || ---/ /| || || \/ | | | | 83 c1 13 | | | \ \-| | || ---\ \| \/ |\----/ | | | f3 a4 | | for | \ | +-+| | | |\----/ --- | c3 | | - reversing | \---| +-+--- --- | by Orion | | - analysis | | | | | | <lawlor@ | | - archival | |-| |-| | alaska.edu> | ---------------- ---------------- A chroot container lets you run a binary inside a custom-built filesystem, and is a good way to constrain code execution, and to understand how a binary actually runs. UNIX's 'everything is a file' concept means modern file systems expose a huge attack surface with many suid executables, named pipes, and sensitive temp files. A chroot container denies this access by default, but isn't bloated like docker. ---------------------------------------------------------------------------- |Main UNIX Filesystem| Set up by your distribution, bloated with crap | ---------------------- | | | | / ------------------------------------------------------- | | /bin |chroot filesystem| Set up by you, tiny and light | | | /lib ------------------- | | | /etc | | | | /home/your/chroot| / | | | /usr | /bin Only contains the utility programs you want | | | /dev | /lib Shared libraries you decide to include | | | /proc | /etc Sanitized or customized config files | | | ... ------------------------------------------------------- | ---------------------------------------------------------------------------- ;;;;;;;;;;; chroot.0: ; ---- ch'ing the root filesystem ---- Syntax: sudo chroot <path to new root directory> <command to run there> Start by making a directory with the binary you want to run: $ mkdir -p /home/your/chroot $ cd /home/your/chroot $ mkdir bin $ cp /bin/bash bin/sh The chroot command just takes the path to the new filesystem: $ sudo chroot /home/your/chroot /bin/sh This will basically always fail with: chroot: failed to run command '/bin/sh': No such file or directory If the binary exists, it's missing a shared library loaded by that binary. Check the shared libraries used with the ldd script: $ ldd bin/sh The kernel provides linux-vdso.so.1, but you need to make everything else. This degree of shared library control can be very handy to run ancient binaries, or if you need to gdb a particular combo of lib versions without bricking your host system. On a recent arm64 linux machine, I needed: $ mkdir lib $ cp /lib/ld-linux-aarch64.so.1 lib $ cp /lib/aarch64-linux-gnu/libtinfo.so.6 lib $ cp /lib/aarch64-linux-gnu/libc.so.6 lib That's the dynamic linker ld-linux, ncurses, and the C standard library. We're dumping them all into lib/ wherever they came from. On x86_64 linux, binaries have /lib64/ld-linux-x86-64.so.2 hardcoded, but will look for all their other libs in /lib. Run your ld-linux .so with "--help" (it's a runnable ELF binary!) to get the full list of lib paths it will search in. (ldd is ld-linux.so --list). Once the libraries are in place, try the chroot again: $ sudo chroot /home/your/chroot /bin/sh bash-5.2# echo It Works It Works bash-5.2# ls bash: ls: command not found bash-5.2# echo * bin lib bash-5.2# cd bin bash-5.2# echo * sh ^D Shell builtins work fine, like cd or echo or pwd, but not ls. Let's fix that! $ cp /bin/ls bin/ls $ sudo chroot /home/your/chroot /bin/sh sh-5.2# ls ls: error while loading shared libraries: libselinux.so.1: cannot open shared object file: No such file or directory ldd on bin/ls shows I need libselinux.so.1 and libpcre2-8.so.0, and then ls works ... ish? sh-5.2# ls -l total 8 drwxrwxr-x 2 1000 1000 4096 Dec 19 20:35 bin drwxrwxr-x 2 1000 1000 4096 Dec 19 20:36 lib ^^^^ ^^^^ File owner and group are shown numerically, since we don't have an /etc yet. ;;;;;;;;;;; chroot.1: ; ---- strace all the syscalls ---- Usually when a program misbehaves in a chroot, it's because it needs some random files, and the hard part is figuring out *which* files it wants where. Syntax: strace <command to run> Output: every kernel syscall made by that command as it runs Let's use strace to watch exactly what syscalls `ls` makes in our chroot: $ cp /usr/bin/strace bin/ (Do the ldd dance to get strace running in the chroot) $ sudo chroot /home/your/chroot /bin/sh sh-5.2# strace ls -l execve("/bin/ls", ["ls", "-l"], 0xfffff79532c8 /* 17 vars */) = 0 ... 100+ lines of shared libraries thrashing around ... openat(AT_FDCWD, "/etc/passwd", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory) Trapped in the huge spew of library bloat is the one file we need to add, the famous /etc/passwd. We can just make up a username for this file: $ mkdir etc $ cat > etc/passwd lol:x:1000:1000:never:/gonna/give/you:/bin/up ^D Trying this from inside the chroot, our fake username works! sh-5.2# ls -l total 12 drwxrwxr-x 2 lol 1000 4096 Dec 19 20:50 bin drwxrwxr-x 2 lol 1000 4096 Dec 19 20:56 etc drwxrwxr-x 2 lol 1000 4096 Dec 19 20:36 lib ^^^^ But the group is still listed numerically. Checking strace again, we see another ENOENT when ls tries to open /etc/group, so we just make one: $ cat > etc/group nope:x:1000: ^D sh-5.2# ls -l total 12 drwxrwxr-x 2 lol nope 4096 Dec 19 20:50 bin drwxrwxr-x 2 lol nope 4096 Dec 19 23:24 etc drwxrwxr-x 2 lol nope 4096 Dec 19 20:36 lib Most programs don't check things very closely, so you can fake things in /proc or /dev with just flat files: `echo predictable > dev/random` will silently backdoor most crypto inside the chroot! Some programs require access to /proc or /sys, so if you can tolerate the attack surface you can just bind mount the real thing into your chroot: $ mount -o bind /dev dev $ mount -o bind /proc proc $ mount -o bind /sys sys (But try faking it, it's more controllable and surprisingly effective.) ;;;;;;;;;;; chroot.2: ; ---- chroot jailbreak ---- In a complicated system chroot has a lot of escape opportunities: https://github.com/earthquake/chw00t Everything the kernel touches except the filesystem is still accessible: - process lists and kill(), so `kill -9 -1` will still nuke the box - network access (the attacker is coming from 127.0.0.1 or ::1/128!) - device access (in the chroot, mknod /dev/sda and mount escape) True container systems are quite an evolution from a basic chroot: - Podman or Docker or LXC isolate the network, PIDs, and cgroups - FreeBSD jails allow syscall translation and network isolation ;;;;;;;;;;; chroot.3: ; ---- architectural chroot ---- A working chroot is a fully encapsulated system, with binaries and libs, so you can move it between machines easily. An "architectural chroot" can help you run binaries from other CPUs like x86/arm/risc-v/mips. On modern linux, "sudo apt install qemu-user-static" makes chroot automagically run binaries from any of the 34(!) supported architectures. On older linux, you can register the ELF header and emulator into /proc/sys/fs/binfmt_misc/register via a binary mask of the ELF bits. Chroot is under-rated for cross-platform reversing and analysis: you can grab an old MIPS or ARM32 firmware image, run its binaries in a chroot, and try even GOT/PLT/ROP vulns using your desktop CPU but the old libs and binaries. (Also useful for running your favorite old tool/game on new hardware!) ;;;;;;;;;;; chroot.FF: ; ---- bonus challenges ---- Easy: - Build a chroot from one of your boxes. - Copy your chroot to another CPU arch (x86, arm64, risc-v) and run it. Hard: - Use binwalk to pull a filesystem image from a firmware update file, and use (architectural) chroot to get it running on your machine. - Get a CUDA program running inside a chroot. --[ PREV | HOME | NEXT ]--