[免费专栏] Android安全之Android Xposed插件开发,小白都能看得懂的教程


也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大

少走了弯路,也就错过了风景,无论如何,感谢经历


转移发布平台通知:将不再在CSDN博客发布新文章,敬请移步知识星球

感谢大家一直以来对我CSDN博客的关注和支持,但是我决定不再在这里发布新文章了。为了给大家提供更好的服务和更深入的交流,我开设了一个知识星球,内部将会提供更深入、更实用的技术文章,这些文章将更有价值,并且能够帮助你更好地解决实际问题。期待你加入我的知识星球,让我们一起成长和进步


Android安全付费专栏长期更新,本篇最新内容请前往:

0x01 前言

1.1 安卓操作系统架构

Android是一种基于Linux的自由及开放源代码的操作系统。而Android系统构架是安卓系统的体系结构,其系统架构和其操作系统一样,采用了分层的架构,共分为四层五部分,四层指的是从高到低分别是Android应用层,Android应用框架层,Android系统运行层和Linux内核层;五部分指的是Linux Kernel、Android Runtime、Libraries、Application Framework、Applications。

1.1.1 Linux Kernel

在所有层的最底下是 Linux,它提供了基本的系统功能,比如进程管理,内存管理,设备管理(如摄像头,键盘,显示器)。

1.1.2 Android Runtime

Android 运行时同时提供一系列核心的库来为 Android 应用程序开发者使用标准的 Java 语言来编写 Android 应用程序。Dalvik 虚拟机使得每一个 Android 应用程序运行在自己独立的虚拟机进程。Dalvik虚拟机可执行文件格式是.dex,dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。

1.1.3 Libraries

Android包含一个C/C++库的集合,供Android系统的各个组件使用。这些功能通过Android的应用程序框架(application framework)暴露给开发者。

1.1.4 Application Framework

通过提供开放的开发平台,Android使开发者能够编制极其丰富和新颖的应用程序。

1.1.5 Applications

应用框架层以 Java 类的形式为应用程序提供许多高级的服务。

1.2 安卓应用程序组件

应用程序组件是一个Android应用程序的基本构建块。在AndroidManifest.xml中描述了应用程序的每个组件,以及他们如何交互。

1.2.1 Android应用程序中四个主要组件

组件名 描述
Activities 描述UI,并且处理用户与机器屏幕的交互
Services 处理与应用程序关联的后台操作
Broadcast Receivers 处理Android操作系统和应用程序之间的通信
Content Providers 处理数据和数据库管理方面的问题

1.2.2 附加组件

组件名 描述
Fragments 代表活动中的一个行为或者一部分用户界面
Views 绘制在屏幕上的UI元素,包括按钮,列表等
Layouts 控制屏幕格式,展示视图外观的View的继承
Intents 组件间的消息连线
Resources 外部元素,例如字符串资源、常量资源及图片资源等
Manifest 应用程序的配置文件

1.3 什么是 Hook?

Hook 又叫“钩子”,它可以在事件传送的过程中截获并监控事件的传输,将自身的代码与系统方法进行融入

这样当这些方法被调用时,也就可以执行我们自己的代码,这也是面向切面编程的思想(AOP)

1.4 Hook 分类

1)根据Android开发模式,Native模式(C/C++)和Java模式(Java)区分,在Android平台上

  • Java层级的Hook
  • Native层级的Hook

2)根 Hook 对象与 Hook 后处理事件方式不同,Hook还分为:

  • 消息Hook
  • API Hook

3)针对Hook的不同进程上来说,还可分为:

  • 全局Hook
  • 单个进程Hook

1.5 Hook原理

Hook技术本质是函数调用,由于处于Linux用户状态,每个进程有自己独立的进程控件,所以必须先注入所要Hook的进程空间,修改其内存中进程代码,替换过程表的符号地址,通过ptrace函数附加进程,向远程进程注入so库,从而达到监控以及远程进程关键函数挂钩

Hook工作流程:

  • Android相关内核函数:

    • ptrace函数:跟踪一个目标进程,结束跟踪一个目标进程,获取内存字节,像内存写入地址
    • dlopen函数:以指定模式打开指定的动态链接库文件
    • mmap函数:分配一段临时的内存来完成代码的存放
  • 向目标进程注入代码总结后的步骤分为以下几步:

    • 用ptrace函数attch上目标进程
    • 发现装载共享库so函数
    • 装载指定的.so
    • 让目标进程的执行流程跳转到注入的代码执行
    • 使用ptrace函数的detach释放目标集成

1.6 常见 Hook 框架

在Android开发中,有以下常见的一些Hook框架:

1)Xposed

Xposed 是国外大牛开发的一个工具,Xposed通过拦截安卓程序运行过程来达到修改程序行为的目的。不需要修改安卓源文件,而是通过分析程序运行来拦截并影响运行情况。具体需要把安卓apk逆向后然后分析代码,定位到具体的类,方法等,然后通过xposed来拦截修改方法等。

