ClipboardManager Hook总结

最近学习了如何Hook Android中的剪贴板服务,特此写下一篇博客记录。
首先说明下什么是Hook,Hook即通过使用代理对象替换系统原有对象,达到增强或修改系统类的功能的手段。一般我们替换的对象都会选择不易改变的静态对象。
下面首先介绍Android中获取系统服务的步骤,然后再介绍如何Hook 剪贴板服务ClipboardManager。
以下代码版本为Android 4.4
首先我们调用Context的getSystemService方法,由于Context的实际功能实现类为 ContextImpl,故我们追踪ContextImpl的代码:

1
2
3
4
5
@Override
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}

ContextImpl去检查了一个系统服务的缓存HashMap SYSTEM_SERVICE_MAP,如下所示(以下仅截取关键代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =
new HashMap<String, ServiceFetcher>();

private static void registerService(String serviceName, ServiceFetcher fetcher) {
if (!(fetcher instanceof StaticServiceFetcher)) {
fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
}
SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
}

static {

registerService(ALARM_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(ALARM_SERVICE);
IAlarmManager service = IAlarmManager.Stub.asInterface(b);
return new AlarmManager(service, ctx);
}});

registerService(CLIPBOARD_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return new ClipboardManager(ctx.getOuterContext(),
ctx.mMainThread.getHandler());
}});
}

ServiceFetcher是ContextImpl的一个内部类,当我们初次调用getService方法时,会调用createService返回结果
SYSTEM_SERVICE_MAP通过静态代码块初始化注册完毕,其中主要的createService返回方式有两种:

  1. 返回xxxManager(我们这次实验的剪贴板就是这种形式),其中一般在内部会有接口的缓存,初次获取缓存的方式与下一种方法相同
  2. 调用ServiceManager的getService方法返回一个通信用的IBinder,通过IInterface(即IxxxManager)的内部类Stub的asInterface方法转换为可用的接口(可能是实体也是能是代理)

ClipboardManager的关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ClipboardManager extends android.text.ClipboardManager {
private static IClipboard sService;

static private IClipboard getService() {
synchronized (sStaticLock) {
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService("clipboard");
sService = IClipboard.Stub.asInterface(b);
return sService;
}
}
}

ClipboardManager实际服务的提供都会使用getService来调用。

先来看看ServiceManager的getService方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public final class ServiceManager {
private static final String TAG = "ServiceManager";

private static IServiceManager sServiceManager;
private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return getIServiceManager().getService(name);
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}

}

该方法首先回去检查sCache的缓存service,如果没有则会调用IServiceManager去获取真正的Service服务。

接下来asInterface的关键代码如下(IClipboard.Stub类,实际就是AIDL自动生成的类文件):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements android.content.IClipboard {

private static final java.lang.String DESCRIPTOR = "android.content.IClipboard";

/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}

/**
* Cast an IBinder object into an android.content.IClipboard interface,
* generating a proxy if needed.
*/
public static android.content.IClipboard asInterface(android.os.IBinder obj) {
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof android.content.IClipboard))) {
return ((android.content.IClipboard)iin);
}
return new android.content.IClipboard.Stub.Proxy(obj);
}
}

在asInterface方法中,首先调用了IBinder对象的queryLocalInterface方法来查找是否本地含有此接口(如果是同一进程就含有),如果不是则返回代理对象。

总的来说,Android获取系统服务步骤如下图所示:
Service获取_pic1

其中我们可以进行Hook的点,主要是红色的两个部分:

  1. Hook xxxManager中的sService服务缓存
  2. 利用sCache缓存表Hook SystemService中getService返回的IBinder对象

