1. 程式人生 > >[Android 脫殼] 使用 IDA 進行簡單的脫殼

[Android 脫殼] 使用 IDA 進行簡單的脫殼

0x00 摘要


Apk脫殼方法有兩種: 
(1)使用脫殼神器ZjDroid進行脫殼(現在此方法不是很好使,因為很多應用都同步更新自己的防禦機制,個人覺得熟練脫殼還是得使用 IDA 進行操練)。 

 

(2)使用 IDA Pro 在 dvmDexFileOpenPartial 這個函式下斷點進行脫殼。(大殺技)

加殼能防止原始碼被偷窺,但是這隻能防止靜態分析,無法防止動態除錯。不管怎麼加殼保護,原始的classes.dex在App執行時都要載入到記憶體中。所以如果在App載入classes.dex處下個斷點,然後再把classes.dex對應記憶體中的內容摳出來還原成原始的classes.dex檔案,就能達到脫殼的目的了。


0x01 實驗


(1)以第1屆Alictf的EvilApk300(如圖0所示)為例,簡單介紹一下使用IDA Pro 進行脫殼的步驟

 

 

  • step 1:將手機連線電腦

[Bash shell] 純文字檢視 複製程式碼

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

[/size][size=16px]adb install C:\Users\Bravelee\Desktop\jscrack.apk[/size]

 

[size=16px]-------啟動服務,開啟監聽-------[

/size]

[size=16px]adb shell[/size]

[size=16px]su root[/size]

[size=16px]chmod 777 /data/local/tmp/android_server[/size]

[size=16px]/data/local/tmp/android_server[/size]

 

[size=16px]------------埠轉發------------[/size]

[size=16px]再次開啟一個終端視窗:[/size]

[size=16px]adb forward tcp:23946 tcp:23946[/size]

 

[size=16px]-----------root 模式下啟動該 app----------[

/size]

[size=16px]因為加殼了,所以只能檢視 manifest.xml 檔案才能獲取 apk 對應的包名[/size]

[size=16px]adb shell[/size]

[size=16px]su root[/size]

[size=16px]am start -D -n com.ali.tg.testapp/com.ali.tg.testapp.MainActivity[/size]

[size=16px]


如果成功,手機上 app 會彈出 “Waiting For Debugger”

  • step 2:操作 IDA

依次點選”Debbuger -> Attach -> Remote ARMLinux/Android debugger”啟動IDA Pro中的Android Debbuger 
然後在彈出的對話方塊中點選”Debug options”按鈕,將“Suspend on process entry point”,“Suspend on thread start/exit”,“Suspend on library load/unload”這幾個選項勾選上,再將Hostname配置為localhost,埠:23946

 

 

 

 

注意:Ctrl + F 方便查詢

 

  • step 3:繼續 IDA 
    脫殼的時候重點關注:dvmDexFileOpenPartial 函式(在該函式處下斷點)

 

依次點選“Debugger -> Debugger windows -> Module list”,找到so檔案列表 
在Module list中找到libdvm.so這個檔案(注意:Ctrl + F 方便查詢)

 

雙擊libdvm.so,在彈出的函式列表中找到dvmDexFileOpenPartial函式,然後雙擊該函式就看到dvmDexFileOpenPartial函式的具體實現

 

在dvmDexFileOpenPartial函式處下斷點(F2 下斷點)

 

  • step 4:使用jdb命令動態除錯Apk 

點選 ida 左上角的綠色執行按鈕(F9)

 


開啟 DDMS 工具(android-sdk\sdk\tools\ddms.bat)
 
使用jdb命令進行除錯時,一般選擇8700埠,因為8700是預設的除錯埠;開啟終端視窗,輸入:

[Bash shell] 純文字檢視 複製程式碼

?

1

2

3

[/size]

[size=16px]jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8700[/size]

[align=left][size=16px]

 

此時 IDA 會彈出 ”Add map…” 視窗(點選 cancel 按鈕即可):

 

此時程序就執行到了dvmDexOpenPartial函式斷點處,dvmDexOpenPartial 函式的定義:

[C++] 純文字檢視 複製程式碼

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

