搜索

前段时间将系统语言切换至英文后,发现常用的天气云图无法打开,气象预警也不显示,如果通过xposed模块单独将天气调至中文显得十分割裂,正好借此机会学一下安卓逆向

工具

手机上可以使用MT管理器对apk进行解包编辑,并且使用auto.js的悬浮窗调试工具对程序布局进行解析

之前已经装过MagiskHide Props Config,因此直接用它设置系统ro.debuggable打开全局调试,再使用DDMS读取程序的log、跟踪函数调用栈

电脑上则使用了dex-tools(dex2jar)、以及cfr对程序进行反编译,使用vscode在java中定位到问题后再修改smali代码。

过程

无法打开云图

由动态调试代码,对比英文状态下与中文下不同

英文下

07-31 10:52:24.618: D/Wth2:MajesticCloud(19455): afterFrictionValue 0.0
07-31 10:52:24.622: D/Wth2:WeatherScrollView(19455): mDailyForecastView bottom canSee:true
07-31 10:52:24.734: D/Wth2:MajesticWeather(19455): go_touch_move: false
07-31 10:52:24.734: D/Wth2:MajesticWeather(19455): go_touch_move: com.miui.weather2.majestic.detail.MajesticBackSunny@ba7ad23
07-31 10:52:24.735: D/Wth2:MajesticCloud(19455): startTouchAnim: 
07-31 10:52:24.735: D/Wth2:MajesticCloud(19455): startTouchAnim
07-31 10:52:24.735: D/Wth2:MajesticCloud(19455): touch up is -1400.0
07-31 10:52:24.761: D/Wth2:MajesticCloud(19455): afterFrictionValue 0.0
07-31 10:52:24.781: D/Wth2:MajesticCloud(19455): afterFrictionValue 0.0

中文下

07-31 01:28:55.907: D/Wth2:MajesticCloud(29817): afterFrictionValue 0.0
07-31 01:28:55.909: D/Wth2:WeatherScrollView(29817): mDailyForecastView bottom canSee:true
07-31 01:28:55.962: D/Wth2:MajesticWeather(29817): go_touch_move: false
07-31 01:28:55.962: D/Wth2:MajesticWeather(29817): go_touch_move: com.miui.weather2.majestic.detail.MajesticBackNightSunny@d3e8e9a
07-31 01:28:55.962: D/Wth2:MajesticCloud(29817): startTouchAnim: 
07-31 01:28:55.962: D/Wth2:MajesticCloud(29817): startTouchAnim
07-31 01:28:55.962: D/Wth2:MajesticCloud(29817): touch up is -1400.0
07-31 01:28:55.964: D/Wth2:Navigator(29817): gotoActivityMinuteRain
07-31 01:28:55.966: I/Timeline(29817): Timeline: Activity_launch_request time:817503
07-31 01:28:55.983: D/Wth2:AnalyzeTransferManager(29817): canAnalyzeByKey event: minute_rain_click, result: true
...
07-31 01:28:55.993: D/Wth2:MajesticCloud(29817): afterFrictionValue 0.0

可以观察到从Navigator(29817): gotoActivityMinuteRain开始产生了不同,定位到log gotoActivityMinuteRain的函数,并动态调试记录其调用栈,定位到com.miui.weather2.view.onOnePage.WeatherAqiMinuteView.c(View view),找到问题


public /* synthetic */ void c(View view) {
    if (this.x != null && c1.t(this.getContext())) {
        o0.a(this.getContext(), this.x, -1, false, 1);
        r0.a("minute_rain_click");  // 该处log能对应到中文状态下log的不同
    }
}
// com.miui.weather2.tools.c1
public static boolean t(Context context) {
    boolean bl = !Build.IS_INTERNATIONAL_BUILD && c1.r(context);
    return bl;
}
public static boolean r(Context context) {
    return c1.f(context).equals(Locale.SIMPLIFIED_CHINESE);
}

