[Android 脱壳] 使用 IDA 进行简单的脱壳

时间:2024-04-12 18:25:07

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 进行脱壳的步骤

[Android 脱壳] 使用 IDA 进行简单的脱壳 

 

  • 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

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

注意:Ctrl + F 方便查找

 

  • step 3:继续 IDA 
    脱壳的时候重点关注:dvmDexFileOpenPartial 函数(在该函数处下断点)

 

依次点击“Debugger -> Debugger windows -> Module list”,找到so文件列表 
在Module list中找到libdvm.so这个文件(注意:Ctrl + F 方便查找)

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

双击libdvm.so,在弹出的函数列表中找到dvmDexFileOpenPartial函数,然后双击该函数就看到dvmDexFileOpenPartial函数的具体实现

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

在dvmDexFileOpenPartial函数处下断点(F2 下断点)

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

  • step 4:使用jdb命令动态调试Apk 

点击 ida 左上角的绿色运行按钮(F9)

[Android 脱壳] 使用 IDA 进行简单的脱壳 [Android 脱壳] 使用 IDA 进行简单的脱壳


打开 DDMS 工具(android-sdk\sdk\tools\ddms.bat)
[Android 脱壳] 使用 IDA 进行简单的脱壳 
使用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]

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

此时 IDA 会弹出 ”Add map…” 窗口(点击 cancel 按钮即可):

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

[Android 脱壳] 使用 IDA 进行简单的脱壳

此时进程就执行到了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 保存着函数从左至右的第二个参数),可以查看到寄存器列表中的内容如图所示:

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

  • step 5:在 IDA 中编写idc脚本dump内存还原dex文件 

选择 File -> Script command…
[Android 脱壳] 使用 IDA 进行简单的脱壳 
[Android 脱壳] 使用 IDA 进行简单的脱壳

[Android 脱壳] 使用 IDA 进行简单的脱壳 

 

稍等片刻,即可以把 dump 出来的 dex 文件保存在 C 盘根目录

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

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.****.net/guiguzi1110/article/details/38727753 
[2] 安卓逆向学习笔记(9)- 使用IDA Pro进行简单的脱壳 :http://blog.****.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 进行脱壳的步骤

[Android 脱壳] 使用 IDA 进行简单的脱壳 

 

  • 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

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

注意:Ctrl + F 方便查找

 

  • step 3:继续 IDA 
    脱壳的时候重点关注:dvmDexFileOpenPartial 函数(在该函数处下断点)

 

依次点击“Debugger -> Debugger windows -> Module list”,找到so文件列表 
在Module list中找到libdvm.so这个文件(注意:Ctrl + F 方便查找)

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

双击libdvm.so,在弹出的函数列表中找到dvmDexFileOpenPartial函数,然后双击该函数就看到dvmDexFileOpenPartial函数的具体实现

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

在dvmDexFileOpenPartial函数处下断点(F2 下断点)

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

  • step 4:使用jdb命令动态调试Apk 

点击 ida 左上角的绿色运行按钮(F9)

[Android 脱壳] 使用 IDA 进行简单的脱壳 [Android 脱壳] 使用 IDA 进行简单的脱壳


打开 DDMS 工具(android-sdk\sdk\tools\ddms.bat)
[Android 脱壳] 使用 IDA 进行简单的脱壳 
使用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]

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

此时 IDA 会弹出 ”Add map…” 窗口(点击 cancel 按钮即可):

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

[Android 脱壳] 使用 IDA 进行简单的脱壳

此时进程就执行到了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 保存着函数从左至右的第二个参数),可以查看到寄存器列表中的内容如图所示:

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

  • step 5:在 IDA 中编写idc脚本dump内存还原dex文件 

选择 File -> Script command…
[Android 脱壳] 使用 IDA 进行简单的脱壳 
[Android 脱壳] 使用 IDA 进行简单的脱壳

[Android 脱壳] 使用 IDA 进行简单的脱壳 

 

稍等片刻,即可以把 dump 出来的 dex 文件保存在 C 盘根目录

[Android 脱壳] 使用 IDA 进行简单的脱壳

 

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.****.net/guiguzi1110/article/details/38727753 
[2] 安卓逆向学习笔记(9)- 使用IDA Pro进行简单的脱壳 :http://blog.****.net/pengyan0812/article/details/46275317
[3] Android应用方法隐藏及反调试技术浅析:http://www.kuqin.com/shuoit/20151012/348473.html