当前位置:网站首页>[免费专栏] Android安全之Xposed插件开发【从零手把手带】教程

[免费专栏] Android安全之Xposed插件开发【从零手把手带】教程

2022-08-09 17:59:00 菠萝_橙留香


欢迎新同学的光临
… …
人若无名,便可专心练剑


我不是一条咸鱼,而是一条死鱼啊!


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方法主要用于加载应用程序包时执行用户的操作。

findAndHookMethodhook一个类中的方法
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:translationX水平方向的移动距离
android:translationY垂直方向的移动距离
android:transformPivotX相对于一点的水平方向偏转量
android:transformPivotY相对于一点的垂直方向偏转量

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:rotationX水平旋转度数
android:rotationY垂直旋转度数
android:scaleX设置X轴缩放
android:scaleY设置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


我自横刀向天笑,去留肝胆两昆仑


原网站

版权声明
本文为[菠萝_橙留香]所创,转载请带上原文链接,感谢
https://orangey.blog.csdn.net/article/details/126219878