一、穿靴子的猫
1.反编译后在values/strings.xml文件中找到“好友代付”这个字符串内容,看到这个字符串的id是:gc_billing_net_contacts,这里可以不用在通过public.xml中的id值转化得到对应的int类型值,再去搜索了,可以直接到jadx中进行搜索“gc_billing_net_contacts”即可。
2.这里看到有一个BillingView类,从命名bill(收费)可以猜想这个应该就是上面那个收费的页面。
在看看这个类在哪些地方有调用,到这里看到应该是两个地方,一个是BillingActivity类,一个是GameInterface类:
cn.emagsoftware.gamebilling.activity.BillingActivity.i():void
cn.emagsoftware.gamebilling.api.GameInterface.getBillingView(,,)
这里用一个巧妙的办法直接定位到是哪个类,假如是BillingActivity类,那么当前的topActivity就是他,我们用命令查看当前设备的topActivity看一下:adb shell dumpsys activity top(可以查看当前应用的activity信息)
ACTIVITY:
com.halfbrick.fruitninjapib/com.halfbrick.mortar.MortarGameActivity
这里看到并不是BillingActivity类,所以可以断定,处理收费的页面是在GameInterface类中,那么直接进入这个类的getBillingView方法中,然后查看这个方法在哪个地方被调用了,这里如果发现Find Usage菜单不好使,也可以直接全局搜这个方法:
getBillingView
看到有两个地方用到了这个方法,依然是按照上面那个topActivity判断,不可能是DGCPaymentActivity这个类了,所以直接看下面那个类,点击进入即可:
注意:
1.我们在平时的开发中,有时候可能会需要一些全局数据,来让应用中的所有Activity和View都能访问到,大家在遇到这种情况时,可能首先会想到自己定义一个类,然后创建很多静态成员。
但是这种方法不符合Android的框架架构,不过andorid已经为我们提供了这种情况的解决方案:在Android中,有一个名为Application的类,我们可以在Activity中使用getApplication(),方法来获得,它是代表我们的应用程序的类,使用它可以获得当前应用的主题,资源文件中的内容等,这个类更灵活的一个特性就是可以被我们继承,来添加我们自己的全局属性,让整个 App 的 Activity 和 View 都能访问到。
2.dex文件是Android系统的可执行文件;class文件时java虚拟机的可执行文件。
当java程序编译成class后,还需要使用dx工具将所有的class文件整合到一个dex文件,目的是其中各个类能够共享数据,在一定程度上降低了冗余,同时也是文件结构更加经凑,实验表明,dex文件是传统jar文件大小的50%左右。
二、加固遗留的问题
1、解密之后的apk源程序放在指定目录的话,还是存在被破解的风险,因为这种落地方式解密,是很容易获取解密之后的apk的
2、在解密得到源程序apk,然后再用DexClassLoader进行加载,这里相当于两次把apk加载到内存中,第一次是解密的时候,第二次是加载apk的时候,那么这效率就会大大降低了
A.解决问题(理论分析)
我们先来猜想一下,系统既然能够加载dex文件,那么他会不会有一个能够直接加载文件字节码的方法呢?因为不管怎么样,加载一个文件到最后还是需要解析dex文件,然后map到内存中的,那么我们可以通过源码来看看有没有这样的方法?
(1)加载dex
那么我们既然最后都是要加载,肯定是用DexClassLoader类,那么我们看看这个类的源码:
源码位置:Android源码目录\libcore\dalvik\src\main\java\dalvik\system\DexClassLoader.java
public class DexClassloader exdends BaseDexClassLoader{
public DexclassLoader(String dexPath,String optimizedDirectory,String LibraryPath,ClassLoader parent){
super(dexPath,new File(optimizedDirectory),libraryPath,parent)
}
}
看到只有一个构造方法(构造方法不用再声明class了啊),就是需要传入加载文件的路径,没有能够直接出入字节数据的方法,那怎么破呢?不急,我们继续看他的父类BaseDexClassLoader源码:
源码位置:Android源码目录\libcore\dalvik\src\main\java\dalvik\system\BaseDexClassLoader.java
看到,在BaseDexClassLoader的构造方法中,有一个重要的类DexPathList,他就是解析加载文件的类:
源码位置:Android源码目录\libcore\dalvik\src\main\java\dalvik\system\DexPathList.java
看到了,这里知道了Android中能够加载的四种文件格式:dex/jar/zip/apk
查看他的构造方法:
有一个makeDexElements方法,进入查看:
在这里,用loadDexFile方法来加载文件,返回一个DexFile对象,那么我们再去查看这个类
源码位置:Android源码目录\libcore\dalvik\src\main\java\dalvik\system\DexFile.java
调用loadDex方法,返回DexFile对象:
在进入看构造方法:
这里有一个核心的地方,调用了openDexFile方法,然后返回一个int值:
擦,原来openDexFile是一个native方法,读取dex文件放在native层做的,而且,我们看到返回值代表什么意思呢?我们可以简单的理解为,VM中会维护一个Map结构,保存的内容就是dexFile文件和他对应的cookie值,每次在寻找这个dex中的类功能的时候,都是需要这个cookie进行操作的。
同时我们这里无意中看到了一个非常重要的方法:openDexFile的重载形式,参数就是一个字节数组,那么我们是不是就可以使用这个方法直接来进行操作呢?
好了,到这里我们分析完了dex加载的Java层的流程了,我们获取到的信息有:
1、Android中能够动态加载的文件格式只有四种:dex/jar/zip/apk
2、在DexFile中有两个openDexFile方法,一个是传递文件名称,一个是传递文件字节码,同时这两个方法是native层的。
我们继续来看看默认的DexClassLoader类加载一个类的流程是什么?
加载类的流程
首先看的是loadClass方法:
我们在DexClassLoader和BaseDexClassLoader中都没有找到这个方法,但是BaseDexClassLoader继承了ClassLoader类:
在loadClass方法中其实是调用了findClass方法返回一个Class对象的,在看这个方法,在BaseDexClassLoader中:
这个方法中又继续调用了DexPathList类的findClass方法:
在这个方法中继续调用了DexFile的loadClassBinaryName方法:
public Class loadClassBinaryName(String name,ClassLoader){
return defineClass(name,loader,mCookie);
}
private native static Class defineClass(String name,ClassLoader loader,int cookie);
好吧,这里最后调用了defineClass方法,又是一个native的方法,注意这个方法的最后一个参数是我们上面说到的那个dex对应的cookie值。这个值是openDexFile方法返回的。
上面分析完了dex的加载流程,下面总结一下就是:
ClassLoader的loadClass方法=》BaseDexClassLoader的findClass方法=》DexPathList的findClass方法=》DexFile的loadClassBinaryName方法=》DexFile的defineClass方法。
B.解决问题(实践操作)
一般是findClass方法中会抛出ClassNotFoundException的异常,defineClass会抛出NoClassDefFoundError的错误,我们看到findClass是在外部存储器中查找class文件的,defineClass是在内存中定义class的时候
所以总结:
加载时从外存储器找不到需要的class就出现ClassNotFoundException
连接时从内存找不到需要的class就出现NoClassDefFoundError
那么我们的流程很清楚了:
肯定要重写findClass方法,在这个方法中需要做一些事情,就是需要进行class的名称转化,我们知道在代码中类的名称是用点号进行连接的,但是在磁盘中的文件是靠路径符/来进行连接的,所以这里需要做一个转化。同时需要把dex文件中的其他类进行define,所以这里还有一个问题,就是如何获取dex中所有的类,还好这个方法在DexFile中,叫做getClassNameList:
也是一个native方法
在磁盘中找到了这个类的话,那么这时候就需要调用defineClass方法,进行定义,之后得到了Class对象。
具体实现步骤如下: