SystemHelper究竟能干什么,笔者将其从系统中提取出来后通过逆向来刨析它。

提取APK

接下来,我们需要将手机中的软件进行提供出来,方便我们进行逆向分析。
手机打开USB试调后,执行下面命令。

adb pull /product/privapp/SystemHelper/SystemHelper.apk ./SystemHelper.apk

这样我们便将手机中的SystemHelper提取为APK包。接下来我们进行逆向分析。

初步逆向

接下来,我们将APK进行初步的逆向分析。

jadx -d /tmp/jadx-out --no-res "/root/my like/11/SystemHelper.apk"

查看目录信息

ls -laR resources sources

文件比较多,进行整理如下:

采集了哪些敏感数据(字段清单)

control/DeviceInfo.java中定义了相关的字段。

大致上定义的字段有

采集字段代码常量敏感信息风险
IMEI1/2IMEI1/IMEI2设备唯一标识(永久追踪设备)高
IMSI1/2IMSI1/IMSI2国际移动用户标识(唯一锁定用户)高
ICCID1/2ICCID1/ICCID2SIM 卡序列号高
MSISDN1/2MSISDN1/MSISDN2手机号码极高
CellID1/2 + LAC1/2CellID1/LAC1…基站定位(可推算用户实时位置)极高
MNC1/2 + NCLS1/2MNC1/NCLS1…运营商 + 网络制式中
AppInfoAppInfo已安装应用列表+ 使用时长 + 逐应用流量高
Ipv4/Ipv6IPv4/IPv6公网 IP中
SignalInfoSignalInfo信号强度(可辅助定位)中

能干什么

01 敏感数据的采集

接下来,我们来看看采集代码,位于DeviceInfoReaderBase.java

// device/DeviceInfoReaderBase.java:238-296
void readAndroidR(Context context, DeviceInfo deviceInfo) {
    ...
    TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService("phone");
    deviceInfo.IMEI1 = telephonyManager.getImei(0);          // ← 采集 IMEI(卡1)
    deviceInfo.IMEI2 = telephonyManager.getImei(1);          // ← 采集 IMEI(卡2)
    int subId = getSubIdBySlotId(0);
    TelephonyManager tm = telephonyManager.createForSubscriptionId(subId);
    deviceInfo.IMSI1   = tm.getSubscriberId();               // ← 采集 IMSI
    deviceInfo.ICCID1  = tm.getSimSerialNumber();            // ← 采集 ICCID(SIM 序列号)
    deviceInfo.MSISDN1 = tm.getLine1Number();                // ← 采集手机号码
    deviceInfo.MNC1    = tm.getNetworkOperator();            // ← 采集运营商
    ...
    setCellLoc(context, 0, deviceInfo);                      // ← 采集基站位置
}

02 基站定位采集

// device/DeviceInfoReaderBase.java:175-235
protected synchronized void setCellLoc(Context context, int i, DeviceInfo deviceInfo) {
    ...
    ServiceState serviceState = tm.createForSubscriptionId(subId).getServiceState();
    for (NetworkRegistrationInfo nri : serviceState.getNetworkRegistrationInfoList()) {
        CellIdentity cellIdentity = nri.getCellIdentity();
        if (cellIdentity instanceof CellIdentityGsm) {
            strValueOf2 = String.valueOf(((CellIdentityGsm) cellIdentity).getCid());  // ← 基站 CellID
            strValueOf  = String.valueOf(((CellIdentityGsm) cellIdentity).getLac());  // ← 基站 LAC
        } else if (cellIdentity instanceof CellIdentityLte) {
            strValueOf2 = String.valueOf(((CellIdentityLte) cellIdentity).getCi());   // ← LTE 基站
            strValueOf3 = String.valueOf(((CellIdentityLte) cellIdentity).getTac());
        } else if (cellIdentity instanceof CellIdentityCdma) {
            // ← 甚至采集 CDMA 基站的经纬度
            strValueOf3 = String.format("%d:%d", cellIdentityCdma.getLongitude(), cellIdentityCdma.getLatitude());
        } // … CellIdentityWcdma / Tdscdma / Nr 同理
    }
    deviceInfo.CellID1 = strValueOf2;   // 写入采集结果
    deviceInfo.LAC1    = strValueOf3;
}

针对高通多卡平台,反射调用 android.telephony.MSimTelephonyManager

// device/PhoneInterfaceReflection.java:11, 70-72, 85-87
private static final String MSIMTELEPHONY_Manager = "android.telephony.MSimTelephonyManager";

public String getDeviceId(int i) {
    return (String) getMethod(MSIMTELEPHONY_Manager, "getDeviceId", Integer.TYPE)
                       .invoke(this.mManager, Integer.valueOf(i));   // ← 反射采集 IMEI
}
public String getLine1Number(int i) {
    return (String) getMethod(MSIMTELEPHONY_Manager, "getLine1Number", Integer.TYPE)
                       .invoke(this.mManager, Integer.valueOf(i));   // ← 反射采集手机号
}

03 应用列表 + 使用时长 + 流量采集

位于QueryAppInfoThread.java

// control/QueryAppInfoThread.java:74-87
private List<AppInfo> queryAppUsageStatsDescend() {
    List<ApplicationInfo> installedApplications = this.pm.getInstalledApplications(0);  // ← 全部已安装应用
    List<UsageStats> list = queryAppUsageStats();                                       // ← 使用统计
    ...
}
// control/QueryAppInfoThread.java:89-97
private List<UsageStats> queryAppUsageStats() {
    UsageStatsManager usm = (UsageStatsManager) this.mCtx.getSystemService("usagestats");
    return new ArrayList(usm.queryAndAggregateUsageStats(start, end).values());  // ← 每个应用前台使用时长
}
// control/QueryAppInfoThread.java:109
appInfo.setRunTime(usageStats.getTotalTimeInForeground() / 60000);  // ← 前台时长(分钟)

