Lompat ke konten
dokumentasi.id
Commit dd5271a
v2
Star6,4 rb

Kamu membaca versi lama. Halaman ini hanya bisa dibaca.

2.2

Siklus Hidup Kelas APKLeaks

APKLeaks adalah satu kelas yang instansnya dibentuk sekali per pemanggilan CLI. Urutan fase hidupnya tidak ditentukan oleh kelas itu sendiri, melainkan oleh main di apkleaks/cli.py: APKLeaks(args) dibuat sebagai init, lalu init.integrity(), init.decompile(), dan init.scanning() dipanggil di dalam try, sementara init.cleanup() dipanggil di blok finally apkleaks/cli.py:32-41. Karena finally selalu dieksekusi, cleanup tetap berjalan walaupun salah satu fase memanggil sys.exit.

Diagram keadaan siklus hidup

stateDiagram-v2
    [*] --> Integrity
    Integrity --> Decompile: self.apk terisi
    Integrity --> Berhenti: sys.exit
    Decompile --> Scanning: perintah jadx selesai
    Scanning --> Berhenti: self.apk masih None
    Scanning --> Cleanup: semua thread extract dimulai
    Berhenti --> Cleanup: blok finally
    Cleanup --> Tersimpan: self.scanned True
    Cleanup --> Kosong: self.scanned False
    Tersimpan --> [*]
    Kosong --> [*]
    style Integrity fill:#f59e0b,color:#000
    style Decompile fill:#8b5cf6,color:#fff
    style Scanning fill:#2d7d46,color:#fff
    style Cleanup fill:#336791,color:#fff
    style Berhenti fill:#dc2626,color:#fff
    style Tersimpan fill:#2d7d46,color:#fff
    style Kosong fill:#dc2626,color:#fff
%% fig: Siklus hidup instance APKLeaks
%% desc: Empat fase dijalankan berurutan oleh cli.main; sys.exit di fase mana pun tetap berakhir di cleanup lewat blok finally.
Fig. 1: Siklus hidup instance APKLeaksEmpat fase dijalankan berurutan oleh cli.main; sys.exit di fase mana pun tetap berakhir di cleanup lewat blok finally.

Satu-satunya jalur menuju Decompile adalah integrity yang berhasil mengisi self.apk. integrity memang mengembalikan nilai self.apk untuk berkas yang valid, tetapi main memanggilnya tanpa menampung nilai kembalian; yang dipakai scanning adalah atribut self.apk sebagai efek samping apkleaks/apkleaks.py:78-87. Berhenti bukan state internal kelas, melainkan nama untuk setiap sys.exit di dalam fase, dan semuanya bermuara ke cleanup yang sama.

Atribut yang dibentuk __init__

Seluruh state siklus hidup disiapkan di __init__, sebelum satu pun fase berjalan apkleaks/apkleaks.py:25-39. Tabel berikut memetakan setiap atribut ke nilai awalnya dan ke fase yang memakainya.

AtributNilai awalDipakai oleh
self.apkNonediisi integrity, diperiksa scanning
self.fileos.path.realpath(args.file)integrity, decompile
self.jsonargs.jsonextract, cleanup
self.disargargs.argsdecompile
self.prefix"apkleaks-"prefiks tempdir dan output
self.tempdirtempfile.mkdtemp(prefix=self.prefix)decompile, scanning, cleanup
self.main_diros.path.dirname(os.path.realpath(__file__))self.pattern, self.jadx, dependencies
self.outputtempfile.mkstemp(...) atau args.outputself.fileout, cleanup
self.fileoutopen(self.output, "w" if self.json else "a")extract, cleanup
self.patternconfig/regexes.json atau args.patternscanning
self.jadxwhich("jadx") atau <root>/jadx/bin/jadxintegrity, dependencies, decompile
self.out_json{}scanning, extract, cleanup
self.scannedFalseextract, cleanup

Dua efek samping penting melekat pada konstruksi ini. Pertama, self.tempdir dan self.output sudah dibuat saat __init__ selesai, sebelum integrity tahu apakah jadx ada apkleaks/apkleaks.py:31. Kedua, self.fileout langsung dibuka sehingga berkas output sudah ada di disk sejak awal, dan penanganannya diserahkan ke cleanup. __init__ juga memanggil logging.config.dictConfig({"version": 1, "disable_existing_loggers": True}) untuk mematikan logger yang sudah terdaftar, sehingga satu-satunya keluaran yang terlihat adalah yang ditulis lewat util.

Gerbang integrity: jadx, berkas, dan APK

integrity adalah satu-satunya fase yang punya interaksi dengan pengguna, dan ia memeriksa dua hal secara berurutan: keberadaan binary jadx lalu validitas berkas masukan apkleaks/apkleaks.py:55-87.

graph TD
    A["integrity()"] --> B{"os.path.exists(self.jadx)"}
    B -- False --> C["writeln: Can't find jadx binary."]
    C --> P["while True: prompt (Y/n)"]
    P --> D{"choice = input().lower()"}
    D -- "kosong, yes, y, ye" --> E["dependencies()"]
    D -- "no, n" --> I["sys.exit: ** Aborted."]
    D -- "nilai lain" --> R["writeln: Please respond with 'yes' or 'no' (or 'y' or 'n')."]
    R --> P
    D -- KeyboardInterrupt --> J["sys.exit: ** Interrupted. Aborting."]
    E --> F{"urlopen, extractall, chmod"}
    F -- error --> G["writeln str(error) lalu sys.exit()"]
    F -- sukses --> H{"os.path.isfile(self.file)"}
    B -- True --> H
    H -- False --> K["sys.exit: It's not a valid file!"]
    H -- True --> L["self.apk = self.apk_info()"]
    L -- exception --> M["writeln str(error) lalu sys.exit()"]
    L -- sukses --> N["return self.apk"]
    style A fill:#f59e0b,color:#000
    style E fill:#8b5cf6,color:#fff
    style N fill:#2d7d46,color:#fff
    style G fill:#dc2626,color:#fff
    style I fill:#dc2626,color:#fff
    style J fill:#dc2626,color:#fff
    style K fill:#dc2626,color:#fff
    style M fill:#dc2626,color:#fff
%% fig: Keputusan di dalam integrity
%% desc: integrity memeriksa keberadaan jadx lebih dulu, menawarkan unduhan, lalu memvalidasi berkas dan membentuk objek APK.
Fig. 2: Keputusan di dalam integrityintegrity memeriksa keberadaan jadx lebih dulu, menawarkan unduhan, lalu memvalidasi berkas dan membentuk objek APK.

Loop prompt memakai input() tanpa argumen; pesan Do you want to download jadx? (Y/n) ditulis lebih dulu lewat util.write, dan jawaban kosong dipetakan ke valid["y"] sehingga menekan Enter berarti setuju apkleaks/apkleaks.py:56-77. Kamus valid hanya memuat kunci yes, y, ye, no, dan n; masukan lain memicu peringatan dan loop diulang. KeyboardInterrupt saat menunggu masukan langsung mengakhiri proses dengan pesan ** Interrupted. Aborting..

Bila pengguna memilih unduh, dependencies mengambil zip jadx dari URL rilis yang dipatok ke versi 1.2.0, mengekstraknya ke <root>/jadx, lalu memanggil os.chmod(self.jadx, 33268) apkleaks/apkleaks.py:44-53. Kegagalan di jalur mana pun ditangkap sebagai satu Exception, ditulis dengan warna col.WARNING, lalu diakhiri sys.exit(). Perhatikan bahwa self.jadx sudah dihitung di __init__ dari which("jadx") dengan cadangan path bawaan yang memakai akhiran .bat ketika os.name == "nt" apkleaks/apkleaks.py:36.

