上一次,我们用Hook技术对startActivity进行了操作。我们这一届继续Hook一些比较深入的东西。更加深入的理解Hook。
通过上一次,我们小结,Hook就是用我们的代理去替换原本的服务,然后达到拓展的目的,甚至改变原有服务的目的。那我们今天就尝试改变系统剪贴板。要想改变系统剪贴板,我们就得了解剪贴板服务最基本的流程,从而我们好控制系统剪贴板的Hook点在哪里。
剪贴板服务的基本流程
我们知道系统服务有个大管家,就是ServiceManager这个类,它管理着我们所有的系统服务。并且我们通过如下方法进行系统服务的获取
eg:得到ActivityManager服务
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
这个服务的作用是和ActivityManagerService进行通信,从而达到系统控制当前App的进程,进程中包含四大组件也得到控制。
ContextImpl.java
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
SystemServiceRegistry.java
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
得到服务
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
注册服务
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
这里可以知道,ContextImpl对象利用SystemServiceRegistry管理我们的服务。
我们裁剪服务的存储
registerService(Context.CLIPBOARD_SERVICE, ClipboardManager.class,
new CachedServiceFetcher<ClipboardManager>() {
@Override
public ClipboardManager createService(ContextImpl ctx) throws ServiceNotFoundException {
return new ClipboardManager(ctx.getOuterContext(),
ctx.mMainThread.getHandler());
}});
看到ClipboardManager就是这个服务的管理者
ClipboardManager.java
public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException {
mContext = context;
mService = IClipboard.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));
}
ServiceManager.java
private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();
public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
final IBinder binder = getService(name);
if (binder != null) {
return binder;
} else {
throw new ServiceNotFoundException(name);
}
}
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return Binder.allowBlocking(getIServiceManager().getService(name));
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
public static void initServiceCache(Map<String, IBinder> cache) {
if (sCache.size() != 0) {
throw new IllegalStateException("setServiceCache may only be called once");
}
sCache.putAll(cache);
}
这里我们可以转化成两句核心代码为:
IBinder service = sCache.get(name);
IClipboard.Stub.asInterface(service);
那我们的思路就可以写成j将sCache中的服务换成我们hook过的服务。然后hookasInterface方法使用我们自己的服务。
接下来我们就分析hook项目的代码
解析项目代码
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
setBinderHook();//方法主入口
} catch (Exception e) {
e.printStackTrace();
}
EditText editText = new EditText(this);
setContentView(editText);
this.getSystemService("");
}
private void setBinderHook() throws Exception {
final String CLIPBOARD_SERVICE = "clipboard";
//对通过ServiceManager得到的clipboard服务进行hook
Class<?> serviceManager = Class.forName("android.os.ServiceManager");
Method getService = serviceManager.getDeclaredMethod("getService", String.class);
IBinder rawBinder = (IBinder) getService.invoke(null, CLIPBOARD_SERVICE);
//用BinderProxyHookHandler进行处理原始裁剪服务的IBinder对象
IBinder hookedBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),
new Class<?>[]{IBinder.class},
new BinderProxyHookHandler(rawBinder));
Field cacheField = serviceManager.getDeclaredField("sCache");
cacheField.setAccessible(true);
Map<String, IBinder> cache = (Map) cacheField.get(null);
//将替换过的IBinder对象存入ServiceManager的缓存中替换掉原来的对象
cache.put(CLIPBOARD_SERVICE, hookedBinder);
}
}
下面这个代码可以看出来,将IBinder中的queryLocalInterface方法进行hook,让此方法返回我们让他返回的对象,这个对象是我们要替换掉系统服务的对象。要使得我们hook的对象听话,也就是一些方法达到我们的目的,我们必须继续使用动态代理模式对其进行代理。也就有了BinderHookHandler
public class BinderProxyHookHandler implements InvocationHandler {
private static final String TAG = "BinderProxyHookHandler";
IBinder base;
Class<?> stub;
Class<?> iinterface;
public BinderProxyHookHandler(IBinder base) {
this.base = base;
try {
this.stub = Class.forName("android.content.IClipboard$Stub");
this.iinterface = Class.forName("android.content.IClipboard");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("queryLocalInterface".equals(method.getName())) {
Log.d(TAG, "hook queryLocalInterface");
return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),
new Class[] { IBinder.class, IInterface.class, this.iinterface },
new BinderHookHandler(base, stub));
}
Log.d(TAG, "method:" + method.getName());
return method.invoke(base, args);
}
}
此时我们已经进入到我们裁剪服务的代理对象中,其中base是我们原Binder,我们通过android.content.IClipboard$Stub
对其进行执行asInterface()然后除了我们需要改变的其余方法我们都可以用这个对象处理。其余我们进行拦截
public class BinderHookHandler implements InvocationHandler {
private static final String TAG = "BinderHookHandler";
// 原始的Service对象 (IInterface)
Object base;
public BinderHookHandler(IBinder base, Class<?> stubClass) {
try {
Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface", IBinder.class);
// IClipboard.Stub.asInterface(base);
this.base = asInterfaceMethod.invoke(null, base);
} catch (Exception e) {
throw new RuntimeException("hooked failed!");
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 把剪切版的内容替换为 "I am wang cai niao"
if ("getPrimaryClip".equals(method.getName())) {
Log.d(TAG, "hook getPrimaryClip");
return ClipData.newPlainText(null, "you are hooked");
}
// 欺骗系统,使之认为剪切版上一直有内容
if ("hasPrimaryClip".equals(method.getName())) {
return true;
}
return method.invoke(base, args);
}
}