Bagaimana saya menemukan telur paskah di keamanan Android dan tidak mendapatkan pekerjaan di Google

Google menyukai telur paskah. Itu sangat mencintai mereka, sehingga Anda dapat menemukannya di hampir setiap produk mereka. Tradisi telur paskah Android dimulai pada versi OS yang paling awal (saya pikir semua orang di sana tahu apa yang terjadi ketika Anda masuk ke pengaturan umum dan mengetuk nomor versi beberapa kali).

Tetapi kadang-kadang Anda dapat menemukan telur paskah di tempat-tempat yang paling tidak mungkin. Bahkan ada legenda urban bahwa suatu hari, seorang programmer Googled “mutex lock”, tetapi bukannya hasil pencarian mendarat di foo.bar, selesaikan semua tugas dan dapatkan pekerjaan di Google.

Rekonstruksi
gambar

Hal yang sama (kecuali tanpa akhir yang bahagia) terjadi pada saya. Pesan tersembunyi di mana pasti tidak ada, membalikkan kode Java dan perpustakaan aslinya, VM rahasia, wawancara Google - semua itu ada di bawah.

Droidguard


Suatu malam yang membosankan saya mengatur ulang pabrik ponsel saya dan mengaturnya kembali. Hal pertama yang pertama, instalasi Android baru meminta saya untuk masuk ke akun Google. Dan saya bertanya-tanya: bagaimana proses masuk ke Android bekerja? Dan malam tiba-tiba menjadi kurang membosankan.

Saya menggunakan Burp Suite PortSwigger untuk mencegat dan menganalisis lalu lintas jaringan. Versi Komunitas gratis sudah cukup untuk tujuan kita. Untuk melihat permintaan https, pertama-tama kita perlu menginstal sertifikat PortSwigger ke perangkat. Sebagai perangkat pengujian, saya memilih Samsung Galaxy S yang berusia 8 tahun dengan Android 4.4. Apa pun yang lebih baru dari itu dan Anda mungkin memiliki masalah dengan penyematan sertifikat dan lainnya.

Dalam semua kejujuran, tidak ada yang khusus dengan permintaan Google API. Perangkat mengirim informasi tentang dirinya sendiri dan mendapat token sebagai tanggapan ... Satu-satunya langkah yang penasaran adalah permintaan POST ke layanan anti-penyalahgunaan.



Setelah permintaan dibuat, di antara banyak parameter yang sangat normal muncul yang menarik, bernama droidguard_result . Ini adalah string Base64 yang sangat panjang:



DroidGuard adalah mekanisme Google untuk mendeteksi bot dan emulator di antara perangkat nyata. SafetyNet, misalnya, juga menggunakan data DroidGuard. Google juga memiliki hal serupa untuk browser - Botguard.

Tapi apa data itu? Ayo cari tahu.

Buffer protokol


Apa yang menghasilkan tautan itu ( www.googleapis.com/androidantiabuse/v1/x/create?alt=PROTO&key=AIzaSyBofcZsgLSS7BOnBjZPEkk4rYwzOIz-lTI ) dan apa yang ada di dalam Android yang membuat permintaan ini? Setelah penyelidikan singkat, ternyata tautan itu, dalam bentuk persis ini, terletak di dalam salah satu kelas Google Play Services yang dikaburkan:

public bdd(Context var1, bdh var2) { this(var1, "https://www.googleapis.com/androidantiabuse/v1/x/create?alt=PROTO&key=AIzaSyBofcZsgLSS7BOnBjZPEkk4rYwzOIz-lTI", var2); } 

Seperti yang telah kita lihat di Burp, permintaan POST pada tautan ini memiliki Content-Type - application / x-protobuf (Google Protocol Buffer, protokol Google untuk serialisasi biner). Tapi bukan json - sulit untuk mengungkap apa yang sebenarnya dikirim.

Protokol buffer berfungsi seperti ini:

  • Pertama kami menggambarkan struktur pesan dalam format khusus dan menyimpannya ke dalam file .proto;
  • Kemudian kita mengkompilasi file .proto, dan kompiler protoc menghasilkan kode sumber dalam bahasa yang dipilih (dalam kasus Android itu Java);
  • Akhirnya, kami menggunakan kelas yang dihasilkan dalam proyek kami.