⚠️ Jalur keluar tercepat ada di jawaban no atau n: proses berhenti dengan ** Aborted. tanpa pernah menyentuh decompile. Pembatalan ini tetap memicu cleanup lewat finally, sehingga tempdir dihapus dan berkas output yang sudah dibuka di __init__ ikut dibuang.

decompile: satu perintah jadx ke tempdir

Fase ini menyusun daftar argumen [self.jadx, self.file, "-d", self.tempdir], menambahkan argumen disassembler dengan re.split(r"\s|=", self.disarg), menggabungkan semuanya lewat quote, mengganti petik tunggal menjadi petik ganda, lalu menjalankannya melalui os.system apkleaks/apkleaks.py:89-98. Karena hasil os.system tidak diperiksa dan tidak ada nilai kembali, decompile menganggap apa pun yang terjadi sebagai selesai, sehingga scanning akan tetap dijalankan walaupun jadx gagal menulis ke tempdir. Rincian opsi --args dan pengaruhnya dibahas di Opsi CLI dan Argumen Disassembler.

scanning: satu thread per pola

scanning dibuka dengan pemeriksaan self.apk is None; bila jadx atau validasi berkas tadi berhenti tanpa mengisi self.apk, fase ini mengakhiri proses dengan ** Undefined package. Exit! apkleaks/apkleaks.py:117-119. Setelah itu self.out_json["package"] diisi self.apk.package dan self.out_json["results"] direset menjadi daftar kosong.

Berkas self.pattern dibaca sebagai JSON, lalu setiap entri diproses menurut bentuknya: nilai berupa list menghasilkan satu threading.Thread untuk setiap elemen, sedangkan nilai tunggal menghasilkan satu thread apkleaks/apkleaks.py:120-138. Setiap thread menjalankan self.extract dengan (name, util.finder(p, self.tempdir)) sebagai argumen; cara util.finder mencocokkan pola dijelaskan di Mesin Pencarian Regex dan Keluaran Warna.

extract adalah satu-satunya tempat self.scanned berubah menjadi True, dan perubahan itu hanya terjadi bila len(matches) tidak nol apkleaks/apkleaks.py:100-115. Method ini juga yang menambahkan {"name": name, "matches": matches} ke self.out_json["results"] dan menulis baris hasil ke self.fileout ketika mode JSON tidak aktif.

⚠️ scanning memanggil thread.start() tanpa join, sehingga method itu kembali tanpa menunggu thread mana pun selesai apkleaks/apkleaks.py:120-138. cleanup di blok finally langsung berjalan sesudahnya: shutil.rmtree(self.tempdir) dapat menghapus direktori yang masih dibaca thread, dan self.fileout dapat ditutup saat thread masih menulis. self.scanned juga masih False pada saat itu, sehingga berkas output bisa dihapus padahal hasil pemindaian menyusul.

cleanup: dua cabang berdasarkan scanned

cleanup selalu menghapus self.tempdir lebih dulu, lalu memilih cabang berdasarkan self.scanned apkleaks/apkleaks.py:140-149.

KondisiAksiAkibat pada self.output
self.scanned True, self.json Truetulis json.dumps(self.out_json, indent=4) lalu close()berkas berisi laporan JSON
self.scanned True, self.json Falsetulis string kosong lalu close(), cetak ** Results saved into ...berkas berisi baris hasil dari extract
self.scanned Falseclose() lalu os.remove(self.output)berkas dihapus, tercetak ** Done with nothing. ¯\_(ツ)_/¯

⚠️ os.remove(self.output) menghapus self.output apa adanya, dan self.output bisa berasal dari opsi --output pengguna, bukan hanya berkas sementara di __init__. Pemindaian yang tidak menemukan satu pun kecocokan akan menghapus berkas tujuan tersebut tanpa konfirmasi. Format isi berkas hasil dibahas di Format Keluaran Hasil Pemindaian.