// control/QueryAppInfoThread.java:114-203 —— 逐应用流量统计
NetworkStatsManager nsm = (NetworkStatsManager) getSystemService("netstats");
networkStats = nsm.querySummary(0, this.mDeviceInfo.IMSI1, start, end);   // ← 按 IMSI 查询移动数据流量
rxBytes += bucket.getRxBytes();   // 每个应用的下行字节
txBytes += bucket.getTxBytes();   // 每个应用的上行字节
// 分别统计卡1/卡2 移动数据 + WiFi 流量

即:该应用能获知「你装了哪些应用、每个应用用了多久、每个应用消耗了多少流量」。

04 IP 采集

位于GetIPAddressThread.java

// control/GetIPAddressThread.java:50-54
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
Enumeration<InetAddress> inetAddresses = networkInterfaces.nextElement().getInetAddresses();
InetAddress inetAddress = inetAddresses.nextElement();

数据上报

服务器地址

匿名化处理

// device/DeviceManager.java:539-552 —— 非联通卡数据做 MD5 匿名化
if (deviceInfo.MNC1 != null && !isUnicomNetwork(deviceInfo.MNC1)) {
    deviceInfo.ICCID1  = MD5.getMD5Str(deviceInfo.ICCID1);   // 非联通卡 → MD5
    deviceInfo.MSISDN1 = MD5.getMD5Str(deviceInfo.MSISDN1);
    deviceInfo.IMSI1   = MD5.getMD5Str(deviceInfo.IMSI1);
    deviceInfo.CellID1 = MD5.getMD5Str(deviceInfo.CellID1);
    deviceInfo.LAC1    = MD5.getMD5Str(deviceInfo.LAC1);
}

联通卡(MNC 46001/46006/46009)的数据是明文采集上报的;其他运营商卡的数据会被 MD5 匿名化。但 MD5 对手机号/IMSI 这种有限空间(11 位号码 / 固定前缀)仍可被彩虹表反查。

厂商适配

内置 60+ 个 DeviceInfoReader*,针对不同手机厂商,通过 Java 反射(Class.forName + getMethod + invoke) 调用厂商私有/隐藏 API 来采集 IMEI、IMSI、ICCID、手机号、基站位置等。

关键代码片段

(1) MIUI 采集 IMSI/ICCID/手机号

// DeviceInfoReaderXiaomi.java:286-303(readQualcomm_QR 内)
Class<?> miuiTM = Utils.getClassByName("miui.telephony.TelephonyManager");        // ← MIUI 私有类
Object miui = Utils.invokeMethod(Utils.getMethodByName(miuiTM, "getDefault"), null);

deviceInfo.IMSI1   = (String) Utils.invokeMethod(
    Utils.getMethodByName(miuiTM, "getSubscriberIdForSubscription", Integer.TYPE), miui, subId);  // ← IMSI
deviceInfo.ICCID1  = (String) Utils.invokeMethod(
    Utils.getMethodByName(miuiTM, "getSimSerialNumberForSubscription", Integer.TYPE), miui, subId); // ← ICCID
deviceInfo.MSISDN1 = (String) Utils.invokeMethod(
    Utils.getMethodByName(miuiTM, "getLine1NumberForSubscription", Integer.TYPE), miui, subId);   // ← 手机号
deviceInfo.MNC1    = (String) Utils.invokeMethod(
    Utils.getMethodByName(miuiTM, "getSimOperatorForSubscription", Integer.TYPE), miui, subId);    // ← 运营商

(2)vivo DeviceInfoReaderVIVO.java
vivo 私有 API 调用链 —— 反射 getITelephony → vivoTelephonyApi**

// DeviceInfoReaderVIVO.java:348-380
public HashMap<String, Object> invokeTelephonyApi(Context context, String str, HashMap map) {
    TelephonyManager tm = (TelephonyManager) context.getSystemService("phone");
    // ① 反射获取隐藏的 getITelephony() 方法,拿到 ITelephony Binder 对象
    Method getITelephony = tm.getClass().getDeclaredMethod("getITelephony");
    getITelephony.setAccessible(true);                        // ← 绕过可见性限制
    Object iTelephony = getITelephony.invoke(tm);

    // ② 反射实例化 vivo 私有参数类
    Class<?> cls = Class.forName("com.android.internal.telephony.VivoTelephonyApiParams");
    Object params = cls.getConstructor(String.class).newInstance(str);   // str = API_TAG_xxx

    // ③ 把 slot/phoneId 参数塞进 VivoTelephonyApiParams
    Method put = cls.getMethod("put", String.class, Object.class);
    put.invoke(params, "slot", 0); ...

    // ④ 反射调用 vivo 自定义的 vivoTelephonyApi() 方法
    Object result = iTelephony.getClass()
        .getMethod("vivoTelephonyApi", cls).invoke(iTelephony, params);

    // ⑤ 从返回结果取参数值
    return (HashMap) result.getClass().getMethod("getParamValues").invoke(result);
}

总结

他究竟能干什么?将反编译后的源码交给AI进行分析。结论如下:

更多精彩文章 欢迎关注我们


本文首发于微信公众号,原文链接:逆向来看看SystemHelper干了什么?

最后修改:2026 年 11 月 08 日
可怜可怜吧!正在沿街乞讨~