12下一页
返回列表 发新帖

Android 插件化之Hook机制

[复制链接]

173

主题

2124

帖子

6659

积分

硕士生

Rank: 6Rank: 6

金币
844
好评
64
贡献
0
QQ
发表于 2020-6-19 23:52:59 来自手机  | 显示全部楼层 | 阅读模式
Android Hook简介
什么是Hook
Hook 英文翻译过来就是「钩子」的意思,就是在程序执行的过程中去截取其中的信息。Android 操作系统中系统维护着自己的一套事件分发机制,那么Hook就是在事件传送到终点前截获并监控事件的传输。其原理示意图如下:

众所周知,Android 系统中使用了沙箱机制,普通用户程序的进程空间都是独立的,程序的运行互不干扰,而进程之间要实现通信需要借助Android的Binder机制。
在Hook技术中,根据 Hook 对象与 Hook 后处理的事件方式不同,Hook 可以分为消息 Hook、API Hook 等。
Hook 框架
在Android开发中,有以下常见的一些Hook框架:
1,Xposed
通过替换 /system/bin/app_process 程序控制 Zygote 进程,使得 app_process 在启动过程中会加载 XposedBridge.jar 这个 Jar 包,从而完成对 Zygote 进程及其创建的 Dalvik 虚拟机的劫持。
Xposed 在开机的时候完成对所有的 Hook Function 的劫持,在原 Function 执行的前后加上自定义代码。
2,Cydia Substrate
Cydia Substrate 框架为苹果用户提供了越狱相关的服务框架,当然也推出了 Android 版 。Cydia Substrate 是一个代码修改平台,它可以修改任何进程的代码。不管是用 Java 还是 C/C++(native代码)编写的,而 Xposed 只支持 Hook app_process 中的 Java 函数。
3,Legend
Legend 是 Android 免 Root 环境下的一个 Apk Hook 框架,该框架代码设计简洁,通用性高,适合逆向工程时一些 Hook 场景。大部分的功能都放到了 Java 层,这样的兼容性就非常好。
原理是这样的,直接构造出新旧方法对应的虚拟机数据结构,然后替换信息写到内存中即可。
Hook基础之反射
反射是运行于JVM中的程序检测和修改运行时的一种行为,通过反射可以在运行时获取对象的属性和方法,这种动态获取信息以及动态调用对象方法的功能特性被称为反射机制。
关于反射更详细的内容可以查看下面的链接:Java基础之反射
为了方便后面理解,这里先说下对于Hook的一些基本的常识:
Hook的选择点
Hook时应尽量选择静态变量和单例对象,因为一旦创建对象,它们不容易变化,非常容易定位。
Hook流程

寻找 Hook 点,原则是静态变量或者单例对象,尽量 Hook public 的对象和方法;
选择合适的代理方式,如果是接口可以用动态代理;
使用代理对象替换原始对象。
1,使用Hook拦截点击事件
下面以如何HooK Android的OnClickListener来讲解如何Hook API 。首先,我们看一下setOnClickListener的源码。
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}

ListenerInfo getListenerInfo() {
if (mListenerInfo ≠ null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
可以发现,OnClickListener 对象被保存在了一个叫做 ListenerInfo 的内部类里,其中 mListenerInfo 是 View 的成员变量。而ListeneInfo 里面保存了 View 的各种监听事件,比如 OnClickListener、OnLongClickListener、OnKeyListener 等等。
如果我们要Hook OnClickListener事件,可以给 View 设置监听事件后,然后注入自定义的操作。
private void hookOnClickListener(View view) {
try {
// 得到 View 的 ListenerInfo 对象
Method getListenerInfo = View.class.getDeclaredMethod(“getListenerInfo”);
getListenerInfo.setAccessible(true);
Object listenerInfo = getListenerInfo.invoke(view);
// 得到 原始的 OnClickListener 对象
Class<?> listenerInfoClz = Class.forName(“android.view.View$ListenerInfo”);
Field mOnClickListener = listenerInfoClz.getDeclaredField(“mOnClickListener”);
mOnClickListener.setAccessible(true);
View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get(listenerInfo);
// 用自定义的 OnClickListener 替换原始的 OnClickListener
View.OnClickListener hookedOnClickListener = new HookedOnClickListener(originOnClickListener);
mOnClickListener.set(listenerInfo, hookedOnClickListener);
} catch (Exception e) {
log.warn(“hook clickListener failed!”, e);
}
}

class HookedOnClickListener implements View.OnClickListener {
private View.OnClickListener origin;

HookedOnClickListener(View.OnClickListener origin) {
this.origin = origin;
}

@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, “hook click”, Toast.LENGTH_SHORT).show();
http://log.info(“Before click, do what you want to to.”);
if (origin ≠ null) {
origin.onClick(v);
}
http://log.info(“After click, do what you want to to.”);
}
}