使用Xposed模块的两个条件:

  • 手机必须root(Xposed需要往/system里写入东西)
  • 安装Xposed Installer

通过替换 /system/bin/app_process 程序控制 Zygote 进程,使得 app_process 在启动过程中会加载 XposedBridge.jar 这个 Jar 包,从而完成对 Zygote 进程及其创建的 Dalvik 虚拟机的劫持。

Xposed 在开机的时候完成对所有的 Hook Function 的劫持,在原 Function 执行的前后加上自定义代码。

下载地址:

下面这个仅适用于 Android 4.0.3 至 Android 4.4 上的 root 访问
https://repo.xposed.info/module/de.robv.android.xposed.installer

对于 Android 5.0 或更高版本改用下面这个
https://forum.xda-developers.com/t/official-xposed-for-lollipop-marshmallow-nougat-oreo-v90-beta3-2018-01-29.3034811/

2)Cydia Substrate

Cydia Substrate是一个基于Hook的代码修改框架,其可以在Android、iOS平台使用,并实现修改系统默认代码

Cydia Substrate 框架为苹果用户提供了越狱相关的服务框架,当然也推出了 Android 版 。Cydia Substrate 是一个代码修改平台,它可以修改任何进程的代码。

不管是用 Java 还是 C/C++(native代码)编写的,而 Xposed 只支持 Hook app_process 中的 Java 函数。

下载地址:

http://www.cydiasubstrate.com

3)Legend

Legend 是 Android 免 Root 环境下的一个 Apk Hook 框架,该框架代码设计简洁,通用性高,适合逆向工程时一些 Hook 场景。大部分的功能都放到了 Java 层,这样的兼容性就非常好

原理是这样的,直接构造出新旧方法对应的虚拟机数据结构,然后替换信息写到内存中即可

下载地址:

https://github.com/asLody/legend

4)VirtualXposed

VirtualXposed 是基于VirtualApp 和 epic 在非ROOT环境下运行Xposed模块的实现(支持5.0~10.0)。

与 Xposed 相比,目前 VirtualXposed 有两个限制:

不支持修改系统(可以修改普通APP中对系统API的调用),因此重力工具箱,应用控制器等无法使用

暂不支持资源HOOK,因此资源钩子不会起任何作用;使用资源HOOK的模块,相应的功能不会生效

下载地址:

https://github.com/android-hacker/VirtualXposed

1.7 Hook 必须掌握的知识

  • 反射

  • java 的动态代理

动态代理是指在运行时动态生成代理类,不需要我们像静态代理那个去手动写一个个的代理类。在 java 中,可使用 InvocationHandler 实现动态代理

1.8 Hook 选择的关键点

Hook 的选择点:尽量静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位

Hook 过程:

  • 寻找 Hook 点,原则是尽量静态变量或者单例对象,尽量 Hook public 的对象和方法
  • 选择合适的代理方式,如果是接口可以用动态代理
  • 偷梁换柱 —— 用代理对象替换原始对象

Android 的 API 版本比较多,方法和类可能不一样,所以要做好 API 的兼容工作

1.9 Xposed的一些类的介绍

  • IXposedHookLoadPackage.java

加载回调接口,在xposed入口类继承,实现handleLoadPackage方法

IXposedHookLoadPackage接口。该接口提供了一个名为handleLoadPackage的方法,这个方法在Android系统每次加载一个包的时候都会被调用

handleLoadPackage 用于在加载应用程序的包的时候执行用户的操作
LoadPackageParam loadPackageParam 包含了加载的应用程序的一些基本信息
  • IXposedHookInitPackageResources.java

加载回调接口,用于修改app的资源文件,在xposed入口类继承,实现handleInitPackageResources(InitPackageResourcesParam resparam)方法

handleInitPackageResources 用于在加载应用程序的包的时候执行用户的操作
InitPackageResourcesParam resparam 包含了加载的应用程序的一些资源基本信息
  • XposedHelpers.java

一些辅助方法,简化连接和调用方法/构造函数,获取和设置字段

HOOK无参数的方法,XposedHelpers.findAndHookMethod()方法一般四个参数,分别为完整类名、ClassLoader对象、方法名以及一个回调接口

XposedHelpers.findAndHookMethod("com.example.demo.MortgageActivity", loadPackageParam.classLoader,
        "showLoan", new XC_MethodHook() {
    
    
	//……
});

HOOK有参数的方法,比如要传入两个int参数,则:

XposedHelpers.findAndHookMethod("com.example.demo.MortgageActivity", loadPackageParam.classLoader,
        "showLoan", int.class, int.class, new XC_MethodHook() {
    
    
	//……
});

PS:new XC_MethodHook()有两个重要的内部函数beforeHookedMethod()和afterHookedMethod(),通过重写它们可以实现对任意方法的挂钩,它们的区别在于Hook前调用还是后调用

beforeHookedMethod 该方法在hook目标方法执行前调用,
其中,参数param指的是目标方法的相关参数、回调、方法等信息

