Skip to content

Commit

Permalink
Add detection using mountinfo
Browse files Browse the repository at this point in the history
  • Loading branch information
JingMatrix committed Dec 30, 2024
1 parent 3de7f15 commit 834c9ef
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 10 deletions.
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,22 @@ The following cases are considered as injections:

See blog [Android 用户态注入隐藏已死](https://nullptr.icu/index.php/archives/182/).

## Detection using `mountinfo`

Current root solutions of Android are implemented in a systmeless way, meaning that they overlay the filesystems of the device by [mounting](https://man7.org/linux/man-pages/man8/mount.8.html) instead of overwriting the actual file contents.

The following cases are considered as injections:
1. common mount points of known root implementations present in [proc_pid_mountinfo](https://man7.org/linux/man-pages/man5/proc_pid_mountinfo.5.html);
2. gaps between mount IDs or mounting peer IDs appearing before mounting points specific to current application.


## Detection using `module counter`

A call to `dlclose` will increase the counter [g_module_unload_counter](https://cs.android.com/android/platform/superproject/main/+/main:bionic/linker/linker.cpp;l=1956).

This detection highly depends on Android OS and vendor customization, which is shown to be false positive on Samsung and OnePlus.

## State of bypassing current test
# How to bypass all of them

Open source solution: [JingMatrix/NeoZygisk](https://github.com/JingMatrix/NeoZygisk) with [JingMatrix/APatch](https://github.com/JingMatrix/APatch).

- [ ] [Zygisk of Magisk](https://github.com/topjohnwu/Magisk)
- [ ] [ZygiskNext](https://github.com/Dr-TSNG/ZygiskNext)
- [x] [ReZygisk](https://github.com/PerformanC/ReZygisk) (fixed by JingMatrix in https://github.com/PerformanC/ReZygisk/pull/101)
2 changes: 1 addition & 1 deletion app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ set(CMAKE_CXX_STANDARD 20)
# used in the AndroidManifest.xml file.
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
elf_util.cpp native-lib.cpp smap.cpp solist.cpp vmap.cpp)
elf_util.cpp mount.cpp native-lib.cpp smap.cpp solist.cpp vmap.cpp)

target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC include)
# Specifies libraries CMake should link to your target library. You
Expand Down
34 changes: 34 additions & 0 deletions app/src/main/cpp/include/mount.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include <functional>
#include <string>
#include <sys/types.h>
namespace Mount {
using sFILE = std::unique_ptr<FILE, decltype(&fclose)>;
sFILE MakeFile(FILE *fp);

struct MountInfo {
unsigned int id;
unsigned int parent;
dev_t device;
std::string root;
std::string target;
std::string vfs_option;
struct {
unsigned int shared;
unsigned int master;
unsigned int propagate_from;
} optional;
std::string type;
std::string source;
std::string fs_option;
};

void ReadLine(bool trim, FILE *fp,
const std::function<bool(std::string_view)> &fn);
void ReadLine(bool trim, const char *file,
const std::function<bool(std::string_view)> &fn);
void ReadLine(const char *file,
const std::function<bool(std::string_view)> &fn);
int ParseInt(std::string_view s);
std::vector<MountInfo> ParseMountInfo(const char *pid);
std::vector<MountInfo> DetectInjection();
} // namespace Mount
191 changes: 191 additions & 0 deletions app/src/main/cpp/mount.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#include "mount.hpp"
#include "logging.h"
#include <sys/sysmacros.h>

using namespace std::string_view_literals;
namespace Mount {
static inline sFILE open_file(const char *path, const char *mode) {
return MakeFile(fopen(path, mode));
}

sFILE MakeFile(FILE *fp) {
return sFILE(fp, [](FILE *fp) { return fp ? fclose(fp) : 1; });
}

void ReadLine(bool trim, FILE *fp,
const std::function<bool(std::string_view)> &fn) {
size_t len = 1024;
char *buf = (char *)malloc(len);
char *start;
ssize_t read;
while ((read = getline(&buf, &len, fp)) >= 0) {
start = buf;
if (trim) {
while (read && "\n\r "sv.find(buf[read - 1]) != std::string::npos)
--read;
buf[read] = '\0';
while (*start == ' ')
++start;
}
if (!fn(start))
break;
}
free(buf);
}

void ReadLine(bool trim, const char *file,
const std::function<bool(std::string_view)> &fn) {
if (auto fp = open_file(file, "re"))
ReadLine(trim, fp.get(), fn);
}

void ReadLine(const char *file,
const std::function<bool(std::string_view)> &fn) {
ReadLine(false, file, fn);
}

std::vector<MountInfo> ParseMountInfo(const char *pid) {
char buf[PATH_MAX] = {};
snprintf(buf, sizeof(buf), "/proc/%s/mountinfo", pid);
std::vector<MountInfo> result;

ReadLine(buf, [&result](std::string_view line) -> bool {
int root_start = 0, root_end = 0;
int target_start = 0, target_end = 0;
int vfs_option_start = 0, vfs_option_end = 0;
int type_start = 0, type_end = 0;
int source_start = 0, source_end = 0;
int fs_option_start = 0, fs_option_end = 0;
int optional_start = 0, optional_end = 0;
unsigned int id, parent, maj, min;
sscanf(line.data(),
"%u " // (1) id
"%u " // (2) parent
"%u:%u " // (3) maj:min
"%n%*s%n " // (4) mountroot
"%n%*s%n " // (5) target
"%n%*s%n" // (6) vfs options (fs-independent)
"%n%*[^-]%n - " // (7) optional fields
"%n%*s%n " // (8) FS type
"%n%*s%n " // (9) source
"%n%*s%n", // (10) fs options (fs specific)
&id, &parent, &maj, &min, &root_start, &root_end, &target_start,
&target_end, &vfs_option_start, &vfs_option_end, &optional_start,
&optional_end, &type_start, &type_end, &source_start, &source_end,
&fs_option_start, &fs_option_end);

auto root = line.substr(root_start, root_end - root_start);
auto target = line.substr(target_start, target_end - target_start);
auto vfs_option =
line.substr(vfs_option_start, vfs_option_end - vfs_option_start);
++optional_start;
--optional_end;
auto optional = line.substr(
optional_start,
optional_end - optional_start > 0 ? optional_end - optional_start : 0);

auto type = line.substr(type_start, type_end - type_start);
auto source = line.substr(source_start, source_end - source_start);
auto fs_option =
line.substr(fs_option_start, fs_option_end - fs_option_start);

unsigned int shared = 0;
unsigned int master = 0;
unsigned int propagate_from = 0;
if (auto pos = optional.find("shared:"); pos != std::string_view::npos) {
shared = ParseInt(optional.substr(pos + 7));
}
if (auto pos = optional.find("master:"); pos != std::string_view::npos) {
master = ParseInt(optional.substr(pos + 7));
}
if (auto pos = optional.find("propagate_from:");
pos != std::string_view::npos) {
propagate_from = ParseInt(optional.substr(pos + 15));
}

result.emplace_back(MountInfo{
.id = id,
.parent = parent,
.device = static_cast<dev_t>(makedev(maj, min)),
.root{root},
.target{target},
.vfs_option{vfs_option},
.optional{
.shared = shared,
.master = master,
.propagate_from = propagate_from,
},
.type{type},
.source{source},
.fs_option{fs_option},
});
return true;
});
return result;
}

int ParseInt(std::string_view s) {
int val = 0;
for (char c : s) {
if (!c)
break;
if (c > '9' || c < '0')
return -1;
val = val * 10 + c - '0';
}
return val;
}

std::vector<MountInfo> DetectInjection() {
std::vector<MountInfo> sus_mount = {};
auto infos = ParseMountInfo("self");
auto root_directory = infos[0];
if (root_directory.id != root_directory.parent + 1) {
// Caused by multiple calls of unshare
sus_mount.emplace_back(root_directory);
}

int consistent_mount_id = root_directory.id;
for (auto &info : infos) {
if (info.target == "/data/data")
// Skip mount points specilized for current app
break;
if (consistent_mount_id != info.id) {
sus_mount.emplace_back(info);
LOGD("Inconsistent mount ID %i", info.id);
break;
}
consistent_mount_id++;
if (info.root.starts_with("/adb") ||
info.target.starts_with("/debug_magisk") || info.source == "magisk" ||
info.source == "KSU" || info.source == "APatch") {
LOGD("Found root mounting points");
sus_mount.emplace_back(info);
break;
}
}

std::sort(infos.begin(), infos.end(),
[](MountInfo const &a, MountInfo const &b) {
return a.optional.master < b.optional.master;
});

int consistent_peer_group = 1;
for (auto &info : infos) {
LOGD("Checking mount point %i %i %s %s master:%i", info.id, info.parent,
info.root.data(), info.target.data(), info.optional.master);

if (info.root.find("org.matrix.demo") != std::string::npos)
// Skip mount points specilized for current app
break;
if (consistent_peer_group < info.optional.master) {
sus_mount.emplace_back(info);
LOGD("Mounting peer group %i was unmounted", consistent_peer_group);
break;
}
consistent_peer_group = info.optional.master + 1;
}

return sus_mount;
}
} // namespace Mount
20 changes: 15 additions & 5 deletions app/src/main/cpp/native-lib.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "logging.h"
#include "mount.hpp"
#include "smap.h"
#include "solist.hpp"
#include "vmap.hpp"
Expand All @@ -13,8 +14,10 @@ Java_org_matrix_demo_MainActivity_stringFromJNI(JNIEnv *env,
std::string solist_detection = "No injection found using solist";
std::string vmap_detection = "No injection found using vitrual map";
std::string counter_detection = "No injection found using module counter";
std::string mount_detection = "No injection found using mountinfo";
SoList::SoInfo *abnormal_soinfo = SoList::DetectInjection();
VirtualMap::MapInfo *abnormal_vmap = VirtualMap::DetectInjection();
auto abnormal_mount = Mount::DetectInjection();
size_t module_injected = SoList::DetectModules();

if (abnormal_soinfo != nullptr) {
Expand All @@ -31,12 +34,19 @@ Java_org_matrix_demo_MainActivity_stringFromJNI(JNIEnv *env,
abnormal_vmap->start, abnormal_vmap->end);
}

if (abnormal_mount.size() > 0) {
auto mount = abnormal_mount[0];
mount_detection = std::format("MountInfo: injection at {}", mount.target);
LOGE("Abnormal mount: %i %i %s %s", mount.id, mount.parent,
mount.root.data(), mount.target.data());
}

if (module_injected > 0) {
counter_detection =
std::format("Module counter: {} shared libraries unloaded", module_injected);
counter_detection = std::format(
"Module counter: {} shared libraries unloaded", module_injected);
}

return env->NewStringUTF(
(solist_detection + "\n" + vmap_detection + "\n" + counter_detection)
.c_str());
return env->NewStringUTF((solist_detection + "\n" + vmap_detection + "\n" +
mount_detection + "\n" + counter_detection)
.c_str());
}

0 comments on commit 834c9ef

Please sign in to comment.