到此,我们成功Hook 了 OnClickListener事件,然后我们可以使用下面的代码来进行调用。
Button btnSend = (Button) findViewById(R.id.btn_send);
btnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
http://log.info(“onClick”);
}
});
hookOnClickListener(btnSend);
Hook技术除了上面的作用外,还可以用于无痕埋点等功能。
2,使用 Hook 拦截通知
当我们在项目中使用众多的SDK,而美国SDK内部可能会使用NotificationManager 发送通知,这就导致通知难以管理和控制。发送通知使用的是 NotificationManager 的notify方法,而NotificationManager 会返回一个INotificationManager 类型的对象,并调用其 enqueueNotificationWithTag 方法完成通知的发送。以下是notify方法的源码:
public void notify(String tag, int id, Notification notification)
{
INotificationManager service = getService();
…… // 省略部分代码
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
stripped, idOut, UserHandle.myUserId());
if (id ≠ idOut[0]) {
Log.w(TAG, “notify: id corrupted: sent ” + id + “, got back ” + idOut[0]);
}
} catch (RemoteException e) {
}
}

而INotificationManager的源码如下:
private static INotificationManager sService;

/** @hide */
static public INotificationManager getService()
{
if (sService ≠ null) {
return sService;
}
IBinder b = ServiceManager.getService(“notification”);
sService = INotificationManager.Stub.asInterface(b);
return sService;
}
INotificationManager 是跨进程通信的 Binder 类,sService 是 NMS(NotificationManagerService) 在客户端的代理,也就是说发送通知后首先委托给 sService,由它传递给 NMS。由上面的源码可以发现 sService 是一个静态成员变量,所以该对象只会初始化一次。所以,要想Hook NotificationManager,可以通过反射拿到sService。核心代码如下:
rivate void hookNotificationManager(Context context) {
try {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
// 得到系统的 sService
Method getService = NotificationManager.class.getDeclaredMethod(“getService”);
getService.setAccessible(true);
final Object sService = getService.invoke(notificationManager);

Class iNotiMngClz = Class.forName(“android.app.INotificationManager”);
// 动态代理 INotificationManager
Object proxyNotiMng = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{iNotiMngClz}, new InvocationHandler() {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log.debug(“invoke(). method:{}”, method);
if (args ≠ null && args.length > 0) {
for (Object arg : args) {
log.debug(“type:{}, arg:{}”, arg ≠ null ? arg.getClass() : null, arg);
}
}
// 操作交由 sService 处理,不拦截通知
// return method.invoke(sService, args);
// 拦截通知,什么也不做
return null;
// 或者是根据通知的 Tag 和 ID 进行筛选
}
});
// 替换 sService
Field sServiceField = NotificationManager.class.getDeclaredField(“sService”);
sServiceField.setAccessible(true);
sServiceField.set(notificationManager, proxyNotiMng);
} catch (Exception e) {
log.warn(“Hook NotificationManager failed!”, e);
}
}
对于Hook,应当尽可能的早,例如可以在attachBaseContext进行Hook。
3,Hook Activity
熟悉Android的Activity启动流程的同学都知道,启动Activity是由Instrumentation类的execStartActivity来执行的,而execStartActivity函数有一个核心的对象ActivityManagerService(AMS)。
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
….
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);

