解决Android模拟器中修改IMSI后无法上网问题

0x00 前言

百度百科中对IMSI的介绍如下:

国际移动用户识别码(IMSI:International Mobile Subscriber Identification Number)是区别移动用户的标志,储存在SIM卡中,可用于区别移动用户的有效信息。其总长度不超过15位,同样使用0~9的数字。其中MCC是移动用户所属国家代号,占3位数字,中国的MCC规定为460;MNC是移动网号码,由两位或者三位数字组成,中国移动的移动网络编码(MNC)为00;用于识别移动用户所归属的移动通信网;MSIN是移动用户识别码,用以识别某一移动通信网中的移动用户。

通过IMSI可以知道移动用户所在的国家。在Android中可以通过以下方法获取设备的IMSI号:

TelephonyManager telephonyManager= (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
String android_imsi = telephonyManager.getSubscriberId();

Android模拟器中默认使用的IMSI是:310260000000000。其中,310代表美国,260代表T-Mobile US。事实上,我们期望在模拟器上获取的IMSI应当以460开头(460代表中国)。

0x01 问题定位

但是,这串数字是硬编码在模拟器中的,路径是external/qemu/android/telephony/modem.c,只能通过修改模拟器源码来实现。

#define  OPERATOR_HOME_MCC   310
#define  OPERATOR_HOME_MNC   260
#define  OPERATOR_HOME_MCCMNC  STRINGIFY(OPERATOR_HOME_MCC) \
                               STRINGIFY(OPERATOR_HOME_MNC)
{ "+CIMI", OPERATOR_HOME_MCCMNC "0000000000", NULL },   /* request internation subscriber identification number */

将以上代码改为:

#define  OPERATOR_HOME_MCC   460 //中国
#define  OPERATOR_HOME_MNC   00  //移动
#define  OPERATOR_HOME_MCCMNC  STRINGIFY(OPERATOR_HOME_MCC) \
                               STRINGIFY(OPERATOR_HOME_MNC)
{ "+CIMI", OPERATOR_HOME_MCCMNC "00000000000", NULL }

重新编译,运行,使用getSubscriberId获取的值的确变成了我们期望的值:460000000000000 ,但是出现了新的问题:模拟器不能上网了。一番Google之后,发现也有别人遇到了这个问题,但是也没有找到好的解决方法。

于是,决定自己寻找原因,从TelephonyManager一路翻下去,最终到ril层,也没有看出问题出在哪儿。但是,直觉告诉我,问题应当出在APN上。

Android系统中APN的配置信息是在/system/etc/apns-conf.xml中。下面是模拟器中默认的APN配置。

<!-- use empty string to specify no proxy or port -->
<!-- This version must agree with that in apps/common/res/apns.xml -->
<apns version="8">
    <apn carrier="T-Mobile US"
         mcc="310"
         mnc="260"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
    />

    <apn carrier="T-Mobile US 250"
         mcc="310"
         mnc="250"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 660"
         mcc="310"
         mnc="660"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 230"
         mcc="310"
         mnc="230"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 310"
         mcc="310"
         mnc="310"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 580"
         mcc="310"
         mnc="580"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 240"
         mcc="310"
         mnc="240"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 800"
         mcc="310"
         mnc="800"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 210"
         mcc="310"
         mnc="210"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 160"
         mcc="310"
         mnc="160"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 270"
         mcc="310"
         mnc="270"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 200"
         mcc="310"
         mnc="200"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 220"
         mcc="310"
         mnc="220"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <apn carrier="T-Mobile US 490"
         mcc="310"
         mnc="490"
         apn="epc.tmobile.com"
         user="none"
         server="*"
         password="none"
         mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
            />

    <!-- T-Mobile Europe -->
    <apn carrier="T-Mobile UK"
         mcc="234"
         mnc="30"
         apn="general.t-mobile.uk"
         user="t-mobile"
         password="tm"
         server="*"
         mmsproxy="149.254.201.135"
         mmsport="8080"
         mmsc="http://mmsc.t-mobile.co.uk:8002"
    />

    <apn carrier="T-Mobile D"
         mcc="262"
         mnc="01"
         apn="internet.t-mobile"
         user="t-mobile"
         password="tm"
         server="*"
         mmsproxy="172.028.023.131"
         mmsport="8008"
         mmsc="http://mms.t-mobile.de/servlets/mms"
    />

    <apn carrier="T-Mobile A"
         mcc="232"
         mnc="03"
         apn="gprsinternet"
         user="t-mobile"
         password="tm"
         server="*"
         mmsproxy="010.012.000.020"
         mmsport="80"
         mmsc="http://mmsc.t-mobile.at/servlets/mms"
         type="default,supl"
    />

    <apn carrier="T-Mobile A MMS"
         mcc="232"
         mnc="03"
         apn="gprsmms"
         user="t-mobile"
         password="tm"
         server="*"
         mmsproxy="010.012.000.020"
         mmsport="80"
         mmsc="http://mmsc.t-mobile.at/servlets/mms"
         type="mms"
    />

    <apn carrier="T-Mobile CZ"
         mcc="230"
         mnc="01"
         apn="internet.t-mobile.cz"
         user="wap"
         password="wap"
         server="*"
         mmsproxy="010.000.000.010"
         mmsport="80"
         mmsc="http://mms"
         type="default,supl"
    />

    <apn carrier="T-Mobile CZ MMS"
         mcc="230"
         mnc="01"
         apn="mms.t-mobile.cz"
         user="mms"
         password="mms"
         server="*"
         mmsproxy="010.000.000.010"
         mmsport="80"
         mmsc="http://mms"
         type="mms"
    />

    <apn carrier="T-Mobile NL"
         mcc="204"
         mnc="16"
         apn="internet"
         user="*"
         password="*"
         server="*"
         mmsproxy="010.010.010.011"
         mmsport="8080"
         mmsc="http://t-mobilemms"
         type="default,supl"
    />

    <apn carrier="T-Mobile NL MMS"
         mcc="204"
         mnc="16"
         apn="mms"
         user="tmobilemms"
         password="tmobilemms"
         server="*"
         mmsproxy="010.010.010.011"
         mmsport="8080"
         mmsc="http://t-mobilemms"
         type="mms"
    />
</apns>

可以看出,这里是没有国内运营商的配置的。我从小米手机中提取出的国内运营商配置。

<apn carrier="中国移动 (China Mobile) GPRS"
    mcc="460"
    mnc="00"
    apn="cmnet"
    type="default,supl"
/>

<apn carrier="中国移动 (China Mobile) WAP"
    mcc="460"
    mnc="00"
    apn="cmwap"
    port="80"
    proxy="10.0.0.172"
    type="default,supl"
/>

<apn carrier="中国移动彩信 (China Mobile)"
    mcc="460"
    mnc="00"
    apn="cmwap"
    mmsc="http://mmsc.monternet.com"
    mmsport="80"
    mmsproxy="10.0.0.172"
    port="80"
    proxy="10.0.0.172"
    type="mms"
/>

<apn carrier="沃3G连接互联网 (China Unicom)"
    mcc="460"
    mnc="01"
    apn="3gnet"
    type="default,supl"
/>

<apn carrier="沃3G手机上网 (China Unicom)"
    mcc="460"
    mnc="01"
    apn="3gwap"
    port="80"
    proxy="10.0.0.172"
    type="default,supl"
/>

<apn carrier="联通彩信 (China Unicom)"
    mcc="460"
    mnc="01"
    apn="3gwap"
    mmsc="http://mmsc.myuni.com.cn"
    mmsport="80"
    mmsproxy="10.0.0.172"
    type="mms"
/>

<apn carrier="中国移动 (China Mobile) GPRS"
    mcc="460"
    mnc="02"
    apn="cmnet"
    type="default,supl"
/>

<apn carrier="中国移动 (China Mobile) WAP"
    mcc="460"
    mnc="02"
    apn="cmwap"
    port="80"
    proxy="10.0.0.172"
    type="default,supl"
/>

<apn carrier="中国移动彩信 (China Mobile)"
    mcc="460"
    mnc="02"
    apn="cmwap"
    mmsc="http://mmsc.monternet.com"
    mmsport="80"
    mmsproxy="10.0.0.172"
    port="80"
    proxy="10.0.0.172"
    type="mms"
/>

添加到apns-conf.xml中,重启模拟器,能识别出“中国移动”了,但依然不能上网。

此时,我想到通过对比默认情况和修改后的radio日志,来分析原因。

使用

adb logcat -b radio

命令可以查查看ril相关的日志。接着,就发现了如下两条日志:

D/DCT ( 1739): [0]createAllApnList: selection=numeric = ‘460000’ D/DCT ( 1739): [0]createAllApnList: No APN found for carrier: 460000

注意到这里的460000,正确的应该是46000,多了一个0。由于美国的MNC是3个字符,而中国的MNC是2个字符,所以导致这里多了一个字符。来看下源码在这里是怎么实现的。

/frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DcTracker.java

/**
 * Based on the sim operator numeric, create a list for all possible
 * Data Connections and setup the preferredApn.
 */
private void createAllApnList() {
    mAllApnSettings = new ArrayList<ApnSetting>();
    IccRecords r = mIccRecords.get();
    String operator = (r != null) ? r.getOperatorNumeric() : "";
    if (operator != null) {
        String selection = "numeric = '" + operator + "'";
        // query only enabled apn.
        // carrier_enabled : 1 means enabled apn, 0 disabled apn.
        // selection += " and carrier_enabled = 1";
        if (DBG) log("createAllApnList: selection=" + selection);

        Cursor cursor = mPhone.getContext().getContentResolver().query(
                Telephony.Carriers.CONTENT_URI, null, selection, null, null);

        if (cursor != null) {
            if (cursor.getCount() > 0) {
                mAllApnSettings = createApnList(cursor);
            }
            cursor.close();
        }
    }

    addEmergencyApnSetting();

    dedupeApnSettings();

    if (mAllApnSettings.isEmpty()) {
        if (DBG) log("createAllApnList: No APN found for carrier: " + operator);
        mPreferredApn = null;
        // TODO: What is the right behavior?
        //notifyNoData(DataConnection.FailCause.MISSING_UNKNOWN_APN);
    } else {
        mPreferredApn = getPreferredApn();
        if (mPreferredApn != null && !mPreferredApn.numeric.equals(operator)) {
            mPreferredApn = null;
            setPreferredApn(-1);
        }
        if (DBG) log("createAllApnList: mPreferredApn=" + mPreferredApn);
    }
    if (DBG) log("createAllApnList: X mAllApnSettings=" + mAllApnSettings);

    setDataProfilesAsNeeded();
}

getOperatorNumeric的实现是在/frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/SIMRecords.java中。

@Override
public String getOperatorNumeric() {
    if (mImsi == null) {
        log("getOperatorNumeric: IMSI == null");
        return null;
    }
    if (mMncLength == UNINITIALIZED || mMncLength == UNKNOWN) {
        log("getSIMOperatorNumeric: bad mncLength");
        return null;
    }

    // Length = length of MCC + length of MNC
    // length of mcc = 3 (TS 23.003 Section 2.2)
    return mImsi.substring(0, 3 + mMncLength);
}

可见,numeric的长度是受mMncLength控制的,而mMncLength的值是从SIM卡中读出来的。

case EVENT_GET_AD_DONE:
    try {
        isRecordLoadResponse = true;

        ar = (AsyncResult)msg.obj;
        data = (byte[])ar.result;

        if (ar.exception != null) {
            break;
        }

        log("EF_AD: " + IccUtils.bytesToHexString(data));

        if (data.length < 3) {
            log("Corrupt AD data on SIM");
            break;
        }

        if (data.length == 3) {
            log("MNC length not present in EF_AD");
            break;
        }

        mMncLength = data[3] & 0xf;
        log("setting4 mMncLength=" + mMncLength);

        if (mMncLength == 0xf) {
            mMncLength = UNKNOWN;
            log("setting5 mMncLength=" + mMncLength);
        }
    }

最终定位到模拟器中的external/qemu/android/telephony/sim_card.c中。

    { "+CRSM=192,28589,0,0,15", "+CRSM: 144,0,000000046fad04000aa0aa01020000" },
    { "+CRSM=176,28589,0,0,4",  "+CRSM: 144,0,00000003" },

00000003改成00000002,重新编译,运行,终于可以正常上网了。

0x02 解决更新问题

此时,对于新创建的模拟器已经正常了,但是对于存量模拟器,由于telephony.db数据库中的carriers表中的数据没有更新,因此重启后还是不能上网。

查看carriers表中的内容可以在adb shell中执行命令: content query --uri content://telephony/carriers

/packages/providers/TelephonyProvider/src/com/android/providers/telephony/TelephonyProvider.java

@Override
public void onCreate(SQLiteDatabase db) {
    if (DBG) log("dbh.onCreate:+ db=" + db);
    createSimInfoTable(db);
    createCarriersTable(db);
    initDatabase(db);
    if (DBG) log("dbh.onCreate:- db=" + db);
}

private void initDatabase(SQLiteDatabase db) {
    if (VDBG) log("dbh.initDatabase:+ db=" + db);
    // Read internal APNS data
    Resources r = mContext.getResources();
    XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
    int publicversion = -1;
    try {
        XmlUtils.beginDocument(parser, "apns");
        publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
        loadApns(db, parser);
    } catch (Exception e) {
        loge("Got exception while loading APN database." + e);
    } finally {
        parser.close();
    }

    // Read external APNS data (partner-provided)
    XmlPullParser confparser = null;
    // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
    File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);
    File oemConfFile =  new File(Environment.getOemDirectory(), OEM_APNS_PATH);
    if (oemConfFile.exists()) {
        // OEM image exist APN xml, get the timestamp from OEM & System image for comparison
        long oemApnTime = oemConfFile.lastModified();
        long sysApnTime = confFile.lastModified();
        if (DBG) log("APNs Timestamp: oemTime = " + oemApnTime + " sysTime = "
                + sysApnTime);

        // To get the latest version from OEM or System image
        if (oemApnTime > sysApnTime) {
            if (DBG) log("APNs Timestamp: OEM image is greater than System image");
            confFile = oemConfFile;
        }
    } else {
        // No Apn in OEM image, so load it from system image.
        if (DBG) log("No APNs in OEM image = " + oemConfFile.getPath() +
                " Load APNs from system image");
    }

    FileReader confreader = null;
    if (DBG) log("confFile = " + confFile);
    try {
        confreader = new FileReader(confFile);
        confparser = Xml.newPullParser();
        confparser.setInput(confreader);
        XmlUtils.beginDocument(confparser, "apns");

        // Sanity check. Force internal version and confidential versions to agree
        int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
        if (publicversion != confversion) {
            throw new IllegalStateException("Internal APNS file version doesn't match "
                    + confFile.getAbsolutePath());
        }

        loadApns(db, confparser);
    } catch (FileNotFoundException e) {
        // It's ok if the file isn't found. It means there isn't a confidential file
        // Log.e(TAG, "File not found: '" + confFile.getAbsolutePath() + "'");
    } catch (Exception e) {
        loge("Exception while parsing '" + confFile.getAbsolutePath() + "'" + e);
    } finally {
        try { if (confreader != null) confreader.close(); } catch (IOException e) { }
    }
    if (VDBG) log("dbh.initDatabase:- db=" + db);

}