afterHookedMethod 该方法在hook目标方法执行后调用,
其中,参数param指的是目标方法的相关参数、回调、方法等信息。

Xposed运行多个模块对同一个方法进行hook时,
框架就会根据Xposed模块的优先级来排序

XposedBridge类中hookAllMethods和log方法主要用于一次hook每个类的所有方法或够造函数

hookAllMethods(
	Class<?> hookClass,//需要进行hook的类
	String methodName,//需要进行hook的方法名
	XC_MethodHook callback//回调函数
)

XposedHookLoadPackage中的handleLoadPackage方法主要用于加载应用程序包时执行用户的操作。

findAndHookMethod hook一个类中的方法
className 要hook的方法的所在类
classloader 要hook的包的classLoader,一般都写loadPackageParam.classLoader
methodName 要hook的方法
parameterTypesAndCallback 方法的参数和监听器
callMethod 在目标app中调用方法
Object 要调用方法的所在类
methodName 要调用的方法名称
args 方法的参数
findClass 获取class类实例
className 类名
classLoader 类加载器
  • XposedBridge.java
log 在Xposed的app的日志功能里输出日志
text 要输出的内容
  • XC_MethodHook中定义了回调方法

1.10 Android应用各部分说明

1.10.1 MainActivity.java文件

主要活动代码,实际的应用程序文件,将被转化为Dalvik可执行文件并运行。R.layout.activity_main引用res/layout/activity_main.xml文件。

onCreate() 活动被加载之后众多被调用的方法之一。

在这里插入图片描述

1.10.2 AndroidManifest.xml文件

AndroidManifest.xml文件是整个应用程序的信息描述文件,定义了应用程序中包含的Activity、Service、Content provider和BroadcastReceiver组件信息。每个应用程序在根目录下必须包含一个AndroidManifest.xml文件,且文件名不能修改。在AndroidManifest.xml文件中,首先看到是的<manifest>节点,它是整个应用程序的基本属性,涵盖了默认进程名字,应用程序标识,安装位置,对系统的要求以及应用程序的版本等。

  • android:icon是普通图标
  • android:roundIcon是圆形图标
  • android:label属性指定应用的名称
  • android:name属性指定一个Activity类子类的全名

意图过滤器的action被命名为android.intent.action.MAIN,表明这个活动被用做应用程序的入口。

意图过滤器的category被命名为android.intent.category.LAUNCHER,表明应用程序可以通过设备启动器的图标来启动。

@string指的是strings.xml,因此@string/app_name指的是定义在strings.xml中的app_name,这里实际为"HO22K"

在这里插入图片描述
在这里插入图片描述

1.10.3 activity_main.xml文件

activity_main.xml可能将频繁修改这个文件来改变应用程序的布局。

TextView是一个用于构建用户图形界面的Android控件。它包含有许多不同的属性,诸如android:layout_widthandroid:layout_height等用来设置它的宽度和高度等。这里我们给它显示一句话“橙留香的Hook”,引用自strings.xml文件

在这里插入图片描述

1.11 Android layout属性大全

activity_main.xml 编写时需要用到

1.11.1 第一类:属性值 true或者 false

属性 描述
android:layout_centerHrizontal 水平居中
android:layout_centerVertical 垂直居中
android:layout_centerInparent 相对于父元素完全居中
android:layout_alignParentBottom 贴紧父元素的下边缘
android:layout_alignParentLeft 贴紧父元素的左边缘
android:layout_alignParentRight 贴紧父元素的右边缘
android:layout_alignParentTop 贴紧父元素的上边缘
android:layout_alignWithParentIfMissing 如果对应的兄弟元素找不到的话就以父元素做参照物
android:layout_alignParentStart 紧贴父元素结束位置开始
android:layout_alignParentEnd 紧贴父元素结束位置结束
android:animateLayoutChanges 布局改变时是否有动画效果
android:clipChildren 定义子布局是否一定要在限定的区域内
android:clipToPadding 定义布局间是否有间距
android:animationCache 定义子布局也有动画效果
android:alwaysDrawnWithCache 定义子布局是否应用绘图的高速缓存
android:addStatesFromChildren 定义布局是否应用子布局的背景
android:splitMotionEvents 定义布局是否传递touch事件到子布局
android:focusableInTouchMode 定义是否可以通过touch获取到焦点
android:isScrollContainer 定义布局是否作为一个滚动容器 可以调整整个窗体
android:fadeScrollbars 滚动条自动隐藏
android:fitsSystemWindows 设置布局调整时是否考虑系统窗口(如状态栏)
android:visibility 定义布局是否可见
android:requiresFadingEdge 定义滚动时边缘是否褪色
android:clickable 定义是否可点击
android:longClickable 定义是否可长点击
android:saveEnabled 设置是否在窗口冻结时(如旋转屏幕)保存View的数据
android:filterTouchesWhenObscured 所在窗口被其它可见窗口遮住时,是否过滤触摸事件
android:keepScreenOn 设置屏幕常亮
android:duplicateParentState 是否从父容器中获取绘图状态(光标,按下等)
android:soundEffectsEnabled 点击或触摸是否有声音效果
android:hapticFeedbackEnabled 设置触感反馈

