5 ms·
Hiding Linux Processes with Bind Mounts
- awaythrow999 2y agoInteresting but >> 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.
- theteapot 2y agoDid we read the same article? What two identical PIDs? The process doesn't show up in ps so doesn't show up in "ps | grep ..." either.
- jaimehrubiks 2y agoProbably yes, because in the op's article it's using a bind mount from another kernel thread and it does show up in ps twice
- Sophira 2y ago> Did we read the same article? 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.
- theteapot 2y agoYeah I did, oops :). I agree new approach isn't really any better.
- o11c 2y agoIf you're relying on such superficial disguising, just put your process name in brackets directly, no mount privileges required. 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.
- breakingcups 2y agoIt rather involved being on the other side of this airtight hatchway: https://devblogs.microsoft.com/oldnewthing/20060508-22/?p=31283 https://devblogs.microsoft.com/oldnewthing/20060508-22/?p=31...
- tanelpoder 2y agoThis would help against basic ps commands but if your security tool reads the task's PF_ flags (from /proc/PID/stat or kernel memory), you'd still see that your [task] is not an actual kernel task... (the PF_KTHREAD flag is 0x200000): $ 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)
- bpoyner 2y agoI'm so old I remember when ps would directly get the processes via system calls to the kernel and none of this reading from /proc.
- deepsun 2y agoTo me it actually sounds like a better approach than blindly following "everything is a file" mantra.
- kjeetgill 2y agoI'm totally with you for processes should probably use glibc/platform API calls, but procfs is nothing short of brilliant and a great validation of the power of the "everything is a file".
- Sophira 2y agoExcept that any process that has obtained root (through a security flaw or otherwise) can extremely easily hide itself, as shown here. I will admit that it's likely that the same thing could be done with loadable kernel modules, that said.
- yjftsjthsd-h 2y agoWhat's blind about it? Reading files is nicer than parsing ps output or needing to write native code over a syscall.
- efitz 2y agoThe abstraction of representing everything as files is often leaky (and often clunky).
- jordemort 2y agoIf you have the privilege to pull off bind-mounting on top of a particular process directory in /proc you should also be able to mount something on top of /proc itself that will present a false /proc/mounts and conceal your trickery. I experimented a bit and wasn't able to bind a single file on top of /proc/mounts and I seem to be having trouble getting procfs to participate in an overlayfs, but if you were really dedicated you could construct a whole fake /proc by hand, or create something using FUSE to create a /proc that redacts anything you want, including mountpoints.
- indigodaddy 2y agoInteresting. Could you not perhaps tank the system attempting something like that?
- yjftsjthsd-h 2y agoHow? I don't think the kernel cares what the directory looks like - userspace will break in funny ways, but you can boot without procfs even mounted (well, I say boot, but more like "you can start a shell and some things will still work")
- tetris11 2y agoTIL that the kernel presents /proc as a convenience to the user, and does not actively read from it
- yjftsjthsd-h 2y agoIt might help to think of how it's implemented: 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.
- anyfoo 2y agoCoincidentally, I just used a veritable litany of bind mounts this week. I did not attempt to hide anything, though one of those bind mounts exists in order to fake something. I needed this to be able to properly run some x86 software under Rosetta 2 in an arm64 Ubuntu VM. I 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.
- Szpadel 2y agoin Linux you can rename your own process name, many applications do that to name their threads or to describe what they are doing 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)
- tetris11 2y agoIf it's taking up a lot of CPU and MEM, someone runnning htop will begin to wonder what that process is doing. By bind mounting it, you obscure it from htop altogether
- pengaru 2y agoBrackets aren't the only indication a process is a kernel thread, it's fairly trivial to verify via proc: $ grep Kthread /proc/$$/status Kthread: 0
- zamalek 2y agoIf you're looking for naughty processes you could start with: mkdir /tmp/foo mount -t proc none /tmp/foo
- amelius 2y agoWhat I want to hide is mounts. With containers, snap (squashfs), etc., the number of mounts on a modern Linux system is growing to ridiculous numbers. And why should they all be visible to normal users?
- dfgdgf 2y ago[dead]