Kami memiliki dua cara untuk memecahkan kode pesan protobuf. Yang pertama adalah menggunakan penganalisa protobuf dan mencoba untuk membuat ulang deskripsi asli file .proto. Yang kedua adalah mencabut kelas yang dihasilkan oleh protoc dari Google Play Services, yang saya putuskan untuk lakukan.

Kami mengambil file .apk Layanan Google Play dari versi yang sama yang diinstal pada perangkat (atau, jika perangkat di-root, ambil saja file langsung dari sana). Menggunakan dex2jar kita mengkonversi file .dex kembali ke .jar dan buka di dekompiler pilihan. Saya pribadi suka Fernflower JetBrains. Ini berfungsi sebagai plugin untuk IntelliJ IDEA (atau Android Studio), jadi kami cukup meluncurkan Android Studio dan membuka file dengan tautan yang kami coba analisis. Jika proguard tidak berusaha terlalu keras, kode Java yang didekompilasi untuk membuat pesan protobuf bisa saja disalin ke proyek Anda.

Melihat kode yang didekompilasi, kita melihat bahwa Build. * Konstanta sedang dikirim di dalam pesan protobuf. (Oke, itu tidak terlalu sulit ditebak).

 ... var3.a("4.0.33 (910055-30)"); a(var3, "BOARD", Build.BOARD); a(var3, "BOOTLOADER", Build.BOOTLOADER); a(var3, "BRAND", Build.BRAND); a(var3, "CPU_ABI", Build.CPU_ABI); a(var3, "CPU_ABI2", Build.CPU_ABI2); a(var3, "DEVICE", Build.DEVICE); ... 

Namun sayangnya, dalam balasan server semua bidang protobuf berubah menjadi sup alfabet setelah kebingungan. Tetapi kita dapat menemukan apa yang ada di sana menggunakan penangan kesalahan. Berikut cara memeriksa data yang berasal dari server:

 if (!var7.d()) { throw new bdf("byteCode"); } if (!var7.f()) { throw new bdf("vmUrl"); } if (!var7.h()) { throw new bdf("vmChecksum"); } if (!var7.j()) { throw new bdf("expiryTimeSecs"); } 

Rupanya, begitulah cara bidang dipanggil sebelum kebingungan: byteCode , vmUrl , vmChecksum dan expiryTimeSecs . Skema penamaan ini sudah memberi kita beberapa ide.

Kami menggabungkan semua kelas yang didekompilasi dari Google Play Services ke dalam proyek uji coba, menamainya kembali, menghasilkan tes Build. * Perintah dan peluncuran (meniru perangkat apa pun yang kita inginkan). Jika seseorang ingin melakukannya sendiri, inilah tautan ke GitHub saya .

Jika permintaannya benar, server mengembalikan ini:
00: 06: 26.761 [main] INFO daresponse.AntiabuseResponse - byteKode kode: 34446
00: 06: 26.761 [main] INFO daresponse.AntiabuseResponse - vmChecksum: C15E93CCFD9EF178293A2334A1C9F9B08F115993
00: 06: 26.761 [main] INFO daresponse.AntiabuseResponse - vmUrl: www.gstatic.com/droidguard/C15E93CCFD9EF178293A2334A1C9F9B08F115993
00: 06: 26.761 [main] INFO daresponse.AntiabuseResponse - expiryTimeSecs: 10

Langkah 1 selesai. Sekarang mari kita lihat apa yang tersembunyi di balik tautan vmUrl .

APK rahasia


Tautan membawa kita langsung ke file .apk, dinamai hash SHA-1-nya sendiri. Ini agak kecil - hanya 150KB. Dan itu cukup dibenarkan: jika diunduh oleh setiap satu dari 2 miliar perangkat Android, itu adalah 270TB lalu lintas pada layanan Google.



Kelas DroidGuardService , sebagai bagian dari Google Play Services, mengunduh file ke perangkat, membongkar, mengekstrak .dex dan menggunakan com.google.ccc.abuse.droidguard.DroidGuard melalui refleksi. Jika ada kesalahan, maka DroidGuardService beralih dari DroidGuard kembali ke Droidguasso. Tapi itu cerita lain sepenuhnya.