因此只要在mt管理器中将该处条件跳转去除即可

invoke-static {p1}, Lcom/miui/weather2/tools/c1;->t(Landroid/content/Context;)Z

move-result p1

if-nez p1, :cond_e

# goto :cond_1f

.line 2
:cond_e
invoke-virtual {p0}, Landroid/view/ViewGroup;->getContext()Landroid/content/Context;
IMG_20220731_111921

云图中提示空白

虽然强行打开了云图,但是提示文字显示为空白。这里看日志没能找到不同,只能老老实实看代码定位问题

观察代码分析出com.miui.weather2.w.f$c中函数MinuteRainData a(Context object, CityData object2)获取了CityDataLocale,并且作为参数传入至com.miui.weather2.a0.a.a函数中请求数据

private MinuteRainData a(Context object, CityData object2) {
    if (this.a) {
        WeatherData weatherData = i.a(object, (CityData)object2);
        j1.a(object, weatherData, false, ((CityData)object2).isFirstCity());
        object = weatherData != null ? weatherData.getMinuteRainData() : null;
    } else {
        String string2 = this.e; // 实为Context类,反编译错误
        String string3 = this.d;
        String string4 = ((CityData)object2).getLocale();
        String string5 = ((CityData)object2).getExtra();
        object2 = ((CityDataLight)object2).isLocationCity() ? "true" : "false";
        // 获取的各个string参数请求天气数据
        // com.miui.weather2.a0.a.a中有Log信息为getAllWeatherJson
            object = com.miui.weather2.z.c.a(com.miui.weather2.a0.a.a(string2, string3, string4, string5, object, (String)object2), System.currentTimeMillis(), object);
    }
    return object;
}

