2.1
Alur Eksekusi CLI
Satu request CLI masuk lewat dua pintu yang sama-sama berakhir di main(): entry point apkleaks = "apkleaks.cli:main" (pyproject.toml:16-171) dan berkas pembungkus di akar repository (apkleaks.py:1-52). main() sendiri hanya berisi lima langkah: mencetak banner, mem-parsing argumen, membangun objek APKLeaks, menjalankan tiga fase di dalam try, lalu memanggil cleanup() di blok finally (apkleaks/cli.py:32-413).
Urutan pemanggilan satu request
sequenceDiagram
autonumber
actor U as "shell"
participant E as "console script apkleaks"
participant C as "main() di cli.py"
participant P as "argparse"
participant A as "APKLeaks"
participant J as "jadx"
participant T as "thread extract()"
U->>E: "apkleaks -f file.apk --json"
E->>C: "main()"
C->>C: "header(): VERSION + banner ke stderr"
C->>P: "argument()"
P-->>C: "Namespace(file, output, pattern, args, json)"
C->>A: "APKLeaks(args)"
A->>A: "mkdtemp(prefix=apkleaks-) + open(self.output)"
Note over C,A: "blok try dimulai"
C->>A: "integrity()"
A->>A: "which(jadx), prompt unduh bila tidak ada"
A->>A: "APK(self.file) menjadi self.apk"
C->>A: "decompile()"
A->>J: "os.system(jadx file -d tempdir)"
J-->>A: "kode balik tidak diperiksa"
C->>A: "scanning()"
A->>T: "start() satu thread per pola"
Note over C,A: "blok finally"
C->>A: "cleanup()"
A->>A: "rmtree(tempdir), close(fileout)"
%% fig: Urutan pemanggilan dari main() sampai cleanup()
%% desc: main() menyiapkan banner dan argumen, membangun APKLeaks, lalu menjalankan integrity, decompile, dan scanning sebelum cleanup di blok finally.header() mencetak banner ke sys.stderr, bukan ke stdout (apkleaks/cli.py:10-204). Pemisahan itu penting karena hasil pemindaian ditulis ke stdout lewat print(stdout) di extract() (apkleaks/apkleaks.py:100-1155), sehingga keluaran bisa dialihkan ke berkas tanpa tercampur banner. Versi yang dicetak diambil bertingkat: importlib.metadata.version("apkleaks"), lalu pkg_resources.require("apkleaks")[0].version, dan terakhir isi berkas VERSION (apkleaks/cli.py:10-204).
argument() mengembalikan satu Namespace dari argparse: -f/--file wajib, -o/--output, -p/--pattern, dan -a/--args opsional, --json memakai action="store_true" (apkleaks/cli.py:22-306). Arti tiap flag dan bentuk --args dibahas di Opsi CLI dan Argumen Disassembler.
Keadaan yang disiapkan sebelum try
Objek APKLeaks tidak hanya menyimpan argumen. Konstruktornya sudah membuat direktori kerja sementara dan membuka berkas keluaran (apkleaks/apkleaks.py:25-397):
| Atribut | Nilai saat APKLeaks(args) dijalankan | Rujukan |
|---|---|---|
self.file | os.path.realpath(args.file) | apkleaks/apkleaks.py:278 |
self.tempdir | tempfile.mkdtemp(prefix="apkleaks-") | apkleaks/apkleaks.py:319 |
self.output | tempfile.mkstemp(...) bersuffix .json atau .txt, kecuali args.output diisi | apkleaks/apkleaks.py:3310 |
self.fileout | open(self.output, "w") bila --json, selain itu "a" | apkleaks/apkleaks.py:3411 |
self.pattern | config/regexes.json milik paket, kecuali args.pattern diisi | apkleaks/apkleaks.py:3512 |
self.jadx | which("jadx"), jika tidak ada memakai jadx/bin/jadx di dalam paket | apkleaks/apkleaks.py:3613 |
self.out_json, self.scanned | {} dan False | apkleaks/apkleaks.py:37-3814 |
Pembuatan objek berada di luar blok try (apkleaks/cli.py:35-4115). Artinya cleanup() hanya menjamin pembersihan untuk tiga fase sesudahnya, bukan untuk self.tempdir dan self.fileout yang dibuat di apkleaks/apkleaks.py:31-3416.
⚠️ Bila
mkstempatauopen(self.output)gagal,finallytidak pernah menyala.self.tempdiryang sudah dibuat di baris sebelumnya tertinggal di sistem berkas.
Fase dan jalur keluar
graph TD
MAIN["main() cli.py:32"] --> HDR["header()"]
HDR --> ARG["argument(): argparse"]
ARG --> INIT["APKLeaks(args): mkdtemp + open(output)"]
INIT --> INT["integrity()"]
INT -->|"self.jadx tidak ada"| ASK{"prompt: Do you want to download jadx? (Y/n)"}
ASK -->|"y"| DEP["dependencies()"]
ASK -->|"n"| ABORT["sys.exit: Aborted"]
DEP --> CHK
INT -->|"self.jadx ada"| CHK{"os.path.isfile(self.file)"}
CHK -->|"bukan berkas"| INVALID["sys.exit: not a valid file"]
CHK -->|"berkas valid"| APKI["APK(self.file) menjadi self.apk"]
APKI --> DEC["decompile(): os.system jadx"]
DEC --> SCN["scanning(): thread per pola"]
SCN --> CLN["finally: cleanup()"]
ABORT --> CLN
INVALID --> CLN
CLN --> SCANNED{"self.scanned"}
SCANNED -->|"True"| SAVED["tulis out_json, Results saved into ..."]
SCANNED -->|"False"| EMPTY["os.remove(output), Done with nothing"]
style INIT fill:#336791,color:#fff
style CLN fill:#f59e0b,color:#000
style ASK fill:#f59e0b,color:#000
style CHK fill:#f59e0b,color:#000
style SCANNED fill:#f59e0b,color:#000
style DEP fill:#2d7d46,color:#fff
style SAVED fill:#2d7d46,color:#fff
style ABORT fill:#dc2626,color:#fff
style INVALID fill:#dc2626,color:#fff
style EMPTY fill:#dc2626,color:#fff
style DEC fill:#8b5cf6,color:#fff
style SCN fill:#8b5cf6,color:#fff
%% fig: Percabangan integrity(), decompile(), dan cleanup()
%% desc: integrity() dapat berhenti lewat sys.exit, sementara cleanup() selalu jalan dan memilih antara menyimpan hasil atau menghapus berkas keluaran berdasarkan self.scanned.| Fase | Masukan | Efek samping | Rujukan |
|---|---|---|---|
integrity() | self.jadx, self.file | meminta konfirmasi unduh, memanggil dependencies(), menetapkan self.apk | apkleaks/apkleaks.py:55-8717 |
dependencies() | URL jadx-1.2.0.zip | ekstraksi ke direktori jadx/, lalu os.chmod(self.jadx, 33268) | apkleaks/apkleaks.py:44-5318 |
decompile() | self.jadx, self.file, self.tempdir, self.disarg | os.system menulis hasil jadx ke self.tempdir | apkleaks/apkleaks.py:89-9819 |
scanning() | self.pattern, self.tempdir | mengisi self.out_json, self.scanned, dan self.fileout | apkleaks/apkleaks.py:117-13820 |
cleanup() | self.tempdir, self.scanned, self.fileout | shutil.rmtree, menutup fileout, os.remove(self.output) bila tidak ada temuan | apkleaks/apkleaks.py:140-14821 |
decompile() menyusun args menjadi [self.jadx, self.file, "-d", self.tempdir], menambahkan re.split(r"\s|=", self.disarg) di dalam try/except yang menelan semua error, lalu menjalankannya lewat os.system (apkleaks/apkleaks.py:89-9819). scanning() menolak berjalan bila self.apk is None (apkleaks/apkleaks.py:117-11922).
Pesan keluar dan pemicunya
Semua pesan ditulis lewat util.writeln dengan warna dari apkleaks.colors.color seperti col.OKBLUE, col.WARNING, dan col.FAIL (apkleaks/colors.py:4-923).
| Pesan | Pemicu | Rujukan |
|---|---|---|
Can't find jadx binary. | self.jadx tidak ada, sebelum prompt unduh | apkleaks/apkleaks.py:5724 |
Please respond with 'yes' or 'no' (or 'y' or 'n'). | jawaban prompt tidak ada di valid | apkleaks/apkleaks.py:7025 |
** Interrupted. Aborting. | KeyboardInterrupt saat prompt | apkleaks/apkleaks.py:7226 |
** Aborted. | prompt dijawab n | apkleaks/apkleaks.py:7727 |
It's not a valid file! | os.path.isfile(self.file) bernilai salah | apkleaks/apkleaks.py:8728 |
** Undefined package. Exit! | self.apk is None saat scanning() | apkleaks/apkleaks.py:11929 |
** Results saved into ... | self.scanned bernilai benar | apkleaks/apkleaks.py:14530 |
** Done with nothing. ¯\_(ツ)_/¯ | self.scanned bernilai salah, berkas keluaran dihapus | apkleaks/apkleaks.py:14931 |
⚠️
sys.exit()melemparSystemExit, bukanException. Blokfinallydi apkleaks/cli.py:40-4132 tetap dieksekusi, sehingga sesi yang dibatalkan di tengahintegrity()pun menghapusself.tempdirdan menghapusself.outputlewat jalur** Done with nothing.di apkleaks/apkleaks.py:146-14933.
Dua jebakan di ujung alur
os.system di decompile() tidak memeriksa kode balik (apkleaks/apkleaks.py:96-9834). jadx yang gagal tetap dilanjutkan ke scanning(), yang menyusuri self.tempdir kosong, tidak menemukan apa pun, dan berakhir di ** Done with nothing. sehingga kegagalan disassembler tampak seperti APK bersih.
scanning() tidak memanggil join() pada thread yang dijalankannya (apkleaks/apkleaks.py:129-13635). Nilai util.finder(p, self.tempdir) dievaluasi lebih dulu di thread pemanggil (apkleaks/utils.py:17-3136), jadi thread hanya membawa extract() yang menulis ke self.fileout. Karena cleanup() langsung menutup self.fileout setelah scanning() kembali (apkleaks/apkleaks.py:140-14437), penulisan hasil bisa terpotong.