Apk脱壳圣战之—脱掉“爱加密”家的壳

Android技术篇 尼古拉斯.赵四 44897℃ 0评论

一、前言

今天是端午节,然而小编不能吃粽子了,只能继续破解之路,今天我们来看一下在了解了破解三部曲之后,如何开始脱掉各个市场中的apk壳,关于破解三部曲在之前已经介绍了:

第一篇:Android中使用Eclipse动态调试smali源码

第二篇:Android中使用IDA动态调试so源码

第三篇:Android中破解加固的apk

在看完这三篇文章之后,我们开始操作如何破解市场中的加壳方案,现在市场中比较流行的加壳平台就那么几个:爱加密,梆梆加固,360加固,腾讯加固等,所以后面会一一介绍如何脱掉这些平台的壳。之前也说过现在加固的方案大体思路都是:把源apk进行加密拆分处理,然后在套一个外部的壳Application做一些初始化操作:比如解密apk,动态加载运行即可。但是我们已经知道了如何去破解那些加固的apk了,就是使用IDA给dvmDexFileOpenPartial函数下断点,然后dump出内存中的dex数据即可。因为内存中的dex肯定是解密之后的,所以大体思路知道了,但是这些加固平台也有对策,他们会把做一些反调试操作,对so文件进行混淆加密等,让我们的调试变得比较困难。这才是我们脱壳的阻碍地方。

 

二、案例分析

好了,说了这么多,下面我们就开始脱壳第一站:爱加密家的壳

为了脱掉他家的壳,我们得首先有一个案例程序,这个比较简单,我们自己弄一个demo程序,然后去他家的网站上加固一下,得到加固之后的apk,然后这时候我们开始破解了,按照惯例:

第一步:解压apk,看看大体的目录,得到classes.dex文件,然后用dex2jar+jd-gui得到Java源码

看到,这里只有Application的壳,而且这个是爱加密加固之后的特点,都是这两个Application的。

第二步:使用apktool来反编译apk,获取资源文件信息

分析一下爱加密的加密流程

也是国际惯例,爱加密把我们的源程序进行加密操作然后隐藏到了一个地方,在之前破解加固apk的那篇文章中也说过了,隐藏的地方就那么几个:assets目录、libs目录、自己的dex文件中

这里我们直接看assets目录:

多了这个东东,猜想这个可能就是处理之后的源apk了。我们在AndroidManifest.xml中看到了入口的Application类,先来看这个类

下面我们来分析一下这个SuperApplication类:

这里一般都是在attachBaseContext这个方法中进行操作的,这里的时机比较早,我们看到首先会调用loadLibs方法进行加载libs:

这里区分不同的平台,然后进行拷贝不同的so文件,继续看copyLib方法:

这里我们可以看到了,从assets目录下把爱加密增加的两个so文件:libexec.so和libexecmain.so拷贝到应用程序的files目录下,我们可以去看看assets/ijm_lib目录下的so文件:

到这里loadLibs方法就执行完了,下面就开始调用NativeApplication的load方法进行加载数据,继续看NativeApplication类:

这里会开始从应用程序的files目录中加载这两个so文件,而且load方法也是一个native方法,我们继续看看这两个so文件内容:

我们首先用IDA打开libexecmain.so文件,但是发现,他里面并没有什么重要信息,连JNI_OnLoad函数都没有东东

我们继续在查看libexec.so文件:

擦,可惜的是,打开提示so文件格式错误,到这里,我们就猜到了,这个so可能被加密处理了,elf格式改了,关于so如何进行加密操作的,不了解的同学可以看这里:Android中如何对so文件进行加密 那么这里我们点击Yes继续强制打开之后,在使用Ctrl+S查看so的各个段信息:

现在可以百分百的确定,这个so文件被处理了,段格式被修改了。我们没办法分析so文件了,当然这里我们可以在dump出内存中的so文件,然后在分析的,但是这个不是今天讲解的重点。我们先分析到这里,也知道了爱加密的大体加密流程。

好了,到这里,我们差不多分析完了爱加密的加密流程了:

1、按照国际惯例把源apk进行加密处理存放在一个地方,通过分析猜想是assets目录下的ijiami.dat文件

2、添加壳Application:SuperApplication类,在这个壳的attachContext方法中,主要做了两件事:

1》第一件事是把assets/ijm_lib目录下的两个so文件copy到程序的files目录中;

2》第二件事是调用NativeApplication的load方法,在这个类中同时也把上面的两个so文件加载到内存中

3、对apk的加密操作都是放在底层的两个so文件中操作的,我们通过IDA去分析这两个so文件之后,发现核心功能的so文件被加密了,IDA打开是看不到具体信息了

到这里,我们知道爱加密加固之后的特点是:在程序的assets目录下多了一个ijiami.dat文件和两个so文件,同时这两个so文件被加密处理了,增加破解难度。

 