Pada dasarnya, kelas DroidGuard adalah pembungkus JNI sederhana di sekitar perpustakaan .so asli. ABI perpustakaan asli cocok dengan apa yang kami kirim di bidang CPU_ABI dalam permintaan protobuf: kita dapat meminta armeabi, x86 atau bahkan MIPS.

Layanan DroidGuardService sendiri tidak mengandung logika menarik untuk bekerja dengan kelas DroidGuard . Ini hanya membuat instance baru DroidGuard , mengirimkannya byteCode dari pesan protobuf, memanggil metode publik, yang mengembalikan array byte. Array ini kemudian dikirim ke server di dalam parameter droidguard_result .

Untuk mendapatkan gambaran kasar tentang apa yang terjadi di dalam DroidGuard kita dapat mengulangi logika DroidGuardService (tetapi tanpa mengunduh .apk, karena kita sudah memiliki pustaka asli). Kita dapat mengambil file .dex dari APK rahasia, mengubahnya menjadi .jar dan kemudian digunakan dalam proyek kami. Satu-satunya masalah adalah bagaimana kelas DroidGuard memuat pustaka asli. Blok inisialisasi statis memanggil metode loadDroidGuardLibrary() :

 static { try { loadDroidGuardLibrary(); } catch (Exception ex) { throw new RuntimeException(ex); } } 

Kemudian metode loadDroidGuardLibrary() membaca library.txt (terletak di root file .apk) dan memuat perpustakaan dengan nama itu melalui System.load(String filename) . Sangat tidak nyaman bagi kami, karena kami harus membangun .apk dengan cara yang sangat spesifik untuk meletakkan library.txt dan file .so ke dalam root. Akan jauh lebih nyaman untuk menyimpan file .so di folder lib dan memuatnya melalui System.loadLibrary(String libname) .

Itu tidak sulit untuk dilakukan. Kami akan menggunakan smali / baksmali - assembler / disassembler untuk file .dex. Setelah menggunakannya, class.dex berubah menjadi banyak file .smali. Kelas com.google.ccc.abuse.droidguard.DroidGuard harus dimodifikasi, sehingga blok inisialisasi statis memanggil metode System.loadLibrary("droidguard") alih-alih loadDroidGuardLibrary() . Sintaksis Smali cukup sederhana, blok inisialisasi baru terlihat seperti ini:

 .method static constructor <clinit>()V .locals 1 const-string v0, "droidguard" invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V return-void .end method 

Kemudian kami menggunakan backsmali untuk membangun semuanya kembali ke .dex, dan kemudian kami mengubahnya menjadi .jar. Pada akhirnya kita mendapatkan file .jar yang bisa kita gunakan dalam proyek kita - ini dia .

Seluruh bagian yang berhubungan dengan DroidGuard adalah beberapa string. Bagian terpenting adalah mengunduh array byte yang kami dapatkan di langkah sebelumnya setelah menangani layanan anti-penyalahgunaan dan menyerahkannya ke konstruktor DroidGuard :

 private fun runDroidguard() { var byteCode: ByteArray? = loadBytecode("bytecode.base64"); byteCode?.let { val droidguard = DroidGuard(applicationContext, "addAccount", it) val params = mapOf("dg_email" to "test@gmail.com", "dg_gmsCoreVersion" to "910055-30", "dg_package" to "com.google.android.gms", "dg_androidId" to UUID.randomUUID().toString()) droidguard.init() val result = droidguard.ss(params) droidguard.close() } } 

Sekarang kita dapat menggunakan profiler Android Studio dan melihat apa yang terjadi selama pekerjaan DroidGuard:



Metode asli initNative () mengumpulkan data tentang perangkat dan memanggil metode Java hasSystemFeature(), getMemoryInfo(), getPackageInfo() ... itu sesuatu, tapi saya masih tidak melihat logika yang solid. Nah, yang tersisa hanyalah membongkar file .so.

libdroidguard.so