private DatabaseHelper mOpenHelper;

private void restoreDefaultAPN(int subId) {
    SQLiteDatabase db = mOpenHelper.getWritableDatabase();

    try {
        db.delete(CARRIERS_TABLE, null, null);
    } catch (SQLException e) {
        loge("got exception when deleting to restore: " + e);
    }
    setPreferredApnId((long)-1, subId);
    mOpenHelper.initDatabase(db);
}

可以看出,在restoreDefaultAPN的时候可以重新初始化CARRIERS_TABLE。也就说,只要进入APN界面,点击右上角菜单 => 重置为默认设置,就可以解决存量设备的上网问题了。

0x03 解决方法总结

  1. 修改模拟器源码modem.c中的MCC和MNC
  2. 修改模拟器源码sim_card.c中控制mMncLength的值
  3. 修改Android镜像中的/system/etc/apns-conf.xml,添加国内运营商的配置信息
  4. 存量设备更新APN

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ChaMd5安全团队

N1CTF2018 APFS&Lipstick题解

APFS题目描述 Apple released the brand new APFS on WWDC 2017 with a bunch of new feat...

34510
来自专栏SAP最佳业务实践

SAP S/4HANA最佳业务实践:Order-to-Cash订单到收款-3合同处理

•The tile Manage Sales Contracts is part of the business catalog Sales –Contract...