[/size]

[size=16px]/*[/size]

[size=16px]* Create a DexFile structure for a "partial" DEX.  This is one that is in[/size]

[size=16px]* the process of being optimized.  The optimization header isn't finished[/size]

[size=16px]* and we won't have any of the auxillary data tables, so we have to do[/size]

[size=16px]* the initialization slightly differently.[/size]

[size=16px]*[/size]

[size=16px]* Returns nonzero on error.[/size]

[size=16px]*/[/size]

[size=16px]int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)[/size]

[size=16px]{[/size]

[size=16px]    DvmDex* pDvmDex;[/size]

[size=16px]    DexFile* pDexFile;[/size]

[size=16px]    int parseFlags = kDexParseDefault;[/size]

[size=16px]    int result = -1;[/size]

 

[size=16px]    /* -- file is incomplete, new checksum has not yet been calculated[/size]

[size=16px]    if (gDvm.verifyDexChecksum)[/size]

[size=16px]        parseFlags |= kDexParseVerifyChecksum;[/size]

[size=16px]    */[/size]

 

[size=16px]    pDexFile = dexFileParse((u1*)addr, len, parseFlags);[/size]

[size=16px]    if (pDexFile == NULL) {[/size]

[size=16px]        ALOGE("DEX parse failed");[/size]

[size=16px]        goto bail;[/size]

[size=16px]    }[/size]

[size=16px]    pDvmDex = allocateAuxStructures(pDexFile);[/size]

[size=16px]    if (pDvmDex == NULL) {[/size]

[size=16px]        dexFileFree(pDexFile);[/size]

[size=16px]        goto bail;[/size]

[size=16px]    }[/size]

 

[size=16px]    pDvmDex->isMappedReadOnly = false;[/size]

[size=16px]    *ppDvmDex = pDvmDex;[/size]

[size=16px]    result = 0;[/size]

 

[size=16px]bail:[/size]

[size=16px]    return result;[/size]

[size=16px]}[/size]

[size=16px]


 

dvmDexFileOpenPartial 函式的原型如下所示: 
int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex) 
其中 addr表示Dex檔案在記憶體中的起始地址, 
len 表示Dex檔案的大小, 
ppDvmDex是一個指向DvmDex型別的二級指標,具體表示什麼,我也不知道

脫殼只用到 addr 和 len 這兩個引數,所以需要獲取 R0 和 R1 暫存器的值(ARM的傳遞引數機制規定 R0 儲存著函式從左至右的第一個引數,R1 儲存著函式從左至右的第二個引數),可以檢視到暫存器列表中的內容如圖所示:

 

  • step 5:在 IDA 中編寫idc指令碼dump記憶體還原dex檔案 

選擇 File -> Script command…
 


 

 

稍等片刻,即可以把 dump 出來的 dex 檔案儲存在 C 盤根目錄

 

IDC指令碼:

[C] 純文字檢視 複製程式碼

?

1

2

3

4

5

auto fp, dex_addr,end_addr;

    fp = fopen("C:\\dump.dex","wb");

    end_addr = r0 + r1;

    for (dex_addr = r0; dex_addr < end_addr; dex_addr ++)

        fputc(Byte(dex_addr),fp);


 

  • step 6:使用 JEB 分析dump 出來的 dex 檔案[此處內容請參考原文]
本文脫殼核心思想:在 dvmDexFileOpenPartial 函式處下斷點,然後動態除錯 Apk,待App 執行到斷點處後,寫一個 idc 指令碼將 dex 檔案所對應的記憶體 dump 出來,然後還原成 dex 檔案就完成脫殼操作了,最後再分析反編譯 dex 所得到的 smali 檔案。

 

  • 參考文獻

[1] 聽鬼哥說ZJDROID脫殼的簡單使用:http://blog.csdn.net/guiguzi1110/article/details/38727753 
[2] 安卓逆向學習筆記(9)- 使用IDA Pro進行簡單的脫殼 :http://blog.csdn.net/pengyan0812/article/details/46275317
[3] Android應用方法隱藏及反除錯技術淺析:http://www.kuqin.com/shuoit/20151012/348473.html
0x00 摘要


Apk脫殼方法有兩種: 
(1)使用脫殼神器ZjDroid進行脫殼(現在此方法不是很好使,因為很多應用都同步更新自己的防禦機制,個人覺得熟練脫殼還是得使用 IDA 進行操練)。 

 

(2)使用 IDA Pro 在 dvmDexFileOpenPartial 這個函式下斷點進行脫殼。(大殺技)

加殼能防止原始碼被偷窺,但是這隻能防止靜態分析,無法防止動態除錯。不管怎麼加殼保護,原始的classes.dex在App執行時都要載入到記憶體中。所以如果在App載入classes.dex處下個斷點,然後再把classes.dex對應記憶體中的內容摳出來還原成原始的classes.dex檔案,就能達到脫殼的目的了。


0x01 實驗


(1)以第1屆Alictf的EvilApk300(如圖0所示)為例,簡單介紹一下使用IDA Pro 進行脫殼的步驟

 

 

  • step 1:將手機連線電腦

[Bash shell] 純文字檢視 複製程式碼

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

[/size][size=16px]adb install C:\Users\Bravelee\Desktop\jscrack.apk[/size]

 

[size=16px]-------啟動服務,開啟監聽-------[/size]

[size=16px]adb shell[/size]

[size=16px]su root[/size]

[size=16px]chmod 777 /data/local/tmp/android_server[/size]

[size=16px]/data/local/tmp/android_server[/size]

 

[size=16px]------------埠轉發------------[/size]

[size=16px]再次開啟一個終端視窗:[/size]

[size=16px]adb forward tcp:23946 tcp:23946[/size]

 

[size=16px]-----------root 模式下啟動該 app----------[/size]

[size=16px]因為加殼了,所以只能檢視 manifest.xml 檔案才能獲取 apk 對應的包名[/size]

[size=16px]adb shell[/size]

[size=16px]su root[/size]

[size=16px]am start -D -n com.ali.tg.testapp/com.ali.tg.testapp.MainActivity[/size]

[size=16px]


如果成功,手機上 app 會彈出 “Waiting For Debugger”

  • step 2:操作 IDA

依次點選”Debbuger -> Attach -> Remote ARMLinux/Android debugger”啟動IDA Pro中的Android Debbuger 
然後在彈出的對話方塊中點選”Debug options”按鈕,將“Suspend on process entry point”,“Suspend on thread start/exit”,“Suspend on library load/unload”這幾個選項勾選上,再將Hostname配置為localhost,埠:23946

 

 

 

 

注意:Ctrl + F 方便查詢

 

  • step 3:繼續 IDA 
    脫殼的時候重點關注:dvmDexFileOpenPartial 函式(在該函式處下斷點)

 

依次點選“Debugger -> Debugger windows -> Module list”,找到so檔案列表 
在Module list中找到libdvm.so這個檔案(注意:Ctrl + F 方便查詢)

 

雙擊libdvm.so,在彈出的函式列表中找到dvmDexFileOpenPartial函式,然後雙擊該函式就看到dvmDexFileOpenPartial函式的具體實現

 

在dvmDexFileOpenPartial函式處下斷點(F2 下斷點)

 

  • step 4:使用jdb命令動態除錯Apk 

點選 ida 左上角的綠色執行按鈕(F9)

 


開啟 DDMS 工具(android-sdk\sdk\tools\ddms.bat)
 
使用jdb命令進行除錯時,一般選擇8700埠,因為8700是預設的除錯埠;開啟終端視窗,輸入:

[Bash shell] 純文字檢視 複製程式碼

?

1

2

3

[/size]

[size=16px]jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8700[/size]

[align=left][size=16px]

 

此時 IDA 會彈出 ”Add map…” 視窗(點選 cancel 按鈕即可):

 

此時程序就執行到了dvmDexOpenPartial函式斷點處,dvmDexOpenPartial 函式的定義:

[C++] 純文字檢視 複製程式碼

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

[/size]

