安卓 day4 想吃蛋挞

@source: https://github.com/r0ysue/AndroidSecurityStudy/tree/master/FRIDA/B01

获取蓝牙设备#

enumerateLoadedClasses() 枚举已加载的类

setTimeout(function() {
    Java.perform(function() {
        console.log("\n[*] enumerating classes...");
        Java.enumerateLoadedClasses({
            onMatch: function(_className) {
                console.log("[*] found instance of '" + _className + "'");
            },
            onComplete: function() {
                console.log("[*] class enumeration complete");
            }
        });
    });
});

加个判断可以定位目标类。蓝牙相关类属于系统框架,被 zygote 预加载进进程内存,几乎所有 app 进程都能看到。

setTimeout(function() {
    Java.perform(function() {
        console.log("\n[*] enumerating classes...");
        Java.enumerateLoadedClasses({
            onMatch: function(instance) {
                if (instance.split(".")[1] == "bluetooth") {
                    console.log("[->]\t" + instance);
                }
            },
            onComplete: function() {
                console.log("[*] class enumeration complete");
            }
        });
    });
});

配合 Java.choose 可以拿到目标类实例。

setTimeout(function() {
    Java.perform(function() {
        const BluetoothDevice = Java.use("android.bluetooth.BluetoothDevice");
        
        function bluetoothDeviceInfo(anyObj) {
            try {
                // 如果不是 BluetoothDevice,先尝试 cast
                let dev = anyObj;
                if (anyObj.$className !== "android.bluetooth.BluetoothDevice") {
                    dev = Java.cast(anyObj, BluetoothDevice); // cast 失败会抛异常
            }

            console.log("    name=" + dev.getName() +
                        ", address=" + dev.getAddress() +
                        ", type=" + dev.getType());
            } catch (e) {
            console.log("    [!] not a BluetoothDevice / cast failed: " + anyObj.$className + " => " + e);
            }
        }
        
        console.log("\n[*] enumerating classes...");
        Java.enumerateLoadedClasses({
            onMatch: function(instance) {
                if (instance.split(".")[1] == "bluetooth") {
                    console.log("[->]\t" + instance);
                }
            },
            onComplete: function() {
                console.log("[*] class enumeration complete");
            }
        });
        
        console.log("[*] finding android.bluetooth.BluetoothDevice instance...");
        Java.choose("android.bluetooth.BluetoothDevice", {
            onMatch: function(instance) {
                console.log("[*] android.bluetooth,BluetoothDevice" + idx + " instance found" + " :=> '" + instance + "'");
                console.log("class=" + instance.$className);

                bluetoothDeviceInfo(instance);
            },
            onComplete: function() {
                console.log("[*] -----")
            }
        })
    });
});

对原文代码做了一些修改,但是本质差不多。以上脚本在安卓12下扫不到蓝牙设备,可能和注入时机有关(实例已经被gc),那么我们可以通过调用 BluetoothAdapter.getBondedDevices() 在进程中创建 BluetoothDevice 实例后马上开始扫。

setTimeout(function () {
    Java.perform(function () {
        const BluetoothDevice = Java.use("android.bluetooth.BluetoothDevice");
        const BluetoothAdapter = Java.use("android.bluetooth.BluetoothAdapter");

        function jtype(o) {
            try { return o.$className || (typeof o); } catch (_) { return typeof o; }
        }
        function bluetoothDeviceInfo(anyObj) {
            try {
                // 如果不是 BluetoothDevice,先尝试 cast
                let dev = anyObj;
                if (anyObj.$className !== "android.bluetooth.BluetoothDevice") {
                    dev = Java.cast(anyObj, BluetoothDevice); // cast 失败会抛异常
                }

                console.log("    name=" + dev.getName() +
                    ", address=" + dev.getAddress() +
                    ", type=" + dev.getType());
            } catch (e) {
                console.log("    [!] not a BluetoothDevice / cast failed: " + anyObj.$className + " => " + e);
            }
        }

        let adapter;
        try {
            adapter = BluetoothAdapter.getDefaultAdapter.overload().call(BluetoothAdapter);
            console.log("[*] adapter = " + adapter + " type=" + jtype(adapter));
        } catch (e) {
            console.log("[!] getDefaultAdapter failed: " + e);
            return;
        }

        if (adapter === null) {
            console.log("[!] No Bluetooth adapter");
            return;
        }
        let bonded;
        try {
            bonded = adapter.getBondedDevices.overload().call(adapter); // returns java.util.Set
            // console.log("[*] bonded = " + bonded + " type=" + jtype(bonded));
        } catch (e) {
            // console.log("[!] getBondedDevices failed: " + e);
            return;
        }

        console.log("[*] finding android.bluetooth.BluetoothDevice" + idx + " instance...");
        Java.choose("android.bluetooth.BluetoothDevice", {
            onMatch: function (instance) {
                console.log("[*] android.bluetooth,BluetoothDevice instance found" + " :=> '" + instance + "'");
                console.log("class=" + instance.$className);

                bluetoothDeviceInfo(instance);
            },
            onComplete: function () {
                console.log("[*] -----")
            }
        })
    });
});

