Android App崩溃之谜

前言

App崩溃退出,不仅影响业务功能,也会严重影响用户的体验
Android App崩溃之谜_第1张图片

有些崩溃场景,是由于开发者疏忽:未判断参数是否为空,或未对未知异常进行捕获。
但这里将要谈到的崩溃,却是几乎所有开发者都始料未及的。

反序列化异常

先看一段攻击代码:

Intent i2 = new Intent();
i2.setClassName("com.afmobi.boomplayer", "com.tecno.boomplayer.play.MusicPlayerCoverActivity");
i2.putExtra("bomb", new SerializbleTest());
startActivity(i2);


//SerializbleTest.java
package com.ts.sectest;
import java.io.Serializable;

public class SerializbleTest implements Serializable {
    public static long uuid = 111111L;
    public SerializbleTest(){
        super();
    }
}

对应的App崩溃的调用栈日志:

16842-16842/? E/AndroidRuntime: FATAL EXCEPTION: main Process:
com.afmobi.boomplayer, PID: 16842 java.lang.RuntimeException: Unable
to start activity
ComponentInfo{com.afmobi.boomplayer/com.tecno.boomplayer.play.MusicPlayerCoverActivity}:
java.lang.RuntimeException: Parcelable encountered
ClassNotFoundException reading a Serializable object (name =
com.ts.sectest.SerializbleTest)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3874)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:4031)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:103)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread H . h a n d l e M e s s a g e ( A c t i v i t y T h r e a d . j a v a : 2358 ) a t a n d r o i d . o s . H a n d l e r . d i s p a t c h M e s s a g e ( H a n d l e r . j a v a : 106 ) a t a n d r o i d . o s . L o o p e r . l o o p O n c e ( L o o p e r . j a v a : 241 ) a t a n d r o i d . o s . L o o p e r . l o o p ( L o o p e r . j a v a : 342 ) a t a n d r o i d . a p p . A c t i v i t y T h r e a d . m a i n ( A c t i v i t y T h r e a d . j a v a : 8160 ) a t j a v a . l a n g . r e f l e c t . M e t h o d . i n v o k e ( N a t i v e M e t h o d ) a t c o m . a n d r o i d . i n t e r n a l . o s . R u n t i m e I n i t H.handleMessage(ActivityThread.java:2358) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:241) at android.os.Looper.loop(Looper.java:342) at android.app.ActivityThread.main(ActivityThread.java:8160) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit H.handleMessage(ActivityThread.java:2358)atandroid.os.Handler.dispatchMessage(Handler.java:106)atandroid.os.Looper.loopOnce(Looper.java:241)atandroid.os.Looper.loop(Looper.java:342)atandroid.app.ActivityThread.main(ActivityThread.java:8160)atjava.lang.reflect.Method.invoke(NativeMethod)atcom.android.internal.os.RuntimeInitMethodAndArgsCaller.run(RuntimeInit.java:583)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1045) Caused by: java.lang.RuntimeException: Parcelable encountered
ClassNotFoundException reading a Serializable object (name =
com.ts.sectest.SerializbleTest)
at android.os.Parcel.readSerializable(Parcel.java:3543)
at android.os.Parcel.readValue(Parcel.java:3307)
at android.os.Parcel.readArrayMapInternal(Parcel.java:3653)
at android.os.BaseBundle.initializeFromParcelLocked(BaseBundle.java:292)
at android.os.BaseBundle.unparcel(BaseBundle.java:236)
at android.content.Intent.getInt(Intent.java:8582)
at com.boomplay.common.base.BaseActivity.onCreate(BaseActivity.java:5)
at com.boomplay.common.base.TransBaseActivity.onCreate(TransBaseActivity.java:1)
at com.tecno.boomplayer.play.MusicPlayerCoverActivity.onCreate(MusicPlayerCoverActivity.java:1)
at android.app.Activity.performCreate(Activity.java:8105)
at android.app.Activity.performCreate(Activity.java:8085)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1329)
//为了讲解需要,日志有删改

从调用栈日志中可以看出,漏洞触发点在

com.boomplay.common.base.BaseActivity.onCreate,代码如下
public void onCreate(Bundle bundle) {
    this.sourceEvtData = (SourceEvtData) getIntent().getInt(SOURCE_EVTDATA_KEY);
    
}

看到这儿,可能你会惊呼,这不是常规获取参数的操作吗?为什么会有异常?所有开发者都始料未及吧!!!

而这个潜在的异常,导致几乎所有App都面临着被攻击者“崩溃”的风险。
异常原因在调用栈中也可以看出:Parcelable encountered ClassNotFoundException

原理分析

Android平台中组件间相互通信时,其参数通过Intent进行传递。为了传递复杂的类实例对象等参数数据,Intent会将复杂数据进行序列化(Parcelable)包装,到通信对端时再进行反序列化操作。
在进行反序列化操作时,需要能在当前的ClassLoader中找到对应类的定义,否则会抛出异常(AndroidRuntime)

有人可能有疑问,那我的App不get序列化参数,是不是就不会崩溃了?
非也,截至Android 12,从Intent中获取extra参数时,会把所有参数都进行反序列化(android.os.BaseBundle.unparcel函数)。即只要App中调用了getXXX函数,就会遇到反序列化异常

Android提供了两种序列化接口Parcelable和Serializable,所以会有两种反序列化异常:BadParcelableException 和 ClassNotFoundException

其中BadParcelableException在Android framework中已有捕获:
Android App崩溃之谜_第2张图片
而ClassNotFoundException依然会被throw,若App获取intent extra参数时未进行try-catch,就会有被“崩溃”的风险

如何解决

1、修改App

① 所有获取intent extra参数时进行try-catch
参考华为App的做法:扩展了一个SafeIntent,对所有参数获取进行try-catch
Android App崩溃之谜_第3张图片
② 实现Thread.UncaughtExceptionHandler,捕获全局的所有未捕获异常

public class CrashHandler implements UncaughtExceptionHandler { 
    //自定义实现
}

//Application中设置
public class CrashApplication extends Application {  
    @Override  
    public void onCreate() {  
        super.onCreate();  
        CrashHandler crashHandler = CrashHandler.getInstance();  
        crashHandler.init(getApplicationContext());  
    }  
}

2、修改framework

在framework中对ClassNotFoundException进行catch

Android App崩溃之谜_第4张图片

但由于方便调试等原因,Android并未在framework中增加此catch

好消息是,在Android 13中,由于修复Bundle反序列化安全漏洞,而引入的LazyBundle机制,意外的规避了绝大部分的ClassNotFoundException异常。
因为LazyBundle机制,framework不会在getXXX调用时,不会对所有参数进行反序列化,而是只反序列化需要获取的参数。
故除非你的App没有对getSerializable函数进行try-catch捕获异常,否则就不会再受到此类崩溃的困扰

参考文章

1、Bundle反序列化安全漏洞: https://i.blackhat.com/EU-22/Wednesday-Briefings/EU-22-Ke-Android-Parcels-Introducing-Android-Safer-Parcel.pdf
2、https://developer.android.com/reference/java/lang/Thread.UncaughtExceptionHandler

你可能感兴趣的:(android,java,开发语言)