TL; DR. Artikel tersebut berbicara tentang pengembangan terbalik klien Dropbox, meretas mekanisme kebingungan dan dekompilasi klien dengan Python, serta mengubah program untuk mengaktifkan fungsi debugging yang tersembunyi dalam mode normal. Jika Anda hanya tertarik pada kode dan instruksi yang sesuai, gulir sampai akhir. Pada saat penulisan ini, kode ini kompatibel dengan versi Dropbox terbaru berdasarkan penerjemah CPython 3.6.Pendahuluan
Dropbox langsung membuat saya terpesona. Konsepnya masih tampak sederhana. Ini foldernya. Letakkan file di sana. Ini disinkronkan. Pergi ke perangkat lain. Disinkronkan lagi. Folder dan file sekarang juga muncul di sana!
Jumlah pekerjaan latar belakang yang tersembunyi benar-benar menakjubkan. Pertama, semua masalah yang harus Anda tangani saat membuat dan memelihara aplikasi lintas platform untuk sistem operasi desktop utama (OS X, Linux, Windows) tidak hilang. Tambahkan juga dukungan berbagai browser web, berbagai sistem operasi seluler. Dan kita hanya berbicara tentang sisi klien. Saya juga tertarik pada backend Dropbox, yang memungkinkan saya mencapai skalabilitas dan latensi rendah seperti itu dengan beban kerja sangat besar yang diciptakan oleh setengah miliar pengguna.
Karena alasan inilah saya selalu suka menonton apa yang dilakukan Dropbox di bawah tenda dan bagaimana itu berkembang selama bertahun-tahun. Sekitar delapan tahun yang lalu, saya pertama kali mencoba mencari tahu bagaimana sebenarnya klien Dropbox bekerja ketika saya melihat siaran lalu lintas yang tidak diketahui saat berada di hotel. Investigasi menunjukkan bahwa ini adalah bagian dari fitur Dropbox yang disebut LanSync, yang memungkinkan Anda untuk menyinkronkan lebih cepat jika host Dropbox di LAN yang sama memiliki akses ke file yang sama. Namun, protokolnya tidak didokumentasikan, dan saya ingin tahu lebih banyak. Oleh karena itu, saya memutuskan untuk melihat lebih detail, dan sebagai hasilnya saya melakukan rekayasa balik dari hampir seluruh program. Penelitian ini tidak pernah dipublikasikan, meskipun saya terkadang berbagi catatan dengan beberapa orang.
Ketika kami membuka Anvil Ventures, Chris dan saya menghargai sejumlah alat untuk penyimpanan, berbagi, dan kolaborasi dokumen. Salah satunya, jelas, adalah Dropbox, dan bagi saya ini adalah alasan lain untuk menggali studi lama dan memeriksanya pada versi klien saat ini.
Dekripsi dan deobfusiasi
Pertama, saya mengunduh klien untuk Linux dan dengan cepat mengetahui bahwa itu ditulis dengan Python. Karena lisensi Python cukup permisif, mudah bagi orang untuk memodifikasi dan mendistribusikan interpreter Python bersama dengan dependensi lain seperti perangkat lunak komersial. Kemudian saya mulai melakukan reverse engineering untuk memahami cara kerja klien.
Pada saat itu, file bytecode berada dalam file ZIP yang dikombinasikan dengan biner yang dapat dieksekusi. Biner utama hanyalah interpreter Python yang dimodifikasi yang diambil dengan menangkap mekanisme impor Python. Setiap panggilan impor berikutnya dialihkan ke biner ini dengan parsing file ZIP. Tentu saja, mudah untuk mengekstrak ZIP ini dari biner. Misalnya, alat
binwalk yang berguna mengambilnya dengan semua file .pyc yang dikompilasi dengan byte.
Kemudian saya tidak dapat memecahkan enkripsi untuk file .pyc, tetapi pada akhirnya saya mengambil objek umum dari perpustakaan Python standar dan mengkompilasi ulang, menyuntikkan backdoor di dalam. Sekarang klien Dropbox sedang memuat objek ini, saya bisa dengan mudah mengeksekusi kode Python sewenang-wenang dalam juru bahasa yang berfungsi. Meskipun saya menemukan ini sendiri, metode yang sama digunakan oleh Florian Leda dan Nicolas Raff dalam
presentasi di Hack.lu pada 2012.
Kemampuan untuk menjelajahi dan memanipulasi kode yang sedang berjalan di Dropbox telah mengungkapkan banyak hal. Kode menggunakan beberapa trik perlindungan untuk membuatnya sulit untuk membuang
objek kode . Misalnya, dalam juru bahasa CPython biasa, mudah untuk memulihkan bytecode yang dikompilasi mewakili suatu fungsi. Contoh sederhana:
>>> def f(i=0): ... return i * i ... >>> f.__code__ <code object f at 0x109deb540, file "<stdin>", line 1> >>> f.__code__.co_code b'|\x00|\x00\x14\x00S\x00' >>> import dis >>> dis.dis(f) 2 0 LOAD_FAST 0 (i) 2 LOAD_FAST 0 (i) 4 BINARY_MULTIPLY 6 RETURN_VALUE >>>
Tetapi dalam versi
Objek / codeobject.c yang dikompilasi, properti
co_code co_code
dihapus dari daftar terbuka. Daftar anggota ini biasanya terlihat seperti ini:
static PyMemberDef code_memberlist[] = { ... {"co_flags", T_INT, OFF(co_flags), READONLY}, {"co_code", T_OBJECT, OFF(co_code), READONLY}, {"co_consts", T_OBJECT, OFF(co_consts), READONLY}, ... };
Hilangnya properti
co_code
membuatnya tidak mungkin untuk membuang objek kode ini.
Selain itu, perpustakaan lain, seperti
disassembler Python standar, telah dihapus. Pada akhirnya, saya masih berhasil membuang objek kode ke file, tetapi saya masih tidak bisa mendekompilasi mereka. Butuh beberapa saat sebelum saya menyadari bahwa opcodes yang digunakan oleh penerjemah Dropbox tidak cocok dengan opcodes standar Python. Dengan demikian, perlu untuk memahami opcodes baru untuk menulis ulang objek kode kembali ke bytecode Python asli.
Salah satu opsi adalah memetakan ulang opcode. Sejauh yang saya tahu, teknik ini dikembangkan oleh Rich Smith dan diperkenalkan di
Defcon 18 . Dalam pembicaraan itu, ia juga menunjukkan alat
pyretic untuk reverse-engineering bytecode Python dalam memori. Tampaknya kode pyretic kurang didukung, dan alat ini menargetkan biner Python 2.x "lama". Untuk berkenalan dengan teknik-teknik yang muncul dengan Rich, sangat disarankan untuk menonton penampilannya.
Metode terjemahan opcode mengambil semua objek kode dari pustaka Python standar dan membandingkannya dengan objek yang diekstrak dari biner Dropbox. Misalnya, objek kode dari
hashlib.pyc atau
socket.pyc , yang ada di pustaka standar. Katakanlah, jika setiap kali opcode
0x43
sesuai dengan
0x21
0x21 yang didobobekan, kita secara bertahap dapat membangun tabel terjemahan untuk menulis ulang objek kode. Objek kode ini kemudian dapat dilewatkan melalui dekompiler Python. Untuk melakukan dump, Anda masih membutuhkan penerjemah yang diperbaiki dengan objek
co_code
benar.
Pilihan lain adalah meretas format serialisasi. Dalam Python, serialisasi disebut
marshaling . Deserialisasi file yang dikaburkan dengan cara biasa tidak berhasil. Ketika merekayasa balik biner dalam IDA Pro, saya menemukan langkah dekripsi. Sejauh yang saya tahu, yang pertama mempublikasikan sesuatu tentang hal ini adalah Hagen Fritch di
blognya . Di sana ia merujuk pada perubahan dalam versi baru Dropbox (ketika Dropbox beralih dari Python 2.5 ke Python 2.7). Algoritma bekerja sebagai berikut:
- Saat membongkar file pyc, header dibaca untuk menentukan versi marshaling. Format ini tidak didokumentasikan, kecuali untuk implementasi CPython itu sendiri.
- Format mendefinisikan daftar jenis yang dikodekan di dalamnya. Jenis
True
, False
, floats
, dll., Tetapi yang paling penting adalah jenis code object
Python di atas, code object
.
- Saat memuat
code object
, dua nilai tambahan pertama kali dibaca dari file input.
- Yang pertama adalah nilai
random
32-bit.
- Yang kedua adalah nilai
length
32-bit yang menunjukkan ukuran objek kode serial.
- Kemudian nilai
rand
dan length
rand
ke fungsi RNG sederhana yang menghasilkan seed
.
- Benih ini dikirim ke vortex Mersenne , yang menghasilkan empat nilai 32-bit.
- Dikombinasikan bersama, keempat nilai ini menyediakan kunci enkripsi untuk data berseri. Algoritme enkripsi kemudian mendekripsi data menggunakan Tiny Encryption Algorithm .
Dalam kode saya, saya menulis prosedur unmarshaling Python dari awal. Bagian yang mendekripsi objek kode terlihat seperti fragmen di bawah ini. Perlu dicatat bahwa metode ini harus disebut secara rekursif. Objek tingkat atas untuk file pyc adalah objek kode yang berisi objek kode, yang bisa berupa kelas, fungsi, atau lambda. Pada gilirannya, mereka juga dapat berisi metode, fungsi, atau lambda. Ini semua adalah objek kode di hierarki!
def load_code(self): rand = self.r_long() length = self.r_long() seed = rng(rand, length) mt = MT19937(seed) key = [] for i in range(0, 4): key.append(mt.extract_number())
Kemampuan untuk mendekripsi objek kode berarti bahwa setelah deserializing prosedur, Anda perlu menulis ulang kode byte yang sebenarnya. Objek kode berisi informasi tentang nomor baris, konstanta, dan informasi lainnya.
co_code
sebenarnya ada di objek
co_code
. Ketika kami membangun tabel terjemahan opcode, kami cukup mengganti nilai Dropbox yang dikaburkan dengan standar Python 3.6.
Sekarang objek kode berada dalam format Python 3.6 biasa, dan mereka dapat diteruskan ke dekompiler. Kualitas dekompiler Python telah tumbuh secara signifikan berkat proyek
uncompyle6 R. Bernstein. Dekompilasi memberikan hasil yang cukup baik, dan saya dapat menggabungkan semuanya dalam alat yang mendekompilasi versi klien Dropbox saat ini dengan kemampuan terbaiknya.
Jika Anda mengkloning
repositori ini dan mengikuti instruksi, hasilnya akan seperti ini:
...
__main__ - INFO - Dropbox / klien / fitur / browse_search / __ init __. pyc yang berhasil didekompilasi
__main__ - INFO - Mengurai sandi, menambal, dan mendekompilasi _bootstrap_overrides.pyc
__main__ - INFO - Berhasil mendekompilasi _bootstrap_overrides.pyc
__main__ - INFO - Diproses 3713 file (3591 berhasil didekompilasi, 122 gagal)
opcodemap - PERINGATAN - JANGAN menulis peta opcode karena force overwrite tidak diatur
Ini berarti bahwa Anda sekarang memiliki direktori
out/
dengan versi kode sumber Dropbox yang didekompilasi.
Mengaktifkan Pelacakan Dropbox
Di open source, saya mulai mencari sesuatu yang menarik, dan fragmen berikut menarik perhatian saya. Penangan jejak di
out/dropbox/client/high_trace.py
diinstal hanya jika unit tidak beku atau kunci ajaib atau cookie yang membatasi fungsi tidak diatur dalam baris
1430
.
1424 def install_global_trace_handlers(flags=None, args=None): 1425 global _tracing_initialized 1426 if _tracing_initialized: 1427 TRACE('!! Already enabled tracing system') 1428 return 1429 _tracing_initialized = True 1430 if not build_number.is_frozen() or magic_trace_key_is_set() or limited_support_cookie_is_set(): 1431 if not os.getenv('DBNOLOCALTRACE'): 1432 add_trace_handler(db_thread(LtraceThread)().trace) 1433 if os.getenv('DBTRACEFILE'): 1434 pass
Menyebutkan build beku mengacu pada build debug internal Dropbox. Dan sedikit lebih tinggi di file yang sama Anda dapat menemukan baris seperti itu:
272 def is_valid_time_limited_cookie(cookie): 273 try: 274 try: 275 t_when = int(cookie[:8], 16) ^ 1686035233 276 except ValueError: 277 return False 278 else: 279 if abs(time.time() - t_when) < SECONDS_PER_DAY * 2 and md5(make_bytes(cookie[:8]) + b'traceme').hexdigest()[:6] == cookie[8:]: 280 return True 281 except Exception: 282 report_exception() 283 284 return False 285 286 287 def limited_support_cookie_is_set(): 288 dbdev = os.getenv('DBDEV') 289 return dbdev is not None and is_valid_time_limited_cookie(dbdev) build_number/environment.py
Seperti yang Anda lihat dari metode
limited_support_cookie_is_set
pada baris
287
, pelacakan hanya diaktifkan jika variabel lingkungan bernama
DBDEV
diatur dengan benar ke cookie dengan masa hidup terbatas. Yah, itu menarik! Dan sekarang kita tahu cara menghasilkan cookie dengan batas waktu tersebut. Menilai dari namanya, insinyur Dropbox dapat menghasilkan cookie semacam itu, dan kemudian mengaktifkan penelusuran sementara untuk beberapa kasus saat diperlukan untuk mendukung pelanggan. Setelah memulai ulang Dropbox atau menyalakan kembali komputer, bahkan jika cookie yang ditentukan masih ada, secara otomatis akan kedaluwarsa. Saya kira ini harus mencegah, misalnya, penurunan kinerja karena penelusuran terus-menerus. Ini juga menyulitkan untuk melakukan rekayasa ulang Dropbox.
Namun, skrip kecil hanya dapat secara konstan menghasilkan dan mengatur cookie ini. Sesuatu seperti ini:
Kemudian cookie berbasis waktu dibuat:
$ python3 setenv.py DBDEV=38b28b3f349714; export DBDEV;
Kemudian muat dengan benar output skrip ini ke lingkungan dan jalankan klien Dropbox.
$ eval `python3 setenv.py` $ ~/.dropbox-dist/dropbox-lnx_64-71.4.108/dropbox
Ini termasuk jejak hasil, dengan format warna-warni dan semua itu. Itu terlihat seperti klien yang tidak terdaftar ini:

