1、 hook的定义
hook,钩子。勾住系统的程序逻辑。在某段SDK源码逻辑执行的过程中,通过代码手段拦截执行该逻辑,加入自己的代码逻辑。
Hook 简单类似网络传输中的中间人拦截,我拦截APP中的原始方法,自己定义一个方法,替换原始的东西,实现我不可描述的目的,大白话就是这样,但是实际过程和应用还是比较复杂的。
常见的使用场景,举几个栗子:
听起来很牛b,但是要实现,需要掌握很多的逆向技术和其它需要技术,可是非常不易的。
2、实用价值
3、前置技能
@hide
的,外部直接无法访问,所以只能通过反射,去创建源码中的类,方法,或者成员.关于Java反射的基础知识,请参见博文:Java-反射机制
4、hook通用思路
无论多么复杂的源码,我们想要干涉其中的一些执行流程,最终的杀招只有一个: “偷梁换柱”。
而 “偷梁换柱”的思路,通常都是一个套路:
frida是一款方便并且易用的跨平台Hook工具,使用它不仅可以Hook Java写的应用程序,而且还可以Hook原生的应用程序。Frida分客户端环境和服务端环境。假如我们要用PC来对Android设备上的某个进程进行操作,那么PC就是客户端,而Android设备就是服务端。
在客户端我们可以编写Python代码,用于连接远程设备,提交要注入的代码到远程,接受服务端的发来的消息等。在服务端,我们需要用Javascript代码注入到目标进程,操作内存数据,给客户端发送消息等操作。我们也可以把客户端理解成控制端,服务端理解成被控端。
pip install frida
,进行frida的安装。我们需要在已root的手机上安装frida-server,并启动frida-server。服务端环境准备步骤如下:
/data/local/tmp
目录,并修改文件的权限,然后运行程序:frida-ps -U
, Frida 检查手机正在运行的进程列表,出现下图则表示所有配置工作已完成。
Android SDK下的Adb Shell工具如下图所示:
如何通过数据线在PC端使用adb shell命令操作手机呢?
adb shell
”即可连接手机:如何不通过数据线在PC端直接使用adb shell命令操作手机呢?
首先保证手机跟电脑处于同一个无线网(处于同一网段,连接同一个Wifi);
需要在你的Android设备上下载一个apk,链接是您也可以到 http://jackpal.github.com/Android-Terminal-Emulator/downloads/Term.apk 下载或在互联网上搜索Android Terminal Emulator来下载安装此APK。
然后在PC端重新win+R打开命令行到sdk的plat_tools下,输入命令adb connect
****(**代表的是手机的ip地址,即可连接通过adb连接上手机,以后再也不用数据线了)
断开连接:adb disconnect 手机IP
Adb shell常用命令:https://www.cnblogs.com/JianXu/p/5161179.html 。
这是一个最简单的案例:我们自己的代码里面,给一个view
设置了点击事件,现在要求在不改动这个点击事件的情况下,添加额外的点击事件逻辑。
View v = findViewById(R.id.tv);
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "别点啦,再点我咬你了...", Toast.LENGTH_SHORT).show();
}
});
这是view
的点击事件,toast
了一段话,现在要求,不允许改动这个OnClickListener
,要在toast
之前添加日志打印 Log.d(...)
.
乍一看,无从下手。来看看看hook如何解决(按照上面的思路来):
我们的目的是在OnClickListener
中,插入自己的逻辑。所以,确定要hook的,是v.setOnClickListener()
方法的实参。
进入v.setOnClickListener
源码:发现我们创建的OnClickListener
对象被赋值给了getListenerInfo().mOnClickListener
:
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
继续索引:getListenerInfo()
是个什么玩意?继续追查:
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
结果发现这个其实是一个伪单例,一个View对象中只存在一个ListenerInfo
对象。进入ListenerInfo
内部:发现OnClickListener
对象被ListenerInfo
所持有。
static class ListenerInfo {
...
public OnClickListener mOnClickListener;
...
}
到这里为止,完成第二步,找到了点击事件的实际持有者:ListenerInfo
。
我们要hook的是View.OnClickListener
对象,所以,创建一个类 实现View.OnClickListener
接口。
static class ProxyOnClickListener implements View.OnClickListener {
View.OnClickListener oriLis;
public ProxyOnClickListener(View.OnClickListener oriLis) {
this.oriLis = oriLis;
}
@Override
public void onClick(View v) {
Log.d("HookSetOnClickListener", "点击事件被hook到了");
if (oriLis != null) {
oriLis.onClick(v);
}
}
}
到这里为止,第三步:定义“要hook的对象”的代理类,并且创建该类的对象完成。
利用反射,将我们创建的代理点击事件对象,传给这个view:
field.set(mListenerInfo, proxyOnClickListener);
这里,贴出最终代码:
/**
* hook的辅助类
* hook的动作放在这里
*/
public class HookSetOnClickListenerHelper {
/**
* hook的核心代码
* 这个方法的唯一目的:用自己的点击事件,替换掉 View原来的点击事件
*
* @param v hook的范围仅限于这个view
*/
public static void hook(Context context, final View v) {
try {
// 反射执行View类的getListenerInfo()方法,拿到v的mListenerInfo对象,这个对象就是点击事件的持有者
Method method = View.class.getDeclaredMethod("getListenerInfo");
//由于getListenerInfo()方法并不是public的,所以要加这个代码来保证访问权限
method.setAccessible(true);
//这里拿到的就是mListenerInfo对象,也就是点击事件的持有者
Object mListenerInfo = method.invoke(v);
//要从这里面拿到当前的点击事件对象,得到原始的OnClickListener对象
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
Field mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");
mOnClickListener.setAccessible(true);
View.OnClickListener originOnClickListener = (View.OnClickListener)
mOnClickListener.get(mListenerInfo);
// 用自定义的 OnClickListener 替换原始的 OnClickListener
View.OnClickListener hookedOnClickListener = new HookedOnClickListener
(originOnClickListener);
mOnClickListener.set(listenerInfo, hookedOnClickListener);
//完成
} catch (Exception e) {
e.printStackTrace();
}
}
//自定义代理类
class HookedOnClickListener implements View.OnClickListener {
private View.OnClickListener origin;
HookedOnClickListener(View.OnClickListener origin) {
this.origin = origin;
}
@Override
public void onClick(View v) {
Log.d("HookSetOnClickListener", "点击事件被hook到了");
if (oriLis != null) {
oriLis.onClick(v);
}
}
}
}
效果展示
同时我并没有改动setOnClickListener
的代码,我只是在它的后面,加了一行HookSetOnClickListenerHelper.hook(this, v)
:
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "别点啦,再点我咬你了...", Toast.LENGTH_SHORT).show();
}
});
//这个hook的作用,是用我们自己创建的点击事件代理对象,替换掉之前的点击事件。
HookSetOnClickListenerHelper.hook(this, v);
Ok,目的达成。v.setOnClickListener
已经被成功hook了。
前方有坑,高能提示:
我曾经尝试,是不是可以将上面两段代码换个顺序. 结果证明,换了之后,hook就不管用了,原因是,hook方法的作用,是将v已有的点击事件,替换成 我们代理的点击事件。所以,在v还没有点击事件的时候进行hook,是没用的。