Untungnya, menganalisis perpustakaan asli tidak lebih sulit daripada melakukannya dengan file .dex dan .jar. Kami membutuhkan aplikasi yang mirip dengan Hex-Rays IDA dan beberapa pengetahuan tentang kode assembler x86 atau ARM. Saya memilih ARM, karena saya memiliki perangkat yang telah di-root untuk debug. Jika Anda tidak memilikinya, Anda bisa mengambil pustaka x86 dan debug menggunakan emulator.

Sebuah aplikasi yang mirip dengan Hex-Rays IDA mendekompilasi biner menjadi sesuatu yang menyerupai kode C. Jika kita membuka Java_com_google_ccc_abuse_droidguard_DroidGuard_ssNative metode, kita akan melihat sesuatu seperti ini:

 __int64 __fastcall Java_com_google_ccc_abuse_droidguard_DroidGuard_initNative(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) ... v14 = (*(_DWORD *)v9 + 684))(v9, a5); v15 = (*(_DWORD *)v9 + 736))(v9, a5, 0); ... 

Tidak terlihat terlalu menjanjikan. Pertama kita perlu membuat beberapa langkah awal untuk mengubahnya menjadi sesuatu yang lebih berguna. Dekompiler tidak tahu apa-apa tentang JNI, jadi kami menginstal Android NDK dan mengimpor file jni.h. Seperti yang kita ketahui, dua parameter pertama dari metode JNI adalah JNIEnv* dan jobject (this) . Kita bisa mengetahui tipe-tipe parameter lain dari kode Java DroidGuard. Setelah menetapkan jenis yang benar, offset yang tidak berarti berubah menjadi panggilan metode JNI:

 __int64 __fastcall Java_com_google_ccc_abuse_droidguard_DroidGuard_initNative(_JNIEnv *env, jobject thiz, jobject context, jstring flow, jbyteArray byteCode, jobject runtimeApi, jobject extras, jint loggingFd, int runningInAppSide) { ... programLength = _env->functions->GetArrayLength)(_env, byteCode); programBytes = (jbyte *)_env->functions->GetByteArrayElements)(_env, byteCode, 0); ... 

Jika kami memiliki cukup kesabaran untuk melacak array byte yang diterima dari server anti-penyalahgunaan, kami akan ... kecewa. Sayangnya, tidak ada jawaban sederhana untuk "apa yang terjadi di sini?". Ini murni, kode byte yang disuling, dan pustaka asli adalah mesin virtual. Beberapa enkripsi AES ditaburkan di atas dan kemudian VM membaca kode byte, byte demi byte, dan menjalankan perintah. Setiap byte adalah perintah yang diikuti oleh operan. Tidak banyak perintah, hanya sekitar 70: baca int, baca byte, baca string, panggil metode Java, gandakan dua angka, if-goto dll.

Bangun neo


Saya memutuskan untuk melangkah lebih jauh dan mencari tahu struktur kode byte untuk VM ini. Ada masalah lain dengan panggilan: kadang-kadang (sekali setiap beberapa minggu) ada versi baru dari perpustakaan asli di mana pasangan byte-perintah diacak. Itu tidak menghentikan saya dan saya memutuskan untuk membuat ulang VM menggunakan Java.

