也许每个人出生的时候都以为这世界都是为他一个人而存在的,当他发现自己错的时候,他便开始长大
少走了弯路,也就错过了风景,无论如何,感谢经历
转移发布平台通知:将不再在CSDN博客发布新文章,敬请移步知识星球
感谢大家一直以来对我CSDN博客的关注和支持,但是我决定不再在这里发布新文章了。为了给大家提供更好的服务和更深入的交流,我开设了一个知识星球,内部将会提供更深入、更实用的技术文章,这些文章将更有价值,并且能够帮助你更好地解决实际问题。期待你加入我的知识星球,让我们一起成长和进步
Android安全付费专栏长期更新,本篇最新内容请前往:
- [车联网安全自学篇] Android安全之Android Xposed插件开发,小白都能看得懂的教程
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_width
,android: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.jar与build.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://www.toymoban.com/news/detail-491374.html
你以为你有很多路可以选择,其实你只有一条路可以走文章来源地址https://www.toymoban.com/news/detail-491374.html
到了这里,关于[免费专栏] Android安全之Android Xposed插件开发,小白都能看得懂的教程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!