三、破解脱壳

上面就简单分析了爱加密的原理和流程,但是我们没有继续往下面分析了,因为这个不是我们今天讲解的重点,我们今天的重点是如何脱掉爱加密的壳,那么还是开始说到的,脱壳的核心就一个:给dvmDexFileOpenPartial函数下断点,dump出内存的dex文件即可,那么下面我们就是用IDA开始脱壳操作了:

第一步:启动设备中的android_server,然后进行端口转发

adb forward tcp:23946 tcp:23946

第二步:用debug模式启动程序

adb shell am start -D -n com.droider.crackme0201/.MainActivity

这里的包名和入口Activity都可以在上面反编译之后的AndroidManifest.xml中找到

第三步:双开IDA,一个用于静态分析libdvm.so,一个用于动态调试

记录dvmDexFileOpenPartial函数的相对地址:4777C

再次打开一个IDA,进行attach调试进程

第四步:使用jdb命令attach上调试器

jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=8700

第五步:对dvmDexFileOpenPartial函数下断点

进入调试页面之后,Ctrl+S查找libdvm.so的内存基地址:415BB000

在第三步得到相对地址:4777C+415BB000=4160277C 得到了dvmDexFileOpenPartial在内存中的绝对地址

注意:

当然这里还有一个更方便的办法:

就是直接打开Modules View:

在这里查找libdvm.so文件:

然后双击libdvm.so文件:

查找需要下断点的函数名称,看到这里的绝地地址也是:4160277C

这里有两种方式可以得到一个函数在内存中的绝对地址。

然后我们使用G键,直接跳转到函数处,下断点:

第六步:设置Debugger Options选项

能够让程序断在dvmDexFileOpenPartial函数处

注意:

上面的第四步,第五步,第六步,没有顺序的,只要在运行之前设置到就可以了。

第七步:运行程序

出现这个对话框,不要在意,一路点击Cancel即可

jdb也attach上了调试程序:

我们一路点击运行按钮,知道运行到dvmDexFileOpenPartial处的断点,但是可惜的是,这里我们遇到了错误:

我们点击OK之后,出现了下面对话框:

再次点击任何一个按钮,都会退出了调试页面:

我们在重新尝试一次上面的流程,开始调试,但是错误是一样的,好了,到这里我们就立马想到了,之前说的IDA调试so的那篇文章遇到的那个问题:反调试检测

当时我们也是遇到这个情况,在没有运行到我们下的断点处,就退出了调试页面,其实这个是现在加固平台必要选择的一种方式,其实反调试原理很简单,就是在程序运行最早的时机比如so加载的时候即:JNI_OnLoad方法中,读取本进程的status文件,查看TracerPid字段是否为0,如果不为0,那么就表示自己的进程被别人跟踪了,也就是attach了,那么这时候立马退出程序,下面我们使用IDA在attach进程成功之后,查看本进程的status信息:

看到这里的TracerPid为11340,不为0,表示被11340进程attach了,那么我们可以查看一下这个进程是谁:

其实这个进程就是我们在设备中安插的android_server,它用于和IDA进行通信。

好了到这里,我们可以看到爱加密做了反调试检测,但是按照之前的那篇文章中,我们可以给JNI_OnLoad函数下断点,然后找到检测代码,把对应的arm指令改成空指令,检测失效了,但是这里我们知道爱加密的两个so文件被处理了,IDA没法分析了,那么这里我们该怎么办呢?如何应对反调试呢?其实我们可以借助IDA可以修改寄存器和内存数据的特性来做到?

首先我们上面分析了反调试的原理,一般在native代码去做检测的话,都是用fopen系统函数打开status文件,然后用fgets函数读取一行的内容,这个是国际惯例的,操作文件都是用的fopen函数的

好了,那么这里思路就有了:既然反调试肯定用到了fopen和fgets这两个函数,那么我们直接像给dvmDexFileOpenPartial下断点的方式一样,给这两个函数下断点,然后运行到fgets断点处的时候,发现如果是读取TracerPid这行内容的时候,就开始修改内存内容,把TracerPid字段的值改成0,或者修改R0寄存器的内容,跳过反调试检测

这两个函数是在libc.so文件中的,我们可以把设备的/system/lib/libc.so使用adb pull到本地即可,然后用IDA得到他的相对地址,在调试页面得到基地址,然后相加得到绝对地址,跳转即可,但是这里不用这种复杂的方式,有两种方式可以进行跳转:

第一种方式:在Modules界面,找到libc.so,然后在找到这两个函数,就可以得到他们的绝对地址了

然后使用G键,跳转下断点即可:

第二种方式:也是最简单的方式,就是G键,本身就有可以直接输入函数名进行跳转的功能

下断点:

看到了吧,这种方式是不是非常简单高效