1.11.2 第二类:属性值必须为id的引用名“@id/id-name”

属性 描述
android:layout_alignBaseline 本元素的文本与父元素文本对齐
android:layout_below 在某元素的下方
android:layout_above 在某元素的的上方
android:layout_toLeftOf 在某元素的左边
android:layout_toRightOf 在某元素的右边
android:layout_toStartOf 本元素从某个元素开始
android:layout_toEndOf 本元素在某个元素结束
android:layout_alignTop 本元素的上边缘和某元素的的上边缘对齐
android:layout_alignLeft 本元素的左边缘和某元素的的左边缘对齐
android:layout_alignBottom 本元素的下边缘和某元素的的下边缘对齐
android:layout_alignRight 本元素的右边缘和某元素的的右边缘对齐
android:layout_alignStart 本元素与开始的父元素对齐
android:layout_alignEnd 本元素与结束的父元素对齐
android:ignoreGravity 指定元素不受重力的影响
android:layoutAnimation 定义布局显示时候的动画
android:id 为布局添加ID方便查找
android:tag 为布局添加tag方便查找与类似
android:scrollbarThumbHorizontal 设置水平滚动条的drawable
android:scrollbarThumbVertical 设置垂直滚动条的drawable
android:scrollbarTrackHorizontal 设置水平滚动条背景(轨迹)的色drawable
android:scrollbarTrackVertical 设置垂直滚动条背景(轨迹)的色drawable
android:scrollbarAlwaysDrawHorizontalTrack 设置水平滚动条是否含有轨道
android:scrollbarAlwaysDrawVerticalTrack 设置垂直滚动条是否含有轨道
android:nextFocusLeft 设置左边指定视图获得下一个焦点
android:nextFocusRight 设置右边指定视图获得下一个焦点
android:nextFocusUp 设置上边指定视图获得下一个焦点
android:nextFocusDown 设置下边指定视图获得下一个焦点
android:nextFocusForward 设置指定视图获得下一个焦点
android:contentDescription 说明
android:OnClick 点击时从上下文中调用指定的方法

1.11.3 第三类:属性值为具体的像素值,如30dip,40px,50dp

属性 描述
android:layout_width 定义本元素的宽度
android:layout_height 定义本元素的高度
android:layout_margin 本元素离上下左右间的距离
android:layout_marginBottom 离某元素底边缘的距离
android:layout_marginLeft 离某元素左边缘的距离
android:layout_marginRight 离某元素右边缘的距离
android:layout_marginTop 离某元素上边缘的距离
android:layout_marginStart 本元素里开始的位置的距离
android:layout_marginEnd 本元素里结束位置的距离
android:scrollX 水平初始滚动偏移
android:scrollY 垂直初始滚动偏移
android:background 本元素的背景
android:padding 指定布局与子布局的间距
android:paddingLeft 指定布局左边与子布局的间距
android:paddingTop 指定布局上边与子布局的间距
android:paddingRight 指定布局右边与子布局的间距
android:paddingBottom 指定布局下边与子布局的间距
android:paddingStart 指定布局左边与子布局的间距与android:paddingLeft相同
android:paddingEnd 指定布局右边与子布局的间距与android:paddingRight相同
android:fadingEdgeLength 设置边框渐变的长度
android:minHeight 最小高度
android:minWidth 最小宽度
android:translation X水平方向的移动距离
android:translation Y垂直方向的移动距离
android:transformPivot X相对于一点的水平方向偏转量
android:transformPivot Y相对于一点的垂直方向偏转量

1.11.4 第四类:属性值为Android内置值

属性 描述
android:gravity 控件布局方式
android:layout_gravity 布局方式
android:persistentDrawingCachehua 定义绘图的高速缓存的持久性
android:descendantFocusability 控制子布局焦点获取方式 常用于listView的item中包含多个控件点击无效
android:scrollbars 设置滚动条的状态
android:scrollbarStyle 设置滚动条的样式
android:fitsSystemWindows 设置布局调整时是否考虑系统窗口(如状态栏)
android:scrollbarFadeDuration 设置滚动条淡入淡出时间
android:scrollbarDefaultDelayBeforeFade 设置滚动条N毫秒后开始淡化,以毫秒为单位
android:scrollbarSize 设置滚动调大小
android:fadingEdge 设置拉滚动条时,边框渐变的放向
android:drawingCacheQuality 设置绘图时半透明质量
android:OverScrollMode 滑动到边界时样式
android:alpha 设置透明度
android:rotation 旋转度数
android:rotation X水平旋转度数
android:rotation Y垂直旋转度数
android:scale X设置X轴缩放
android:scale Y设置Y轴缩放
android:verticalScrollbarPosition 摄者垂直滚动条的位置
android:layerType 设定支持
android:layoutDirection 定义布局图纸的方向
android:textDirection 定义文字方向
android:textAlignment 文字对齐方式
android:importantForAccessibility 设置可达性的重要行
android:labelFor 添加标签

