作者: Lateautumn4lin
来源:云爬虫技术研究笔记
首先注意!!这个Hook
不是邓紫棋要给你唱的Hook
哦!
而是在程序界流传的强大秘技-Hook
函数,Hook
原意是指钩子
,它表示的就是在某个函数的上下文做自定义的处理来实现我们想要的黑科技
。
在很多技术领域都存在的这种Hook
技术,比如下面这些:
Python
的Web框架
中,如Django
,Flask
都存在这种Hook
技术,可以在请求的上下文
,应用的上下文
做自定义操作。Scrapy
框架中,可以自定义MiddlerWare
,在请求
,解析
的时候做自定操作。K8S
编排框架中,我们也可以在执行某些函数的上下文中插入Hook
函数,这也是和Web
框架同理
而今天我们讲解的是关于Android
的Hook
技术,而有一款神器能够帮助我们快速地开发Hook
模块,也就是Xposed
框架。
其实网上的关于Xposed
模块编写的教程可谓是一抓一大把。但由于时间的推移,很多工具和方法都发生了变化(如Eclipse
退出安卓编程舞台,AndroidStudio
不断升级导致其一些设置也随之变化等)也正因此,网上的教程往往有一些时限性,比如现如今 provide
这个关键字已经被舍弃了却仍有人在用,还有些说要把jar
包放到lib
文件夹而非libs
文件夹……种种错误或者落伍的教程对新手产生了很大的误导。
之前也搞过一阵子Xposed
框架,而今天在重新部署环境的时候参考某些教程的时候也遇到了很多的坑,所以想重新结合最新的配套工具写个小教程,主要讲解的以下两个方面:
Xposed
框架介绍以及原理Xposed
框架实战Xposed框架介绍以及原理
Xposed
是Github
上rovo89
大佬设计的一个针对Android平台
的动态劫持项目,通过替换/system/bin/app_process
程序控制Zygote
进程,使得app_process
在启动过程中会加载XposedBridge.jar
这个jar
包,从而完成对Zygote
进程及其创建的Dalvik虚拟机
的劫持。
因为Xposed
工作原理是在/system/bin
目录下替换文件,在install
的时候需要root
权限,但是运行时不需要root
权限。
看到这里很多人会很懵,什么是Zygote
?简单来说在Android
系统中,应用程序进程都是由Zygote
进程孵化出来的,而Zygote
进程是由Init
进程启动的。Zygote
进程在启动时会创建一个Dalvik
虚拟机实例,每当它孵化一个新的应用程序进程时,都会将这个Dalvik
虚拟机实例复制到新的应用程序进程里面去,而一个应用程序进程被Zygote
进程孵化出来的时候,不仅会获得Zygote
进程中的Dalvik
虚拟机实例拷贝,还会与Zygote
一起共享Java运行时
库。这也就是可以将XposedBridge
这个jar
包加载到每一个Android
应用程序中的原因。XposedBridge
有一个私有的Native(JNI)
方法hookMethodNative
,这个方法也在app_process
中使用。这个函数提供一个方法对象利用Java
的Reflection
机制来对内置方法覆写。。。。等等这些都会借鉴各路大神的思路和分析,总而言之,就是从底层替换方法,可以让我们在不修改APK
源码的情况下,通过自己编写的模块来影响程序运行的框架服务,实现类似于自动抢红包、微信消息自动回复等功能。
其实,从本质上来讲,Xposed
模块也是一个Android
程序。但与普通程序不同的是,想要让写出的Android
程序成为一个``Xposed 模块,要额外多完成以下四个硬性任务:
硬性任务清单
1、让手机上的xposed框架知道我们安装的这个程序是个xposed模块。
2、模块里要包含有xposed的API的jar包,以实现下一步的hook操作
3、这个模块里面要有对目标程序进行hook操作的方法。
4、要让手机上的xposed框架知道,我们编写的xposed模块中,哪一个方法是实现hook操作的。
这就引出我即将要介绍的四大件(与前四步一一对照):
四大件
1、AndroidManifest.xml
2、XposedBridgeApi-xx.jar 与 build.gradle
3、实现hook操作的具体代码
4、xposed_Init
牢记以上四大件,按照顺序一个一个实现,就能完成我们的第一个Xposed模块编写。以上的原理我们大致就介绍这么多,下面我们实战开始吧!
Xposed框架实战
1. 迈开第一步,新建项目并编辑AndroidManifest.xml
我们使用的IDE
是Android Studio
,首先打开AndroidStudio
(以版本3.4.2
为例,还在用老版本的请升级),建立一个工程,提示我们选择“Activity
”,那就选一个Empty Activity
吧。(这个是模块的界面,随意选择即可)。
2、快速运行模板的Xposed模块
我们可以把项目查看方式设置为Project
模式,以方便查看。然后在 “项目名称/app/src/main/
”目录下找到AndroidManifest.xml
,打开这个文件,并在指定位置插入以下三段代码:
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="微信hook" />
<meta-data
android:name="xposedminversion"
android:value="53" />
效果如图:
插入代码之后,我们可以点击Run
运行App
。
不过,此时会出现如图提示,也就是缺少Device
设备来运行这个App
下一步我们要把手机连接Android Studio
,连接的办法很多,包括通过USB连接(物理连接)
和Wifi连接(也就是网络连接)
,我们为了节省方法,就采用物理连接,Ps: 有关于远程连接可以参考这篇文章,连接好我们的实体机之后我们点击这里
我们等待Android Studio
连接手机,连接好我们就可以看到在Logcat
选项里面看到我们的手机运行的日志报告。
有如图所示的日志打印之后我们就会发现我们就可以运行了,点击Run
之后会提示我们的手机安装我们刚才刚写的Apk
,不过我的手机提示安装时验证超时
,不能直接安装,苦恼,以后选手机也要选个正常的。关于Android Studio
安装Apk
失败的原因可以参考这篇文章,既然我们不能直接安装Apk
,我们就使用adb
直接来安装
安装好应用之后我们在Xposed
框架中勾选我们刚才的模块,然后我们重启一下Xposed
框架,就可以啦
这一步只是说明Xposed
框架已经认出了我们写的程序。但先别高兴太早——虽然框架已经觉得他是一个Xposed
模块了,但我们自己心里清楚,这个模块还啥都不会干呢。下一步,我们让这个模块长点本事。
3、搞定XposedBridgeApi-xx.jar 与 build.gradle
我们知道,Xposed
模块主要功能是用来Hook
其他程序的各种函数。但是,如何让前一步中的那个“一穷二白”的模块长本事呢?那就要引入 XposedBridgeApi.jar
这个包,你可以理解为一把兵器,模块有了这把宝刀才能施展出Hook
本领。很多以前的老教程都需要手动下载诸如XposedBridgeApi-54.jar
、 XposedBridgeApi-82.jar
等jar
包,然后手工导入到libs
目录里,才能走下一步道路,而这些jar
没有官方的渠道来安装,通常只是一个传一个的,都不知道变成了什么版本。其实在最新的AndroidStudio 3.1
以后,我们完全不用这么麻烦,只需要多写一行代码,就让AndroidStuido
自动给我们配置XposedBridgeApi.jar
!下面操作开始:
在 “项目名称/app/src/main/”
目录下找到build.gradle
,在图示位置加上:
repositories {
jcenter()
}
compileOnly 'de.robv.android.xposed:api:82'
compileOnly 'de.robv.android.xposed:api:82:sources'
这句代码是告诉AndroidStuido
使用jcenter
作为代码仓库,从这个仓库里远程寻找 de.robv.android.xposed:api:82
这个API
。这个网上很少有Xposed
教程介绍它的!(我们不用自己找XposedBridgeApi.jar
了。注意!此处要用compileOnly
这个修饰符!网上有些写的是provide
,但是现在已经停用了!坑人啊!)
写完之后, build.gradle
会提示文件已经修改,是否同步。点击 “sync now”
,同步即可:
等待依赖构建完成
【Ps:如果网络不通,或者同步不畅,就不要进行第三步的repositories { jcenter()}
这个步骤了,改做这个步骤:手动下载XposedBridgeApi-82.jar
,拖放到“项目名称/app/libs/”
里面(不是网上说的单独建立lib
文件夹,那是很久以前的故事了!),然后右键“Add As Library”
自行添加这个jar
包。而compileOnly ‘de.robv.android.xposed:api:82′
和compileOnly ‘de.robv.android.xposed:api:82:sources’
这两句仍然照常添加。】
好了,我们现在已经搞好了所有的准备工作。下一步,就要开始“施展刀法”(编写hook
代码)了。
4、实现hook操作的具体代码
在“施展刀法”(编写hook
代码)之前,我们先要立一个靶子。在界面上画一个按钮,并在MainAcitiviy
里写代码如下:
package com.example.wx;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Toast.makeText(MainActivity.this, toastMessage(), Toast.LENGTH_SHORT).show();
}
});
}
public String toastMessage() {
return "我未被劫持";
}
}
而在页面布置的文件中,也就是activity_main.xml
中增加如下红框的代码
这个靶子很简单:MainActivity
界面有个按钮,点击按钮后会弹出一个toast
提示,该提示的内容由toastMessage()
方法提供,而toastMessage()
的返回值为“我未被劫持”
。
现在,我们已经做好了我们的App
了,下面我们正式开始“施展刀法”(编写hook
代码) 来hook
我们的MainActivity
并修改这个类的toastMessage()
方法,让它的返回值为“你已被劫持”
:
5、在MainActivity的同级路径下新建一个类“HookTest.java”
package com.example.wx;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class HookTest implements IXposedHookLoadPackage {
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if (loadPackageParam.packageName.equals("com.example.root.xposd_hook_new")) {
XposedBridge.log(" has Hooked!");
Class clazz = loadPackageParam.classLoader.loadClass(
"com.example.root.xposd_hook_new.MainActivity");
XposedHelpers.findAndHookMethod(clazz, "toastMessage", new XC_MethodHook() {
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
//XposedBridge.log(" has Hooked!");
}
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult("你已被劫持");
}
});
}
}
}
由代码可知,我们是通过IXposedHookLoadPackage
接口中的handleLoadPackage
方法来实现Hook
并篡改程序的输出结果的。代码中“com.example.wx”
是目标程序的包名,”com.example.wx.MainActivity”
是想要Hook的类,“toastMessage”
是想要Hook
的方法。我们在afterHookedMethod
方法(用来定义Hook了目标方法之后的操作)中,修改了toastMessage()
方法的返回值为“你已被劫持”。在完成代码编写之前,说一下为什么要Hook toastMessage
这个方法,我们先用Jadx
查看一下我们Apk
的源代码
这个源码是没有经过混淆的,所以我们可以看到这个源码和我们之前写的一样,我们根据项目结构可以判断出我们需要Hook
的函数。
OK,以上用来hook
的代码编写完毕,让我们进行下一步操作。
6、最后一步,添加入口点
右键点击 “main ”
文件夹 , 选择new –> Folder –>Assets Folder
,新建assets
文件夹:
然后右键点击 assets
文件夹, new–> file
,文件名为xposed_init
(文件类型选text
),并在其中写上入口类的完整路径(就是自己编写的那一个Hook
类),这样,Xposed
框架就能够从这个xposed_init
读取信息来找到模块的入口,然后进行Hook
操作了:
好了,曙光就在前面!最后选择禁用Instant Run
:单击 File -> Settings -> Build, Execution, Deployment -> Instant Run
,把勾全部去掉。
我们重新之前的安装Xposed
模块的方法,运行模块,点击,奇迹出现~
大功告成!!!
结语
从上面的实战中我们可以发现Hook
的基本原理以及步骤,重新看看我们之前说的四大步,Hook
的关键其实我们需要知道针对哪个模块的哪个方法进行Hook
像我们这个例子很简单,没有特意的进行代码混淆
以及程序入口改写
等等,我们寻找还是很简单的,一般市面上的App
都是有很多反Xposed
的行为,我们其实要学习的还有很多,这个小教程就当做个小入门吧。
下一篇文章和最近工作上的需求有关系,针对的是2019.10.28
之后搜狗微信关闭了在某些公众号内搜索的功能,所以我们想要获取最新的公众号文章就不能采取搜狗微信这个渠道了,网上有很多教程都在谈论其他的方法,相比较来说,还是Hook
这个渠道是最实际的,我们将会在之后的文章里详细谈论,大家可以期待一下~
注意:项目已经完成,想要获得源码可以关注下面的微信号,回复“hook入门”
即可获得项目地址以及现成的Apk