好了到这里就给这两个函数下好了断点,当然这里还需要给dvmDexFileOpenPartial函数下断点,一切弄好了之后,这时候我们再次运行:

停在了fopen断点处,我们使用F8单步调试,看到R7寄存器中的内容是/proc/…,我们直接点击R7查看全部内容:

内容有点长,大致的内容是:/proc/self/cmdline.debug.atrace.app_cmdlines,这个是干什么的?

我们看看这个目录内容:

发现没有这个文件内容,只有cmdline文件,但是这里先不管他了,我们知道这个肯定不是读取status文件的,那我们直接略过这个断点,点击F9运行到下一个断点,中间过程先忽略,一路F9,直到运行到了fopen这个断点:

果然,这里使用了fopen来读取status文件了,点击R7寄存器查看全部内容:

这个16396就是我们本进程的id:

到这里,我们知道下一个断点肯定是fgets,所以点击F9进入到fgets断点处:

这里还看不到什么信息,我们继续点击F8单步调试:

途中,会看到有memchr和memcpy这两个重要函数,这个也是操作字符串的核心点,继续往下走:

到了fgets函数结束的地方,我们看到了R0寄存器的内容是Name…点击R0查看全部内容:

全部内容是:Name:   der.crackme0201;这个就是status文件的第一行内容:

到这里,我们知道了,开始读取status文件的每行内容了,但是到TracerPid那行还要继续执行5次fgets函数,所以还会进入5次断点,为了节省时间,这里点击5次F9,直接运行到读取TracerPid那行的内容的fgets断点处:

看到了关键的内容了TracerPid字段了,这时候,我们打开Hex View 查看16进制的内存数据:

但是我们看到,这个并没有和调试页面View位置相对应,我们可以这么操作:

在寄存器窗口查看到R0寄存器的内容:

这里就是TracerPid字段在内存的地址,记录一下,然后在Hex View页面中使用G键,进行跳转,这里一定要注意是在HexView,而不是调试页面,调试页面使用G键跳转到的是指令地址了。

好了,这里我们看到了TracerPid的内存内容了,这里我们就开始修改吧,选择我们要修改的内容:是11340那里:

选择内容开始处,右击,选择Edit,进入修改状态:

改了之后的内容是橘黄色的,修改完成之后,在点击右键,选择Apply changes:

完成修改,颜色变成灰土色了:

注意:

这里其实还可以直接修改寄存器R0的值:

这时候就表示修改成了,我们继续使用F8单步调试下去:

这里就开始把TracerPid字段的值和0作比较了,我们点击R0寄存器查看全部内容:

这里的值已经被改成了0,所以这里就骗过去了。继续运行,我们会发现,又进入了fopen函数的断点处,而且查看还是读取status文件,这个也不好奇,因为是反调试检测肯定是一个轮训机制的,所以肯定会反复的读取的这个文件,fopen走多次也是正常的,但是这个反调试肯定是在子线程的,所以只要到了主线程中解密dex文件就肯定到了dvmDexFileOpenPartial,所以这里会多次重复上面的操作,修改多次TracerPid的值,这里就不在演示了,我在操作的过程中修改了三次,当没有在走fopen函数的时候,遇到了这个错误,这里不关心,直接点击ok就可以了。

再次点击运行:

这里说明已经改开始解密dex文件了,应该离成功不远了,继续运行:

终于到了我们想要的地方了,到这里就好办了,直接点击Shirt+F2,打开脚本运行窗口,运行下面脚本:

static main(void)
{
auto fp, dex_addr, end_addr;
fp = fopen(“F:\\dump.dex”, “wb”);
end_addr = r0 + r1;
for ( dex_addr = r0; dex_addr < end_addr; dex_addr ++ )
fputc(Byte(dex_addr), fp);
}

把内存中的dex保存到F:\dump.dex中,这里不再解释了,之前的一篇文章已经介绍过了,这里R0寄存器就是dex在内存中的起始地址,R1寄存器就是dex文件的大小:

我们使用G键,可以在HexView页面中查看R0寄存器中的地址内容:

看到了吧,这里就是dex的头文件格式。

 

四、还原应用apk

我们得到了内存中的dex数据之后,可以使用baksmali工具转化成smali源码,查看代码逻辑即可,这里不再演示了。

然后最后还有一步:还原apk

首先我们修改反编译之后的AndroidManifest.xml中:

把这段内容删除,如果有自己的Application的话,就改成自己的Application即可,同时删除assets目录下面的文件。

然后使用apktool进行回编译,这时候,先不要着急签名apk,而是替换classes.dex:

我们把上面得到的dump.dex改成classes.dex然后直接用压缩软件,替换未签名的apk中的dex文件即可

最后在进行签名操作,完成还原apk工作。

 

五、总结爱加密的破解流程