0x02 Xposed环境搭建

下载Xposed APK:

https://dl-xda.xposed.info/modules/de.robv.android.xposed.installer_v33_36570c.apk

在这里插入图片描述在这里插入图片描述

打开刚刚安装好的Xposed

在这里插入图片描述在这里插入图片描述

这里为了方便选择永久记住

在这里插入图片描述
在这里插入图片描述

安装Xposed可能会出现如下情况:

在这里插入图片描述

安装完毕后可能会出现未激活, 拉入JustTrustMe.apk, 然后重启模拟器就可以了
然后修改wifi的ip和端口

在这里插入图片描述

  • 下载xposed-x86_64.zip

下载xposed作者模拟器是x86_64的

https://github.com/youling257/XposedTools/files/1931996/xposed-x86_64.zip

如果模拟器是x86下载下面这个

https://dl-xda.xposed.info/framework/

  • 下载script.sh

找到安卓对应版本,作何雷电模拟器是7.1,搜索对应为安卓7.1 sdk

x86_64的下载这个:

https://forum.xda-developers.com/attachment.php?attachmentid=4489568&d=1525092710

x86的下载这个,script.txt:

https://forum.xda-developers.com/attachments/script-txt.4489568/,改名为script.sh

创建文件夹xposed,然后解压一下xposed压缩包,最后把xposed压缩包中的system文件夹和script.sh放入刚刚创建的xposed文件夹,最后的最后执行如下命令:

adb.exe remount
adb.exe push D:\test\xposed  /system
adb.exe shell
cd /system
mount -o remount -w /system
chmod 777 script.sh
sh script.sh

在这里插入图片描述

0x03 Xposed原理分析

Xposed框架的原理是修改系统文件,替换了/system/bin/app_process可执行文件,在启动Zygote时加载额外的jar文件(/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar),并执行一些初始化操作(执行XposedBridge的main方法)。然后开发人员就可以在这个Zygote上下文中进行某些Hook操作。

在Android中,zygote是整个系统创建新进程的核心进程。zygote进程在内部会先启动Dalvik虚拟机,继而加载一些必要的系统资源和系统类,最后进入一种监听状态。

在之后的运作中,当其他系统模块(比如AMS)希望创建新进程时,只需向zygote进程发出请求,zygote进程监听到该请求后,会相应地fork出新的进程,于是这个新进程在初生之时,就先天具有了自己的Dalvik虚拟机以及系统资源。

zygote进程是由init进程启动起来,由init.rc 脚本中关于zygote的描述可知:zygote对应的可执行文件就是/system/bin/app_process,也就是说系统启动时会执行到这个可执行文件的main()函数里

Xposed 提供了几个接口类供xposed模块继承,不同的接口类对应不同的hook时机 IXposedHookZygoteInit zygote 初始化前就执行挂钩,即loadModule执行时就挂钩了 IXposedHookLoadPackage apk包加载的时候执行挂钩,先将挂钩函数保存起来,等加载apk函数执行后触发callback (这里的callback是xposed框架自己挂钩的函数),再执行模块注册的挂钩函数 IXposedHookInitPackageResources apk资源实例化时执行挂钩,同上

0x03 编写Xposed 模块

从本质上来讲,Xposed 模块也是一个 Android 程序。但与普通程序不同的是,想要让写出的Android程序成为一个Xposed 模块,要额外多完成以下四个硬性任务:

  • 让手机上的xposed框架知道我们安装的这个程序是个xposed模块
  • 模块里要包含有xposed的API的jar包,以实现下一步的hook操作
  • 这个模块里面要有对目标程序进行hook操作的方法
  • 要让手机上的xposed框架知道,我们编写的xposed模块中,哪一个方法是实现hook操作的

这就引出如下的四大件(与前四步一一对照):

  • AndroidManifest.xml
  • XposedBridgeApi-xx.jar 与 build.gradle
  • 实现hook操作的具体代码
  • xposed_Init

PS:牢记以上四大件,按照顺序一个一个实现,就能完成Xposed模块编写

3.1 创建一个Android项目

官网下载地址:https://developer.android.com/studio?hl=zh-cn

傻瓜式一键点点安装,安装过程忽略不写,不懂的可百度自行搜索

首先打开AndroidStudio(以版本3.1为例),建立一个工程,提示我们选择“Activity”,那就选一个Empty Activity吧。(这个是模块的界面,随意选择即可)

在这里插入图片描述

