因一紙設計稿,我把競品APP扒得褲衩不剩(中)

嚴正聲明

  • 1、相關破解技術僅限於技術研究使用,不得用於非法目的,否則後果自負。
  • 2、筆者僅出於對技術的好奇,無惡意破壞APP,尊重原開發者的勞動成果,未用於商業用途。

0x1、無形之刃,最爲致命 => 碎碎念

上一篇文章《因一紙設計稿,我把競品APP扒得褲衩不剩(上)》是一篇比較簡單的:

  • jsw => 技師文,呸,
  • jsw => 記述文,呸呸,
  • jsw => 技術文,呸呸呸,這什麼垃圾輸入法!

技術文,但是這評論區的風氣,貌似有點不對???

冤枉啊,小弟真沒去過這種地方,也沒體驗過這種“服務”,只是道聽途說,可能:

我描述得「繪聲繪色」,加之各位看官「浮想聯翩」,纔會覺得「煞有介事」。

em…那個,可以扶下我起來麼,那個…跪久了腿有點麻

順帶恭喜下:FPX 3-0 G2喜提S9總冠軍FPX牛逼!!!破音!!!

亞索的快樂你不懂~

哈哈,回到本文

  • 發現 => 很多童鞋對APP逆向很感興趣;
  • 但是 => 網上關於APP逆向文章的比較零散;
  • 不知 => 如何入手,畢竟逆向的水可深了;
  • 筆者 => 也只是個小白玩家,興趣使然玩玩而已;
  • 分享 => 目前會的一些「Android APP基礎逆向姿勢」;
  • 還望 => 各位真丶逆向大佬輕噴;
  • 如有 => 有更好的工具或者方法安利,歡迎評論區指出,謝謝~

順帶分享幾個筆者常逛的逆向論壇

貼心提醒

此文內容較多,可能會有些枯燥,建議先點贊收藏,茶餘飯後再慢慢品嚐~


0x2、提莫隊長,正在待命 => 硬件準備


在開始折騰Android APP逆向前,你需要:

1、一臺「具有完整Root權限」的Android手機,注意是「完整Root」權限!!!

比如「魅族手機」在設置->安全->Root權限,中可以開啓Root權限,但是卻是「閹割的Root權限」,安裝SuperSu重啓後就一直卡氣球。

2、怎麼Root根據自己的手機機型百度和逛各種搞機論壇吧(不要問我!)一般的常見的流程:

解BL鎖(BootLoader) -> 刷第三方Recovery(如TWRP) -> 卡刷MagliskSuperSU(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」,跟到眼花,可以試下「反混淆」,方案有兩類,一種是通過「代碼逆推」出名字,另一種是通過「統計逆推」出名字。

第一種方案的工具有很多(Jeb2simplify等),前者付費需破解,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排除,但是,編譯挺耗時的,而且我的電腦風扇呼呼呼地響。

第二種是通過統計的方法,利用統計推斷出名字:DEGUARDhttp://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脫殼Githubhttps://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,回覆對應序號下載,謝謝~


參考文獻


發佈了306 篇原創文章 · 獲贊 1857 · 訪問量 1661萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章