3749
来自专栏编程之旅

iOS开发 —— Swift版地址选择器

已经有二十多天没有更新自己的博客了,这段时间经历了很多事情,离开了生活了六七年的杭州,从离职再入职,忙的是一塌糊涂。

1112
来自专栏SAP最佳业务实践

SAP最佳业务实践:MM–组件收费的委外加工(251)-6开销售发票

4.7 创建出具发票凭证 创建出具发票凭证给委外加工商。 完成了对委外加工商的发货。 SAP ECC菜单Processes -Create Invoices f...

3908
来自专栏小灰灰

Spring之定时任务基本使用篇

文章链接:https://liuyueyi.github.io/hexblog/2018/08/01/180801-Spring之定时任务基本使用篇/

1511
来自专栏字根中文校对软件

Java 错别字检查接口 API

Java 错别字检查接口 API 为了方便广大程序员朋友快速把错别字检查功能集成到自己的系统中,我们开发了一个支持HTTP协议的 Java 错别字检查接口 AP...

4015
来自专栏杨建荣的学习笔记

dg的奇怪问题终结和分区问题答疑 (r7笔记第77天)

今天来说几个问题,一个是对昨天《让我焦灼的四个问题》的升华,不能起博眼球的题目,技术分析给大家兜底了,你们看看有没有类似的问题。 还有几个小问题说说今天的感受和...

3485
来自专栏PHP在线

php中关于mysqli和mysql区别的一些知识点分析

一: PHP-MySQL 是 PHP 操作 MySQL 资料库最原始的 Extension ,PHP-MySQLi 的 i 代表 Improvement ,...

28910
来自专栏转载gongluck的CSDN博客

波形音频(WAVE)底层接口的学习与使用

在WINDOWS下,音频函数有多种类型,如MCI、多媒体OLE控制、高级音频等,使用方法都比较简单。 但如果想编写一个功能较强大的音频处理程序,那...

1.2K5
来自专栏Android 开发学习

音频开发ijkplayer小结 android

2262

扫码关注云+社区

领取腾讯云代金券