qq游戏大厅中解析不安装apk的研究
2014-08-01 00:05
459 查看
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暂时无解。
这个也可以再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暂时无解。
相关文章推荐
- qq游戏大厅中解析不安装apk的研究
- android开发之启动模拟器并安装游戏apk
- android开发之启动模拟器并安装游戏apk
- 探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法 3ff8
- apk时存放到data下时引导安装解析失败解决办法
- Android中APK安装过程及原理解析
- QQ游戏大厅 2008 去广告/支持多开功能破解版下载
- android开发,安装带有特殊字符名称的apk,解析包时出现问题
- Android中APK安装过程及原理解析
- Android中APK安装过程及原理解析
- Android中APK安装过程及原理解析
- 下载游戏apk,并安装
- 过QQ游戏大厅的SX保护
- 探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法
- Android APK应用安装原理(1)-解析AndroidManifest原理-PackageParser.parserPackage
- 类似QQ游戏大厅导航的树型控件
- Android APK应用安装原理(1)-解析AndroidManifest原理-PackageParser.parserPackage
- 【转】探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法
- AWStats+QQ纯真库IP解析插件安装
- (转)Android中APK安装过程及原理解析