新建完成后下载依赖时,可能会出现如下报错(是没有科学上网导致的,要么你科学上网,要么你把gradle版本下载到本地来安装

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

改为如下地址:

distributionUrl=file:///C:/Users/xxxxx/.gradle/wrapper/dists/gradle-7.2-bin.zip

然后又有可能发现gradle插件下载又出现问题,解决办法如下:

在这里插入图片描述

如上图,注释掉google(),新增如下maven:

        //google()
        maven {
    
     url 'https://maven.aliyun.com/repository/google'}
        maven {
    
     url 'https://maven.aliyun.com/repository/jcenter'}
        maven {
    
     url 'https://maven.aliyun.com/repository/public'}

以及如下位置:

在这里插入图片描述

最后关闭Android Studio,再重新打开项目会自动加载,此时如果没有再有红色警示就代表OK了,然后直接编译一个Demo的APK(app/build/outputs/apk/debug目录下,可以看到app-debug.apk),看看是否能正常运行,如下图:

在这里插入图片描述

放到模拟器中,发现能正常安装并运行,如下图:

在这里插入图片描述

3.2 配置AndroidManifest.xml

为了让Xposed 识别出这是个Xposed模块,需要添加如下内容:

        <!--告诉xposed框架这是一个xposed模块 -->
        <meta-data
            android:name="xposedmodule"
            android:value="true"/>

        <!--关于xposed模块的描述 -->
        <meta-data
            android:name="xposeddescription"
            android:value="XposeHook例程"/>

        <!--xposed模块支持的最低版本(以为54为例) -->
        <meta-data
            android:name="xposedminversion"
            android:value="54"/>

在这里插入图片描述

修改文本内容为:橙留香的HooK

在这里插入图片描述
在这里插入图片描述

此时把编译好的APK丢进去,打开后,如下

在这里插入图片描述

PS:从上图已看出,Xposed框架已经认出了刚刚写的程序,但是现在这个模块什么都没有做,因为我们还没有做出修改

3.3 配置XposedBridgeApi-xx.jarbuild.gradle

Xposed模块主要功能是用来Hook其他程序的各种函数。

  • 接下来,如何让刚刚创建的那个Xposed“一穷二白”的模块增加一些其它的功能呢?

引入 XposedBridgeApi.jar包,可理解该包是一把兵器,Xposed模块有了这把绝世神器才能施展出Hook武功。

3.x以前,都需要手动下载诸如XposedBridgeApi的jar包,然后手工导入到libs目录里

  • XposedBridgeApi-54下载

https://forum.xda-developers.com/attachment.php?s=5903ce1b3edb1032faba7292b21e1801&attachmentid=2748878&d=1400342298

PS:除了XposedBridgeApi-54,还有XposedBridgeApi-82.jar、XposedBridgeApi-87.jar、XposedBridgeApi-89.jar等版本

官网下载:https://bintray.com/rovo89/de.robv.android.xposed/api

在AndroidStudio 3.1里面,只需要一行代码,AndroidStuido就会自动配置XposedBridgeApi.jar

  • 方法1:
    Android Studio的依赖:

Xposed框架需要用到第三方库,在 app --> build-gradle 添加依赖(最后一行)

repositories {
    
    
    // 告诉AndroidStuido使用jcenter作为代码仓库,
    // 从这个仓库里远程寻找 
    // de.robv.android.xposed:api:82 这个API
    // 但最新版的3.x版本已不推荐使用
    jcenter();
}

dependencies {
    
    
    provided 'de.robv.android.xposed:api:82'
}
注:
   此处要用compileOnly这个修饰符,
   网上有些写的是provide,
   已经停用了
    compileOnly 'de.robv.android.xposed:api:82'
    compileOnly 'de.robv.android.xposed:api:82:sources'

修改完成后,build.gradle会提示文件已经修改,是否同步。点击 “sync now”,同步即可,如下:

在这里插入图片描述

3.4 实现hook操作修改

在MainActivity的同级路径下新建一个类“HO22K.java”,操作如下图:

在这里插入图片描述
在这里插入图片描述

代码如下:

package com.example.ho22k;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class HO22K implements IXposedHookLoadPackage
{
    
    
    /**
     * xpose插件入口点
     * @param lpparam
     * @throws Throwable
     */
    @Override
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
    
    
        // 获取加载的apk程序包名
        XposedBridge.log("当前启动的应用程序是: " + loadPackageParam.packageName);
        XposedBridge.log("Hook成功咯,宝们~_~!");
    }
}

在这里插入图片描述

3.5 添加入口点xposed_Init

右键点击 “main” 文件夹 , 选择new --> Folder -->Assets Folder,新建assets 文件夹:

在这里插入图片描述

然后右键点击 assets文件夹, new–> file,文件名为xposed_init(文件类型选text),并在其中写上入口类的完整路径(就是自己编写的那一个Hook类),这样, Xposed框架就能够从这个 xposed_init 读取信息来找到模块的入口,然后进行Hook操作了

在这里插入图片描述
在这里插入图片描述

xposed_init里写当前类的路径 如果存在多个类,那么每行写一个,多个写多行

在这里插入图片描述

编译APK,在如下路径:

在这里插入图片描述

