Hiding Linux Processes with Bind Mounts
righteousit.com
righteousit.com
When your application calls open() or read() or stat() or whatever, it passes through libc and then makes a syscall to ask the kernel "hey, give me a file handle for /proc/foo", "hey, give me the first 500 bytes from this file handle", etc. Then in the kernel, that syscall triggers a function that figures out what filesystem you're talking to and passes the request to that filesystem driver. AFAIK, the procfs driver just reads (and writes) a bunch of data structures in kernel memory and passes the result through as a filesystem. So the actual underlying data structure does always exist and is a hard requirement for the system to work, but procfs is just a userspace view into that underlying data in the kernel. Incidentally sysfs would be the same - the stuff you see in /sys exists in the kernel regardless, but the kernel doesn't really care whether those data structures are exposed to userspace as a virtual filesystem.
How did chroot used to work in this regard? Were mountpoints outside the chroot jail itself accessible? Because if so, that ... would violate some key points of using a chroot jail in the first place. Though if chroot applied strictly to the filesystem and not mount points that might still offer benefits, and I seem to recall running into ... unexpected behaviours ... using chroots in the past.
If an unconstrained uid zero process is inside the chroot but can access or create device nodes, it can mount devices within the chroot just fine.
mount is a bit harder (as its calls into code that is more in the core kernel fs code, but one can just reimplement it oneself, to filter out the mounts you don't want to show.
The trick is to hide in plain sight. Call your process “tracker-miner-fs” (common suspiciously named GNOME process”). When the system administrator sees your process eating up huge amount of CPU time and does a web search, they’ll come to the conclusion it’s just GNOME being GNOME.
Signed, greybeard gnu/linux admin who hates procs like that!
edit: this is also why lsof, ss, strace, and bpftrace etc., are helpful
A sysadmin just would see . . .. as directory entries.
As a general rule, if malware gets root privilege, no other process on the same system (container, at least) should be expected to be able to detect it.
$ ln -s /bin/sleep '[kernelstuff]'
$ ./\[kernelstuff\] 999 &
[2] 29499
$ awk '{ printf("%x\n", $9) }' /proc/29499/stat
40400000
$ pgrep kthreadd
2
$ awk '{ printf("%x\n", $9) }' /proc/2/stat
208040
Edit: as you said, it's superficial disguising (that may even work in some cases)I will admit that it's likely that the same thing could be done with loadable kernel modules, that said.
Not sure if relevant to this discussion, but here's the problems I've experienced working with files for system devices:
[1] here's just a discovery of a logitech mouse receiver, notice that it touches:
* /sys/class/*
* /sys/module/*
* /sys/class/hidraw/*/device/driver
I must take care that each dir might not be present, symlinks may be dangling, and access to multiple files is not atomic.It would be much easier and stable using just one SQLite query, for example.
[1] https://github.com/Lekensteyn/ltunify/blob/master/ltunify.c#...
FWIW, /proc was added in Linux v0.97.3, September 1992, which is early enough I couldn't find ps source for linux earlier then that date.
Edit: My mistake, it was /dev/mem - https://github.com/lsahn-gh/unix-v6/blob/master/source/s2/ps...
Yep, found it: https://www.ibiblio.org/pub/historic-linux/ftp-archives/suns...
And the Linux kernel CREDITS file has an entry mentioning it too:
N: Rick Sladkey
D: utility hacker: Emacs, NFS server, mount, kmem-ps, UPS debugger, strace, GDB
[...]On FreeBSD there seems to be some trick where opening "/dev/null" instead of "/dev/kmem" causes the process "to not access kernel memory directly". Looking at the code it seeems to me that it means that libkvm will really open /dev/null and treat it as if it was /dev/kmem, which raises a question of exactly how that works.
[Edit: apparently this works because in case of querying processes of running kernel, the code in kvm_proc.c does not read from the file and instead calls sysctl().]
alias df='df -t vfat -t ext4'I just checked and there is also an exclude option. This comes close to what I want:
df -x tmpfs -x squashfs -x devtmpfsI wasn't happy with how handling packages for multiple architectures seems to work in Ubuntu/Debian: It looks like you basically have to choose what package you want to install with each architecture, because if you try to install a package for both architectures, they almost always conflict with each other (trying to occupy the same files).
So instead, I made an x86 chroot. I used "debootstrap" to populate the x86 chroot with an x86 Ubuntu base system, and schroot so that a regular non-root user could use that chroot. That way, I can install any x86 package I want without conflicting with the surrounding VM by just chroot'ing into it and regularly typing e.g. "apt install emacs".
But both because a lot of software needs it, and for interop with the surrounding VM (unix domain sockets etc.), I had to create a bunch of bind mounts to bring shared "state" directories into the x86 chroot:
mount --make-private --bind /proc /data/x86/proc
mount --make-private --bind /proc /data/x86/sys
...
I did this for at least /proc, /sys, /run, /dev, /dev/pts and /home. Depending on how much you want to bring the environments together, you could also add /run, /tmp, and others.I also added bind mounts for individual files:
mount --make-private --bind /etc/passwd /data/x86/etc/passwd
mount --make-private --bind /etc/shadow /data/x86/etc/shadow
mount --make-private --bind /etc/group /data/x86/etc/group
so that my x86 environment has the same user/group database.Finally, and I think this is the most interesting piece, some x86 software I tried to run did not work because it tried to parse /proc/cpuinfo for some x86 CPU features. Rosetta 2 implements those, but of course /proc/cpuinfo in the system describes the actual (virtualized) ARM CPUs, which is of course not what x86 software expects.
So, I crafted my own fake cpuinfo text file that looked roughly like it would look in a real x86 environment (I did not put much effort in it, just copied an output from a random real x86 machine and adjusted the number of CPUs), and bind mounted that into the x86 /proc overlay:
mount --make-private --bind /data/fake-x86-cpuinfo.txt /data/x86/proc/cpuinfo
Now, /proc/cpuinfo inside the chroot (so, /data/x86/proc/cpuinfo) would give the fake cpuinfo and make the x86 software that parses it happy. This is also where the --make-private that I applied to /data/x86/proc earlier becomes important: Without that, that last command would not just overlay /data/x86/proc/cpuinfo, but also /proc/cpuinfo itself, now in turn making arm64 software potentially unhappy. With --make-private on the /proc bind mount, it effectively becomes a separate filesystem in that regard (only).Finally, the biggest hurdle I had was getting systemd to properly mount all of these in the right order at startup. systemd parallelizes the mounts in fstab (and mounts generally). But if e.g. the /data/x86/proc mount does not happen after both /proc and /data/x86 have been mounted already, you effectively get an empty directory (you can easily work out yourself why that results in all improper cases). This was even more complicated because I use ZFS, and so /data/x86 gets mounted by zfs.mount-service. After much fiddling, I gave up. No combination of "x-systemd.requires=<mountpoint>", "x-systemd.after=zfs-mount.service" and whatever else in the fstab options really fully did the right thing.
I resorted to having a shell script that just runs the bind mounts in the correct order.
isn't it just much easier to rename our process to looks like kernel thread or some other process? we could easily escape process tree by forking child and exiting parent (we would get adopted by init)
$ grep Kthread /proc/$$/status
Kthread: 0>> My guess is that nobody is going to notice this unless they are specifically looking for this technique.
But having two identical PIDs is a pretty weak cloak. Even more so when reducing terminal clutter e.g. run "ps | grep procname" ... anyone not completely asleep is bound to notice it.
Probably you read the article that was linked from this article, which does indeed make the process completely vanish, but leaves a suspicious empty /proc directory.
This article 'solves' the problem of an empty directory by simply bind-mounting another process instead - but that causes ps to output a duplicate line (including process ID) for the other process, in lieu of the process being hidden.
mkdir /tmp/foo
mount -t proc none /tmp/foo