Apa yang dilakukan kode byte adalah melakukan semua pekerjaan rutin mengumpulkan informasi tentang perangkat. Sebagai contoh, ini memuat string dengan nama metode, mendapatkan alamatnya melalui dlsym dan dijalankan. Dalam versi Java saya dari VM, saya hanya menciptakan 5 metode atau lebih dan belajar menafsirkan 25 perintah pertama dari kode byte layanan anti-penyalahgunaan. Pada perintah ke-26, VM membaca string terenkripsi lain dari kode byte. Tiba-tiba ternyata itu bukan nama metode lain. Jauh dari itu.
Perintah Mesin Virtual # 26
Metode doa vm-> vm_method_table [2 * 0x77]
Metode vmMethod_readString
indeks adalah 0x9d
panjang string adalah 0x0066
(kunci baru dihasilkan)
byte string yang dikodekan adalah EB 4E E6 DC 34 13 35 4A DD 55 B3 91 33 05 61 04 C0 54 FD 95 2F 18 72 04 C1 55 E1 92 28 11 66 04 DD 4F B3 94 33 04 35 0A C1 4E B2 DB 12 17 79 4F 92 55 FC DB 33 05 35 45 C6 01 F7 89 29 1F 71 43 C7 40 E1 9F 6B 1E 70 48 DE 4E B8 CD 75 44 23 14 85 14 A7 C2 7F 40 26 42 84 17 A2 BB 21 19 7A 43 DE 44 BD 98 29 1B
byte string yang didekodekan adalah 59 6F 75 27 72 65 20 6E 6F 74 20 6A 75 73 74 20 72 75 6E 6E 69 6E 67 20 73 74 72 69 6E 67 73 20 6F 6E 20 6F 75 72 20 2E 73 6F 21 20 54 61 6C 6B 20 74 6F 20 75 73 20 61 74 20 64 72 6F 69 64 67 75 61 72 64 2D 68 65 6C 6C 6F 2B 36 33 32 36 30 37 35 34 39 39 36 33 66 36 36 31 40 67 6F 6F 67 6C 65 2E 63 6F 6D
nilai string yang didekodekan adalah ( Anda tidak hanya menjalankan string pada .so! Bicaralah kepada kami di droidguard@google.com )
Itu aneh. Mesin virtual belum pernah berbicara dengan saya sebelumnya. Saya pikir jika Anda mulai melihat pesan rahasia diarahkan kepada Anda, Anda akan menjadi gila. Untuk memastikan saya masih waras, saya menjalankan beberapa ratus jawaban berbeda dari layanan anti-penyalahgunaan melalui VM saya. Secara harfiah setiap 25-30 perintah ada pesan yang disembunyikan dalam kode byte. Mereka sering diulang, tetapi di bawah ini adalah beberapa yang unik. Namun, saya mengedit alamat email: setiap pesan memiliki alamat yang berbeda, seperti "droidguard+tag@google.com", dengan tag yang unik untuk masing-masingnya.
droidguard@google.com: Jangan menjadi orang asing!
Kamu masuk! Hubungi kami di droidguard@google.com
Salam dari droidguard@google.com traveler pemberani! Katakan hai!
Apakah mudah menemukan ini? droidguard@google.com ingin tahu
Orang-orang di droidguard@google.com akan sangat menghargai mendengar dari Anda!
Apa semua omong kosong ini? Tanyakan droidguard@google.com ... mereka akan tahu!
Hai! Senang melihatmu di sini. Sudahkah Anda berbicara dengan droidguard@google.com?
Anda tidak hanya menjalankan string pada .so! Hubungi kami di droidguard@google.com
Apakah saya yang terpilih? Saya pikir sudah waktunya untuk berhenti mengacaukan DroidGuard dan berbicara dengan Google, karena mereka bertanya kepada saya.

Panggilan Anda sangat penting bagi kami


Saya mengatakan temuan saya di email yang saya temukan. Untuk membuat hasil sedikit lebih mengesankan, saya mengotomatiskan proses analisis sedikit. Masalahnya, string dan byte array disimpan dalam kode byte yang dienkripsi. VM menerjemahkannya menggunakan konstanta yang diuraikan oleh kompiler. Dengan menggunakan aplikasi yang mirip dengan Hex-Rays IDA, Anda dapat mengekstraknya dengan mudah. Tetapi dengan setiap perubahan konstanta versi baru dan itu cukup nyaman untuk selalu mengekstraknya secara manual.

Tetapi Java-parsing perpustakaan asli ternyata sangat sederhana. Menggunakan jelf (pustaka untuk parsing file ELF) kami menemukan offset Java_com_google_ccc_abuse_droidguard_DroidGuard_initNative metode dalam biner, dan kemudian menggunakan Capstone (kerangka kerja pembongkaran dengan binding untuk berbagai bahasa, termasuk Java) kami mendapatkan kode assembler dan mencarinya untuk memuat konstanta ke dalam berbagai bahasa, termasuk Jawa) pendaftar.