好了到这里,我们算是脱壳成功了,下面来总结一下吧:

目标:

在脱壳的过程中,我们就一个目标:dump处内存中的dex文件

但是在上面分析了爱加密的加固流程之后,发现他做了这些事:

1、把源程序apk加密处理放到了assets目录下的ijiami.dat,也同时在assets\ijm_lib目录下添加两个so文件:libexec.so和libexecmain.so,这里两个so文件用来处理整个apk解密,动态加载等逻辑,但是我们用IDA查看得知,这两个so文件被加密处理了

2、添加自己的壳Application:SuperApplication类,这个类中的attachContext方法中,首先把assets目录中的两个so文件copy到应用的files目录下,然后在使用System.load方法,加载这个files目录中的两个so文件

3、我们在给dvmDexFileOpenPartial下断点,进行调试的时候,发现有反调试检测,因为无法给JNI_OnLoad下断点来去除反调试功能的arm指令,所以只能去修改内存数据,把TracerPid的值变成0,骗过检测了。这里我们的思路就是给fopen和fgets这两个函数下断点,因为我们知道反调试的原理就是读取本进程的status文件,然后获取TracerPid那行内容即可,所以这里肯定用到了fopen和fgets函数,在使用fgets这个函数的时候,会读取每行内容,那么我们只要发现在读取到TracerPid那行内容的时候,去修改内存值,把TracerPid字段的值改成0即可。

4、有了上面的反调试思路之后,我们就开始进行操作了,但是在操作的过程中发现多次执行了fopen和fgets函数,而且我们需要修改多次TracerPid的值,原因很简单,因为是反调试检测,肯定是在子线程中轮训去检测这个值,所以会执行多次很正常,所以我们要修改多次TracerPid的值,骗过检测,直到当在主线程中,代码运行到了解密dex文件的时候,即到了dvmDexFileOpenPartial函数处的断点处为止

5、最后修改多次TracerPid值,骗过检测,到了dvmDexFileOpenPartial这里,这时候,在执行dump脚本,把内存中的dex数据dump到本地即可。

通过上面的调试和破解流程其实不难发现,爱加密的流程是这样的:

1》fopen—/proc/self/cmdline.debug.atrace.app_cmdlines
2》fgets—-com.droider.crackme0201
3》dvmLoadNativeCode–加载libexec.so
4》dvmLoadNativeCode–加载libexecmain.so
5》建立反调试线程(通过检查是否存在调试进程)
6》调用fopen—-打开/proc/pid/status
7》调用fgets—读取调试进程pid

这里除了dvmDexFileOpenPartial函数,还有一个重要的函数dvmLoadNativeCode,它是加载和初始化so的函数,如果感兴趣的同学,可以去给这个函数下断点看看运行逻辑。

所以我们只要记住我们的目的只有一个:达到dvmDexFileOpenPartial函数处,dump处内存中的dex文件,就算是完成脱壳工作

 

六、总结

到这里我们就分析完了如何去脱掉爱加密的壳,其实在之前说过,现在各个加固平台的原理都差不多,最后看到的就是各家的加固算法了,所以我们在脱壳的过程中目标也很明确,就是dump出内存中的dex文件即可。不管他上层再怎么牛逼的加密拆分操作,到了内存肯定是完整的dex文件了,所以现在加固平台也是一个思想就是不让你dump出来,就是让你给dvmDexFileOpenPartial函数下断点失败,调试失败。

《Android应用安全防护和逆向分析》

点击立即购买:京东  天猫

更多内容:点击这里

关注微信公众号,最新Android技术实时推送

转载请注明:尼古拉斯.赵四 » Apk脱壳圣战之—脱掉“爱加密”家的壳

喜欢 (27)or分享 (0)
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
(6)个小伙伴在吐槽
  1. 大哥 如何给SO加一个 节,我看了你加节的代码,但是 和 UPX压缩壳 不兼容。你QQ多少
    有种你来脱我的壳2016-06-24 00:28 回复
    • 名字好霸气。。有加固的样本吗,我来脱 😉
      冰雪2016-06-28 15:42 回复
  2. 代码抽取的,用你这种在dvmDexFileOpenPartial下断没用哦。完整的dex根本没在内存出现过
    冰雪2016-06-28 15:41 回复
  3. 关于移动加固的我也很喜欢玩,但是爱加密的最新版如同评论里的同学所说,的确是没有整体dump了,我的微信和qq就是邮箱的数字,加一下好友交流一下吧
    wnagzihxain2016-07-04 22:38 回复
  4. 楼主能提供一下样本链接吗?很不错的一篇文章,还有那个脱360的,现在在学习安卓逆向,能看到这些文章真心不错。
    Banyan2016-08-15 23:01 回复
  5. 主能提供一下样本链接吗?
    黑之暗夜2016-08-22 11:32 回复