PS:编译的时候需要关闭Android Studio的instant Run功能(不太清楚为什么要关闭,我没有关闭一样可以正常使用),注意注意Android Studio 3.5往后的版本,Instant Run被HotSwap代替,如下图

在这里插入图片描述

安装apk,在Xposed模块中勾选插件,重启,此时插件已经可正常使用

在这里插入图片描述

观察日志,可通过Xposed框架内部日志模块或LogCat 查看

在这里插入图片描述

PS:Windows CMD查看日志可能是会有乱码,解决方法如下:

执行如下命令后,代码页就被变成UTF-8
chcp 65001 

在这里插入图片描述

然后修改窗口属性,改变字体在命令行标题栏上点击右键,选择"属性"->“字体”,将字体修改为True Type字体"Lucida Console",然后点击确定将属性应用到当前窗口

adb.exe shell

logcat

在这里插入图片描述

  • 方法2:下载jar文件,存放至libs目录,其它细节自行百度了解

不是网上说的单独建立lib文件夹,那是很久以前的方法,然后右键“Add As Library” 自行添加这个jar包。而compileOnly 'de.robv.android.xposed:api:82'compileOnly 'de.robv.android.xposed:api:82:sources'仍然照常添加

0x04 其它一些案例

4.1 按钮劫持Hook

实现一个APK,基础功能就是点击界面的按钮,就会弹出消息你未被劫持的消息,具体完整代码如下(test APK代码):

  • MainActivity:

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;


public class MainActivity extends AppCompatActivity {
    private Button button;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button = (Button) findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, toastMessage(), Toast.LENGTH_SHORT).show();
            }
        });
    }

    public String toastMessage() {
        return "想啥呢?同学,我未被劫持";
    }
}
  • activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="按一下按钮,确认是否被劫持"
        tools:layout_editor_absoluteX="78dp"
        tools:layout_editor_absoluteY="364dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

在这里插入图片描述

然后在HO22K 项目里面创建Xposed插件,这里博主为了偷懒,直接在一个项目里面写了,所以不加载Xposed,只要APK执行就会调用HO22K类,达到类似HO22K效果(不建议跟博主一样,自己重新创建一个APK和插件APK,不要两个都写在一起),代码和效果如下:

package com.example.ho22k;


import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class HO22K implements IXposedHookLoadPackage
{
    
    

    //Module继承了IXposedHookLoadPackage接口,当系统加载应用包的时候回回调 handleLoadPackage;
    public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
    
    
        //过滤包名,定位要Hook的包名
        if (loadPackageParam.packageName.equals("com.example.ho22k")) {
    
    

            //定位要Hook的具体的类名
            Class clazz = loadPackageParam.classLoader.loadClass("com.example.ho22k.MainActivity");
            //Hook的方法为toastMessage,XposedHelpers的静态方法 findAndHookMethod就是hook函数的的方法,其参数对应为   类名+loadPackageParam.classLoader(照写)+方法名+参数类型(根据所hook方法的参数的类型,即有多少个写多少个,加上.class)+XC_MethodHook回调接口;
            XposedHelpers.findAndHookMethod(clazz, "toastMessage", new XC_MethodHook() {
    
    
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
    
    
                    super.beforeHookedMethod(param);
                }

                protected void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {
    
    
                    //param.setResult("你已被劫持")将返回的结果设置成了你已被劫持
                    param.setResult("哦吼,同学你已被劫持");
                }
            });
        }
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.2 登陆劫持

登陆劫持密码这样的操作,首先写一个简单的登陆程序,完整代码如下:

  • MainActivity:
