嚴正聲明:
- 1、相關破解技術僅限於技術研究使用,不得用於非法目的,否則後果自負。
- 2、筆者僅出於對技術的好奇,無惡意破壞APP,尊重原開發者的勞動成果,未用於商業用途。
0x1、無形之刃,最爲致命 => 碎碎念
上一篇文章《因一紙設計稿,我把競品APP扒得褲衩不剩(上)》是一篇比較簡單的:
- jsw => 技師文,呸,
- jsw => 記述文,呸呸,
- jsw => 技術文,呸呸呸,這什麼垃圾輸入法!
技術文,但是這評論區的風氣,貌似有點不對???
冤枉啊,小弟真沒去過這種地方,也沒體驗過這種“服務”,只是道聽途說,可能:
我描述得「繪聲繪色」,加之各位看官「浮想聯翩」,纔會覺得「煞有介事」。
em…那個,可以扶下我起來麼,那個…跪久了…腿有點麻…
順帶恭喜下:FPX 3-0 G2,喜提S9總冠軍,FPX牛逼!!!破音!!!
亞索的快樂你不懂~
哈哈,回到本文:
- 發現 => 很多童鞋對APP逆向很感興趣;
- 但是 => 網上關於APP逆向文章的比較零散;
- 不知 => 如何入手,畢竟逆向的水可深了;
- 筆者 => 也只是個小白玩家,興趣使然玩玩而已;
- 分享 => 目前會的一些「Android APP基礎逆向姿勢」;
- 還望 => 各位真丶逆向大佬輕噴;
- 如有 => 有更好的工具或者方法安利,歡迎評論區指出,謝謝~
順帶分享幾個筆者常逛的逆向論壇:
- 看雪論壇:https://bbs.pediy.com/
- 吾愛破解:https://www.52pojie.cn/
- 蟻安網:https://bbs.mayidui.net/
- 逆向大佬姜維:http://www.520monkey.com/
貼心提醒:
此文內容較多,可能會有些枯燥,建議先點贊收藏,茶餘飯後再慢慢品嚐~
0x2、提莫隊長,正在待命 => 硬件準備
在開始折騰Android APP逆向前,你需要:
1、一臺「具有完整Root權限」的Android手機,注意是「完整Root」權限!!!
比如「魅族手機」在設置->安全->Root權限,中可以開啓Root權限,但是卻是「閹割的Root權限」,安裝SuperSu重啓後就一直卡氣球。
2、怎麼Root?根據自己的手機機型百度和逛各種搞機論壇吧(不要問我!)一般的常見的流程:
解BL鎖(BootLoader) -> 刷第三方Recovery(如TWRP) -> 卡刷Maglisk 或 SuperSU(Android 8.0以前)
3、不要輕易嘗試使用哪種「一鍵Root」的軟件(大部分是毒瘤,如KingRoot),當然不是就不能用,可以過河拆橋,比如我的魅藍E2的Root流程:
- 安裝KingRoot(v5.0)授予Root權限,修復Root權限異常,此時就有完整Root權限了;
- 接着用「移除KingRoot」刪掉KingRoot,此時還有完整Root權限;
- 安裝SuperSu(v2.8.2),常規方式更新二進制文件,重啓,Root完成。
4、推薦些能Root的手機?
Google Pixel親兒子(真原生,香,就是性價比不高),小米,一加 等。
5、沒錢買Android機或者已經有不能Root的手機了,可以試試「Android模擬器」
AS自帶的AVD模擬器Root可以參見《搞機:AS自帶模擬器AVD Root 和 Xposed安裝》
也可以使用其他第三方的安卓模擬器,比如「夜遊安卓模擬器、BlueStacks藍疊」等。
0x3、一點寒芒先到,隨後槍出如龍 => 概念與名詞
在開始折騰APP逆向前,先來了解一些概念與名詞~
① APK文件裏都有什麼?
獲取APK的渠道:酷安、應用寶,豌豆莢等應用市場下載,有些還提供「應用歷史版本」下載。
APK本質上是一個「壓縮包」,把「.apk後綴」改爲「.zip後綴」後解壓,可以看到如下目錄結構 (可能還有其他文件):
簡單介紹下:
② 編譯APK和反編譯APK
所謂的「編譯」,就是把「源碼、資源文件等」按照一定的「規則」打包成APK,官網 提供了詳細的編譯構建過程圖:
簡述下大概流程:
Step 1:資源文件處理「AAPT」
- assets會原封不動地打包在APK中;
- res中每一個資源會賦予資源ID,以常量形式定義在R.java中,生成一個resource.arsc文件(資源索引表)。
Step 2:aidl文件「aidl」
- 將aidl後綴的文件轉換爲可用於進程通信的C/S端Java代碼。
Step 3:Source Code「Java Compiler」
- 編譯生成.class文件。
Step 4:代碼混淆「ProGuard」(可選)
- 增加反編譯難度,命名縮短爲1-2個字母的名字,壓縮(移除無效類、屬性、方法等),優化bytecode移除沒用的結構。
Step 5:轉換爲dex「dx.bat」
- 把所有claas文件轉換爲classes.dex文件,class -> Dalvik字節碼,生成常量池,消除冗餘數據等。(方法數超65535會生成多個dex文件)
Step 6:打包「ApkBuilder」
- 把resources.arsc、classes.dex、其他的資源一塊打包生成未簽名apk。
Step 7:簽名「Jarsigner」
- 對未簽名apk進行debug或release簽名。
Step 8:對齊優化「zipalign」
- 使apk中所有資源文件距離文件起始偏移爲4字節的整數倍,從而在通過內存映射訪問apk文件時會更快。
如果想了解更多編譯構建流程可移步至:《10分鐘瞭解Android項目構建流程》
而「反編譯」則是反過來了,通過一些反編譯工具,提取出源碼,轉換過程如下:
「APK ====> Dex ====> Jar(class文件)/Smali ==> Java源碼」。
③ 加固和脫殼
APK可以說是每個Android開發仔的「心血結晶」,把各種自己覺得「牛逼哄哄的奇淫巧技」封裝其中。但總有些「心懷叵測」想去搞你的APP,通過一些「反編譯工具」獲取你的源碼,然後爲所欲爲:
- 加廣告:你應用免費,給你加點廣告,亦或者改成付費,然後下載量比你的多,氣不氣?
- 破解付費:你應用收費,Hook掉你的檢測方法,發個破解包,還到處傳播,氣不氣?
- 惡意攻擊:逆向得出請求接口規律,批量短信驗證註冊,耗光你的短信池等,氣不氣?
這種「惡劣」的行徑令人「氣憤」像極了某類經典「動作電影」裏的橋段:
- 男子上進,努力工作,妹子賢惠,料理家務;
- 妹子每天做好飯菜,等男子回來,一起吃飯,滿懷憧憬,暢談以後的二人世界;
- 酒飽飯後,溫飽思XX,不可描述一番,卻被「居心叵測」的鄰居給盯上了;
- 和往常一樣,男子出門上班,妹子在家做家務,晾衣服;
- 鄰居 上線,用「謊言」誘騙妹子開門,接而擠門而入;
- 用「暴力和脅迫」,無視妹子的やめて和反抗,違背個人意願;
- 粗暴地把衣服一件件褪去,僅剩下那「萬惡的馬賽克」;
- 守護着最後的一處「絕對領域」;
- 在幾番不可描述後,把妹子佔爲己有,然後像玩物般戲耍。
看着妹子「因情緒過激而身體抽搐」,哭得「梨花帶雨」,不禁讓人「心生憐惜」,像我這種感性的藍孩子:
總會忍不住抽上幾張抽紙,“靜靜抹淚”,擦拭完,頓覺索然無味,一片空明,然後開始反思:
爲什麼那個鄰居不是我?呸呸呸…
除了「同情女主」和「斥責壞人」外,應該如何避免這種事情的發生呢?
- 1、花點錢,請個「保鏢」看門,壞人想進來要先過保鏢這一關;
- 2、給妹子「加個鎖」,讓壞人無法不可描述,只能望而興嘆。
可以把例子中的「妹子」看做是我們編寫的「APK」,而「請保鏢」和「加鎖的操作」則可以看做是「APK加固」,另外加固又稱「加殼」,殼的定義:
一段專門負責「保護軟件不被非法修改或反編譯的程序」,一般先於程序運行,拿到控制權,然後完成它們保護軟件的任務。
有「加殼」,自然也有「脫殼」,即去掉這層殼,拿到源碼,也稱爲「砸殼」。
關於加固技術的發展,看雪上有篇:《一張表格看懂:市面上最爲常見的 Android 安裝包(APK)五代加固技術發展歷程及優缺點比較》,不過圖不怎麼清晰,筆者重新排版了一下,有興趣的讀者可以看看:
④ 混淆與反混淆
「混淆」可以類比爲上面「萬惡的馬賽克」,阻礙人類進步的絆腳石。而混淆則是增加了反編譯的難度,同理,「反混淆」則對應「去除馬賽克」,試圖還原它原來的樣子。
0x4、發動機已啓動,隨時可以出發 => 獲得APP源碼
加固雖然能在一定程度上「防止反編譯和二次打包」,但加固後的APP可能會帶來一些問題:
體積增大,啓動速度變慢,兼容問題等
網上「免費加固」方案有很多,脫殼教程也是爛大街,而且有些噁心的第三方加固還會給你加點料(360加固鎖屏廣告),而使用「企業級的加固」,則需要支付不菲的費用,所以很多APP直接選擇了「裸奔」。先來講解一下未加固的怎麼獲取源碼吧~
① 未加固(筆者使用的工具:apktool + jadx)
- 使用apktool:獲取「素材資源,AndroidManifest.xml以及smail代碼」
- 使用jadx:把「classes.dex」轉換爲「.java」代碼
使用Jadx的注意事項:
使用jadx-gui可直接打開apk查看源碼,但如果APK比較大(classes.dex有好幾個),會直接卡死(比如微信),筆者的做法是命令行一個個dex文件去反編譯,最後再把反編譯的文件夾整合到同一個目錄下。
這樣的操作繁瑣且重複,最適合批處理了,遂寫了個反編譯的批處理腳本(取需):
"""
自動解壓apk,批量使用jadx進行反編譯,結果代碼彙總
"""
import os
import shutil
import zipfile
from datetime import datetime
apk_file_dict = {} # APK路徑字典
# 遍歷構造APK路徑字典(構造文件路徑列表,過濾apk,拼接)
def init_apk_dict(file_dir):
apk_path_list = list(filter(lambda fp: fp.endswith(".apk"),
list(map(lambda x: os.path.join(file_dir, x), os.listdir(file_dir)))))
index_list = [str(x) for x in range(1, len(apk_path_list) + 1)]
return dict(zip(index_list, apk_path_list))
# 移動文件夾
def move_dir(origin_dir, finally_dir):
shutil.move(origin_dir, finally_dir)
# 如果文件夾存在刪除重建
def deal_dir_existed(path):
if os.path.exists(path):
print("檢測到文件夾【%s】已存在,執行刪除..." % path)
shutil.rmtree(path)
os.makedirs(path)
# 判斷目錄是否存在,不存在則創建
def is_dir_existed(path, mkdir=True):
if mkdir:
if not os.path.exists(path):
os.makedirs(path)
else:
return os.path.exists(path)
# 獲取目錄下的所有文件路徑
def fetch_all_file(file_dir):
return list(map(lambda x: os.path.join(file_dir, x), os.listdir(file_dir)))
# 解壓文件到特定路徑中
def unzip_file(file_name, output_dir):
print("開始解壓文件...")
f = zipfile.ZipFile(file_name, 'r')
for file in f.namelist():
f.extract(file, os.path.join(os.getcwd(), output_dir))
print("文件解壓完畢...")
if __name__ == '__main__':
print("遍歷當前目錄下所有APK...")
apk_file_dict = init_apk_dict(os.getcwd())
print("遍歷完畢...\n\n============ 當前目錄下所有的APK ============\n")
for (k, v) in apk_file_dict.items():
print("%s.%s" % (k, v.split(os.sep)[-1]))
print("\n%s" % ("=" * 45))
choice_pos = input("%s" % "請輸入需要反編譯APK的數字編號:")
print("=" * 45, )
choice_apk = apk_file_dict.get(choice_pos)
apk_name = choice_apk.split(os.sep)[-1][:-4] # APK名字
# 創建相關文件夾
crack_dir = os.path.join(os.getcwd(), apk_name) # 工程根目錄
deal_dir_existed(crack_dir)
crack_apktool_dir = os.path.join(crack_dir, "apktool" + os.sep) # APKTool反編譯目錄
deal_dir_existed(crack_apktool_dir)
crack_jadx_dir = os.path.join(crack_dir, "jadx" + os.sep) # JADX反編譯目錄
deal_dir_existed(crack_jadx_dir)
crack_temp_dir = os.path.join(crack_dir, "temp" + os.sep) # 解壓後文件的臨時存儲路徑
deal_dir_existed(crack_temp_dir)
# 利用APKTool提取資源文件
begin = datetime.now() # 計時
print("APKTool提取資源文件...")
os.system("./apktool d %s -f -o %s" % (choice_apk, crack_apktool_dir))
# 複製一份AndroidManifest.xml、res、assets文件到外部
shutil.copy(os.path.join(crack_apktool_dir, "AndroidManifest.xml"), os.path.join(crack_dir, "AndroidManifest.xml"))
shutil.copytree(os.path.join(crack_apktool_dir, "res" + os.sep), os.path.join(crack_dir, "res" + os.sep))
shutil.copytree(os.path.join(crack_apktool_dir, "assets" + os.sep), os.path.join(crack_dir, "assets" + os.sep))
print("資源文件提取完畢")
# 利用jadx反編譯源碼
print("JADX反編譯提取源碼...")
choice_apk_zip = shutil.copy(choice_apk, choice_apk.replace(".apk", ".zip"))
unzip_file(choice_apk_zip, crack_temp_dir)
print("開始批量反編譯dex文件")
for dex in list(filter(lambda fp: fp.endswith(".dex"), fetch_all_file(crack_temp_dir))):
os.system(
"./jadx -d {0} {1}".format(os.path.join(crack_jadx_dir, dex.split(os.sep)[-1][:-4]), dex))
print("所有dex文件反編譯完畢")
# 將資源文件移入
shutil.move(os.path.join(crack_dir, "AndroidManifest.xml"), os.path.join(crack_jadx_dir, "AndroidManifest.xml"))
shutil.move(os.path.join(crack_dir, "res" + os.sep), os.path.join(crack_jadx_dir, "res" + os.sep))
shutil.move(os.path.join(crack_dir, "assets" + os.sep), os.path.join(crack_jadx_dir, "assets" + os.sep))
# 刪除臨時文件夾,壓縮文件
shutil.rmtree(crack_temp_dir)
os.unlink(choice_apk_zip)
end = datetime.now()
print("收尾操作~~~\n反編譯完成,總耗時:%s秒" % (end - begin).seconds)
執行前,你需要把apktool相關的東西,丟到jadx/build/jadx/bin目錄下,如圖所示:
接着終端鍵入:python3 auto_extract_apk.py,回車後輸入對應編號,回車開始編譯:
靜待片刻後:
Tips:這裏沒有把多個classes文件夾整合到一起,是因爲有些APP會出現合併衝突。
打開反編譯後的目錄,有如下兩個文件夾:
按照自己的需要用Android Studio打開其中一個就好了:
- apktool目錄:apktool反編譯後的內容,主要用於smail動態調試。
- jadx目錄:反編譯成Java的內容。
② 反混淆(simplefy、Deguard)
代碼是拿到了,但是打開代碼,「一堆的abcd」,跟到眼花,可以試下「反混淆」,方案有兩類,一種是通過「代碼逆推」出名字,另一種是通過「統計逆推」出名字。
第一種方案的工具有很多(Jeb2,simplify等),前者付費需破解,Java版本有限制,Mac配置有點麻煩,故筆者用的是後者:「Simplefy」,Github倉庫:https://github.com/CalebFenton/simplify,使用方法也很簡單:
打開終端依次鍵入:
# 拉取倉庫代碼
git clone --recursive https://github.com/CalebFenton/simplify.git
# 來到目錄下
cd simplify
# 編譯
./gradlew fatjar
編譯後完,執行下述指令即可反混淆APK:
# 反混淆APK(需要反混淆的APK,反混淆後的APK名)
./gradlew build && cp xxx.apk yyy.apk
靜待反混淆完畢,接着反編譯批處理腳本走一波,打開MapFragment比對下:
相比混淆前,多了一些變量名,當然也不是完全的,偶爾還是有abcd,但是可讀性稍微提高了些,比如查找的時候不用在一個個adcd排除,但是,編譯挺耗時的,而且我的電腦風扇呼呼呼地響。
第二種是通過統計的方法,利用統計推斷出名字:DEGUARD:http://apk-deguard.com/,打開官網:
選擇需要反混淆的APK後,Upload上傳,接着等待處理完成,!!!別關頁面!!!
一般需等待1-10分鐘,處理完成後,點擊output.apk,把APK下載到本地,同樣執行批處理腳本反編譯一波,和simplefy反混淆後的代碼對比下:
大同小異,另外,反混淆並不能100%還原,而且還可能有些小錯誤,比如下面的代碼:
雖說反編譯後的可讀性有所提高,但建議還是搭配着混淆的源碼看。
③ 脫殼(FDex2,反射助手,dumpDex)
終於來到很多同學期待的脫殼環節,先說明下,筆者只是「工具黨」水平,不會Native層的,so文件調試!如果本節的工具,你脫不出來,或者脫出來有問題,筆者也是愛莫能助。看雪有很多幫人脫殼的大佬,可以在上面發個帖子求助下~
1、判斷是哪種加固
解壓apk後在assets目錄下看到so文件,比如360加固寶:libjagu.so和libjiagu_x86.so,百度搜下名字就知道是哪家的加固了,也可以直接用後面講的「MT文件管理器2.0」直接查看。
2、FDex2脫殼(只適用於Android 7或以下版本,可以脫市面上大多數免費加固,成功率較高,推薦)
- 有ROOT:安裝「XposedInstaller」和「FDex2」
- 沒ROOT:安裝「VirtualXposed」「FDex2」
比如:這裏有個「360免費版加固的APK」,直接用jadx反編譯後導入AS,但是反編譯後的classes:
只有這麼一丟丟點東西,把「待脫殼應用」安裝到手機上,接着用FDex2來脫殼
已Root玩家:XposedInstall啓用FDex2插件重啓後,按如下步驟脫:
- Step 1:FDex2選中待脫殼應用:
- Step 2:打開待脫殼應用,接着來到上圖的dex輸出目錄:
- Step 3:把整個目錄拉到電腦上,這裏直接用adb命令拉取:
adb root
adb pull /data/user/0/包名 電腦文件夾
- Step 4:「剔除加固相關的dex」,用jadx-gui依次打開,看到下面這種,直接把dex刪掉
- Step 5:使用jadx命令反編譯dex,順帶改名,命名規則:按照文件大小降序,示例如下:
# 按照文件從大到小排序!!!
jadx aaa.dex -d classes
jadx bbb.dex -d classes1
jadx ccc.dex -d classes2
- Step 6:刪掉沒脫殼前反編譯項目裏的classes,把這幾個複製到其中:
行吧,脫殼成功,這裏其實還可以還原APK的(二次打包),等下再講~
未root玩家,安裝打開VirtualXposed,添加應用:Fdex2和待脫殼應用
如果炮製,只是dex的路徑有些不一樣。
3、反射大師(和FDex類似,下載地址:https://www.lanzous.com/b04xxlujg)
注意,同樣只支持Android 7.0及以下,adb安裝後,xposed啓用插件,重啓手機,接着打開反射大師:
Step 1:選中待脫殼APP,彈出對話框選擇打開
Step 2:點擊中間的六芒星,彈出如下對話框,長按「寫出DEX」
Step 3:等待寫出完畢,可以在/storage/emulated/0中找到導出的dex:
Step 4:pull到電腦上用jadx-gui打開看看:
行吧,脫殼成功,就是我們想要的dex了,另一個classes2.dex則是相關的~:
4、dumpDex脫殼(Github:https://github.com/WrBug/dumpDex/releases)
官方倉庫的README.md中有一句:
可以的話建議自己編譯,流程也很簡單:
# 1、拉取項目代碼到本地
git clone https://github.com/WrBug/dumpDex.git
# 2、AS中Open項目,等待編譯完成
# 3、刪掉build.gradle裏簽名相關的代碼
# 4、點擊頂部菜單欄Build -> Build APK,或者直接在終端./gradlew clean build
# 5、adb命令直接把編譯生成的apk安裝到手機上
# 6、接着來到如下左圖路徑,把對應的so,通過adb push到目錄下:
adb push lib/armeabi-v7a/libnativeDump.so /data/local/tmp
adb push lib/arm64-v8a/libnativeDump.so /data/local/tmp/libnativeDump64.so
# 修改權限
adb shell
su
chmod 777 /data/local/tmp/libnativeDump.so
chmod 777 /data/local/tmp/libnativeDump64.so
# 臨時關閉SELinux(重啓後會失效,可調用getenforce查詢)
setenfore 0
# 7、打開XposedInstaller看已經啓用DumpDex插件,是的話重啓手機
# 8、開機後,打開想脫殼的應用,不用理閃退,接着打開data/data/包名查看是否有Dump目錄
# 9、進入如果出現下圖所示的多個dex,說明脫殼成功,否則可能是脫殼失敗
# (看是否有報錯信息),或者不支持(比如360加固免費版只支持新版,不支持舊版)。
另外,脫出來的dex不一定就可用,比如某個用了「騰訊御安全」的應用:
用jadx-gui打開這的dex,一堆這樣的錯誤:
出現這個的原因是「指令集被抽取」,打開smail文件你就知道了:
方法指令都被nop(零)替換了,工具黨到這裏就可以放棄了,要調試so文件。
Tips:本節用到的東西,都有給出比較官方的下載鏈接!!!你也可以到公號
「摳腚男孩」輸入000,回覆對應序號下載,謝謝~
參考文獻: