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/2 | IMEI1/IMEI2 | 设备唯一标识(永久追踪设备) | 高 |
| IMSI1/2 | IMSI1/IMSI2 | 国际移动用户标识(唯一锁定用户) | 高 |
| ICCID1/2 | ICCID1/ICCID2 | SIM 卡序列号 | 高 |
| MSISDN1/2 | MSISDN1/MSISDN2 | 手机号码 | 极高 |
| CellID1/2 + LAC1/2 | CellID1/LAC1… | 基站定位(可推算用户实时位置) | 极高 |
| MNC1/2 + NCLS1/2 | MNC1/NCLS1… | 运营商 + 网络制式 | 中 |
| AppInfo | AppInfo | 已安装应用列表+ 使用时长 + 逐应用流量 | 高 |
| Ipv4/Ipv6 | IPv4/IPv6 | 公网 IP | 中 |
| SignalInfo | SignalInfo | 信号强度(可辅助定位) | 中 |
能干什么
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干了什么?
2 条评论
从提取APK到分析DeviceInfo字段和反射调用,步骤讲得很清楚,对理解多卡信息采集流程挺有帮助。
谢谢!是啊,不知不觉中信息就这样泄露了。