Terapkan Kode Baru
Semua ini sedikit lucu. Mempelajari kode yang didekompilasi lebih lanjut, kami mencari
out/build_number/environment.pyc
. Ada fungsi yang memeriksa apakah kunci ajaib tertentu diinstal. Kunci ini tidak dikodekan dalam kode, tetapi dibandingkan dengan hash SHA-256. Berikut ini cuplikan yang sesuai.
1 import hashlib, os 2 from typing import Optional, Text 3 _MAGIC_TRACE_KEY_IS_SET = None 4 5 def magic_trace_key_is_set(): 6 global _MAGIC_TRACE_KEY_IS_SET 7 if _MAGIC_TRACE_KEY_IS_SET is None: 8 dbdev = os.getenv('DBDEV') or '' 9 if isinstance(dbdev, Text): 10 bytes_dbdev = dbdev.encode('ascii') 11 else: 12 bytes_dbdev = dbdev 13 dbdev_hash = hashlib.sha256(bytes_dbdev).hexdigest() 14 _MAGIC_TRACE_KEY_IS_SET = dbdev_hash == 'e27eae61e774b19f4053361e523c771a92e838026da42c60e6b097d9cb2bc825' 15 return _MAGIC_TRACE_KEY_IS_SET
Metode ini dipanggil beberapa kali dari tempat yang berbeda dalam kode untuk memeriksa apakah kunci jejak ajaib diatur. Saya mencoba untuk memecahkan hash SHA-256 dengan kekuatan kasar
John the Ripper , tetapi kekuatan kasar sederhana terlalu lama, dan saya tidak dapat mengurangi jumlah opsi karena tidak ada dugaan tentang konten. Di Dropbox, pengembang dapat memiliki kunci pengembangan kode-keras tertentu, yang mereka pasang jika perlu, mengaktifkan mode "kunci ajaib" klien untuk pelacakan.
Ini mengganggu saya karena saya ingin menemukan cara cepat dan mudah untuk meluncurkan Dropbox dengan set kunci ini untuk dilacak. Jadi saya menulis prosedur marshaling yang menghasilkan file pyc terenkripsi sesuai dengan enkripsi Dropbox. Jadi, saya bisa memasukkan kode saya sendiri atau cukup mengganti hash di atas. Kode ini di repositori Github ada di file
patchzip.py
. Akibatnya, hash digantikan oleh hash SHA-256 dari
ANVILVENTURES
. Kemudian objek kode dienkripsi ulang dan ditempatkan di zip, di mana semua kode yang dikaburkan disimpan. Ini memungkinkan Anda melakukan hal berikut:
$ DBDEV = ANVILVENTURES; ekspor DBDEV;
$ ~ / .dropbox-dist / dropbox-lnx_64-71.4.108 / dropbox
Sekarang semua fungsi debugging ditampilkan ketika Anda mengklik kanan ikon Dropbox di baki sistem.
Mempelajari sumber-sumber yang didekompilasi lebih lanjut, di file
dropbox/webdebugger/server.py
saya menemukan metode yang disebut
is_enabled
. Sepertinya sedang memeriksa apakah akan mengaktifkan debugger web bawaan. Pertama-tama, dia memeriksa kunci ajaib yang disebutkan. Karena kami mengganti hash SHA-256, kami cukup mengatur nilainya menjadi
ANVILVENTURES
. Bagian kedua pada baris
201
dan
202
memeriksa untuk melihat apakah ada variabel lingkungan bernama
DB<x>
yang memiliki
x
sama dengan hash SHA-256. Nilai lingkungan menetapkan cookie terbatas waktu, seperti yang telah kita lihat.
191 @classmethod 192 def is_enabled(cls): 193 if cls._magic_key_set: 194 return cls._magic_key_set 195 else: 196 cls._magic_key_set = False 197 if not magic_trace_key_is_set(): 198 return False 199 for var in os.environ: 200 if var.startswith('DB'): 201 var_hash = hashlib.sha256(make_bytes(var[2:])).hexdigest() 202 if var_hash == '5df50a9c69f00ac71f873d02ff14f3b86e39600312c0b603cbb76b8b8a433d3ff0757214287b25fb01' and is_valid_time_limited_cookie(os.environ[var]): 203 cls._magic_key_set = True 204 return True 205 206 return False
Menggunakan teknik yang persis sama, mengganti hash ini dengan SHA-256 yang digunakan sebelumnya, kita sekarang dapat mengubah skrip
setenv
ditulis sebelumnya menjadi seperti ini:
$ cat setenv.py … c = generate_time_cookie() output_env("DBDEV", "ANVILVENTURES") output_env("DBANVILVENTURES", c) $ python3 setenv.py DBDEV=ANVILVENTURES; export DBDEV; DBANVILVENTURES=38b285c4034a67; export DBANVILVENTURES $ eval `python3 setenv.py` $ ~/.dropbox-dist/dropbox-lnx_64-71.4.108/dropbox
Seperti yang Anda lihat, setelah memulai klien, port TCP baru terbuka untuk mendengarkan. Itu tidak akan terbuka jika variabel lingkungan tidak diatur dengan benar.
$ netstat --tcp -lnp | grep dropbox
tcp 0 0 127.0.0.1:4242 0.0.0.0:* DENGARKAN 1517 / dropbox
Lebih lanjut dalam kode, Anda dapat menemukan antarmuka WebSocket di file
webpdb.pyc
. Ini adalah pembungkus untuk utilitas standar python
pdb . Akses ke antarmuka adalah melalui server HTTP pada port ini. Mari kita instal
klien websocket dan
mencobanya :
$ websocat -t ws: //127.0.0.1: 4242 / pdb
--Kembali--
> /home/gvb/dropbox/webdebugger/webpdb.pyc(101)run()->Tidak ada
>
(Pdb) dari build_number.environment import magic_trace_key_is_set sebagai ms
(Pdb) ms ()
Benar
Dengan demikian, sekarang kami memiliki debugger lengkap di klien, yang dalam semua hal lain berfungsi seperti sebelumnya. Kami dapat mengeksekusi kode Python sewenang-wenang, kami dapat mengaktifkan menu debug internal dan melacak fungsi. Semua ini akan sangat membantu dalam analisis lebih lanjut dari klien Dropbox.
Kesimpulan
Kami dapat merekayasa balik Dropbox, menulis dekripsi kode, dan alat injeksi yang berfungsi dengan klien Dropbox saat ini berdasarkan Python 3.6. Kami merekayasa balik fungsi tersembunyi individu dan mengaktifkannya. Jelas, debugger akan sangat membantu dalam peretasan lebih lanjut. Terutama dengan sejumlah file yang tidak dapat berhasil didekompilasi karena kelemahan dari decompyle6.
Kode
Kode dapat ditemukan
di Github . Instruksi untuk digunakan di sana. Repositori ini juga berisi kode lama saya yang ditulis pada tahun 2011. Seharusnya hanya berfungsi dengan beberapa modifikasi, asalkan seseorang memiliki versi Dropbox yang lebih lama berdasarkan Python 2.7.
Repositori juga berisi skrip untuk menyiarkan opcode, instruksi untuk mengatur variabel lingkungan Dropbox dan semua yang diperlukan untuk mengubah file zip.
Ucapan Terima Kasih
Terima kasih kepada Brian dari Anvil Ventures karena meninjau kode saya. Bekerja pada kode ini berlanjut selama beberapa tahun, dari waktu ke waktu saya memperbaruinya, memperkenalkan metode baru dan menulis ulang fragmen untuk mengembalikannya agar berfungsi pada versi baru Dropbox.
Seperti disebutkan sebelumnya, titik awal yang bagus untuk aplikasi reverse engineering Python adalah karya Rich Smith, Florian Ledoux dan Nicolas Raff, serta Hagen Fritch. Pekerjaan mereka sangat relevan untuk pengembangan sebaliknya dari salah satu aplikasi Python terbesar di dunia - klien Dropbox.
Perlu dicatat bahwa dekompilasi kode Python telah sangat maju berkat proyek uncompyle6 yang dipimpin oleh R. Bernstein. Dekompiler ini telah mengkompilasi dan meningkatkan banyak dekompiler Python yang berbeda.
Juga banyak terima kasih kepada rekan-rekan Brian, Austin, Stefan dan Chris untuk meninjau artikel ini.