---------------- /------| ----------------
| 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 ]--