[size=16px]/*[/size]

[size=16px]* Create a DexFile structure for a "partial" DEX.  This is one that is in[/size]

[size=16px]* the process of being optimized.  The optimization header isn't finished[/size]

[size=16px]* and we won't have any of the auxillary data tables, so we have to do[/size]

[size=16px]* the initialization slightly differently.[/size]

[size=16px]*[/size]

[size=16px]* Returns nonzero on error.[/size]

[size=16px]*/[/size]

[size=16px]int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex)[/size]

[size=16px]{[/size]

[size=16px]    DvmDex* pDvmDex;[/size]

[size=16px]    DexFile* pDexFile;[/size]

[size=16px]    int parseFlags = kDexParseDefault;[/size]

[size=16px]    int result = -1;[/size]

 

[size=16px]    /* -- file is incomplete, new checksum has not yet been calculated[/size]

[size=16px]    if (gDvm.verifyDexChecksum)[/size]

[size=16px]        parseFlags |= kDexParseVerifyChecksum;[/size]

[size=16px]    */[/size]

 

[size=16px]    pDexFile = dexFileParse((u1*)addr, len, parseFlags);[/size]

[size=16px]    if (pDexFile == NULL) {[/size]

[size=16px]        ALOGE("DEX parse failed");[/size]

[size=16px]        goto bail;[/size]

[size=16px]    }[/size]

[size=16px]    pDvmDex = allocateAuxStructures(pDexFile);[/size]

[size=16px]    if (pDvmDex == NULL) {[/size]

[size=16px]        dexFileFree(pDexFile);[/size]

[size=16px]        goto bail;[/size]

[size=16px]    }[/size]

 

[size=16px]    pDvmDex->isMappedReadOnly = false;[/size]

[size=16px]    *ppDvmDex = pDvmDex;[/size]

[size=16px]    result = 0;[/size]

 

[size=16px]bail:[/size]

[size=16px]    return result;[/size]

[size=16px]}[/size]

[size=16px]


 

dvmDexFileOpenPartial 函式的原型如下所示: 
int dvmDexFileOpenPartial(const void* addr, int len, DvmDex** ppDvmDex) 
其中 addr表示Dex檔案在記憶體中的起始地址, 
len 表示Dex檔案的大小, 
ppDvmDex是一個指向DvmDex型別的二級指標,具體表示什麼,我也不知道

脫殼只用到 addr 和 len 這兩個引數,所以需要獲取 R0 和 R1 暫存器的值(ARM的傳遞引數機制規定 R0 儲存著函式從左至右的第一個引數,R1 儲存著函式從左至右的第二個引數),可以檢視到暫存器列表中的內容如圖所示:

 

  • step 5:在 IDA 中編寫idc指令碼dump記憶體還原dex檔案 

選擇 File -> Script command…
 


 

 

稍等片刻,即可以把 dump 出來的 dex 檔案儲存在 C 盤根目錄

 

IDC指令碼:

[C] 純文字檢視 複製程式碼

?

1

2

3

4

5

auto fp, dex_addr,end_addr;

    fp = fopen("C:\\dump.dex","wb");

    end_addr = r0 + r1;

    for (dex_addr = r0; dex_addr < end_addr; dex_addr ++)

        fputc(Byte(dex_addr),fp);


 

  • step 6:使用 JEB 分析dump 出來的 dex 檔案[此處內容請參考原文]
本文脫殼核心思想:在 dvmDexFileOpenPartial 函式處下斷點,然後動態除錯 Apk,待App 執行到斷點處後,寫一個 idc 指令碼將 dex 檔案所對應的記憶體 dump 出來,然後還原成 dex 檔案就完成脫殼操作了,最後再分析反編譯 dex 所得到的 smali 檔案。

 

  • 參考文獻

[1] 聽鬼哥說ZJDROID脫殼的簡單使用:http://blog.csdn.net/guiguzi1110/article/details/38727753 
[2] 安卓逆向學習筆記(9)- 使用IDA Pro進行簡單的脫殼 :http://blog.csdn.net/pengyan0812/article/details/46275317
[3] Android應用方法隱藏及反除錯技術淺析:http://www.kuqin.com/shuoit/20151012/348473.html