//通过ActivityManagerNative.getDefault()获取一个对象,开始启动新的Activity
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target ≠ null ? target.mEmbeddedID : null,
requestCode, 0, null, options);

checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException(“Failure from system”, e);
}
return null;
}
而ActivityManagerNative是一个抽象类,它实现了IActivityManager接口。
public abstract class ActivityManagerNative extends Binder implements IActivityManager
也就是说,ActivityManagerNative是为了远程服务通信做准备的”Stub”类,一个完整的AID L有两部分,一个是个跟服务端通信的Stub,一个是跟客户端通信的Proxy。
阅读源码以发现,ActivityManagerNative就是Stub,除此之外,ActivityManagerNative 文件中还有个ActivityManagerProxy,而更加详细的内容本文就不多讲解了。不过要实现Hook效果,需要注意下IActivityManager的getDefault函数。
gDefault()是一个单例,ServiceManager通过获取到AMS远端服务的Binder对象,然后使用asInterface方法转化成本地化对象。涉及的核心代码如下:
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService(“activity”);
if (false) {
Log.v(“ActivityManager”, “default service binder = ” + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v(“ActivityManager”, “default service = ” + am);
}
return am;
}
不过Activity的启动过程可以使用下面的核心类来帮助理解:
ActivityManagerService、ActivityManagerNative、ActivityManagerProxy、ActivityThread、ViewRootImpl、PhoneWindow
下面继续看,那么我们究竟如何实现Hook Activity呢?很简单,只要通过反射拿到IActivityManager对象,然后拿到ActivityManager对象,然后通过动态代理,用代理对象替换掉真实的ActivityManager即可。这也是很多组件化框架的使用的原理。
下面是网上提供的一个实例,通过Hook Activity增加Log输出。
public class HookUtil {
private Class<?> proxyActivity;
private Context context;

public HookUtil(Class<?> proxyActivity, Context context) {
this.proxyActivity = proxyActivity;
this.context = context;
}

public void hookAms() {
try {
//通过完整的类名拿到class
Class<?> ActivityManagerNativeClss = Class.forName(“android.app.ActivityManagerNative”);
//拿到这个类的某个field
Field gDefaultFiled = ActivityManagerNativeClss.getDeclaredField(“gDefault”);
//field为private,设置为可访问的
gDefaultFiled.setAccessible(true);
//拿到ActivityManagerNative的gDefault的Field的实例
//gDefault为static类型,不需要传入具体的对象
Object gDefaultFiledValue = gDefaultFiled.get(null);

//拿到Singleton类
Class<?> SingletonClass = Class.forName(“android.util.Singleton”);
//拿到类对应的field
Field mInstanceField = SingletonClass.getDeclaredField(“mInstance”);
//field是private
mInstanceField.setAccessible(true);
//gDefaultFiledValue是Singleton的实例对象
//拿到IActivityManager
Object iActivityManagerObject = mInstanceField.get(gDefaultFiledValue);

AmsInvocationHandler handler = new AmsInvocationHandler(iActivityManagerObject);
Class<?> IActivityManagerIntercept = Class.forName(“android.app.IActivityManager”);
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{IActivityManagerIntercept}, handler);
mInstanceField.set(gDefaultFiledValue, proxy);

} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}

private class AmsInvocationHandler implements InvocationHandler {

private Object iActivityManagerObject;

private AmsInvocationHandler(Object iActivityManagerObject) {
this.iActivityManagerObject = iActivityManagerObject;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

Log.i(“HookUtil”, method.getName());
//添加日志
if (“startActivity”.contains(method.getName())) {
Log.e(“HookUtil”,“Activity Hook已经开始启动”);
}
return method.invoke(iActivityManagerObject, args);
}
}
}
然后使用的时候在Application注册下即可:
public class MyApplication extends Application {

@Override
public void onCreate() {
super.onCreate();
HookUtil hookUtil=new HookUtil(SecondActivity.class, this);
hookUtil.hookAms();
}
}

以上内容来自百度
冰阔乐开始了引流大业^O^
回复

使用道具 举报

197

主题

3360

帖子

1万

积分

博士生

久情哥哥

Rank: 7Rank: 7Rank: 7

金币
3426
好评
215
贡献
0

考神MT论坛帅哥MT论坛最佳新人MT论坛活跃会员MT论坛侠客MT论坛灌水老大

QQ
发表于 2020-6-20 00:00:38 来自手机  | 显示全部楼层
就看懂最后一句话
回复

使用道具 举报

34

主题

822

帖子

2681

积分

大学生

小白

Rank: 5Rank: 5

金币
303
好评
9
贡献
0

MT论坛帅哥考神

发表于 2020-6-20 00:02:30 来自手机  | 显示全部楼层
太长了
回复

使用道具 举报

3

主题

293

帖子

786

积分

初中生

Rank: 3Rank: 3

金币
162
好评
0
贡献
0
发表于 2020-6-20 01:22:18 来自手机  | 显示全部楼层
太长了,留着有空再看吧
回复

使用道具 举报

4

主题

501

帖子

2020

积分

大学生

Rank: 5Rank: 5

金币
526
好评
2
贡献
0
QQ
发表于 2020-6-20 01:54:21 来自手机  | 显示全部楼层
虽然啥都看不懂,但我还是一本正经的看完了。
回复

使用道具 举报

5

主题

400

帖子

1002

积分

高中生

Rank: 4

金币
186
好评
0
贡献
0
发表于 2020-6-20 02:25:47 来自手机  | 显示全部楼层
感谢分享
回复

使用道具 举报

9

主题

1209

帖子

4164

积分

大学生

Rank: 5Rank: 5

金币
1340
好评
20
贡献
0

考神MT论坛帅哥MT论坛最佳新人

发表于 2020-6-20 02:46:53 来自手机  | 显示全部楼层
看困了
回复

使用道具 举报

2

主题

382

帖子

1900

积分

高中生

Rank: 4

金币
505
好评
2
贡献
0
QQ
发表于 2020-6-20 03:41:05 来自手机  | 显示全部楼层
感谢分享
回复

使用道具 举报

25

主题

4987

帖子

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
4895
好评
1
贡献
0
发表于 2020-6-20 05:42:22 来自手机  | 显示全部楼层
回复

使用道具 举报

0

主题

1600

帖子

3054

积分

大学生

Rank: 5Rank: 5

金币
44
好评
0
贡献
0
发表于 2020-6-20 05:54:52 来自手机  | 显示全部楼层
支持支持
回复

使用道具 举报

4

主题

265

帖子

728

积分

初中生

Rank: 3Rank: 3

金币
116
好评
0
贡献
0
发表于 2020-6-20 07:10:33 来自手机  | 显示全部楼层
辛苦辛苦
回复

使用道具 举报

3

主题

774

帖子

2179

积分

大学生

Rank: 5Rank: 5

金币
1497
好评
0
贡献
1
发表于 2020-6-20 07:16:03 来自手机  | 显示全部楼层
感谢科普,已收藏。
回复

使用道具 举报

0

主题

329

帖子

1186

积分

高中生

Rank: 4

金币
314
好评
0
贡献
0

考神MT论坛帅哥

发表于 2020-6-20 07:19:20 来自手机  | 显示全部楼层
太长,不看了
回复

使用道具 举报

5

主题

643

帖子

2160

积分

大学生

小小白

Rank: 5Rank: 5

金币
576
好评
6
贡献
1

考神MT论坛帅哥MT论坛最佳新人

发表于 2020-6-20 07:20:19 来自手机  | 显示全部楼层
回复

使用道具 举报

19

主题

2262

帖子

5522

积分

硕士生

Rank: 6Rank: 6

金币
1437
好评
14
贡献
0

考神MT论坛最佳新人MT论坛帅哥

QQ
发表于 2020-6-20 11:29:58 来自手机  | 显示全部楼层
扔到收藏夹里吃灰去吧
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表