Pendahuluan
Pada pertengahan April, kami
menerbitkan berita tentang Trojan
Android.InfectionAds.1 , yang mengeksploitasi beberapa kerentanan kritis dalam OS Android. Salah satunya - CVE-2017-13156 (juga dikenal sebagai
Janus ) - memungkinkan program jahat menginfeksi file APK tanpa merusak tanda tangan digital mereka.
Yang lainnya adalah CVE-2017-13315. Ini memberi Trojan hak istimewa tingkat lanjut, dan dapat secara mandiri menginstal dan mencopot aplikasi. Analisis terperinci tentang
Android.InfectionAds.1 tersedia di perpustakaan virus kami, dapat ditemukan di
sini . Kami akan membahas kerentanan CVE-2017-13315 lebih terinci dan melihat seperti apa rasanya.
CVE-2017-13315 termasuk dalam kelompok kerentanan yang menerima nama umum EvilParcel. Mereka ditemukan di berbagai kelas sistem OS Android. Karena kesalahan dalam yang terakhir ketika bertukar data antara aplikasi dan sistem, menjadi mungkin untuk mengganti data ini. Program jahat yang mengeksploitasi kerentanan EvilParcel menerima hak istimewa yang lebih tinggi dan dapat melakukan hal berikut dengan bantuan mereka:
- instal dan hapus instalasi aplikasi dengan izin apa pun tanpa konfirmasi pengguna;
- ketika digunakan bersama dengan kerentanan lain, menginfeksi program yang diinstal pada perangkat dan mengganti "bersih" dokumen asli dengan salinan yang terinfeksi;
- Setel ulang kode kunci layar untuk perangkat Android
- Setel ulang PIN layar kunci perangkat Android.
Saat ini ada 7 kerentanan yang diketahui dari jenis ini:
- CVE-2017-0806 (kesalahan di kelas GateKeeperResponse), diterbitkan pada Oktober 2017;
- CVE-2017-13286 (kesalahan di kelas OutputConfiguration, diterbitkan pada April 2018;
- CVE-2017-13287 (kesalahan dalam kelas VerifyCredentialResponse), diterbitkan pada April 2018;
- CVE-2017-13288 (kesalahan dalam kelas PeriodicAdvertizingReport), diterbitkan pada April 2018;
- CVE-2017-13289 (bug di kelas ParcelableRttResults), diterbitkan pada bulan April 2018;
- CVE-2017-13311 (bug di kelas SparseMappingTable), diterbitkan pada Mei 2018;
- CVE-2017-13315 (kesalahan dalam kelas DcParamObject), diterbitkan pada Mei 2018.
Semuanya mengancam perangkat yang menjalankan OS Android versi 5.0 - 8.1 yang tidak memiliki pembaruan keamanan Mei 2018 dan yang lebih baru.
Prasyarat untuk Kerentanan EvilParcel
Mari kita lihat bagaimana kerentanan EvilParcel muncul. Pertama-tama, kita akan melihat beberapa fitur aplikasi Android. Di Android OS, semua program berinteraksi satu sama lain, serta dengan sistem operasi itu sendiri, dengan mengirim dan menerima objek bertipe Intent. Objek-objek ini dapat berisi jumlah pasangan kunci-nilai yang sewenang-wenang di dalam objek tipe Bundle.
Saat mentransmisikan Intent, objek Bundle dikonversi (serial) ke array byte yang dibungkus Parcel, dan ketika membaca kunci dan nilai-nilai dari Bundel serial, objek akan secara otomatis di-deserialisasi.
Di Bundle, string adalah kuncinya, dan nilainya bisa hampir apa saja. Misalnya, tipe, string, atau wadah primitif yang mengandung tipe atau string primitif. Selain itu, bisa menjadi objek bertipe Parcelable.
Dengan demikian, di Bundle, Anda dapat menempatkan objek jenis apa pun yang mengimplementasikan antarmuka Parcelable. Untuk melakukan ini, Anda perlu mengimplementasikan metode writeToParcel () dan createFromParcel () untuk membuat cerita bersambung dan melakukan deserialisasi objek.
Sebagai contoh yang baik, mari kita buat bundel berseragam sederhana. Mari kita menulis kode yang menempatkan tiga pasangan nilai kunci di Bundle dan membuat serialisasi:
Bundel demo = Bundel baru ();
demo.putString ("String", "Hello, World!");
demo.putInt ("Integer", 42);
demo.putByteArray ("ByteArray", byte baru [] {1, 2, 3, 4, 5, 6, 7, 8});
Parcel parcel = Parcel.obtain ();
parcel.writeBundle (demo);
Setelah mengeksekusi kode ini, kami mendapatkan bundel dari formulir berikut:
Gambar 1. Struktur objek Bundel berseri.
Mari kita perhatikan fitur-fitur berikut dari serialisasi Bundle:
- semua pasangan kunci-nilai ditulis satu demi satu;
- sebelum setiap nilai tipenya ditunjukkan (13 untuk array byte, 1 untuk Integer, 0 untuk string, dan sebagainya);
- sebelum data panjang variabel, ukurannya ditunjukkan (panjang untuk string, jumlah byte untuk array);
- semua nilai ditulis dengan pelurusan 4 byte.
Karena kenyataan bahwa semua kunci dan nilai dalam Bundle ditulis secara berurutan, ketika mengakses kunci atau nilai objek Bundel berserialisasi, yang terakhir dideserialisasi sepenuhnya, termasuk menginisialisasi semua objek Parcelable yang terkandung di dalamnya.
Tampaknya, apa masalahnya? Dan itu adalah bahwa dalam beberapa kelas sistem yang mengimplementasikan Parcelable, metode createFromParcel () dan writeToParcel () mungkin mengalami kesalahan. Di kelas-kelas ini, jumlah byte yang dibaca dalam metode createFromParcel () akan berbeda dari jumlah byte yang ditulis dalam metode writeToParcel (). Jika Anda menempatkan objek kelas semacam itu di dalam Bundel, batas-batas objek di dalam Bundel akan berubah setelah serialisasi ulang. Dan di sinilah kondisi untuk mengeksploitasi kerentanan EvilParcel dibuat.
Berikut adalah contoh kelas dengan kesalahan yang sama:
class Demo implements Parcelable { byte[] data; public Demo() { this.data = new byte[0]; } protected Demo(Parcel in) { int length = in.readInt(); data = new byte[length]; if (length > 0) { in.readByteArray(data); } } public static final Creator<Demo> CREATOR = new Creator<Demo>() { @Override public Demo createFromParcel(Parcel in) { return new Demo(in); } }; @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeInt(data.length); parcel.writeByteArray(data); } }
Jika ukuran array data adalah 0, maka saat membuat objek di createFromParcel () satu int (4 byte) akan dibaca, dan dua int (8 byte) akan ditulis dalam writeToParcel (). Int pertama akan ditulis dalam panggilan eksplisit ke writeInt. Int kedua akan ditulis ketika writeByteArray () dipanggil, karena panjangnya selalu ditulis ke array sebelum Parcel (lihat Gambar 1).
Situasi ketika ukuran array data 0 jarang terjadi. Tetapi bahkan ketika ini terjadi, program masih terus bekerja jika hanya satu objek yang ditransmisikan dalam bentuk serial pada suatu waktu (dalam contoh kita, objek Demo). Oleh karena itu, kesalahan tersebut, sebagai suatu peraturan, tidak diketahui.
Sekarang mari kita coba menempatkan objek Demo dengan panjang array nol di Bundel:
Gambar 2. Hasil menambahkan objek Demo dengan panjang array nol ke Bundel.
Kami cerita bersambung objek:
Gambar 3. Bundel objek setelah serialisasi.
Mari kita coba deserialize:
Gambar 4. Setelah deserializing objek Bundle.
Apa hasilnya? Pertimbangkan cuplikan Parcel:
Gambar 5. Struktur parsel setelah deserialisasi bundel.
Dari Gambar 4 dan 5, kita melihat bahwa selama deserialisasi, satu int dibaca dalam metode createFromParcel alih-alih dua yang ditulis sebelumnya. Oleh karena itu, semua nilai selanjutnya dari Bundel tidak dibaca dengan benar. Nilai 0x0 pada alamat 0x60 dibaca sebagai panjang kunci berikutnya. Dan nilai 0x1 pada alamat 0x64 dibaca sebagai kunci. Dalam hal ini, nilai 0x31 pada alamat 0x68 dibaca sebagai jenis nilai. Tidak ada nilai di Parcel yang tipenya 0x31, jadi readFromParcel () dengan setia melaporkan kesalahan (pengecualian).
Bagaimana ini bisa digunakan dalam praktik dan menjadi kerentanan? Ayo lihat! Kesalahan yang dijelaskan di atas dalam kelas sistem Parcelable memungkinkan Anda untuk membangun Bundle, yang mungkin berbeda selama deserialisasi yang pertama dan berulang. Untuk menunjukkan ini, modifikasi contoh sebelumnya:
Parcel data = Parcel.obtain(); data.writeInt(3);
Kode ini menciptakan bundel berseri yang berisi kelas rentan. Mari kita lihat hasil dari mengeksekusi kode ini:
Gambar 6. Membuat Bundel dengan kelas rentan.
Setelah deserialisasi pertama, bundel ini akan berisi kunci-kunci berikut:
Gambar 7. Hasil deserializing Bundle dengan kelas rentan.
Sekarang lagi buat serial tentang Bundel yang dihasilkan, lalu deserialisasi lagi dan lihat daftar kunci:
Gambar 8. Hasil serialisasi ulang dan deserialisasi bundel dengan kelas rentan.
Apa yang kita lihat Kunci tersembunyi (dengan nilai string "Hai di sana!") Muncul di Bundle, yang sebelumnya tidak ada. Pertimbangkan potongan Parcel Bundel ini untuk memahami mengapa ini terjadi:
Gambar 9. Struktur paket objek Bundle dengan kelas rentan setelah dua siklus serialisasi-deserialisasi.
Di sini esensi kerentanan EvilParcel menjadi lebih jelas. Dimungkinkan untuk membuat Bundle yang dibentuk secara khusus yang akan berisi kelas yang rentan. Mengubah batas-batas kelas ini akan memungkinkan Anda untuk menempatkan objek apa pun di dalam Bundel ini - misalnya, Intent, yang akan muncul dalam Bundel hanya setelah deserialisasi kedua. Ini akan memungkinkannya untuk menyembunyikan Intent dari mekanisme perlindungan sistem operasi.
Operasi EvilParcel
Android.InfectionAds.1 menggunakan CVE-2017-13315 menginstal dan menghapus program sendiri tanpa intervensi dari pemilik perangkat yang terinfeksi. Tapi bagaimana kabarnya?
Pada 2013, kesalahan
7699048 juga ditemukan, juga dikenal sebagai Launch AnyWhere. Itu memungkinkan aplikasi pihak ketiga untuk menjalankan aktivitas sewenang-wenang atas nama sistem pengguna yang lebih istimewa. Diagram di bawah ini menunjukkan mekanisme kerjanya:
Gambar 10. Skema kesalahan 7699048.
Dengan kerentanan ini, aplikasi eksploit dapat mengimplementasikan layanan AccountAuthenticator, yang dirancang untuk menambahkan akun baru ke sistem operasi. Berkat bug 7699048, exploit dapat menjalankan aktivitas untuk menginstal, mencopot, mengganti aplikasi, mengatur ulang PIN atau Pattern Lock, dan melakukan hal-hal tidak menyenangkan lainnya.
Google telah memperbaiki celah ini dengan melarang peluncuran aktivitas sewenang-wenang dari AccountManager. Sekarang Manajer Akun hanya memungkinkan peluncuran kegiatan yang berasal dari aplikasi yang sama. Untuk melakukan ini, ia memeriksa dan membandingkan tanda tangan digital dari program yang memulai awal kegiatan dengan tanda tangan aplikasi di mana aktivitas yang diluncurkan berada. Ini terlihat seperti ini:
if (result != null && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) { int authenticatorUid = Binder.getCallingUid(); long bid = Binder.clearCallingIdentity(); try { PackageManager pm = mContext.getPackageManager(); ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId); int targetUid = resolveInfo.activityInfo.applicationInfo.uid; if (PackageManager.SIGNATURE_MATCH != pm.checkSignatures(authenticatorUid, targetUid)) { throw new SecurityException( "Activity to be started with KEY_INTENT must " + "share Authenticator's signatures"); } } finally { Binder.restoreCallingIdentity(bid); } }
Tampaknya masalah telah diselesaikan, tetapi tidak semua yang ada di sini lancar. Ternyata perbaikan ini dapat dicegah menggunakan kerentanan EvilParcel CVE-2017-13315 yang terkenal! Seperti yang sudah kita ketahui, setelah memperbaiki Launch AnyWhere, sistem memeriksa tanda tangan digital aplikasi. Jika pemeriksaan ini berhasil, Bundel diteruskan ke IAccountManagerResponse.onResult (). Pada saat yang sama, onResult () dipanggil melalui mekanisme IPC, sehingga Bundle diserialisasi lagi. Dalam implementasi onResult (), berikut ini terjadi:
private class Response extends IAccountManagerResponse.Stub { public void onResult(Bundle bundle) { Intent intent = bundle.getParcelable(KEY_INTENT); if (intent != null && mActivity != null) {
Selanjutnya, Bundel diekstraksi kunci niat dan aktivitas diluncurkan tanpa pemeriksaan. Akibatnya, untuk memulai aktivitas sewenang-wenang dengan hak sistem, cukup untuk membangun Bundle sedemikian rupa sehingga bidang maksud disembunyikan pada deserialisasi pertama, dan muncul pada deserialisasi kedua. Dan, seperti yang telah kita lihat, justru tugas inilah yang dipenuhi kerentanan EvilParcel.
Saat ini, semua kerentanan yang diketahui dari tipe ini diperbaiki oleh perbaikan di kelas Parcelable yang rentan itu sendiri. Namun, kemunculan kembali kelas-kelas rentan di masa depan tidak dapat dikesampingkan. Implementasi Bundle dan mekanisme untuk menambah akun baru masih sama seperti sebelumnya. Mereka masih memungkinkan Anda untuk membuat exploit yang persis sama ketika Anda menemukan (atau baru) kelas Parcelable yang rentan. Terlebih lagi, implementasi kelas-kelas ini masih dilakukan secara manual, dan programmer harus mengawasi panjang konstan objek Parcelable berseri. Dan ini adalah faktor manusia dengan segala konsekuensinya. Namun, kami berharap bahwa kesalahan seperti itu akan sesedikit mungkin, dan kerentanan EvilParcel tidak akan mengganggu pengguna perangkat Android.
Anda dapat memeriksa kerentanan perangkat seluler EvilParcel Anda menggunakan antivirus
Dr.Web Security Space kami . "Auditor Keamanan" yang terintegrasi akan melaporkan masalah yang diidentifikasi dan memberikan rekomendasi untuk menyelesaikannya.