Root Detection là cơ chế bảo mật bảo vệ ứng dụng Android khỏi chạy trên thiết bị có đặc quyền siêu người dùng. Ứng dụng ngân hàng, thanh toán và doanh nghiệp chặn hoặc hạn chế chức năng trên thiết bị đã root, vì quyền truy cập root loại bỏ các hạn chế của hộp cát Android và mở ra khả năng chặn lưu lượng, đọc bộ nhớ tiến trình và giả mạo dữ liệu. Theo OWASP Mobile Top 10 (2024), việc thiếu Root Detection thuộc danh mục M8 (Security Decisions via Untrusted Inputs). Root Detection được xây dựng dựa trên sự kết hợp của kiểm tra tĩnh hệ thống tệp và phân tích động hành vi thời gian chạy.
Điểm chính
Root Detection là cơ chế phần mềm phát hiện sự hiện diện của quyền truy cập root trên thiết bị Android. Quyền truy cập root cung cấp toàn quyền kiểm soát hệ điều hành, cho phép ứng dụng và tập lệnh thực thi lệnh với UID 0. Trên thiết bị đã root, sự cô lập ứng dụng (Android Sandbox) bị mất, khiến cho việc chặn đầu vào bàn phím, đọc cơ sở dữ liệu SQLite của ứng dụng khác, chèn mã vào tiến trình và thay thế chứng chỉ SSL trong kho tin cậy trở nên khả thi.
Đối với ứng dụng tài chính và doanh nghiệp, chạy trên thiết bị đã root là rủi ro không thể chấp nhận: kẻ tấn công có quyền truy cập vào token, khóa phiên và dữ liệu cá nhân. Các cơ quan quản lý, bao gồm PCI Security Standards Council, yêu cầu ứng dụng thanh toán phải phát hiện và phản ứng với quyền truy cập root. Đáp lại, nhà phát triển Android nhúng Root Detection như một phần của chiến lược bảo vệ chủ động.
Có hai cách tiếp cận phát hiện: tĩnh, phân tích hệ thống tệp và gói đã cài đặt, và động, thực hiện kiểm tra trong thời gian chạy. Cách tiếp cận kết hợp được coi là đáng tin cậy nhất vì nó bao phủ các vectơ vượt qua khác nhau. Theo nghiên cứu của NowSecure (2025), 76% ứng dụng ngân hàng trong top 100 Google Play chứa một số hình thức Root Detection.
Phương pháp tĩnh thực thi khi khởi động ứng dụng và kiểm tra dấu hiệu quyền truy cập root do công cụ root để lại trong hệ thống tệp. Các phương pháp này không yêu cầu thực thi lệnh đặc quyền và hoạt động trong ngữ cảnh ứng dụng bình thường.
Chỉ báo chính của quyền truy cập root là sự hiện diện của tệp thực thi su trong các đường dẫn tiêu chuẩn: /system/bin/su, /system/xbin/su, /sbin/su, /su/bin/su. Ứng dụng kiểm tra sự tồn tại của tệp qua File.exists() hoặc triển khai native của access() từ libc. Ngoài ra, có thể thử thực thi su --version hoặc su -c id và kiểm tra mã thoát.
Ứng dụng điển hình để quản lý quyền truy cập root: Superuser, SuperSU, Magisk Manager, KingRoot. Sự hiện diện của chúng được kiểm tra qua PackageManager.getPackageInfo() hoặc đọc thư mục /data/app/. Các gói cần kiểm tra: com.topjohnwu.magisk, eu.chainfire.supersu, com.noshufou.android.su, com.thirdparty.superuser, com.koushikdutta.superuser, com.zacharee1.systemuituner.
Android lưu trữ thông tin trạng thái hệ thống trong các thuộc tính hệ thống, có thể truy cập qua System.getProperty và Build.TAGS. Nếu Build.TAGS chứa test-keys thay vì release-keys, điều này cho thấy phần sụn tùy chỉnh, thường có quyền truy cập root. Ngoài ra, ro.build.tags, ro.debuggable và ro.secure được kiểm tra bằng cách đọc /system/build.prop.
public class RootDetectionChecker {
private static final String[] SU_PATHS = {
"/system/bin/su",
"/system/xbin/su",
"/sbin/su",
"/su/bin/su",
"/system/sd/xbin/su"
};
public boolean checkRootByFiles() {
for (String path : SU_PATHS) {
if (new File(path).exists()) {
return true;
}
}
return false;
}
public boolean checkRootByPackages(Context ctx) {
String[] packages = {
"com.topjohnwu.magisk",
"eu.chainfire.supersu",
"com.noshufou.android.su",
"com.koushikdutta.superuser"
};
for (String pkg : packages) {
try {
ctx.getPackageManager().getPackageInfo(pkg, 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
// package not found
}
}
return false;
}
}
Phương pháp động thực thi trong quá trình vận hành ứng dụng và phân tích môi trường thực thi. Không giống phương pháp tĩnh, chúng có thể phát hiện root ẩn qua Magisk Hide hoặc Zygisk, vì chúng kiểm tra hành vi hệ thống chứ không chỉ cấu trúc tệp.
Với quyền truy cập root, một số phân vùng hệ thống được gắn với cờ rw (đọc-ghi) thay vì ro (chỉ đọc). Ứng dụng đọc /proc/mounts và kiểm tra rằng /system được gắn là ro. Nếu /system được gắn là rw, điều này cho thấy hệ thống đã bị sửa đổi. Ngoài ra, sự hiện diện của gắn kết /su qua Magisk được kiểm tra.
Chế độ an toàn Android vô hiệu hóa ứng dụng bên thứ ba, bao gồm trình quản lý root. Triển khai Root Detection đúng cách có thể kiểm tra xem thiết bị có đang chạy ở chế độ an toàn hay không. Nếu ứng dụng phát hiện rằng trình quản lý root không hiển thị nhưng nhị phân su tồn tại, đây là dấu hiệu của Magisk Hide.
Cố gắng thực thi su -c id qua ProcessBuilder hoặc Runtime.exec là kiểm tra trực tiếp quyền truy cập root. Tuy nhiên, Magisk có thể chặn cuộc gọi này. Cách tiếp cận đáng tin cậy hơn là kiểm tra qua mã native: mở /proc/1/limits hoặc /proc/self/maps và phân tích UID của các tiến trình đang chạy. Nếu ứng dụng có thể lấy UID 0 hoặc đọc tệp chỉ root mới truy cập được, thiết bị đã bị xâm phạm.
public boolean checkRootDynamically() {
// Build flags check
String buildTags = Build.TAGS;
if (buildTags != null && buildTags.contains("test-keys")) {
return true;
}
// Checking /system mount
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("/proc/mounts"))
);
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("/system")
&& line.contains("rw")) {
reader.close();
return true;
}
}
reader.close();
} catch (IOException e) {
// error reading mounts
}
return false;
}
Root Detection triển khai bằng Java dễ dàng bị vượt qua qua mô-đun Xposed hoặc Frida, chúng chặn các phương thức Java và thay thế giá trị trả về. Triển khai native bằng C++ qua JNI có khả năng chống chịu cao hơn đáng kể: các công cụ phân tích động hoạt động ở cấp Java không thể thấy các cuộc gọi libc native như stat, access, popen và dlopen.
#include <unistd.h>
#include <sys/stat.h>
#include <cstring>
#include <vector>
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_checker_RootCheck_nativeCheck(
JNIEnv* env, jobject instance) {
std::vector<const char*> paths = {
"/system/bin/su",
"/system/xbin/su",
"/sbin/su",
"/data/local/su"
};
struct stat st;
for (const char* path : paths) {
if (stat(path, &st) == 0) {
return JNI_TRUE;
}
}
return JNI_FALSE;
}
Kiểm tra native không sử dụng API Java, khiến nó vô hình đối với các công cụ vượt qua hoạt động ở cấp Dalvik/ART. Để bảo vệ bổ sung, nên lưu trữ hằng số (danh sách đường dẫn) không phải trong phần chỉ đọc mà tính toán chúng qua các hàm khả nghịch đơn giản. Cuộc gọi stat từ libc truy cập trực tiếp vào nhân Linux, bỏ qua các trình bao bọc Java và không thể bị chặn qua Xposed.
Nhà phát triển bảo vệ cần hiểu các phương pháp vượt qua hiện có để xây dựng hệ thống phát hiện mạnh mẽ. Mỗi phương pháp vượt qua yêu cầu biện pháp đối phó ở cấp độ tương ứng.
Magisk là công cụ root phổ biến nhất trên Android 9–14. Magisk Hide ẩn sự hiện diện của su khỏi /proc và giả mạo kết quả kiểm tra đường dẫn. Magisk hoạt động ở cấp nhân và chặn stat() và access() trước khi ứng dụng nhìn thấy chúng. Biện pháp đối phó: kiểm tra sự hiện diện của chính Magisk qua sự tồn tại của /sbin/.magisk hoặc kiểm tra qua việc đọc maps của chính ứng dụng — Magisk chèn thư viện của nó vào mọi tiến trình.
Frida là công cụ đo lường động có thể chặn các hàm native qua Ptrace hoặc Dobby. Frida thay thế giá trị trả về của bất kỳ kiểm tra nào, giả mạo kết quả stat thành ENOENT. Biện pháp đối phó: xác minh tính toàn vẹn của hàm native bằng cách tính tổng kiểm tra của lệnh trong bộ nhớ và phát hiện Frida qua phân tích /proc/self/maps để tìm sự hiện diện của frida-agent.so hoặc frida-helper.
Root Detection triển khai bằng Java bị loại bỏ trong 2–3 phút: APK được dịch ngược qua apktool, giá trị trả về của phương thức được thay đổi thành false trong mã smali, APK được xây dựng lại và ký. Biện pháp đối phó: chuyển logic quan trọng sang mã native và xác minh chữ ký số của ứng dụng trong thời gian chạy qua API Signature hoặc so sánh băm APK với tham chiếu trên máy chủ.
Root Detection hiệu quả được xây dựng trên kiến trúc đa lớp. Không có phương pháp đơn lẻ nào cung cấp đủ bảo vệ. Sự kết hợp của kiểm tra tĩnh và động, mã native và xác minh phía máy chủ mang lại khả năng chống chịu tối đa.
Đừng chỉ dựa vào kiểm tra phía máy khách. Gửi kết quả Root Detection đến máy chủ cùng với token phiên một lần. Máy chủ quyết định chặn hoặc hạn chế chức năng. Điều này ngăn chặn các cuộc tấn công ở cấp API, nơi ứng dụng khách có thể bị sửa đổi trong khi máy chủ vẫn là bên đáng tin cậy.
Mã Root Detection phải được làm rối. Nếu kẻ tấn công thấy một chuỗi kiểm tra đường dẫn su rõ ràng trong jadx, việc vượt qua sẽ mất vài phút. Sử dụng ProGuard hoặc DexGuard để làm rối luồng điều khiển và mã hóa chuỗi. Làm rối làm tăng thời gian phân tích mã bảo vệ từ vài phút lên vài giờ.
Danh sách đường dẫn, gói và chỉ báo được kiểm tra phải được cập nhật với mỗi phiên bản ứng dụng. Công cụ root và vượt qua mới xuất hiện hàng tháng. Danh sách tĩnh không thay đổi trong một năm sẽ không phát hiện được phương pháp hiện đại. Nên tải chữ ký hiện tại từ máy chủ khi khởi động ứng dụng trước khi thực hiện kiểm tra.
Câu hỏi thường gặp
Root Detection bảo vệ khỏi việc chạy ứng dụng trên thiết bị nơi hộp cát Android bị vô hiệu hóa. Trên thiết bị đã root, bất kỳ ứng dụng nào cũng có thể đọc dữ liệu của ứng dụng khác. Ứng dụng ngân hàng và thanh toán có nghĩa vụ chặn hoạt động trên thiết bị đã root theo yêu cầu PCI DSS và khuyến nghị OWASP Mobile Security.
Magisk Hide sử dụng cơ chế không gian tên gắn kết (mount namespace). Cho mỗi tiến trình trong danh sách loại trừ, Magisk tạo một không gian tên cách ly nơi nhị phân su vô hình. Các cuộc gọi hệ thống stat, access và open trong không gian tên này không thấy tệp Magisk. Có thể phát hiện Magisk bằng cách kiểm tra sự hiện diện của /proc/self/maps và tìm kiếm kết xuất magisk.
Có, nếu ứng dụng không xác minh tính toàn vẹn mã của nó. Qua Frida, có thể chặn phương thức kiểm tra Java và buộc nó trả về false. Biện pháp đối phó là triển khai native logic quan trọng bằng C++ và xác minh tính toàn vẹn qua băm tệp DEX. Không làm rối, bất kỳ Root Detection nào trên Java đều bị vượt qua trong 5–10 phút.
SafetyNet (lỗi thời) và Play Integrity API là các kiểm tra phía máy chủ từ Google xác nhận tính toàn vẹn của thiết bị. Chúng bao gồm kiểm tra bộ nạp khởi động, chữ ký hệ thống và trạng thái root. Play Integrity API là sự thay thế được khuyến nghị cho SafetyNet, cung cấp ba cấp độ: BASIC, DEVICE và STRONG. Root Detection phía máy khách bổ sung cho chứng thực phía máy chủ.
Cài đặt ứng dụng trên thiết bị đã root thực tế (ví dụ: Pixel với Magisk). Kiểm tra xem chặn có kích hoạt không. Sau đó thử ẩn root qua Magisk Hide cho ứng dụng của bạn và khởi động lại kiểm tra. Để kiểm tra chuyên sâu, sử dụng Frida để chặn các phương thức mục tiêu và đảm bảo rằng bảo vệ native không thể bị vượt qua.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm