Phân Tích Cơ Chế Tự Động Loại Bỏ Quảng Cáo Trên Android: Dịch Ngược, Gỡ Lớp Bảo Vệ Và Triển Khai Dịch Vụ Trợ Năng

Cấu Hình Dịch Vụ Và Tổng Quan Hoạt Động

Ứng dụng hoạt động bằng cách yêu cầu người dùng cấp quyền Accessibility Service (Dịch vụ Trợ năng) trong cài đặt hệ thống Android. Sau khi kích hoạt, tiến trình nền sẽ liên tục lắng nghe các sự kiện thay đổi cửa sổ giao diện. Khi phát hiện các thành phần UI chứa văn bản đặc trưng như "Bỏ qua" hoặc "Nhảy" trên màn hình khởi động của ứng dụng bên thứ ba, dịch vụ sẽ tự động mô phỏng thao tác chạm để tắt quảng cáo. Quy trình thiết lập chỉ bao gồm bước cài đặt gói APK, bật công tắc trong mục Trợ năng và đảm bảo ứng dụng không bị hệ thống dừng nền.

Phân Tích Cấu Trúc Tệp Và Nhận Diện Trình Bảo Vệ

Khi tiến hành giải nén và dịch ngược tệp APK bằng apktool kết hợp với công cụ phân tích mã nguồn, cấu trúc tài nguyên trong thư mục res thường bị xáo trộn hoặc mã hóa. Mã nguồn chính thường bị che khuất do kỹ thuật đóng gói (packing/shielding). Dấu hiệu nhận diện nhanh nhất nằm ở lớp Application: thay vì chứa logic nghiệp vụ, nó chỉ đơn thuần kế thừa từ một lớp trừu tượng trung gian và thực hiện tải động các thư viện C++ từ thư mục lib.

Để xác định chính xác công nghệ bảo mật được tích hợp, có thể kiểm tra ba đặc trưng sau:

  • Tệp tencent_stub xuất hiện trực tiếp tại thư mục gốc.
  • Thư mục assets chứa các tệp định danh như tosversion, 0OO00l111l1l hoặc o0oooOO0ooOo.dat.
  • Thư mục lib chứa các tệp chia sẻ có tiền tố libshell- kèm theo phiên bản.

Các tệp này khẳng định ứng dụng đã được tích hợp Tencent Mobile Manager. Việc áp dụng lớp bảo vệ này nhằm mục đích làm nhiễu đặc trưng mã nguồn, tránh bị các kho phân phối hoặc trình quét hệ thống gắn nhãn cảnh báo bảo mật.

Quy Trình Giải Đóng Gói (Unpacking) Với Frida

Các phương pháp giải nén tĩnh thường thất bại khi tái cấu trúc APK do checksum hoặc liên kết động bị phá vỡ. Giải pháp động sử dụng Frida cho phép trích xuất trực tiếp mã đã được giải mã trong bộ nhớ RAM.

Thiết lập môi trường: Cài đặt frida-toolsfrida-dexdump trên máy chủ phát triển. Đẩy binary frida-server phù hợp với kiến trúc CPU của thiết bị (thường là arm64-v8a). Tạm thời vô hiệu hóa SELinux (setenforce 0) và thiết lập chuyển tiếp cổng ADB để client kết nối thành công.

Thực thi trích xuất: Khởi chạy ứng dụng mục tiêu trên thiết bị, sau đó thực thi lệnh dump từ máy tính:

frida-dexdump -FU

Tham số -F nhắm vào tiến trình đang chạy foreground, -U kết nối qua USB. Công cụ sẽ quét không gian nhớ heap, định vị các cấu trúc DEX đã được giải mã và lưu chúng ra tệp. So sánh kích thước và checksum với tệp gốc, người phân tích sẽ thu được phiên bản mã nguồn sạch.

Tái Tạo Gói Cài Đặt Và Cấu Hình Dịch Vụ

Sau khi thu được tệp DEX sạch, sử dụng baksmali để chuyển đổi sang mã Smali. Thay thế toàn bộ thư mục smali trong cấu trúc giải nén ban đầu bằng mã mới. Trong tệp AndroidManifest.xml, cần xác định entry point thực tế (thường nằm trong package chứa logic nghiệp vụ chính), sau đó cập nhật thuộc tính android:name của thẻ <application>. Đồng thời, xóa bỏ toàn bộ các tệp rác liên quan đến Tencent (thư viện .so chứa từ khóa shell, các tệp định danh trong assets). Biên dịch lại bằng apktool và thực hiện ký lại gói.

Cơ Chế Hoạt Động Của Dịch Vụ Trợ Năng

Logic cốt lõi nằm ở việc duyệt cây phân cấp View (View Hierarchy) để định vị nút điều khiển. Dưới đây là minh họa cho luồng xử lý sự kiện giao diện:

public class AutoSkipService extends AccessibilityService {
    private static final Set<String> SKIP_KEYWORDS = Set.of("Bỏ qua", "Nhảy", "Đóng", "Tiếp tục");
    private static final List<String> SYSTEM_IGNORE = List.of("com.android.launcher", "android");

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        CharSequence currentPkg = event.getPackageName();
        if (currentPkg == null || SYSTEM_IGNORE.contains(currentPkg.toString())) return;

        AccessibilityNodeInfo rootNode = event.getSource();
        if (rootNode == null) return;

        processNodeTree(rootNode);
    }

    private void processNodeTree(AccessibilityNodeInfo parent) {
        if (parent == null) return;

        for (int idx = 0; idx < parent.getChildCount(); idx++) {
            AccessibilityNodeInfo child = parent.getChild(idx);
            if (child == null) continue;

            if (child.getText() != null && SKIP_KEYWORDS.contains(child.getText().toString().trim())) {
                boolean success = child.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                if (success) Log.i("SkipService", "Thao tác thành công: " + child.getText());
            }
            
            if (child.getChildCount() > 0) {
                processNodeTree(child);
            }
        }
    }

    @Override
    public void onInterrupt() {}
}

Tệp kê khai yêu cầu cấp quyền ràng buộc dịch vụ trợ năng:

<service
    android:name=".AutoSkipService"
    android:exported="false"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
    </intent-filter>
    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/accessibility_profile" />
</service>

Cấu hình hành vi dịch vụ (accessibility_profile.xml):

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:canRetrieveWindowContent="true"
    android:canPerformGestures="true"
    android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:description="@string/svc_desc"
    android:notificationTimeout="100" />

Đối với các popup xuất hiện ngẫu nhiên trong ứng dụng, có thể áp dụng bộ lọc quy tắc dựa trên tiền tố và hậu tố văn bản:

public class PopupFilter {
    private final String prefixPattern;
    private final String suffixPattern;
    private final String triggerAction;

    public PopupFilter(String startTag, String endTag, String action) {
        this.prefixPattern = startTag;
        this.suffixPattern = endTag;
        this.triggerAction = action;
    }

    public boolean matches(CharSequence input) {
        if (input == null) return false;
        String normalized = input.toString().trim();
        return normalized.startsWith(prefixPattern) &&
               normalized.endsWith(suffixPattern) &&
               normalized.contains(triggerAction);
    }
}

Chiến Lược Duy Trì Tiến Trình Nền

Để ngăn hệ điều hành Android dọn dẹp bộ nhớ hoặc áp dụng giới hạn quản lý năng lượng (Doze/App Standby), các kỹ thuật sau thường được kết hợp:

  • Chuyển đổi sang ForegroundService kèm thông báo cố định để ưu tiên lịch trình CPU.
  • Yêu cầu quyền REQUEST_IGNORE_BATTERY_OPTIMIZATIONS để bỏ qua các ràng buộc tiết kiệm pin.
  • Đăng ký BOOT_COMPLETEDPACKAGE_REPLACED broadcast receiver để tự động khôi phục trạng thái dịch vụ sau khi khởi động hoặc cập nhật hệ thống.

Thẻ: android-accessibility-service frida-dexdump apk-reverse-engineering tencent-protector android-foreground-service

Đăng vào ngày 22 tháng 9 lúc 14:45