猜测是英文Locale下返回的数据中包含的提示信息为空,这里改用中文代替
因此尝试将该处string4修改为常量zh_cn,至于为什么不是zh-cn等值是因为CityData底下用到了这个常量(

invoke-virtual {p2}, Lcom/miui/weather2/structures/CityData;->getLocale()Ljava/lang/String;

move-result-object v2

const-string v2, "zh_cn"

经过修改,已经能正确显示提示信息了

Screenshot_2022-07-31-11-26-20-205_com.miui.weather2

预警卡片不显示

同样不好通过log定位问题,继续读代码(

定位到com.miui.weather2.tools.j1.a(String var0, String var1_4, Context var2_8)中,有一个c1.r对设备Locale进行了判断,直接将返回值设为1

public static ArrayList<Alert> a(String var0, String var1_4, Context var2_8) {
    block15: {
        block16: {
            block14: {
                block17: {
                    var3_9 = c1.r(var2_8);
                    var4_10 = null;
                    var5_11 = null;
                    if (!var3_9) {
                        return null;
                    }
.line 293
invoke-static {p2}, Lcom/miui/weather2/tools/c1;->r(Landroid/content/Context;)Z

move-result v1

const/4 v1, 0x1
Screenshot_2022-07-31-11-42-59-319_com.miui.weather2

可以看到气象预警的框框已经能够正常显示,图标也正常,但是标题不显示

预警卡片标题

被迫学会打log才找到这个问题,中间甚至抓了几次包对比。抓包可以看出。response中并没有诸如”暴雨蓝色预警:南京气象局提醒...“的字样,可以猜测该标题为在客户端中拼接,其中中文冒号“:”出现在代码中都是log部分。如果熟悉安卓开发,应该直接意识到是在资源文件中的格式化字符串拼接的了,可惜不熟悉花了不少时间。

com.miui.weather.view.onOnePage.VerticalCarousel.a(Alert alert)中,使用了Context.getString()获取xml资源文件中的字符串,并通过其格式化。而该资源与Locale绑定,因此切换英文后会返回Null。将该函数改为使用字符串拼接获取输出内容即可,直接用smali写一个StringBuilder拼接即可

.method private a(Lcom/miui/weather2/structures/Alert;)Ljava/lang/String;
    .registers 7

    .line 22
    invoke-virtual {p1}, Lcom/miui/weather2/structures/Alert;->getType()Ljava/lang/String;

    move-result-object v0

    .line 23
    invoke-virtual {p1}, Lcom/miui/weather2/structures/Alert;->getLevel()Ljava/lang/String;

    move-result-object v1

    .line 24
    const-string v2, "\u9884\u8b66\uff1a"

    .line 25

    invoke-virtual {p1}, Lcom/miui/weather2/structures/Alert;->getDetail()Ljava/lang/String;

    move-result-object v3

    .line 26

    new-instance v4, Ljava/lang/StringBuilder;

    invoke-direct {v4}, Ljava/lang/StringBuilder;-><init>()V

    .line 27

    invoke-virtual {v4,v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    .line 28

    invoke-virtual {v4,v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    .line 29

    invoke-virtual {v4,v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    .line 30

    invoke-virtual {v4,v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    .line 31

    invoke-virtual {v4}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object p1

    .line 32

    return-object p1
.end method
显示预警卡片

预警详情标题

这里的“暴雨蓝色预警”字样同样没出现在response当中,因此猜测与预警卡片中标题的原因一致。这里由上一个问题的经验,通过auto.js的悬浮窗调试功能,获取该TextViewID:2131361878,直接全局搜索2131361878即可定位到代码。

public a(View view) {
                super(view);
                this.y = (TextView)view.findViewById(2131361878);
                this.z = (ImageView)view.findViewById(2131361876);
                this.A = (TextView)view.findViewById(2131361874);
                this.B = (TextView)view.findViewById(2131361875);
                this.x = (LinearLayout)view.findViewById(2131361903);
            }

            ...
                textView.setText((CharSequence)activityAlertDetail.getString(2131820591, new Object[]{string2, alert.getLevel()}));
                ((l)((d)b.a(ActivityAlertDetail.this).a(alert.getIconUrl()).a((n)c.e())).b((Drawable)null)).a(this.z);
                this.A.setText((CharSequence)a1.c(alert.getPubTimeNum((Context)ActivityAlertDetail.this), (Context)ActivityAlertDetail.this));
                this.B.setText((CharSequence)alert.getDetail());

同样用StringBuilder修改掉getString即可

    .line 4
    invoke-virtual {p1}, Lcom/miui/weather2/structures/Alert;->getType()Ljava/lang/String;

    move-result-object v2

    .line 5
    invoke-virtual {p1}, Lcom/miui/weather2/structures/Alert;->getLevel()Ljava/lang/String;

    move-result-object v3

    .line 6
    const-string v4, "\u9884\u8b66"

    .line 7
    new-instance v5, Ljava/lang/StringBuilder;

    invoke-direct {v5}, Ljava/lang/StringBuilder;-><init>()V

    .line 8
    invoke-virtual {v5, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    .line 9
    invoke-virtual {v5, v3}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    .line 10
    invoke-virtual {v5, v4}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    .line 11
    invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v1

    .line 12
    invoke-virtual {v0, v1}, Landroid/widget/TextView;->setText(Ljava/lang/CharSequence;)V

    const/4 v5, 0x0

这里最后一行将v5置零,否则会触发报错。原代码中将v5置零不仅在这一段代码中使用,后面也用于if的参数

com.miui.weather2.ActivityAlertDetail$a$a.c(int) failed to verify: void com.miui.weather2.ActivityAlertDetail$a$a.c(int): [0x93] args to 'if' (Precise Reference: java.lang.StringBuilder,Integer) must be integral (declaration of 'com.miui.weather2.ActivityAlertDetail$a$a' appears in base.apk)
对比图

版权属于:XanderC
作品采用:本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
2
查看目录

目录

来自 《[初尝安卓逆向]恢复英文下MIUI天气隐藏的功能》
评论