package com.example.ho22k;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    
    
    EditText Name;   //定义Plain Test控件第一个输入框的名字
    EditText Pass;   //定义Plain Test控件第二个输入框的名字

    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Name = (EditText) findViewById(R.id.TEXT_NAME); //通过findViewById找到输入框控件对应的id并给它起一个名字
        Pass = (EditText) findViewById(R.id.TEXT_PASS);//通过findViewById找到输入框控件对应的id并给它起一个名字
        Button Login = (Button) findViewById(R.id.BTN_Login);//通过findViewById找到按钮控件对应的id并给它起一个名字
        Login.setOnClickListener(new View.OnClickListener() {
    
      //监听有没有点击按钮控件 如果点击了就会执行onClick函数
            @Override
            public void onClick(View view) {
    
    
                check(Name.getText().toString().trim(),Pass.getText().toString().trim()); //调用check函数
            }
        });
    }
    public void check(String name,String pass)   //自定义函数check 这里用来检查用户名和密码是否是cck和1234
    {
    
    
        if(name.equals("Orangey")&&pass.equals("123456"))
        {
    
    
            Toast.makeText(MainActivity.this,"登录成功", Toast.LENGTH_SHORT).show();//弹框
        }
        else
            Toast.makeText(MainActivity.this,"登录失败", Toast.LENGTH_SHORT).show();//弹框
    }
}
  • activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="23dp"
        android:text="账号:"
        app:layout_constraintBaseline_toBaselineOf="@+id/TEXT_NAME"
        app:layout_constraintEnd_toStartOf="@+id/TEXT_NAME"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintStart_toStartOf="parent"
        tools:ignore="MissingConstraints" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="23dp"
        android:text="密码:"
        app:layout_constraintBaseline_toBaselineOf="@+id/TEXT_PASS"
        app:layout_constraintEnd_toStartOf="@+id/TEXT_PASS"
        app:layout_constraintHorizontal_chainStyle="packed"
        app:layout_constraintStart_toStartOf="parent"
        tools:ignore="MissingConstraints" />

    <EditText
        android:id="@+id/TEXT_NAME"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/textView"
        android:layout_marginTop="180dp"
        android:layout_marginEnd="1dp"
        android:layout_toEndOf="@+id/textView"
        android:layout_toRightOf="@+id/textView"
        android:ems="10"
        android:inputType="textPersonName"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/textView"
        app:layout_constraintTop_toTopOf="parent"
        tools:ignore="MissingConstraints" />

    <EditText
        android:id="@+id/TEXT_PASS"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/textView2"
        android:layout_marginTop="35dp"
        android:layout_marginEnd="1dp"
        android:layout_toEndOf="@+id/textView2"
        android:layout_toRightOf="@+id/textView2"
        android:ems="10"
        android:inputType="textPassword"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/textView2"
        app:layout_constraintTop_toBottomOf="@+id/TEXT_NAME"
        tools:ignore="MissingConstraints" />

    <Button
        android:id="@+id/BTN_Login"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="42dp"
        android:text="登录"
        app:layout_constraintEnd_toEndOf="@+id/TEXT_PASS"
        app:layout_constraintStart_toStartOf="@+id/TEXT_PASS"
        app:layout_constraintTop_toBottomOf="@+id/TEXT_PASS" />


</androidx.constraintlayout.widget.ConstraintLayout>

账号:Orangey 密码:123456

正确的账号密码登录如下图:

在这里插入图片描述

账号:a 密码:123456
错误的账号或密码登录如下图:

在这里插入图片描述

PS:如果出现界面布局混乱,只需要设置一下约束即可,如下图:

在这里插入图片描述

目的:不管输入什么都会显示登陆成功,Hook对应的方法,并对相应的参数进行修改,还是使用上面的回调方法来实现

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;

public class HO22K implements IXposedHookLoadPackage {
    
    

    /**
     * 包加载时候的回调
     */
    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
    
    

        // 将包名不是 com.example.ho22k 的应用剔除掉,可以减少管理的类
        if (!lpparam.packageName.equals("com.example.ho22k"))
            return;
        XposedBridge.log("当前APP应用程序是: " + lpparam.packageName);

        //第一个参数是className,表示被注入的方法所在的类
        //第二个参数是类加载器,照抄就行
        //第三个参数是被注入的方法名
        //第四五个参数是第三个参数的两个形参的类型
        //最后一个参数是匿名内部类
        findAndHookMethod("com.example.ho22k.MainActivity", lpparam.classLoader, "check", String.class,
                String.class, new XC_MethodHook() {
    
    

                    /**
                     * 该方法在check方法调用之前被调用,输出一些日志,并且捕获参数的值。
                     * 最后两行的目的是改变参数的值。也就是说无论参数是什么值,都会被替换为name为Orangey,pass为123456
                     * @param param
                     * @throws Throwable
                     */
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
    
    
                        XposedBridge.log("同学,你正在被人开始劫持");
                        XposedBridge.log("参数1 = " + param.args[0]);
                        XposedBridge.log("参数2 = " + param.args[1]);
                        param.args[0] = "Orangey";
                        param.args[1] = "123456";
                    }
                    /**
                     * 该方法在check方法调用之后被调用
                     * @param param
                     * @throws Throwable
                     */

                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
    
    
                        XposedBridge.log("哦吼,同学劫持已结束");
                        XposedBridge.log("参数1 = " + param.args[0]);
                        XposedBridge.log("参数2 = " + param.args[1]);

                    }
                });
    }

}

通过对方法的参数进行了重赋值,效果如下图:

在这里插入图片描述

日志里面看看

Github上的一些Xposed案例APK地址:

  • 原始程序:https://github.com/Gordon0918/XposedHookTarget
  • hook修改源程序地址:https://github.com/Gordon0918/XposedHook

参考链接

https://blog.csdn.net/gdutxiaoxu/article/details/81459830

https://eastmoon.blog.csdn.net/article/details/103810710

https://blog.csdn.net/SouthWind0/article/details/100669530

https://blog.csdn.net/JBlock/article/details/84202240

https://www.cnblogs.com/mukekeheart/p/5662842.html

https://blog.csdn.net/song_lee/article/details/103299353


你以为你有很多路可以选择,其实你只有一条路可以走


猜你喜欢

转载自blog.csdn.net/Ananas_Orangey/article/details/126219878