Android Offline License System တစ်ခုကို Reverse Engineering လုပ်ကြည့်ခဲ့တဲ့ အတွေ့အကြုံ
Android application တွေထဲမှာ license system နဲ့ ကာကွယ်ထားတဲ့ app တွေ အများကြီးရှိပါတယ်။ Server-based license check လုပ်တဲ့ app တွေရှိသလို device ပေါ်မှာပဲ offline စစ်ဆေးတဲ့ app တွေလည်း ရှိပါတယ်။ Offline app တွေက server ဘက်ကို connection မလုပ်ပဲ license ကို ဘယ်လို validate လုပ်တာလဲ။ Key ကို ဘယ်လို generate လုပ်တာလဲ။ Encryption ကို ဘယ်လို handle လုပ်ထားလဲ။
ဒီ write-up မှာ ကျွန်တော် ကိုယ်တိုင် Android app တစ်ခုရဲ့ license system ကို reverse engineering လုပ်ကြည့်ခဲ့တဲ့ process ကို sharing လုပ်ပေးသွားပါမယ်။ ဘယ်နေရာမှာ stuck ဖြစ်ခဲ့တယ်၊ ဘယ်လို အမှားလုပ်မိတယ်၊ နောက်ဆုံးမှာ ဘာ lessons တွေ ယူခဲ့ရတယ်ဆိုတာ ပါပါတယ်။
ပထမဆုံး APK ကို JADX နဲ့ decompile လုပ်ကြည့်ပါတယ်။ Java source code ထွက်လာပြီး ဖတ်ကြည့်တဲ့အခါ ပထမဆုံး သတိထားမိတာက variable names တွေ အကုန်လုံး obfuscated ဖြစ်နေတာပါ။ တစ်ချို့ variable names တွေက underscore နဲ့ v အက္ခရာ ရှည်ရှည်တွေပဲ ဖြစ်နေပါတယ်။ ဖတ်ရ အတော်ခက်ပါတယ်။
Framework ကို ရှာကြည့်တော့ Basic4Android နဲ့ ရေးထားတာ ဖြစ်ကြောင်း သိရပါတယ်။ B4A က Visual Basic-style language ဖြစ်ပြီး compile လုပ်ရင် Java bytecode ထွက်ပါတယ်။ Import statements တွေမှာ B4A framework ရဲ့ package pattern ကို မြင်ရပါတယ်။
နောက်ထပ် သတိထားမိတာ တစ်ခုကတော့ code ထဲမှာ string literals တွေ hardcode လုပ်ထားတာ တစ်ခုမှ မတွေ့ရတာပါ။ AES key, database name, password — ဒီ sensitive strings တွေ အကုန်လုံးကို obfuscation function တစ်ခုနဲ့ ဖုံးကွယ်ထားပါတယ်။ ဒီ function က byte array တစ်ခုနဲ့ integer index တစ်ခု ယူပြီး string return ပြန်ပေးတယ်။
Obfuscation function ကို reverse engineering လုပ်ကြည့်ပါတယ်။ Algorithm ကို နားလည်လာတဲ့အခါ ဒီလို တွေ့ရပါတယ်။ App ရဲ့ package name, version name, version code တို့ကနေ seed data ၅ ခု ဆောက်ပါတယ်။ ပြီးတော့ input byte တစ်ခုချင်းစီကို seed bytes နဲ့ XOR operation လုပ်ပြီး original string ကို ပြန်ထုတ်ပါတယ်။ Seed array ကို rotating pattern နဲ့ ပြောင်းသုံးတာမို့ pattern prediction လုပ်ရ ခက်အောင် ရည်ရွယ်ထားပုံ ရပါတယ်။
ဒီနေရာမှာ ကျွန်တော် သတိထားမိတဲ့ အချက်ကတော့ decode ဖို့ app ရဲ့ package name, version name, version code ၃ ခုလုံး အတိအကျ လိုတယ်ဆိုတာပါ။ ၃ ခုထဲက တစ်ခုခု မှားနေရင် output က garbage ထွက်ပါတယ်။ Package name ကို Java source ရဲ့ declaration ကနေ ရယူနိုင်ပါတယ်။ Version info ကတော့ AndroidManifest ထဲမှာ ပါပါတယ်။
Node.js script ရေးပြီး ဒီ function ကို re-implement လုပ်ကြည့်ပါတယ်။ Run လိုက်တော့ obfuscated strings တွေ decoded ထွက်လာပါတယ်။ AES IV, AES Key, Database name, Database password suffix — ဒီ critical values တွေ အကုန် ရသွားပါတယ်။
ဒီအဆင့်မှာ ကျွန်တော် AES parameters ရပြီဆိုတာ သိသွားပါတယ်။ ဒါပေမယ့် ဒီနေရာမှာ ကျွန်တော် အကြီးဆုံး အမှားလုပ်မိခဲ့ပါတယ်။
Code ထဲမှာ AES encryption class ရဲ့ instance ကို database module ထဲမှာ initialize လုပ်ထားတာ တွေ့ပါတယ်။ Key length ကို ကြည့်တော့ 16 bytes ဖြစ်တယ်၊ ဒါဆို AES-128 ပေါ့လို့ ယူဆပြီး key နဲ့ encrypted key ကို decrypt ကြည့်ပါတယ်။
Result က bad decrypt error ပါ။ Key မှားနေပြီ။
ဒီနေရာမှာ ကျွန်တော် အတော်ကြာ ခေါင်းရှုပ်ခဲ့ရပါတယ်။ ဘာလို့ decrypt မရတာလဲ။ ပြန်စဉ်းစားကြည့်ပြီး code ကို ပိုသေချာ ဖတ်ကြည့်ပါတယ်။ ဒီအချိန်မှာ ကျွန်တော် အရေးကြီးတဲ့ fact တစ်ခု ကို ပြန်သတိရပါတယ်။ Code ထဲမှာ AES class ရဲ့ instance ၂ ခု ရှိနေတယ်ဆိုတာ။ တစ်ခုက database module ထဲမှာ ရှိပြီး နောက်တစ်ခုက main activity ထဲမှာ ရှိပါတယ်။
License key encryption/decryption လုပ်တဲ့ code ကို trace ကြည့်တော့ main activity ရဲ့ instance ကို သုံးထားတာ တွေ့ပါတယ်။ Database module ရဲ့ instance မဟုတ်ဘူး။ Main activity instance ရဲ့ key ကို ဘယ်နေရာမှာ set ထားလဲ ရှာကြည့်တော့ activity onCreate function ထဲမှာ တွေ့ပါတယ်။ IV ကတော့ database module ရဲ့ IV နဲ့ အတူတူပါပဲ။ ဒါပေမယ့် key ကတော့ လုံးဝ ကွဲနေပါတယ်။ Database module ရဲ့ key ကို string literal အနေနဲ့ hardcode ထားပေမယ့် main activity ရဲ့ key ကတော့ obfuscation function ကနေ decode လုပ်ထားတဲ့ 32-byte string ဖြစ်ပါတယ်။ 32 bytes ဆိုတာ AES-256 ပါ။
ဒီတော့ summary ကို ကြည့်ရင် ဒီလိုပါ။ Database operations အတွက်ကတော့ AES-128-CBC နဲ့ 16-byte hardcoded key သုံးထားတယ်။ License key operations အတွက်ကတော့ AES-256-CBC နဲ့ 32-byte obfuscated key သုံးထားတယ်။ IV ကတော့ ၂ ခုလုံး shared ပါပဲ။
ကျွန်တော် Database module ရဲ့ AES-128 key ကို License key decryption အတွက် သုံးမိခဲ့တာပါ။ Instance ကွဲနေတာကို သတိမထားမိခဲ့ဘူး။
AES-256 key (32 bytes) နဲ့ ပြန်စမ်းကြည့်ပါတယ်။ User ပေးထားတဲ့ Short Key ကို decrypt လုပ်ကြည့်တော့ device information ပါတဲ့ plaintext ထွက်လာပါတယ်။ Phone model, Android ID, Manufacturer ၃ ခု pipe character နဲ့ ခြားထားတာ ဖြစ်ပါတယ်။ Long Key ကိုလည်း decrypt လုပ်ကြည့်တော့ license data fields ၇ ခု ထွက်လာပါတယ်။ Module type, Android ID, authorization code, phone model, manufacturer, start timestamp, duration days — ဒီ information တွေ ပါပါတယ်။
Encrypt ပြီး ပြန် decrypt ကြည့်တဲ့ round-trip test လည်း pass ပါတယ်။ ဒီတော့ correct AES parameters ကို confirm ဖြစ်သွားပါတယ်။
License Protocol — ဘယ်လို အလုပ်လုပ်သလဲ
ဒီ license system ရဲ့ flow ကို ပြန်ခြုံကြည့်ရင် challenge-response model ပါ။
App ဘက်မှာ device ရဲ့ hardware information (phone model, Android ID, manufacturer) ကို pipe character နဲ့ join ပြီး AES-256-CBC encrypt လုပ်ပါတယ်။ ထွက်လာတဲ့ hex string ကို Short Key (Device ID) လို့ ခေါ်ပါတယ်။ ဒါက challenge ပါ။
User က ဒီ Short Key ကို admin ဆီကို messaging app ကတဆင့် ကိုယ်တိုင် ပို့ပေးပါတယ်။ Server automation ဘာမှ မပါဘူး။ လူကိုယ်တိုင် copy paste လုပ်ပြီး ပို့ပေးတာပါ။
Admin ဘက်မှာ Short Key ကို decrypt ပြီး device info ရယူပါတယ်။ ပြီးရင် license parameters (module type, duration, start date) တွေ သတ်မှတ်ပြီး response data ကို AES-256-CBC encrypt လုပ်ပါတယ်။ ထွက်လာတဲ့ hex string ကို Long Key (License Key) လို့ ခေါ်ပါတယ်။ ဒါက response ပါ။
Admin က Long Key ကို user ဆီ ပြန်ပို့ပေးပါတယ်။ User က app ရဲ့ setting page မှာ Long Key ကို paste လုပ်ပါတယ်။ App ဘက်မှာ decrypt ပြီး fields တွေကို validate လုပ်ပါတယ်။ Android ID match ဖြစ်ရမယ်, phone model match ဖြစ်ရမယ်, expiry date ကို စစ်ပါတယ်။
ဒီနေရာမှာ ကျွန်တော် ထပ်တွေ့တဲ့ interesting point တစ်ခု ရှိပါတယ်။ License data ရဲ့ field တစ်ခုမှာ "PRO" ဆိုတဲ့ string value ထည့်ထားရင် expiry check ကို လုံးဝ skip သွားပါတယ်။ ဒါက unlimited lifetime license ဖြစ်ပါတယ်။
ဒီ protocol design ရဲ့ fundamental weakness ကတော့ encrypt/decrypt key တစ်ခုတည်း (symmetric) ဖြစ်တာပါ။ Client ဘက်မှာ key ရှိတယ် ဆိုတာ forge လုပ်လို့ ရတယ်ဆိုတာ ဖြစ်ပါတယ်။
App ရဲ့ database ကိုလည်း စစ်ဆေးကြည့်ပါတယ်။ ရိုးရိုး SQLite မဟုတ်ပဲ SQLCipher encryption နဲ့ ကာကွယ်ထားပါတယ်။ Database file ကတော့ APK ရဲ့ assets folder ထဲမှာ pre-built ပါလာတယ်။ App first run မှာ internal storage ထဲ copy ယူပါတယ်။
Database password ကတော့ obfuscation function ကနေ decode လုပ်ထားတဲ့ string ကို prefix string တစ်ခုနဲ့ ပေါင်းထားတာ ဖြစ်ပါတယ်။ ဒီ password ကို code ထဲကနေ ဆွဲထုတ်ပြီး database ဖွင့်ကြည့်ပါတယ်။
ဒီမှာလည်း အဆင်ချက်ချင်း မပြေခဲ့ပါဘူး။ SQLCipher library ရဲ့ legacy mode settings ကို trial and error လုပ်ရပါတယ်။ Mode 1 ကနေ 3 ထိ file is not a database error ပဲ ရပါတယ်။ Mode 4 မှ database ဖွင့်ရပါတယ်။ SQLCipher version 4 default settings ဖြစ်ကြောင်း confirm ဖြစ်ပါတယ်။
Database ဖွင့်ရပြီးတော့ users table ကို query လုပ်ကြည့်ပါတယ်။ Password field ထဲမှာ AES encrypted hex values ရှိပါတယ်။ Database module ရဲ့ AES-128 key (Instance A) နဲ့ decrypt လုပ်ကြည့်ရင် default password ကို ရရှိပါတယ်။ Default user accounts ၂ ခုလုံးမှာ single character password ပဲ ထားထားတာ တွေ့ရပါတယ်။ Login comparison code မှာ toUpperCase() သုံးထားလို့ case-insensitive ပါ။
ဒီ research process တစ်ခုလုံးကနေ vulnerability အဓိက ၇ ခု တွေ့ရှိခဲ့ပါတယ်။
ပထမ အကြီးဆုံး issue ကတော့ symmetric encryption key ကို APK ထဲမှာ embed ထားတာပါ။ Obfuscation လုပ်ထားပေမယ့် reversible ဖြစ်ပါတယ်။ Symmetric encryption ဆိုတာ encrypt/decrypt key တစ်ခုတည်း ဖြစ်လို့ key ရရင် license forge လုပ်လို့ရပါတယ်။ ဒါက critical severity ပါ။
ဒုတိယ critical issue ကတော့ server-side validation လုံးဝ မရှိတာပါ။ License ကို app ထဲမှာပဲ locally စစ်တယ်။ Admin ဘက်ကနေ license revoke လုပ်လို့ မရဘူး။ User ကို remote block လုပ်လို့ မရဘူး။
တတိယ ကတော့ APK integrity verification မရှိတာပါ။ App ကို decompile, modify, recompile လုပ်ပြီး install လို့ရတယ်။ Signature check, root detection, anti-debug protection ဘာမှ မရှိဘူး။
ကျန်တဲ့ issue တွေကတော့ XOR-based string obfuscation ဖြစ်တာ (deterministic, reversible), database password ကို code ထဲ embed ထားတာ, default password weak ဖြစ်တာတို့ ပါပါတယ်။
ဒီ vulnerability တွေအားလုံးရဲ့ root cause ကို ပြန်ခြုံကြည့်ရင် symmetric encryption ကို server verification မပါပဲ client-side ပဲ သုံးထားခြင်းပါ။ Client device ပေါ်မှာ encryption key ရော validation logic ရော နှစ်ခုလုံး ရှိနေတယ်ဆိုရင် attacker က key extract ပြီး arbitrary license forge လုပ်နိုင်ပါတယ်။
ပထမ lesson ကတော့ data flow tracing က code reading ထက် အရေးကြီးတယ်ဆိုတာပါ။ Code ဖတ်ပြီး function တစ်ခုချင်းစီ နားလည်ရုံနဲ့ မလုံလောက်ပါဘူး။ ဘယ် variable ကို ဘယ်နေရာမှာ set လုပ်ပြီး ဘယ်နေရာမှာ သုံးလဲ trace လုပ်ရပါတယ်။ ဒီ case မှာ AES key ကို setting page ကနေ main activity ကို ခေါ်ပြီး main activity ကနေ database module ရဲ့ decoded value ကို ယူသုံးတာ ဖြစ်ပါတယ်။ ဒီ chain ကို trace မလုပ်ရင် wrong key သုံးမိနိုင်ပါတယ်။
ဒုတိယ lesson ကတော့ obfuscation ဆိုတာ security မဟုတ်ဘူးဆိုတာပါ။ String obfuscation က analysis ကို ခက်ခဲစေပေမယ့် impossible မဖြစ်စေပါဘူး။ Algorithm ကို reverse engineer လုပ်ရင် decode ရပါတယ်။ စစ်မှန်တဲ့ security ဖြစ်ဖို့ obfuscation တစ်ခုတည်းနဲ့ မလုံလောက်ပါဘူး။
တတိယ lesson ကတော့ known plaintext attack ရဲ့ တန်ဖိုးပါ။ Input/output pair (encrypted key နဲ့ expected plaintext) ရှိတယ်ဆိုရင် key/IV combination ကို verify လုပ်လို့ ရပါတယ်။ ဒါက reverse engineering process မှာ verify step အနေနဲ့ အရမ်း အသုံးဝင်ပါတယ်။
စတုတ္ထ lesson ကတော့ same class ရဲ့ instance ၂ ခုက configuration မတူနိုင်ဘူးဆိုတာပါ။ ဒီ case မှာ encryption class တစ်ခုတည်းရဲ့ instance ၂ ခု ရှိပြီး key length ကွာတယ်။ ပထမတစ်ခုကို ပဲ ယူသုံးမိရင် decrypt မရပါဘူး။
ငါးခုမြောက် lesson ကတော့ symmetric crypto plus offline equals no real protection ဆိုတာပါ။ ဒါကို ဖြေရှင်းချင်ရင် asymmetric cryptography (RSA/ECDSA) သုံးပြီး server ဘက်မှာ private key ထား client ဘက်မှာ public key ပဲ ထားရပါမယ်။ Client က verify ပဲ လုပ်နိုင်ပြီး forge လုပ်လို့ မရတော့ပါဘူး။
နောက်ဆုံး lesson ကတော့ defense in depth ဆိုတာ layers အရေအတွက် မဟုတ်ဘဲ foundation architecture ကို မှန်အောင် ဆောက်ဖို့ အရေးကြီးဆုံးဆိုတာပါ။ ဒီ app မှာ obfuscation, AES encryption, SQLCipher database encryption ဆိုပြီး layer ၃ ခု ရှိပါတယ်။ ဒါပေမယ့် fundamental architecture (symmetric key plus offline validation) ပေါ်မှာ ဆောက်ထားတာ ဖြစ်လို့ layer ဘယ်လောက် ထပ်ထပ် breach ဖြစ်နိုင်ပါတယ်။
ဒီ type license system ကို ပိုကောင်းအောင် ပြင်ဆင်ချင်ရင် ဒီအချက်တွေ ထည့်သွင်းစဉ်းစားသင့်ပါတယ်။
Asymmetric cryptography ပြောင်းသုံးပါ။ RSA သို့မဟုတ် ECDSA keypair သုံးပြီး private key ကို server ဘက်မှာပဲ ထားပါ။ App ထဲမှာ public key ပဲ embed လုပ်ပြီး license signature ကို verify ပဲ လုပ်နိုင်အောင် ဆောက်ပါ။ ဒါဆိုရင် public key ရှိပေမယ့် license forge လုပ်လို့ မရတော့ပါဘူး။
Server-side license validation ထည့်ပါ။ License generation ကို server ဘက်မှာပဲ လုပ်ပါ။ App startup မှာ server ကို verify စစ်ပါ။ License revocation support ထည့်ပါ။
ProGuard/R8 နဲ့ ပိုကောင်းတဲ့ code obfuscation သုံးပါ။ Root detection ထည့်ပါ။ APK integrity check ထည့်ပါ။ Certificate pinning သုံးပါ။ Android Keystore ကို hardware-backed key storage အတွက် အသုံးပြုပါ။
ဒီ research တစ်ခုလုံးကို အချိန် ၂ နာရီခွဲခန့် အသုံးပြုခဲ့ပါတယ်။ Framework identification နဲ့ code structure mapping အတွက် ၁၅ မိနစ်ခန့်။ String obfuscation algorithm reverse နဲ့ decoder ရေးဖို့ ၂၀ မိနစ်ခန့်။ AES key extraction နဲ့ wrong key debugging ပါပြီး ၃၀ မိနစ်ခန့်။ License format analysis နဲ့ validation logic ဖတ်ဖို့ ၁၅ မိနစ်ခန့်။ SQLCipher database crack ဖို့ ၂၀ မိနစ်ခန့်။ Keygen tool ဆောက်ဖို့ ၃၀ မိနစ်ခန့်။
Tools အနေနဲ့ JADX ကို decompilation အတွက်, Node.js ကို AES testing အတွက်, Web Crypto API ကို browser-based encryption အတွက်, better-sqlite3-multiple-ciphers ကို SQLCipher database access အတွက် အသုံးပြုခဲ့ပါတယ်။
ဒီ research က ကျွန်တော့်အတွက် အများကြီး သင်ယူစရာ ရခဲ့ပါတယ်။ Reverse engineering ဆိုတာ patience လိုတယ်။ Code ဖတ်ရတာ ခက်ပေမယ့် systematically trace လုပ်ရင် ရပါတယ်။ Mistakes are part of the process ပါ။ AES key မှားယူမိတာက ပြန်စစ်ဖို့ motivation ဖြစ်ခဲ့ပါတယ်။
အရေးကြီးဆုံး takeaway ကတော့ offline application ရဲ့ client ပေါ်မှာ secret key ရှိနေရင် extract လုပ်လို့ ရတယ်ဆိုတာပါ။ ဒီ knowledge ကို defensive security perspective နဲ့ ကိုယ့် application တွေရဲ့ license system ကို ဘယ်လို ပိုခိုင်ခံ့အောင် ဆောက်ရမလဲ ဆိုတဲ့ အတွက် အသုံးချနိုင်ပါတယ်။
ဒီ write-up ကို educational purpose နှင့် security awareness အတွက်သာ ရေးသားထားပါတယ်။ ခွင့်ပြုချက်မရှိဘဲ ပြင်ပ application များကို reverse engineering လုပ်ခြင်းသည် သက်ဆိုင်ရာ ဥပဒေများကို ချိုးဖောက်ရာ ရောက်နိုင်ပါတယ်။