直接 Java.choose 扫比较暴力,其实可以直接把拿到的 bonded 迭代出来。

Java.perform(function () {
  function jtype(o) {
    try { return o.$className || (typeof o); } catch (_) { return typeof o; }
  }
  function bluetoothDeviceInfo(anyObj) {
    try {
      // 如果不是 BluetoothDevice,先尝试 cast
      let dev = anyObj;
      if (anyObj.$className !== "android.bluetooth.BluetoothDevice") {
        dev = Java.cast(anyObj, BluetoothDevice); // cast 失败会抛异常
      }

      console.log("    name=" + dev.getName() +
        ", address=" + dev.getAddress() +
        ", type=" + dev.getType());
    } catch (e) {
      console.log("    [!] not a BluetoothDevice / cast failed: " + anyObj.$className + " => " + e);
    }
  }

  const BluetoothAdapter = Java.use("android.bluetooth.BluetoothAdapter");
  const BluetoothDevice = Java.use("android.bluetooth.BluetoothDevice");


  // 1) getDefaultAdapter (static)
  let adapter;
  try {
    adapter = BluetoothAdapter.getDefaultAdapter.overload().call(BluetoothAdapter);
    console.log("[*] adapter = " + adapter + " type=" + jtype(adapter));
  } catch (e) {
    console.log("[!] getDefaultAdapter failed: " + e);
    return;
  }

  if (adapter === null) {
    console.log("[!] No Bluetooth adapter");
    return;
  }

  // 2) getBondedDevices (instance)
  let bonded;
  try {
    bonded = adapter.getBondedDevices.overload().call(adapter); // returns java.util.Set
    console.log("[*] bonded = " + bonded + " type=" + jtype(bonded));
  } catch (e) {
    console.log("[!] getBondedDevices failed: " + e);
    return;
  }

  // 3) size()
  try {
    const n = bonded.size.overload().call(bonded);
    console.log("[*] bonded size = " + n);
  } catch (e) {
    console.log("[!] bonded.size() failed: " + e);
  }

  // 4) iterator + loop
  let it;
  try {
    it = bonded.iterator.overload().call(bonded);
    console.log("[*] iterator type=" + jtype(it));
  } catch (e) {
    console.log("[!] bonded.iterator() failed: " + e);
    return;
  }

  while (true) {
    let has;
    try {
      has = it.hasNext.overload().call(it);
    } catch (e) {
      console.log("[!] it.hasNext() failed: " + e);
      break;
    }
    if (!has) break;

    let dev;
    try {
      dev = it.next.overload().call(it);
      console.log("    dev=" + dev + " type=" + jtype(dev));
    } catch (e) {
      console.log("[!] it.next() failed: " + e);
      break;
    }

    // 5) getName/getAddress(同样强制 overload 调用)
    try {
      const name = BluetoothDevice.getName.call(dev);
      const addr = BluetoothDevice.getAddress.call(dev);
      console.log("    name=" + name + ", addr=" + addr);
    } catch (e) {
      console.log("    [!] getName/getAddress failed: " + e);
    }
    console.log("[*] finding android.bluetooth.BluetoothDevice instance...");
  }
});

非常多代码,使我的眼睛发炎。

利用反射 api 枚举类内所有方法#

Java.perform(function () {
    function enumMethods(targetClass) {
        var hook = Java.use(targetClass);
        var ownMethods = hook.class.getDeclaredMethods();
        hook.$dispose(); // 释放 wrapper 内部引用

        return ownMethods;
    }

    var a = enumMethods("android.bluetooth.BluetoothDevice");
    a.forEach(function (s) {
        console.log(s);
    });

})

.class 获取 Class 对象,.getDeclaredMthods() 获取所有声明的方法。