qq游戏大厅中解析不安装apk的研究







Android运行未安装apk可以使用Android的DexClassLoader类

这个也可以再Android的官方文档中看到


A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.


This class loader requires an application-private, writable directory to cache optimized classes. Use Context.getDir(String, int) to create such a directory:


   File dexOutputDir = context.getDir("dex", 0);


Do not cache optimized classes on external storage. External storage does not provide access controls necessary to protect your application from code injection attacks.


上面说了,可以加载jar文件和apk文件
还有一句加粗的话,不要把dex文件的目录设置到外部存储设备上,否则可能会引起注入攻击,这个问题困扰了我很久,后来看到官方文档,终于有了答案,所以有了疑问第一时间看官方文档才是最靠谱的
不过最近斯巴达开的,google好难打开。。。你妹的
现在知道了怎么加载apk文件,下来说关键的地方
dex文件其实就是一堆的class文件,怎么调用呢?
反射呗
通过动态加载可以加载任何的java类,包括Activity,但是加载出来的Activity是没有生命周期的,我们可以通过在宿主的APP中,通过反射那七个on什么方法,来模拟出一个生命周期,通过反射吧宿主的activity和Resources传到要启动的Activity中,这样就可以在里面正常使用了
补充:
加载所有的class文件:
生成dexClassLoader后,如果想要加入其它的class,必须知道class类名。
可以通过
DexFile.loadDex(sourcePathName, outputPathName, flags)
这个方法获取到这个dex文件中的所有类名称。

加载进去即可


一,加载dex文件:


        DexClassLoader loader = new DexClassLoader(APK_PATH, getApplication().getCacheDir().getPath(), null, this.getClassLoader());
第一个参数是apk路径,第二个参数用调用者的缓存地址作为加载路径,第三个不用管,第四个是调用者的ClassLoader,其实也就是SystemClassLoader(可以换成默认的)


看了QQ游戏大厅的代码,基本上就做到了这一步,把代码加载出来,但是被加载的apk代码里是不能引用任何资源的(例如R.drawable.XX),它的做法是把所有的资源都放在Assets文件夹中,然后用AssetsManager去解析。


这样做好处是绕过了下面我的步骤


坏处很多:   1. .不能利用android提供的为不同dpi设置不同的图片decode比例,要自己写 


2. 不能用xml文件,未安装的apk里所有界面需要在代码里写(我去,这多大的工作量啊!)


3. 基本不能国际化了,他无法用R.string.XXX也就说明显示的字符串都写在代码里了(我去,悲剧啊)


怎么证明我以上说的?反编译下qq游戏大厅下的Apk文件,会发现它res下只有一个icon文件,什么都没有。所有图片都在assets文件夹下,且任何地方都没有布局文件和string.xml


二,加载res文件:


哈哈,qq游戏大厅的人看到这是不是要哭了?其实res文件是可以加载出来的,为什么这么说,我们来看一下Resources类是怎么加载出一个drawable的,然后在试图找到一个方法骗过系统。






public Drawable getDrawable(int id) throws NotFoundException {  
    synchronized (mTmpValue) {  
        TypedValue value = mTmpValue;  
        getValue(id, value, true);//取得id的存储信息到value中  
        return loadDrawable(value, id);//真正拿drawable的方法,我们继续看  
    }  
}  


InputStream is = mAssets.openNonAsset(value.assetCookie, file, AssetManager.ACCESS_STREAMING);//loadDrawable最核心就是这句,发现没?他是从Assetmanager里面读到的(其实loadDrawable还有别的分支,但是最后都是从AssetManager去的资源,有兴趣看源码吧)  
这里我们需要知道两个函数,一个是assetManager中有个方法叫addAssetPath


(可能需要反射,文章中提到的方法在sdk中不能直接找到的都需要反射,或者引用framework.jar,不再重复说明),这里我们就可以把刚才用到的apk路径传进去,让某个AssetManager可以引用到未安装的apk资源。


Resources有一个构造方法:




public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {  
        this(assets, metrics, config, null);  
    }  
这里我们把刚才构造好的AssetManager传进去就得到了一个特别的Resources对吧?离成功又近了一步。这里我要说明一下我们下一步的目标是解析出一个含有自建类的,引用了R资源的xml布局文件(基本可以满足qq游戏大厅的现有加载需求)。
我们都知道,想要加载其它已安装的apk中xml,可以通过Context中这个方法实现:(不知道的去补姿势!^~^)




public abstract Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException;  
想调用这个方法,我们需要一个context,当然不能用调用者自己的,我们moke一个吧:
class MyContext extends ContextWrapper然后它持有我们刚才辛苦搞出来的Resources和classLoader,并且在重载方法getClassLoader、getResources中返回他们,都搞定了?没,我们发现在load xml时候出错了,问题在于我们未安装的apk中解析xml方式使用的方法是调用者的逻辑,而不是我们moke的context的,怎么办?重载getSystemService,在传入LAYOUT_INFLATER_SERVICE的时候用PolicyManager.makeNewLayoutInflater方法造一个假的LayoutInflater,这样解析xml时候会在我们moke的上下文中进行,也就是我们刚造出来的假Resources中。


都搞定了?哈哈,还没有。这时我们发现代码中引用的R.drawable.XXX可以运行了,但是在xml中的引用是不行的,问题出在哪?我们看源码吧:


View构造的时候会调用TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View,\defStyle, 0);实现解析xml里面的资源引用等,这个方法实现很简单getTheme().obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes);刚才出现的问题在于我们没有重载getTheme方法,走了错的路径。可是这东西怎么重载?没关系,我们看看Android自己怎么实现的,我们在ContextThemeWrapper发现了这样的代码(2.3是在ComtextImp类中,而且实现略有不同)




@Override public Resources.Theme getTheme() {  
        if (mTheme != null) {  
            return mTheme;  
        }  
  
        mThemeResource = Resources.selectDefaultTheme(mThemeResource,  
                getApplicationInfo().targetSdkVersion);  
        initializeTheme();  
  
        return mTheme;  
    }  
看懂了吧,我们就按照他的做法,new重来一个新的Theme就可以了。这里我们的context还需要重载getPackageName,原因类似,返回未安装的apk报名就可以了。




到这里,我们终于可以从一个未安装的apk中得到了他的一个layout,其实也就是可以显示我们想要的所有东西(像qq游戏这种只有在一个界面上画东西的应用,其实已经可以满足所有需求了)。特别说明下,未安装apk的preference是写在调用者的data\data目录下。


最后,我们还有哪些做不了?启动activity、service、provider不行,因为没注册在系统里,解决办法:可以在moke的context定义一些特殊回调方法,未安装apk中反射调用它,让调用者启动一个新activity,并且加载自己另外一个layout。service和provider暂时无解。





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值