原文html
如今主流的加固平台有:梆梆加固,爱加密,360加固,腾讯加固,在以前的一篇文章中介绍了:如何脱掉“爱加密”的壳,如今这里要脱掉另一个平台的壳:360加固,由于有了以前的脱壳经验,不少基础知识和准备工做这里就不详细介绍了,为了可以脱掉他家的壳,用一个案例来去360平台进行加固,而后进行脱壳。下面就来开始脱壳:android
首先拿到加固以后的apk,这里为了方便查看内部信息,先不用dex2jar+jd-gui工具进行分析了,直接使用咱们以前分析了源码的一个工具:Jadx,直接查看:shell
其实如今的加固的常规套路都差很少,这里看到和以前分析的爱加密加固的形式几乎同样,这里的壳Application是StubApplication在attachBaseContext中作一些初始化操做,通常是将assets目录中的so文件拷贝到程序的沙盒目录下:/data/data/xxx/files/..;而后再用System.load进行加载,经过查看能够得知源程序apk已经被加密了,就是存放在这里的so中,以前的文章也是分析了,通常源程序加密以后就存放在那几个目录下,通常是:dex文件尾部,libs目录,assets目录。api
下面再来看一下他的AndroidManifest.xml文件:app
找到了他的入口Activity了,可是这里没有android:debuggable="true",因此程序是不能被调试的,因此咱们须要添加这个属性,而后在进行回编译进行调试,这时候就须要使用到apktool工具了:框架
好了,这里看到,360加固为了防止apktool反编译功能,添加了一个qihoo属性,这个属性apktool不认识就报错了,可是咱们以前的一篇文章已经介绍了:Apktool工具错误修复,咱们有了apktool源码,能够直接进行修复的,而后进行反编译:函数
反编译成功了,查看他的AndroidManifest.xml文件内容:工具
的确,是有一个属性qihoo,这个就是Android系统在解析apk文件的时候,发现不存在的属性直接略过,可是apktool工具却不会,360加固就是利用这个漏洞来增长反编译难度的,可是咱们以前的一篇文章中介绍了如何修复,这里修复很简单了。因此说只要有了apktool源码,什么都好作了。性能
而后咱们在添加android:debuggable属性:学习
而后回编译:
这时候看到,在回编译的时候也是报错了,说找不到这个属性,为了方便这里直接把android:qihoo给干掉,由于其实他没有任何做用的,就是为了干扰反编译工做的,因此直接去掉便可,而后在回编译:
好了,回编译成功,而后在进行签名打包便可。这里就不在介绍了。
那么从上面咱们能够看到,其实360加固为了防止反编译,就利用了Android系统自己在解析apk的时候,遇到不认识的属性直接略过,而apktool工具却不会的漏洞来给AndroidManifest.xml中添加一个混淆反编译的属性:qihoo,幸亏咱们有源码,能够修复这个问题,在进行反编译便可,这里也但愿apktool官网可以及时修复这个漏洞。为了回编译成功,咱们能够直接把这个属性删除。否则回编译也是会报错的。这个属性只是360为了混淆反编译工做,因此删除对程序逻辑没有任何影响的。
这里就要开始介绍本文的第一个重点了:如何在不须要反编译的状况下,添加android:debuggable属性,就能够进行调试。
这个如今已经有不少工具能够作了,先来讲说具体的原理吧:
其实Android中有一些经常使用的配置信息都是存放在一个文件中,好比设备的系统,版本号,cpu型号等信息,而这个文件位置在:
/system/build.prop
咱们查看文件的内容,能够看到不少设备的信息,并且这些ro开头的表示这些属性值是只读的,不能进行修改的。
同时Android中提供了两个命令来操做这些信息:getprop和setprop命令:
查看系统的sdk版本号
设置系统的sdk版本号为22,但是这里并无修改为功,缘由就是由于ro开头的属性是不容许后期修改的,改也是能够修改的,须要从新编译系统镜像文件boot.img,可是这里并非本人介绍的重点了。
既然Android中的一些系统属性值存放在一个文件中的,并且这些值是只读的,固然不只能够经过getprop命令读取,有一个api也是能够直接读取的,就是:System.getProperty("ro.build.version.sdk");其实这个方法是native层实现的,具体就不分析了。
那么这个文件是存储这些属性值的,那么是谁来进行解析加载到内存中,可以给每一个app都能访问到呢?
这个工做就是init.rc进程操做的,咱们应该了解了系统启动的时候第一步就是解析init.rc文件,这个文件是在系统的根目录下,这里会作不少初始化操做,这里不详细分析了,后面再分析Android中系统启动流程的时候在详细分析。这里同时会作属性文件的解析工做,因此,Android 属性系统经过系统服务提供系统配置和状态的管理。为了让运行中的全部进程共享系统运行时所须要的各类设置值,系统会开辟一个属性存储区域,并提供访问该内存区域的 API。全部进程均可以访问属性值,可是只有 init 进程能够修改属性值,其余进程若想修改属性值,须要向 init 进程发出请求,最终由 init 进程负责修改属性值。
那么上面说到的是system/build.prop文件。里面主要是系统的配置信息,其实还有一个重要文件在根目录下面:default.prop:
这里有一个重要属性:ro.debuggable,对这里就是关系到系统中每一个应用是否可以被调试的关键。其实在Android系统中一个应用可否被调试是这么判断的:
当Dalvik虚拟机从android应用框架中启动时,系统属性ro.debuggable为1,若是该值被置1,系统中全部的程序都是能够调试的。若是系统中的 ro.debuggable 为0,则会判断程序的AndroidManifest.xml中application标签中的 android:debuggable元素是否为true,若是为true则开启调试支持。
好了到这里,咱们能够总结一下了:
Android系统中有一个能够调试全部设备中的应用的开关,在根目录中的default.prop文件中的ro.debuggable属性值,若是把这个值设置成1的话,那么设备中全部应用均可以被调试,即便在AndroidManifest.xml中没有android:debuggable=true,仍是能够调试的。而这些系统属性的文件system/build.prop和default.prop,都是init进程来进行解析的,系统启动的时候就会去解析init.rc文件,这个文件中有配置关于系统属性的解析工做信息。而后会把这些系统属性信息解析到内存中,提供给全部app进行访问,这块信息也是内存共享的。可是这些ro开头的属性信息只能init进程进行修改。下面来分析一下修改这个属性值的三种方式:
第一种:直接修改default.prop文件中的值,而后重启设备
那么如今若是按照上面的目的:就是不须要反编译apk,添加android:debuggable属性的话,直接修改default.prop文件,把ro.debuggable属性改为1便可,可是经过上面的分析,修改完成以后确定须要重启设备的,由于须要让init进程从新解析属性文件,把属性信息加载内存中方可起做用的。可是并无那么顺利,在实践的过程当中,修改了这个属性,结果出现的结果就是设备死机了,其实想一想也是正常的,若是属性可以经过这些文件来修改的话,那就感受系统会出现各类问题了,感受系统是不会让修改这些文件的内容的。
第二种:改写系统文件,从新编译系统镜像文件,而后刷入到设备中
那么上面修改default.prop文件,结果致使死机,最终也是没有修改为功,咱们还有什么办法呢?其实上面已经提到过一次了,就是这些属性文件实际上是在系统镜像文件boot.img在系统启动的时候,释放到具体目录中的,也就是说若是咱们可以直接修改boot.img中的这个属性便可,那么这个操做是能够进行的,可是困难那是不通常的顺利,至少我没成功过,修改系统文件,而后从新编译镜像文件,最后在刷到设备中。这个过程我尝试过是失败了,不过理论上是能够的。并且这种方式若是成功了,那么这个设备就是永远能够进行各类应用的调试了。
第三种:注入init进程,修改内存中的属性值
那么上面直接从新编译boot.img,而后在刷到设备中的工做是失败的,那么还有其余方法吗?确定是有的,咱们其实在上面分析了,init进程会解析这个属性文件,而后把这些属性信息解析到内存中,给全部app进行访问使用,因此在init进程的内存块中是存在这些属性值的,那么这时候就好办了,有一个技术能够作到了,就是进程注入技术,咱们可使用ptrace注入到init进程,而后修改内存中的这些属性值,只要init进程不重启的话,那么这些属性值就会起效。好了,这个方法能够尝试,可是这个方法有一个弊端,就是若是init进程挂了重启的话,那么设置就没有任何效果了,必须从新操做了,因此有效期不是很长,可是通常状况下只要保证设备不重启的话,init进程会一直存在的,并且若是发生了init进程挂掉的状况,那么设备确定会重启的。到时候在从新操做一下便可。
好了上面分析了三种方式去设置系统中的调试属性总开关,那么最后一种方式是最靠谱的。
并且思路也很简单,可是咱们不会从新去写这个代码逻辑的,由于已经有大神作了这件事,具体工具后面会给出下载地址:
这个工具用法很简单,首先把可执行文件mprop拷贝到设备中的目录下,而后运行命令:
./mprop ro.debuggable 1
这个工具能够修改内存中全部的属性值,包括机型信息。
这里修改完成以后,使用getprop命令在查看值,发现修改为功了,可是须要注意的是,咱们修改的是内存的值,而不是文件中的值。因此default.prop文件中的内容是没有发生变化的。
这时候,咱们可使用Eclipse的DDMS来查看能够调试的应用列表:
固然也可使用adb jdwp命令来查看能够调试的进程id:
可是惋惜的是,发现仍是没有展现设备中全部的应用,其实这里是有一个细节问题了,由于咱们虽然修改了内存值,可是有一个进程咱们须要重启一下,哪一个进程呢?那就是adbd这个进程,这个进程是adb的守护进程,就是设备链接信息传输后台进程,因此想看到能够调试的进程信息的话,那么须要重启这个进程,这样链接信息才会更新。
重启这个进程很简单:直接使用stop;start命令便可
其实这是两个命令,用分号隔开,首先是干掉进程,而后在重启。
运行完命令以后,再去看DDMS窗口信息:
这时候全部的应用进程都是能够调试的了,这时候咱们在使用dumpsys package命令查看一个应用的包信息:
这里能够看到,这个应用的flags标志中并无debuggable属性值,可是这个应用是能够调试的。因此看到ro.debuggable这个是总开关,只要他为1,开启的话,即便没有android:debuggable也是能够的了。
好了到这里,咱们来总结一下:
一、咱们的目的是怎么在不须要反编译apk包,添加android:debuggable属性,就能够进行apk的调试?
二、咱们经过分析系统属性文件和系统启动流程以及解析系统属性文件的流程,知道了设备中关于调试有一个总开关属性值:ro.debuggable,默认是0,不开启的。那么这时候咱们就能够猜测有这几种方式能够去修改。
三、分析了三种方式去修改这个属性值:
第一种方式:直接修改default.prop文件中的这个字段值,可是惋惜的是修改失败,在修改的过程当中出现死机,重启设备以后,属性值仍是0。
第二种方式:修改系统源码的编译脚本,直接修改属性值,而后从新编译镜像文件boot.img,而后刷入到设备中,可是在实践的过程当中并无成功,因此放弃了,并且这种方式有一个好处就是一旦修改了,只要不在从新刷系统,那么这个字段将永远有效。
第三种方式:注入到init进程,修改内存中的这些系统属性值,这种方式实现是最简单的,可是有一个问题,就是一旦设备重启,init进程从新解析default.prop文件的话,那么ro.debuggable值将又从新被清空,须要再次注入修改。
四、最后采用了第三种方式,不过网上已经有人写了这样的工具,用法也很简单:./mprop ro.debuggable 1;可是修改完成以后,必定要记得从新启动adbd进程,这样才可以获取到能够调试应用信息。
五、使用工具修改完成以后,在Eclipse中的DDMS窗口发现,设备中的全部应用都处于能够调试状态了。也就是说咱们的操做成功了。
那么上面的这个过程成功以后的意义仍是很大的:标志着咱们之后若是是单纯的想让一个apk可以被调试,去反编译在添加属性值的话,其实这种方式很高效的。可让任意一个apk出于被调试状态。
讲完了上面的一个重点以后,下面咱们就开始来说解本文的另一个重点,开始脱壳了。
第一步:开启android_server
第二步:端口转发
第三步:启动应用
adb shell am start -D -n com.CMapp/com.e4a.runtime.android.mainActivity
第四步:开启IDA,附加进程
第五步:设置Debugger Option选项
第六步:运行jdb调试等待
jdb -connect com.sun.jdi.SocketAttach:hostname=127.0.0.1,port=10265
注意:这里须要注意了,由于咱们改了系统的ro.debuggable属性,设备中全部的应用都处于可调式状态,基本端口8700已经被占用了,那么这时候须要使用被调试程序的独有端口了,能够在DDMS窗口进行查看。
第七步:关键函数下断点
首先找到mmap函数的内存地址,这里能够直接使用G键,经过函数名来跳转:
注意:这里和以前的脱爱加密的壳方法可能不同了,还记得以前脱爱加密的壳的时候,给fopen和fgets函数下断点,由于若是有反调试的话,确定是读取/proc/pid/status文件中的TracerPid字段值的,而后修改TracerPid值为0便可,可是这个方法对360加固的很差使了,由于360加固的反调试是经过mmap函数来读取/proc/pid/status,因此这里须要给mmap函数下断点了,并且后面还会看到给dvmDexFileOpenPartial这个函数下断点也很差使了,缘由是360加固本身在底层实现了解析dex的函数来替代了这个dvmDexFileOpenPartial函数。可是无论是他本身实现dex解析加载,最终都是须要把dex文件加载到内存中,仍是得用mmap函数来进行操做。因此在脱360加固的壳的时候mmap函数是重点。
好了给mmap函数下了断点,下面就F9运行程序吧:
进入到了mmap的断点处,这里由于mmap函数代码比较长,为了节省时间,咱们能够在mmap函数的结束处下一个断点,而后直接F9运行到函数的结尾处,由于系统中有不少个so须要加载到内存中,因此mmap函数会执行屡次,可是其实咱们最关心的是加载咱们本身的so文件,即libjiagu.so文件,由于这个才是咱们的native层代码,因此等出现以下界面:
这时候,说明这个so文件被加载到内存中了,也就是程序的native层代码开始执行了,注意不能在F9了,而是使用F8单步调试:
F8单步运行到这里的时候,遇到一个问题,就是F8了不少次,始终在这个地方执行,后来分析了arm指令以后,发现原来这里是一个循环,初始值是0,存储在R11中,而后逐步加1,和R3中存储的阈值做比较,经过查看寄存器的值,发现R3寄存器中是A7,因此这里得去修改寄存器R11的值了,否则咱们得单步A7次,这里直接把R11值修改成A6:
修改寄存器也是很容易的,直接右击寄存器:
点击Modify value:
点击OK,以后再来看看R11的寄存器的值:
修改为功了,这时候在单步F8,两次以后就执行完了循环了,从这里也能够看到,这个地方也算是为了防止被调试,加大调试成本的一种方式。继续往下走:
到这里,执行完BL以后就退出调试界面了,尝试屡次都同样,因此猜测反调试确定在这里,能够F7跟进去看看:
到BLX这里,每次以前完也是退出调试界面,因此这里还得F7单步进入看看:
这里看到了一行重要的arm指令:CMP比较指令,并且是和0比较,极可能这里就是比较TracerPid的值是否为0,若是不为0就退出,能够查看R0寄存器的内容:
而后在查看被调试进程的TracerPid的值:
果真R0存储的是TracerPid的值,为了验证正确性,这里继续:
果真,运行到了自杀的地方,一直单步运行:
退出程序了。
那么上面就知道了反调试的地方,就好办了,直接修改寄存器R0的值为0便可:
而后继续单步F8运行,后面还有一个CMP和0进行比较的地方,咱们同样进行置零操做,再次单步F8,当运行到此处的时候:
看到memcpy函数的时候,这时候能够直接运行F9,又会执行到mmap那里,而后依次F9,仍是运行到了上面的那个循环,这样依次类推,在这个过程当中我运行了7次循环,改了R0值改了9次,因此这个地方会执行屡次是正常的,可是这里在我屡次调试以后总了一个好的方法,就是看到屡次执行的路线都差很少:
mmap函数=》循环=》(MOV R0,R8)BL=》(MOV LR,R4)BLX=》CMP R0,#0=》mmap....
这个过程当中,其实为了简便咱们能够
1》在mmap函数的开始处,结束处下一个断点,这两个断点是为了后面加载内存的dex文件作准备
2》在循环处下一个断点,这个断点是为了修改循环值,节省时间
3》在BL处下个断点,是为了进入BLX
4》在BLX处下个断点,是为了进入比较TracerPid处
5》在CMP下断点,是为了修改TracerPid的值
同时在这个过程当中,须要使用F9,直接跳转到下一个断点,高效,只有在到达了CMP处的时候,要用F8单步调试,并且这个地方必定要当心,不能按错了,否则又得从头再来,我吃了不少次亏,也重来了不少次。只要当看到了memcpy函数的时候,再次F9到下一个断点处。更须要注意的是:每次到达mmap断点处的时候,必定要看当前栈信息的视图窗口,看看是否出现了classes.dex的字样,由于最终都是使用mmap来把解密以后的dex加载到内存中的,因此这里必定要注意,是本次调试的核心。
固然这个只是我的的调试思路,每一个人都有本身的思路,只要能成功均可以。
就这样来回搞了几回以后,终于看到了曙光:
当再次来到了mmap函数处的时候,终于看到了classes.dex字样了,说明这里开始解密dex而后进行加载到内存了,这时候不能在F9跳转了,而是F8单步运行,而后查看R0寄存器的值:
每次都是执行完__mmap2这个函数以后,R0就有值了,每次看到R0中有值的时候,能够到Hex View窗口中使用G键开始地址跳转,查看是否为dex内容:
若是发现不是,就仍是单步F8,知道mmap函数结束,而后再次F9,到达mmap函数开始处,时刻看紧Hex View,栈窗口,R0寄存器这三个地方的值:
在屡次尝试以后,终于成功了,这里看到了熟悉的dex文件的头信息,关于dex文件的头部信息能够看这篇文章:Dex文件格式解析
因此这里在头部信息的第33个字节而后连续4个字节就是dex的长度了,那么如今有了dex在内存中的其实位置,长度大小,下面就可使用Shirt+F2打开脚本执行窗口,dump出内存中的dex数据:
static main(void)
{
auto fp, begin, end, dexbyte;
fp = fopen("E:\\dump.dex", "wb");
begin = 0x755A9000;
//偏移0x20处,取4字节为dex文件大小
end = 0x755A9000 + 0x0004BC38;
for ( dexbyte = begin; dexbyte < end; dexbyte ++ )
fputc(Byte(dexbyte), fp);
}
保存到E:\dump.dex,而后在使用Jadx工具进行查看:
这里能够查看到源码了,并且类名,方法名,变量名都是用中文来命名的,感受好不习惯,可是Java中是支持这么干的,由于Java采用的是Unicode编码的。
好了到这里,咱们就成功了脱掉了360加固的壳了,下面来总结一下他的壳的特色和调试须要注意的点:
一、首先360加固依然是外部套一个Application壳:StubApplication,源程序加密存放在libjiagu.so,放在了assets目录下,在Application启动的时候,释放到应用的沙盒目录files下面,而后在使用System.load方法进行加载,这个和爱加密的方式是同样的
二、关于360加固的反调试,依然使用的是读取/proc/[pid]/status中的TracerPid字段值,判断是否为0,可是这里和爱加密不同的是,在读取这个文件的时候不是用的fopen系统函数,而是mmap系统函数,因此在解决反调试的时候须要给这个函数下断点。
三、360加固底层不是采用dvmDexFileOpenPartial这个系统函数来解析dex而后加载到内存中的,而是本身实现了一个函数,因此给这个函数下断点,而后获取参数值来dump内存中的dex数据是行不通的,可是有一个思路就是无论他用哪一个函数去解析dex加载到内存,最终都得使用mmap这个系统函数来操做,因此还得给这个函数下断点,因此这里在调试的时候须要时刻注意的是当断点到达了mmap函数处的时候,须要观察Stack View栈窗口中是否出现了classes.dex字样,若是出现了,说明开始解密dex文件,准备加载到内存中了,那么这时候须要观察R0寄存器的值,而后在Hex View中跳转到指定内存地址,能够观察到是否为dex内存数据
四、在观察是否为内存数据的时候,须要注意dex文件是有本身的文件格式的,那么头信息就是个根据,因此咱们能够查看开头为:dex.35 这样的内容来判断此处为dex数据,由于dex头部信息中也有dex的文件大小,那么这时候就可使用脚本dump处内存中的dex数据了。
五、在调试的过程当中,会发现不少断点屡次执行,特别是有一个循环,须要咱们修改寄存器的值来快速结束循环,并且在关键处下断点,也是加快调试效率的。
一、本文开始的时候介绍了经过注入系统init进程,修改内存中的系统属性值:ro.debuggable,让设备中全部的应用均可以被调试,这个功能将对后续逆向破解有重大意义,也会省去了反编译的工做。因此这个方式仍是很具备里程碑意义的。
二、在脱爱加密的壳的时候,学习到了给fopen和fgets这两个系统函数下断点来解决反调试,在这里咱们又多了一个下断点的好去处就是给mmap下断点,当发现给fopen函数下断点很差使的时候,在尝试给mmap下个断点吧。
三、在脱爱加密的壳的时候,给dvmDexFileOpenPartial函数下断点,来获取dex在内存的起始地址和大小,从而dump处内存中的dex数据,可是360加固并无走这个函数,由于在给这个函数下断点的时候,他压根没走到,因此判定它内部使用了其余的函数去解析dex的,而后加载到内存中的,可是若是最后加载到内存中,那确定要用到mmap函数,因此只要给mmap函数下断点便可。
本篇文章就介绍了如何脱掉360平台加固的apk应用的壳,在结合以前的一篇脱掉爱加密家的壳的知识,看到如今在脱壳的时候其实就两点,一点是找到关键处解决反调试,通常都是fopen,fgets,mmap,open等系统函数下断点,还有一点就是如何找到内存中dex的起始地址和dex的大小,这个通常如今就是dvmDexFileOpenPartial函数下断点,还有就是给mmap函数下断点。