Hướng dẫn sử dụng Unidbg gọi hàm Native từ file SO của ứng dụng Android

Khi tiến hành kỹ thuật đảo ngược (reverse engineering) một ứng dụng Android, chúng ta thường gặp phải các logic mã hóa quan trọng nằm trong các tệp tin thư viện liên kết động (.so). Unidbg là một công cụ mạnh mẽ cho phép các nhà phát triển chạy các thư viện native này trực tiếp trên máy tính mà không cần thiết bị Android thật hay trình giả lập cồng kềnh.

1. Thiết lập môi trường và khởi tạo Emulator

Để bắt đầu, chúng ta cần tạo một lớp Java kế thừa từ AbstractJni để giả lập môi trường JNI. Các bước cơ bản bao gồm cấu hình kiến trúc CPU (32-bit hoặc 64-bit), nạp tệp APK và thư viện .so tương ứng.

package com.tech.unidbg.demo;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;
import java.io.File;

public class NativeLibInvoker extends AbstractJni {
    private final AndroidEmulator deviceEmulator;
    private final VM dalvikVM;
    private final Module targetModule;

    public NativeLibInvoker() {
        // Khởi tạo giả lập cho kiến trúc 32-bit (thường nằm trong thư mục armeabi-v7a)
        deviceEmulator = AndroidEmulatorBuilder.for32Bit()
                .setProcessName("com.example.app")
                .build();

        // Quản lý bộ nhớ và thiết lập phiên bản Android SDK (hỗ trợ tốt nhất API 19 và 23)
        Memory ram = deviceEmulator.getMemory();
        ram.setLibraryResolver(new AndroidResolver(23));

        // Khởi tạo máy ảo Dalvik từ tệp APK
        dalvikVM = deviceEmulator.createDalvikVM(new File("path/to/your/app.apk"));
        dalvikVM.setJni(this);
        // dalvikVM.setVerbose(true); // Bật log chi tiết nếu cần debug

        // Nạp thư viện SO vào bộ nhớ
        DalvikModule dm = dalvikVM.loadLibrary(new File("path/to/libnative-lib.so"), false);
        targetModule = dm.getModule();
    }
}

2. Gọi hàm Native từ Java

Sau khi môi trường đã sẵn sàng, bước tiếp theo là xác định lớp Java chứa phương thức native và ký hiệu (signature) của hàm đó để thực thi.

public void fetchSecretKey() {
    // Trỏ tới class Java chứa khai báo hàm native
    DvmClass checkUtil = dalvikVM.resolveClass("com/autohome/ahkit/jni/CheckSignUtil");

    // Định nghĩa signature của hàm: get3desKey nhận vào Context và trả về String
    String methodSignature = "get3desKey(Landroid/content/Context;)Ljava/lang/String;";

    // Thực thi hàm static JNI
    // Truyền tham số Context giả lập (null) vào hàm
    StringObject result = checkUtil.callStaticJniMethodObject(
            deviceEmulator,
            methodSignature,
            dalvikVM.resolveClass("android/content/Context").newObject(null)
    );

    // Trích xuất giá trị chuỗi trả về từ đối tượng StringObject
    String decryptedKey = result.getValue();
    System.out.println("Kết quả trả về từ Native: " + decryptedKey);
}

public static void main(String[] args) {
    NativeLibInvoker invoker = new NativeLibInvoker();
    invoker.fetchSecretKey();
}

3. Các lưu ý quan trọng

  • Kiến trúc CPU: Đảm bảo lựa chọn đúng for32Bit() hoặc for64Bit() tùy thuộc vào tệp .so bạn đang xử lý.
  • Signature của hàm: Bạn có thể tìm signature chính xác thông qua công cụ như JADX hoặc sử dụng Frida để hook và xem tham số truyền vào.
  • Xử lý Context: Trong nhiều trường hợp, hàm native yêu cầu một đối tượng Context thực sự để kiểm tra chữ ký ứng dụng. Nếu gặp lỗi, bạn cần bổ sung các hàm giả lập JNI trong lớp kế thừa AbstractJni để trả về các giá trị mà thư viện yêu cầu (như PackageName hoặc Signature).

Thẻ: Unidbg android-reverse-engineering JNI native-library Java

Đăng vào ngày 5 tháng 8 lúc 19:15