方法一的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* hook 方法1
*
* @throws Exception
*/
public static void hook1() throws Exception {
// 加载ClipboardManager类
Class<?> clipboardManagerClazz = Class
.forName("android.content.ClipboardManager");
// 通过getService static方法获取真实IClipboard对象
Method getServiceMethod = clipboardManagerClazz
.getDeclaredMethod("getService");
getServiceMethod.setAccessible(true);
// 真实IClipboard对象
Object clipboardManager = getServiceMethod.invoke(null);
// 获取sService的IClipboard缓存
Field sServiceFeild = clipboardManagerClazz
.getDeclaredField("sService");
sServiceFeild.setAccessible(true);
// 替换sService
sServiceFeild.set(null, Proxy
.newProxyInstance(clipboardManager.getClass().getClassLoader(),
clipboardManager.getClass().getInterfaces(),
new ClipboardManagerProxyHandler(
clipboardManager)));
}

Java动态代理的InvocationHandler接口如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* Created by superxlcr on 2016/9/20.
* 剪贴板代理处理类
*/
public class ClipboardManagerProxyHandler implements InvocationHandler {

// 真正的clipboardManager
private Object clipboardManager;

public ClipboardManagerProxyHandler(Object clipboardManager) {
this.clipboardManager = clipboardManager;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
switch (method.getName()) {
case "getPrimaryClip": // 粘贴内容
return ClipData.newPlainText(null, "you are hook!");
case "hasPrimaryClip": // 剪贴板永远有粘贴内容
return true;
}
// 其余情况由真实对象处理
return method.invoke(clipboardManager, args);
}

}

在这里我们替换掉了,判断剪贴板是否有内容的hasPrimaryClip方法与返回剪贴内容的getPrimaryClip方法,使剪贴板粘贴总是返回you are hook

方法二代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* hook方法2
*
* @throws Exception
*/
public static void hook2() throws Exception {
// 加载ServiceManager类
Class<?> serviceManagerClazz = Class
.forName("android.os.ServiceManager");
// 获取getService方法
Method getServiceMethod = serviceManagerClazz
.getMethod("getService", String.class);
// 获取真正的clipboardManager对象
IBinder clipboardManagerIBinder = (IBinder) getServiceMethod
.invoke(null, CLIPBOARD);
// 获取sCache HashMap缓存
Field sCacheField = serviceManagerClazz.getDeclaredField("sCache");
// private变量
sCacheField.setAccessible(true);
// static变量
HashMap<String, IBinder> sCache = (HashMap) sCacheField.get(null);
// 把代理放入缓存
sCache.put(CLIPBOARD, (IBinder) Proxy.newProxyInstance(
clipboardManagerIBinder.getClass().getClassLoader(),
clipboardManagerIBinder.getClass().getInterfaces(),
new ClipboardManagerIBinderProxyHandler(
clipboardManagerIBinder)));
}

其中对IBinder的动态代理如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* Created by superxlcr on 2016/9/21.
* 剪贴板通信代理处理类
*/
public class ClipboardManagerIBinderProxyHandler implements InvocationHandler {

// 真正的clipboardManagerIBinder
private IBinder clipboardManagerIBinder;

public ClipboardManagerIBinderProxyHandler(
IBinder clipboardManagerIBinder) {
this.clipboardManagerIBinder = clipboardManagerIBinder;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
if (method.getName().equals("queryLocalInterface")) { // 返回伪造代理对象
// 加载IClipboard内部类Stub
Class<?> IClipboardStubClazz = Class
.forName("android.content.IClipboard$Stub");
// 获取asInterface方法
Method asInterfaceMethod = IClipboardStubClazz
.getMethod("asInterface", IBinder.class);
// 通过asInterface static方法,得到真正IClipboard对象
Object clipboardManager = asInterfaceMethod
.invoke(null, clipboardManagerIBinder);
return Proxy.newProxyInstance(
clipboardManager.getClass().getClassLoader(),
clipboardManager.getClass().getInterfaces(),
new ClipboardManagerProxyHandler(clipboardManager));
}
return method.invoke(clipboardManagerIBinder, args);
}
}

这里我们修改了queryLocalInterface方法,使其返回我们代理的IClipboard接口对象,其余部分与方法一相同。

最后附上该工程的地址,有兴趣的同学可以下载看看:https://github.com/superxlcr/ClipboardManagerHook