簡単な紹介
フックは何ですか
「フック」と呼ばれるフック、それはコード自体およびシステム統合方法の伝送におけるイベントの過程でのイベントの送信を傍受し、監視することができます。メソッドが呼び出されたとき、あなたは思考(AOP)のプログラミングセクションを向いている私たち自身のコードを実行できるようにします。
フックカテゴリー
1.開発モデルのAndroid、ネイティブモード(C / C ++)とJavaモード(Java)の区別、Androidプラットフォーム
Javaレベルのフック。
ネイティブレベルのフック。
フックの後にイベントを処理するためのさまざまな方法2.フックのルートオブジェクトは、フックはまたに分かれています。
メッセージフック。
APIフック。
その異なるプロセス3.フックは、に分けることができます。
グローバルフック;
単一プロセスフック。
フック共通のフレームワーク
Androidの開発では、いくつかの共通の枠組みのフック以下:
Xposed
/システム/ binに/ app_process受精卵プロセス制御プログラムを置き換えることにより、そのapp_processので、ブートプロセスは、このようのDalvik仮想マシンの受精卵プロセスのハイジャックを完了して作成し、このjarパッケージをXposedBridge.jarロードする時に。
カスタムコードを追加する前に、元の関数の実行後に、ブート時にすべてのフック関数のXposed完全な乗っ取り、。Cydiaの基質
Cydiaの基板フレームワークはもちろん、Androidのバージョンを立ち上げ、Appleユーザーのための関連サービスフレームワークの脱獄を提供します。Cydiaの基板は、それがどのプロセスにコードを変更することができ、コードの修正プラットフォームです。どちらのJavaやC / C ++(ネイティブコード)書かれていますが、Xposedフックapp_processでのみJava関数をサポートしています。
伝説
伝説では、ルート環境、フレームワークのコードシンプルなデザイン、高い汎用性の無料のAndroid APKフックフレームワークであり、一方、リバースエンジニアリングのためのフックシーンの一部。機能のほとんどは、Java層の上に置いているので、互換性は非常に良いです。
このような原則は、直接、古いものと新しい方法に対応した仮想マシンのデータ構造を構築し、その後、情報がメモリに書き込まれている置き換えています。
知識を習得する必要がありますフック
反射
あなたが反射して非常に慣れていない場合、私はあなたがJavaのリフレクションの知識をブラッシュアップをお勧めします。
我々は、手動でプロキシクラスの1という静的なエージェントを記述する必要はありませんように、動的プロキシは、実行時に動的に生成されたプロキシクラスです。Javaでは、私たちはダイナミックのInvocationHandlerエージェントを使用することができます。
この記事の主な内容は、個々のプロセスのフック、そしてどのようにフックを説明することです。
フックユースケース
キーポイントのフックの選択
フックの選択ポイント:オブジェクトが作成されると、彼らは見つけることは非常に簡単で、変更することは容易ではないため、静的変数および単一のケースにしてみてください。
フックプロセス:
フックポイントを見ると、静的変数や原理は、オブジェクトをシングルトン公共フックオブジェクトとメソッドを試してみることです。
エージェントがダイナミックとのインターフェースである場合には、適切なプロキシモードを選択します。
置換 - プロキシオブジェクトと元のオブジェクトを置き換えます。
AndroidのAPIのバージョンより、メソッドやクラスが異なるので、互換性のあるAPIの作業を行うことがあります。
単純なケース:使用フックはView.OnClickListenerイベントを変更
まず、フックは右のポイントを見つける、のはView.setOnClickListenerソースを分析してみましょう。あなたはOnClickListenerのオブジェクトがmListenerInfoビューのメンバ変数であるListenerInfoと呼ばれる内部クラスに格納されて見ることができます。イベントリスニングのビューの多様性を保持しているListeneInfo。したがって、我々はmOnClickListenerのListenerInfoをフックする方法を考えることができます。
公共ボイドsetOnClickListener(@Nullable OnClickListener 1){{場合(isClickable()!) setClickable(真)。 } getListenerInfo()mOnClickListener = L。 }静的クラスListenerInfo { --- ListenerInfo getListenerInfo(){(mListenerInfo = NULL!)なら、{mListenerInfoを返します。 } mListenerInfo =新しいListenerInfo()。mListenerInfoを返します。 } --- }
次に、私たちはどのようにフックView.OnClickListenerイベントを見てみましょうか?
大雑把に3つのステップに分け:
最初のステップは:ListenerInfoオブジェクトを取得します。
ソースコードからの眺め、我々はgetListenerInfo方法によって得ることができることを知っているので、我々は反射によって得られたことはListenerInfoオブジェクト
ステップ2:元OnClickListenerイベントメソッドを取得します。
上記の分析から、我々は、OnClickListenerイベントは内部ListenerInfoに保存されている私たちが取得するためにリフレクションを使用するのと同じ方法を知っています
第三段階:置換、フック元プロキシクラスをOnClickListener置換
公共の静的な無効hookOnClickListener(ビュービュー)が例外をスロー{//第一步:反射得到ListenerInfo对象 方法getListenerInfo = View.class.getDeclaredMethod( "getListenerInfo"); getListenerInfo.setAccessible(真の); listenerInfo = getListenerInfo.invoke(ビュー)オブジェクト。//第二步:得到原始的OnClickListener事件方法 クラスlistenerInfoClz = Class.forNameの( "android.view.View $ ListenerInfo"); <?> フィールドmOnClickListener = listenerInfoClz.getDeclaredField( "mOnClickListener")。 mOnClickListener.setAccessible(真の); View.OnClickListener originOnClickListener =(View.OnClickListener)mOnClickListener.get(listenerInfo); //第三段階:フックでオリジナルプロキシクラスをOnClickListener置換 View.OnClickListener hookedOnClickListener =新しいHookedClickListenerProxy(originOnClickListener)。 mOnClickListener.set(listenerInfo、hookedOnClickListener)。 }
パブリッククラスHookedClickListenerProxyはView.OnClickListener {プライベートView.OnClickListener原点を実装します。公共HookedClickListenerProxy(View.OnClickListener由来){this.origin =原点。 } @Override ます。public void onClickの(ビューV){ Toast.makeText(v.getContext()、 "フッククリックリスナー"、Toast.LENGTH_SHORT).SHOW(); (もし!起源= NULL){ origin.onClick(V); } } }
次のコードを実行し、あなたは私たちがボタンをクリックすると、トースト「フックをクリックしてリスナーを」ポップアップ表示されます表示されます
mBtn1 =(ボタン)findViewById(R.id.btn_1)。 mBtn1.setOnClickListener(この);試みる{ HookHelper.hookOnClickListener(mBtn1)。 }キャッチ(例外e){ e.printStackTrace(); }
単純なケースII:フック通知
コアコードは、通知欄にメッセージを送信します。
NotificationManager notificationManager =(NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE)。 notificationManager.notify(ID、builder.build())。
メソッドを通知追跡呼び出し、最終的にはnotifyAsUserにする方法を見つけました
公共ボイドは(文字列タグ、int型のID、通知通知){通知 notifyAsUser(タグID、通知、新しいUserHandle(UserHandle.myUserId()))。 }
notifyAsUser方法では、我々は、そのサービスがシングルトンである見つけるために驚いたので、私たちは生きてこのサービスをフックする方法を考えることができますが、最終的にはサービスのenqueueNotificationWithTag notifyAsUserメソッドに呼び出されます。フックを生きるためのサービスのためenqueueNotificationWithTag方法
公共ボイドnotifyAsUser(文字列タグ、int型のID、通知通知、UserHandleユーザ){// INotificationManagerサービスのgetService =(); 文字列PKG = mContext.getPackageName()。//私たちができる最善のように通知を修正。 Notification.addFieldsFromContext(mContext、通知)。(もし!notification.sound = NULL){ notification.sound = notification.sound.getCanonicalUri(); (StrictMode.vmFileUriExposureEnabled()){もし notification.sound.checkFileUriExposed( "Notification.sound")。 } } fixLegacySmallIcon(通知、PKG)。もし(mContext.getApplicationInfo()targetSdkVersion> Build.VERSION_CODES.LOLLIPOP_MR1。){場合(notification.getSmallIcon()== NULL){新しい例外:IllegalArgumentException( "無効通知(有効な小さなアイコン):"スロー +通知)。 } }(localLOGV)Log.v(TAG、PKG + ":通知(" + ID + "" +通知+ ")")であれば、最終通知コピー= Builder.maybeCloneStrippedForDelivery(通知)。{試みる service.enqueueNotificationWithTag(PKG、mContext.getOpPackageName()、タグ、ID、 コピー、user.getIdentifier())。 }キャッチ(RemoteExceptionを電子){e.rethrowFromSystemServer()を投げます。 } }プライベート静的INotificationManager sService、静的パブリックINotificationManagerのgetService(){IF(!sService = NULL){sServiceを返します。 } IBinderのB = ServiceManager.getService( "通知")。 sService = INotificationManager.Stub.asInterface(B)。sServiceを返します。 }
要約すると、通知をフックするために、それは約3の手順を実行します。
最初のステップ:取得sService NotificationManager
ステップ2:sServiceはインタフェースであるので、我々は、動的プロキシオブジェクトを取得するために、動的なエージェントを使用することができますので、
第三段階:置換動的プロキシオブジェクトproxyNotiMng代替システムsServiceを使用して
そこで、以下のコードを書くことができます
パブリック静的ボイドhookNotificationManager(最終コンテキスト・コンテキスト)が例外をスロー{ NotificationManager notificationManager =(NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE)。 メソッドGetService = NotificationManager.class.getDeclaredMethod( "のgetService")。 getService.setAccessible(真の); //第一步:得到系统的sService 最終オブジェクトsOriginService = getService.invoke(notificationManager)。 クラスiNotiMngClz = Class.forNameの( "android.app.INotificationManager"); //第二步:得到我们的动态代理对象 物体proxyNotiMng =たとえば、Proxy.newProxyInstance(context.getClass()のgetClassLoader()、新しい クラス[] {iNotiMngClz}、新規のInvocationHandler(){@Override オブジェクト公開起動Throwableをスロー{(プロキシは、この方法は、この方法は、[]引数オブジェクト物体) (TAG ,. "呼び出し()メソッド:" +方法)Log.dと、 文字列名= method.getName(); Log.d(TAG "呼び出し:名="名前+); IF(引数= NULL && args.length> 0){(Argのオブジェクトのための:引数){! Log.d(TAG、 "呼び出し:アルギニン=" +アルギニン); } } Toast.makeText(context.getApplicationContext()は、 "検出された通知を発行した"、Toast.LENGTH_SHORT).SHOW(); // 通知インターセプトしない、sOriginService処理動作をいう 戻りmethod.invoke(sOriginService、引数)を、 //インターセプト通知と何も行う // nullを返します。 //またはタグIDとスクリーニングを記載通知 } }); //第三段階:置換、proxyNotiMng Sservice使用して別のシステム フィールド、sServiceField = NotificationManager.class.getDeclaredField( "Sservice"); sServiceField.setAccessible(真の) ; sServiceField.set(NotificationManager、proxyNotiMng); }
高度の使用をフック
フックClipboardManager
第一の方法
上記の例からNotificationManagerフック、私たちは、この変数はリモートサービスで、静的変数NotificationManager sServiceがあることがわかります。したがって、我々はClipboardManagerが同じ類似した静的変数ではありません探してみてください。
mService変数が存在することが見出され、そのソースを表示、変数は、方法は、発信者には見えないことを示す、@hideマークがコンストラクタClipboardManager、及びClipboardManager工法で初期化されます。
そして、我々はClipboardManagerを知っている、という事実NotificationManager、これらはすなわち、単一の場合は、システムは一度だけ作成されます。したがって、我々はまた考えることができます
mServiceのClipboardManagerはシングルトンです。だから、mServiceは、フックのポイントを考慮することができるはずです。
パブリッククラスClipboardManagerはandroid.text.ClipboardManager {民間最終的なコンテキストmContextを拡張します。民間最終IClipboard mService。/ ** {@hide} * / パブリックClipboardManager(コンテキスト・コンテキスト、ハンドラハンドラ)がServiceNotFoundException {スロー mContext =コンテキストを、 mService = IClipboard.Stub.asInterface( ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE))。 } }
次に、私たちはsetPrimaryClip、getPrimaryClipの関連法のClipboardManagerを見てみましょう
公共のボイドsetPrimaryClip(ClipDataクリップ){しようと{場合(!クリップ= NULL){ clip.prepareToLeaveProcess(真の); } mService.setPrimaryClip(クリップ、mContext.getOpPackageName())。 }キャッチ(RemoteExceptionを電子){e.rethrowFromSystemServer()を投げます。 } } / ** *クリップボード上の現在のプライマリクリップを返します。 * /公共ClipData getPrimaryClip(){しようと{返すmService.getPrimaryClip(mContext.getOpPackageName()); }キャッチ(RemoteExceptionを電子){e.rethrowFromSystemServer()を投げます。 } }
あなたは最終的にmServiceの関連するメソッドを呼び出します。これらのメソッドを見つけることができます。したがって、mServiceのClipboardManagerは確かにポイントをフックすること。
フックClipboardManager.mService実装
それはおよそ3つの手順を取ります
最初のステップ:取得mService ClipboardManager
ステップ2:動的プロキシ・オブジェクトの初期化
第三段階:置換、proxyNotiMng mServiceを使用する代替システム
パブリック静的ボイドhookClipboardService(最終コンテキスト・コンテキスト)が例外をスロー{ ClipboardManager clipboardManager =(ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE)。 フィールドmServiceFiled = ClipboardManager.class.getDeclaredField( "mService")。 mServiceFiled.setAccessible(真の); //第一步:得到系统的mService 最終オブジェクトmService = mServiceFiled.get(clipboardManager)。 //第二步:初始化动态代理对象 クラスAClassは= Class.forNameの( "android.content.IClipboard"); proxyInstance =たとえば、Proxy.newProxyInstance(context.getClass()のgetClassLoader()、新しいオブジェクト クラス[] {} AClassは、新規のInvocationHandler(){@Overrideを パブリックオブジェクト呼び出しは、(オブジェクトのプロキシ、メソッド方法は、[]引数オブジェクト)のThrowableが{スロー Log.d(TAG、 ")(呼び出しメソッド:" +法); 文字列名= method.getName()。{(!引数= NULL && args.length> 0){(引数オブジェクト引数)のためなら Log.d(TAG、 "呼び出す:引数=" +引数)。 } }( "setPrimaryClip" .equals(名))なら、{ オブジェクトのarg =引数[0]。IF(引数のinstanceof ClipData){ ClipData clipData =(ClipData)引数。INT ITEMCOUNT個= clipData.getItemCount()。{ため(; I <ITEMCOUNT個のI ++は、I = 0 INT) ClipDataを。proxyInstance); }
image
第二种方法
对 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; } }
老套路
第一步:通过反射获取剪切板服务的远程Binder对象,这里我们可以通过 ServiceManager getService 方法获得
第二步:创建我们的动态代理对象,动态代理原来的Binder对象
第三步:偷梁换柱,把我们的动态代理对象设置进去
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( )+ "()が呼び出された");( "queryLocalInterface" .equals(method.getName())){//もしここで特定のサービスメソッドをインターセプトしない、などこれは、まだ地元のバインダーオブジェクトに翻訳されていないリモートのバインダー、である ので、我々は最初の迎撃queryLocalInterface方法は、バインダーのローカルプロキシオブジェクトを返します知っ// 返すたとえば、Proxy.newProxyInstance(remoteBinder.getClass()。のgetClassLoader ()、新しいクラス[] {} this.iInterface、新しい新しいClipboardHookLocalBinderHandler(remoteBinder、stubClassは)); } Method.invoke(remoteBinder、引数を返します); } }