Pada akhirnya saya mendapatkan aplikasi yang meniru seluruh proses DroidGuard: membuat permintaan ke layanan anti-penyalahgunaan, mengunduh .apk, membuka paketnya, mem-parsing perpustakaan asli, mengekstrak konstanta yang diperlukan, mengambil pemetaan perintah VM dan memilih mengartikan kode byte. Saya mengkompilasi semuanya dan mengirimkannya ke Google. Sementara saya berada di sana, saya mulai bersiap untuk pindah dan mencari Glassdoor untuk gaji rata-rata di Google. Saya memutuskan untuk tidak menyetujui kurang dari enam angka.

Jawabannya tidak butuh waktu lama. Sebuah email dari anggota tim DroidGuard hanya membaca: "Mengapa kamu melakukan ini?"



"Karena aku bisa" - jawabku. Seorang karyawan Google menjelaskan kepada saya bahwa DroidGuard seharusnya melindungi Android dari peretas (Anda tidak bilang!) Dan akan lebih bijak jika menyimpan kode sumber DroidGuard VM saya sendiri. Percakapan kami berakhir di sana.

Wawancara


Sebulan kemudian saya menerima email lain. Tim DroidGuard di Zurich membutuhkan karyawan baru. Apakah saya tertarik bergabung? Tentu saja!

Tidak ada jalan pintas untuk masuk ke Google. Yang bisa dilakukan oleh kontak saya hanyalah meneruskan CV saya ke departemen SDM. Setelah itu saya harus melalui omong kosong birokrasi yang biasa dan serangkaian wawancara.

Ada banyak cerita di luar sana tentang wawancara Google. Algoritma, tugas Olimpiade, dan pemrograman Google Documents bukanlah tugas saya, jadi saya memulai persiapan. Saya membaca kursus "Algoritma" Coursera puluhan kali, menyelesaikan ratusan tugas di Hackerrank dan belajar untuk berkeliling grafik di kedua dimensi dengan mata tertutup.

Dua bulan berlalu. Untuk mengatakan saya merasa siap akan menjadi pernyataan yang meremehkan. Google Documents menjadi IDE favorit saya. Saya merasa seperti saya tahu segalanya yang perlu diketahui tentang algoritma. Tentu saja, saya tahu kelemahan saya dan menyadari bahwa saya mungkin tidak akan melewati serangkaian 5 wawancara di Zurich, tetapi pergi ke Disneyland programmer secara gratis adalah hadiah dalam dan dari dirinya sendiri. Langkah pertama adalah wawancara telepon untuk menyingkirkan kandidat terlemah dan tidak membuang waktu pengembang Zurich pada pertemuan tatap muka. Hari itu ditetapkan, telepon berdering ...



... dan saya langsung gagal tes pertama saya. Saya beruntung - mereka mengajukan pertanyaan yang pernah saya lihat di internet sebelumnya dan sudah diselesaikan. Itu tentang serialisasi array string. Saya menawarkan kode string di Base64 dan menyimpannya melalui pembagi. Pewawancara meminta saya untuk mengembangkan algoritma Base64. Setelah itu wawancara berubah menjadi semacam monolog, di mana diwawancarai menjelaskan kepada saya bagaimana Base64 bekerja dan saya mencoba mengingat operasi bit di Jawa.

Jika ada orang di Google yang membaca ini
Kawan, kau genius berdarah jika sampai di sana! Serius. Saya tidak bisa membayangkan bagaimana seseorang bisa membersihkan semua hambatan yang mereka hadapi di depan Anda.

3 hari setelah panggilan saya mendapat email yang mengatakan mereka tidak ingin mewawancarai saya lebih lanjut. Dan itulah bagaimana komunikasi saya dengan Google berakhir.

Mengapa ada pesan di DroidGuard yang meminta untuk mengobrol, saya masih tidak tahu. Mungkin hanya untuk statistik. Orang yang saya tulis pada awalnya memberi tahu saya bahwa orang-orang benar-benar menulis di sana, tetapi frekuensinya bervariasi: kadang-kadang mereka mendapat 3 balasan dalam seminggu, kadang-kadang 1 setahun.

Saya percaya ada cara yang lebih mudah untuk mendapatkan wawancara di Google. Lagipula, Anda bisa menanyakan salah satu dari 100.000 karyawan (meskipun tidak semua dari mereka adalah pengembang, diakui). Namun itu adalah pengalaman yang menyenangkan.

Source: https://habr.com/ru/post/id446790/


All Articles