Treat Wheel. Or Threat Wheel, or Tricky Wheel. Whatever you call it, it is a hiding module of age already. It dates back to the beginning of 2025. It has been recently FOSS-ed, so we can take a look on it. For the sake of simplicity, let's see the simplest ones
1. Zygote
mountinfo leakThis detection happens early on Zygote. On initialization, Zygote does several operations regarding mounts. Creating a new mount namespace, marking rootfs as
MS_SLAVE, mounting tmpfs, and... umounting /storage mounts.While plain
umount is fine by itself, Zygote utilizes a very specific libc function to grab the active mounts: getmntent. The special characteristic of that function is that it is backed by a global buffer in libc (getmntent.c#L25). When Zygote uses it, it ends up leaving the last mount on /proc/mounts on that buffer.Root implementations and modules usually mount late. That leads to their mounts being the last ones there. When they
umount, the mounts will disappear from procfs, but the trace of the last mount will be cached in that global buffer, and it can be observed.Treat Wheel fixes this by forking a new process to run
setmntent and getmntent (bypassing procfs related detections), grabbing its address and new buffer value, passing to parent, and having the parent write to the address the children's buffer value. Since they share the same mount namespace, they should have equal values when up-to-date. This fixes this detection.2. AtExit handles
Bjarne did not approve this detection — but let's focus on C++ for this one. When developing any software, especially on C++, the standard library needs to be initialized. However, not only that, it also needs to be de-initialized. That's the issue AtExit handles solve.
libc keeps track of all AtExit handles, so that before it exists, it can call them. However, the problem is that when they are not utilized, instead of being removed, they're simply zero-d. In a list that all of the entries will point to valid addresses, and usually non-zero, having a zero address is abnormal.
Treat Wheel fixes that by directly manipulating the list. However, detecting which ones are from the system, and which ones are created by Zygisk modules is not an easy task when they're already zero. Treat Wheel uses heuristics for that. All entries before
libminikin.so happen before most system libraries — but after Zygisk modules. By removing those, we can safely patch this detection.3. Frida
Frida is extremely detectable as-is. However, those standard Frida detections is not what we go after. Something that got my attention is how Frida left detections on SoList too, similarly to old Zygisks.
This detection involves doing the same as what we used to do in Zygisk before custom linkers came — set the size of the VMAs to 0, then call
soinfo_unload, which removed its SoInfo entry from SoList, evading most generic injection detections.This was simply a post I made rapidly out of boredom and willingness to spread more information about the detections Treat Wheel fixes. While I would love to include URL to sources, Telegram didn't find the same so attracting, by persisting the image of a bird in my clipboard. Enjoy the text and the bird, then.
Sincerely,
ThePedroo.
