探索Android的HOOK之旅
因为项目需求,最近在研究Android的hook方法,总结一下研究的结果,做一个笔记。
因为我只研究了Android中Java层的代码hook,Native层的hook还未实际研究与应用。
一、Hook是什么
Hook的中文翻译是“钩子”,简单的说就是,钩住你的代码,对你的方法执行前后做一些操作,例如:改变方法返回值,改变传入方法的参数,替换现有的方法。
当然,这些改变都是不可以在原有代码上进行更改,因为改变原有方法,有时成本很大,并且很繁琐,而且hook不光是hook自己写的代码,还可以hook系统原有的代码。例如你要更改你代码所有的文字样式,在Android中也就是TextView,如果在应用已经完成,再去自定义一个TextView更改起来就很麻烦,此时你只需要hook住TextView中相应的方法,就可以更改所有文字的样式了。
二、Hook的应用
Hook 的应用场景有很多,前边提到的hook其实并不常用,常用的hook应该就是热更新技术了。所谓的热更新,就是在应用不重新安装新版本,而修复和升级一些bug和功能。
因为热更新技术就是hook应用的完美展现,所以围绕热更新技术去研究hook技术,是一个不错的方向。
三、热更新技术
热更新技术阿里和腾讯都有自己的实现方案,阿里的Sophix,微信用的Tinker,
这是三种热更新技术的对比图。网上有很多详细的,这里就不讲了。
四、我走过的路
因为公司需求并不是热更新,一开始我一直在寻找Android的Hook方法,准确来说是无侵入的Hook方法,就是不改变原有代码的基础上去hook。
最先看的到是Xposed框架,这个框架实现hook更改手机所有应用的代码,框架很好,但是需要手机进行了root权限,这就直接pass了。
后来又找到的Legend的框架,这个的确是实现了我的需求,它只需要很少的代码,就可以hook到你自己应用中的方法,对方法进行更改,但是很可惜,他并不可以在Android 7.0以上的系统上运行。这个作者好像是在高二时候写的(给跪),现在也不更新了,所以只好放弃了。
后来我采用的是YAHFA去做的,这个框架总体还是很好的,支持7.0,虽然7.0是在测试阶段,但是并没有发现什么错误。只不过他不是在本应用中添加代码,而是以插件的形式,把你的代码打包成一个debug.apk,存储到sd卡中,进行加载,从而更改代码。这更像是热更新的操作,只是如果sd卡中删除了这个debug.apk,就失效了。不过热更新用这个应该很不错,我的需求,我更喜欢在本应用中添加hook方法,而不是插件式添加。
五、YAHFA的应用
GitHub地址:https://github.com/rk700/YAHFA
很感谢作者提供的这个框架。
在GitHub中,作者对框架的介绍已经很详细了,我就简单的再说一下。
下载demo后导入Android studio,其中有一个demoApp和demoPlugin,还有一个library,library中就是hook的逻辑,其实hook就是代码的反射调用,AOP编程的一些逻辑,不知道AOP的朋友可以百度一下。
把demoApp安装到你的手机,把demoPlugin打包成一个debug.apk,存入你的手机内存,只要在demoApp中的lication的类中指定这个dubug的路径就好。
在demoPlugin中编写你要hook的方法。
className是指定的hoook的类名
methodName是指定要hook 的方法
methodSig是指定的类型签名
(Ljava/lang/String;)Ljava/net/URI;
(Ljava/lang/String;)Ljava/net/URI;
括号中是参数的签名,括号外是返回值的签名
签名是jni中经常用法哦的,如归不是基本数据类型,就要L开头,后边跟类的全路径名用“/”分割
基本数据类型有自己的类型签名
hook方法是你重写的代码逻辑
origin方法是你的原放啊
如果返回值不是基本数据类型,就写成Object
oringin方法中写什么都可以,不影响运行
1.静态方法
public class Hook_url {
public static String className = "java.net.URI";
public static String methodName = "create";
public static String methodSig = "(Ljava/lang/String;)Ljava/net/URI;";
public static Object hook(String url)
{
Log.w("YAHFA", "in hook_url: "+url);
Log.w("YAHFA", "网址");
url = "http://suggest.taobao.com/sug?code=utf-8&q=袜子&callback=cb";
return origin(url);
}
public static Object origin(String url)
{
Log.w("YAHFA", "String.startsWith() should not be here");
return url;
}
}
这是静态方法的hook,静态方法和静态差不多,区别就是,非静态的方法在hook和origin的参数中,多加一个Object 的参数
url是传入的参数,把url更改,在调用origin,这样就是更改了访问网址
2. 非静态方法
public class hookbean {
public static String className = "lab.galaxy.yahfa.demoApp.DemoBean";
public static String methodName = "getbean";
public static String methodSig =
"(Ljava/lang/String;Ljava/lang/String;)Llab/galaxy/yahfa/demoApp/Bean;";
public static Object hook(Object obj,String a, String b) {
Log.w("YAHFA", "in getBean: "+a+b);
a = "ni";
b = "咱们";
return origin(obj,a, b);
}
public static Object origin(Object obj,String a, String b) {
Log.w("YAHFA", "ClassWithStaticMethod.tac() should not be here");
return "";
}
}
这是非静态方法的hook,返回值是我自定义的类,所以签名是我的全路径,返回值类型是Object
3. 返回值为非基本数据类型
可以参考2,静态和非静态都是如此
4. 参数是非基本数据类型
public class Hook_WebViewClient_onReceivedSslError {
public static String className = "android.webkit.WebViewClient";
public static String methodName = "onReceivedSslError";
public static String methodSig =
"(Landroid/webkit/WebView;Landroid/webkit/SslErrorHandler;Landroid/net/http/SslError;)V";
public static void hook(Object thiz, WebView webView, SslErrorHandler handler, SslError error) {
Log.w("YAHFA", "WebViewClient.onReceivedSslError()");
handler.proceed();
}
}
任何情况下都可以不写origin方法,这样只是不调用原方法了
5. Jni方法
以后补充
6. jar中的方法
如果需要hook第三方依赖,jar包的方法,需要把debug.apk中也添加依赖和jar
7.内部类的hook
内部类只是编译时的概念,一旦编译成功,就会出现两个不同的类,例如,类outClass中有个intClass,那么编译后就出现一个名为outClass.class和一个outClass$intClass.class的类。所以className中就要指定类路径为a.b.c.outClass$intClass。
六、总结
Hook主要就是通过反射,有很多例子,都是直接反射调用类,然后更改,只是用了框架,就不用一个个代码去反射调用更该了,省了很多的时间去做一些你更该后的代码优化等操作。