嗨,你終於來啦 ~ 等你很久啦~ 喜歡的小夥伴歡迎關注,我會按期分享Android知識點及解析,還會不斷更新的BATJ面試專題,歡迎你們前來探討交流,若有好的文章也歡迎投稿javascript
Hook 又叫「鉤子」,它能夠在事件傳送的過程當中截獲並監控事件的傳輸,將自身的代碼與系統方法進行融入。這樣當這些方法被調用時,也就能夠執行咱們本身的代碼,這也是面向切面編程的思想(AOP)。java
1.根據Android開發模式,Native模式(C/C++)和Java模式(Java)區分,在Android平臺上android
2.根 Hook 對象與 Hook 後處理事件方式不一樣,Hook還分爲:面試
3.針對Hook的不一樣進程上來講,還能夠分爲:編程
在Android開發中,有如下常見的一些Hook框架:緩存
經過替換 /system/bin/app_process 程序控制 Zygote 進程,使得 app_process 在啓動過程當中會加載 XposedBridge.jar 這個 Jar 包,從而完成對 Zygote 進程及其建立的 Dalvik 虛擬機的劫持。
Xposed 在開機的時候完成對全部的 Hook Function 的劫持,在原 Function 執行的先後加上自定義代碼。數據結構
Cydia Substrate 框架爲蘋果用戶提供了越獄相關的服務框架,固然也推出了 Android 版 。Cydia Substrate 是一個代碼修改平臺,它能夠修改任何進程的代碼。不論是用 Java 仍是 C/C++(native代碼)編寫的,而 Xposed 只支持 Hook app_process 中的 Java 函數。app
Legend 是 Android 免 Root 環境下的一個 Apk Hook 框架,該框架代碼設計簡潔,通用性高,適合逆向工程時一些 Hook 場景。大部分的功能都放到了 Java 層,這樣的兼容性就很是好。
原理是這樣的,直接構造出新舊方法對應的虛擬機數據結構,而後替換信息寫到內存中便可。框架
若是你對反射還不是很熟悉的話,建議你先複習一下 java 反射的相關知識。ide
動態代理是指在運行時動態生成代理類,不須要咱們像靜態代理那個去手動寫一個個的代理類。在 java 中,咱們可使用 InvocationHandler 實現動態代理。
本文的主要內容是講解單個進程的 Hook,以及怎樣 Hook。
Hook 的選擇點:儘可能靜態變量和單例,由於一旦建立對象,它們不容易變化,很是容易定位。
Hook 過程:
首先,咱們先分析 View.setOnClickListener 源碼,找出合適的 Hook 點。能夠看到 OnClickListener 對象被保存在了一個叫作 ListenerInfo 的內部類裏,其中 mListenerInfo 是 View 的成員變量。ListeneInfo 裏面保存了 View 的各類監聽事件。所以,咱們能夠想辦法 hook ListenerInfo 的 mOnClickListener 。
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
static class ListenerInfo {
---
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
---
}
複製代碼
接下來,讓咱們一塊兒來看一下怎樣 Hook View.OnClickListener 事件?
大概分爲三步:
從 View 的源代碼,咱們能夠知道咱們能夠經過 getListenerInfo 方法獲取,因而,咱們利用反射獲得 ListenerInfo 對象
從上面的分析,咱們知道 OnClickListener 事件被保存在 ListenerInfo 裏面,同理咱們利用反射獲取
public static void hookOnClickListener(View view) throws Exception {
// 第一步:反射獲得 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);
// 第三步:用 Hook代理類 替換原始的 OnClickListener
View.OnClickListener hookedOnClickListener = new HookedClickListenerProxy(originOnClickListener);
mOnClickListener.set(listenerInfo, hookedOnClickListener);
}
複製代碼
public class HookedClickListenerProxy implements View.OnClickListener {
private View.OnClickListener origin;
public HookedClickListenerProxy(View.OnClickListener origin) {
this.origin = origin;
}
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), "Hook Click Listener", Toast.LENGTH_SHORT).show();
if (origin != null) {
origin.onClick(v);
}
}
}
複製代碼
執行如下代碼,將會看到當咱們點擊該按鈕的時候,會彈出 toast 「Hook Click Listener」
mBtn1 = (Button) findViewById(R.id.btn_1);
mBtn1.setOnClickListener(this);
try {
HookHelper.hookOnClickListener(mBtn1);
} catch (Exception e) {
e.printStackTrace();
}
複製代碼
發送消息到通知欄的核心代碼以下:
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(id, builder.build());
複製代碼
跟蹤 notify 方法發現最終會調用到 notifyAsUser 方法
public void notify(String tag, int id, Notification notification) {
notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
}
複製代碼
而在 notifyAsUser 方法中,咱們驚喜地發現 service 是一個單例,所以,咱們能夠想方法 hook 住這個 service,而 notifyAsUser 最終會調用到 service 的 enqueueNotificationWithTag 方法。所以 hook 住 service 的 enqueueNotificationWithTag 方法便可
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user) {
//
INotificationManager service = getService();
String pkg = mContext.getPackageName();
// Fix the notification as best we can.
Notification.addFieldsFromContext(mContext, notification);
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
}
fixLegacySmallIcon(notification, pkg);
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
if (notification.getSmallIcon() == null) {
throw new IllegalArgumentException("Invalid notification (no valid small icon): "
+ notification);
}
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
copy, user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private static INotificationManager sService;
static public INotificationManager getService() {
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService("notification");
sService = INotificationManager.Stub.asInterface(b);
return sService;
}
複製代碼
綜上,要 Hook Notification,大概須要三步:
因而,咱們能夠寫出以下的代碼
public static void hookNotificationManager(final Context context) throws Exception {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Method getService = NotificationManager.class.getDeclaredMethod("getService");
getService.setAccessible(true);
// 第一步:獲得系統的 sService
final Object sOriginService = getService.invoke(notificationManager);
Class iNotiMngClz = Class.forName("android.app.INotificationManager");
// 第二步:獲得咱們的動態代理對象
Object proxyNotiMng = Proxy.newProxyInstance(context.getClass().getClassLoader(), new
Class[]{iNotiMngClz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d(TAG, "invoke(). method:" + method);
String name = method.getName();
Log.d(TAG, "invoke: name=" + name);
if (args != null && args.length > 0) {
for (Object arg : args) {
Log.d(TAG, "invoke: arg=" + arg);
}
}
Toast.makeText(context.getApplicationContext(), "檢測到有人發通知了", Toast.LENGTH_SHORT).show();
// 操做交由 sOriginService 處理,不攔截通知
return method.invoke(sOriginService, args);
// 攔截通知,什麼也不作
// return null;
// 或者是根據通知的 Tag 和 ID 進行篩選
}
});
// 第三步:偷樑換柱,使用 proxyNotiMng 替換系統的 sService
Field sServiceField = NotificationManager.class.getDeclaredField("sService");
sServiceField.setAccessible(true);
sServiceField.set(notificationManager, proxyNotiMng);
}
複製代碼
從上面的 hook NotificationManager 例子中,咱們能夠得知 NotificationManager 中有一個靜態變量 sService,這個變量是遠端的 service。所以,咱們嘗試查找 ClipboardManager 中是否是也存在相同的相似靜態變量。
查看它的源碼發現它存在 mService 變量,該變量是在 ClipboardManager 構造函數中初始化的,而 ClipboardManager 的構造方法用 @hide 標記,代表該方法對調用者不可見。
而咱們知道 ClipboardManager,NotificationManager 其實這些都是單例的,即系統只會建立一次。所以咱們也能夠認爲
ClipboardManager 的 mService 是單例的。所以 mService 應該是能夠考慮 hook 的一個點。
public class ClipboardManager extends android.text.ClipboardManager {
private final Context mContext;
private final IClipboard mService;
/** {@hide} */
public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException {
mContext = context;
mService = IClipboard.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));
}
}
複製代碼
接下來,咱們再來一個看一下 ClipboardManager 的相關方法 setPrimaryClip , getPrimaryClip
public void setPrimaryClip(ClipData clip) {
try {
if (clip != null) {
clip.prepareToLeaveProcess(true);
}
mService.setPrimaryClip(clip, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** * Returns the current primary clip on the clipboard. */
public ClipData getPrimaryClip() {
try {
return mService.getPrimaryClip(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
複製代碼
能夠發現這些方法最終都會調用到 mService 的相關方法。所以,ClipboardManager 的 mService 確實是一個能夠 hook 的一個點。
hook ClipboardManager.mService 的實現
大概須要三個步驟
public static void hookClipboardService(final Context context) throws Exception {
ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
Field mServiceFiled = ClipboardManager.class.getDeclaredField("mService");
mServiceFiled.setAccessible(true);
// 第一步:獲得系統的 mService
final Object mService = mServiceFiled.get(clipboardManager);
// 第二步:初始化動態代理對象
Class aClass = Class.forName("android.content.IClipboard");
Object proxyInstance = Proxy.newProxyInstance(context.getClass().getClassLoader(), new
Class[]{aClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d(TAG, "invoke(). method:" + method);
String name = method.getName();
if (args != null && args.length > 0) {
for (Object arg : args) {
Log.d(TAG, "invoke: arg=" + arg);
}
}
if ("setPrimaryClip".equals(name)) {
Object arg = args[0];
if (arg instanceof ClipData) {
ClipData clipData = (ClipData) arg;
int itemCount = clipData.getItemCount();
for (int i = 0; i < itemCount; i++) {
ClipData.Item item = clipData.getItemAt(i);
Log.i(TAG, "invoke: item=" + item);
}
}
Toast.makeText(context, "檢測到有人設置粘貼板內容", Toast.LENGTH_SHORT).show();
} else if ("getPrimaryClip".equals(name)) {
Toast.makeText(context, "檢測到有人要獲取粘貼板的內容", Toast.LENGTH_SHORT).show();
}
// 操做交由 sOriginService 處理,不攔截通知
return method.invoke(mService, args);
}
});
// 第三步:偷樑換柱,使用 proxyNotiMng 替換系統的 mService
Field sServiceField = ClipboardManager.class.getDeclaredField("mService");
sServiceField.setAccessible(true);
sServiceField.set(clipboardManager, proxyInstance);
}
複製代碼
對 Android 源碼有基本瞭解的人都知道,Android 中的各類 Manager 都是經過 ServiceManager 獲取的。所以,咱們能夠經過 ServiceManager hook 全部系統 Manager,ClipboardManager 固然也不例外。
public final class ServiceManager {
/** * Returns a reference to a service with the given name. * * @param name the name of the service to get * @return a reference to the service, or <code>null</code> if the service doesn't exist */
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;
}
}
複製代碼
老套路
public static void hookClipboardService() throws Exception {
//經過反射獲取剪切板服務的遠程Binder對象
Class serviceManager = Class.forName("android.os.ServiceManager");
Method getServiceMethod = serviceManager.getMethod("getService", String.class);
IBinder remoteBinder = (IBinder) getServiceMethod.invoke(null, Context.CLIPBOARD_SERVICE);
//新建一個咱們須要的Binder,動態代理原來的Binder對象
IBinder hookBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),
new Class[]{IBinder.class}, new ClipboardHookRemoteBinderHandler(remoteBinder));
//經過反射獲取ServiceManger存儲Binder對象的緩存集合,把咱們新建的代理Binder放進緩存
Field sCacheField = serviceManager.getDeclaredField("sCache");
sCacheField.setAccessible(true);
Map<String, IBinder> sCache = (Map<String, IBinder>) sCacheField.get(null);
sCache.put(Context.CLIPBOARD_SERVICE, hookBinder);
}
複製代碼
public class ClipboardHookRemoteBinderHandler implements InvocationHandler {
private IBinder remoteBinder;
private Class iInterface;
private Class stubClass;
public ClipboardHookRemoteBinderHandler(IBinder remoteBinder) {
this.remoteBinder = remoteBinder;
try {
this.iInterface = Class.forName("android.content.IClipboard");
this.stubClass = Class.forName("android.content.IClipboard$Stub");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d("RemoteBinderHandler", method.getName() + "() is invoked");
if ("queryLocalInterface".equals(method.getName())) {
//這裏不能攔截具體的服務的方法,由於這是一個遠程的Binder,尚未轉化爲本地Binder對象
//因此先攔截咱們所知的queryLocalInterface方法,返回一個本地Binder對象的代理
return Proxy.newProxyInstance(remoteBinder.getClass().getClassLoader(),
new Class[]{this.iInterface},
new ClipboardHookLocalBinderHandler(remoteBinder, stubClass));
}
return method.invoke(remoteBinder, args);
}
}
複製代碼
本文轉載自公衆號Android技術人,喜歡的小夥伴能夠過去關注一波