教我兄弟学Android逆向12 编写xposed模块
上一篇 《教我兄弟学Android逆向11 动态调试init_array》我带你 用IDA动态调试了init_array段和JNI_OnLoad里面的方法,虽然你学的很吃力,但是经过自己不断上网查阅资料,花了几天的时间终于把不懂的地方弄清楚了。你从网上面加了好几个Android逆向学习交流群,看到群里面有人说xposed相关的内容,你听的云里雾里的,想到之前面试的时候面试官也问过xpose相关的问题,你对xpose更是产生了浓厚的好奇心。
那么xpose到底是什么东西呢?在开始本节课之前我们先了解一下它。
原理:
Xpose是一款特殊的安卓应用,诞生于著名的XDA论坛,它的原理是替换安卓系统/system/bin目录下的app_process来控制zygote进程,使得app_pross在启动时会加载XposedBridge.jar,从而实现对zygode进程以及其创建的虚拟机的劫持,最终对系统的某些功能实现接管。
优点:
xpose可以在我们不破坏apk自身的情况下实现对函数的hook,修改函数的参数和返回值,改变函数的结构并执行我们自己的代码,用好了xposed可以对我们的逆向过程起到事半功倍的作用。
缺点:
本身不能对so中的函数进行修改 需要结合其他框架。
看完上面的介绍我想你应该对xpose有个基本的了解了 那么本节课我们一起来揭开xpose的神秘面纱。
要么学!要么不学!学和不学之间没有中间值 不学就放弃,学就要去认真的学! --致选择
博客同步更新地址
https://blog.csdn.net/ASSYIRAN/article/details/86139496
编译环境:
AndroidStudio3.0.1版本
测试手机:
Nexus 4
所需框架和jar包:
1.XposedBridge的jar包
[color=rgba(0, 0, 0, 0.75)]主要功能是提供给Xpose的模块开发者所需的api 我们接下来开发xpose模块需要这个jar包
2.xposedInstaller框架
Xpose安装到手机的框架,用来加载我们编写出来的模块
下载链接:
https://pan.baidu.com/s/1fEShR4h1XK7yJIhOKtiYiw
提取码:vrxg
补充:
上面下载链接所给的xpose框架和模块都是事先编译好的,我们平常做项目时直接拿来用就可以。
xpose是一个开源项目,开源地址:
https://github.com/rovo89
一 .搭建hook环境用来编译框架所需要的模块
1.打开AS在项目app目录下新建lib目录并将XposedBridgeApi-54.jar放到lib目录下 右键Add As Library将jar包添加进依赖。
2.打开项目分支src/main目录下的AndroidManifest.xml 在application标签里面添加内容如下图所示:
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="Easy example" />
<meta-data
android:name="xposedminversion"
android:value="54" />
<ignore_js_op>
3.打开app目录下的build.gradle将
compile files('lib/XposedBridgeApi-54.jar')
更改为
provided files('lib/XposedBridgeApi-54.jar')
<ignore_js_op>
4.新建Hook入口类HookMain实现xposed的接口IXposedHookLoadPackage并重写方法handleLoadPackage 如图所示 这个写法格式是固定的。
1
2
3
4
|
public class HookMain implements IXposedHookLoadPackage { public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable { } } |
<ignore_js_op>
5.在src/main/assets下新建文件xposed_init并将HookMain类并将hook的主入口类以包名+类名的格式写进去。
com.example.xposed_test.HookMain
<ignore_js_op>
二.编写框架所需模块 Hook系统的imei检测hook环境是否配置成功
1.打开项目的MainActivity类并写一个getIMEI方法获取当前系统的imei并在程序运行的时候打印出来。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.layout); Log.i( "手机的imei是" ,getIMEI( this )); } public static final String getIMEI(Context context) { try { //实例化TelephonyManager对象 TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); //获取IMEI号 String imei = telephonyManager.getDeviceId(); //在次做个验证,也不是什么时候都能获取到的啊 if (imei == null ) { imei = "" ; } return imei; } catch (Exception e) { e.printStackTrace(); return "" ; } } |
<ignore_js_op>
2.运行程序得到当前手机的imei 355136055053345 说明hook环境配置成功。
<ignore_js_op>
3.打开HookMain编写Hook imei的代码
Hook一个函数需要满足三个条件:
(1)方法的包名+类名
(2)方法名
(3)方法的参数类型
对初学者这里可能会有个疑问
(1)如何找到方法的包名+类名?
(2)如何找到方法参数类型
针对上面的两个问题这里有个小技巧,对于系统函数你要hook哪个方法就先找到这个方法的定义 这里我们要hook的方法是getDeviceId 所以我们要找到它的定义位置
<ignore_js_op>
<ignore_js_op>
<ignore_js_op>
4.方法的包名+类名和参数类型都知道了 下面就可以按照固定格式编写hook代码了。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public class HookMain implements IXposedHookLoadPackage { public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable { //固定格式 findAndHookMethod( "android.telephony.TelephonyManager" , //要hook的包名+类名 lpparam.classLoader, //classLoader固定 "getDeviceId" , //要hook的方法名 //方法参数 没有就不填 new XC_MethodHook() { @Override //方法执行前执行 protected void beforeHookedMethod(MethodHookParam param) throws Throwable { } //方法执行后执行,改方法的返回值一定要在方法执行完毕后更改 protected void afterHookedMethod(MethodHookParam param) throws Throwable { param.setResult( "355888888888888" ); } } ); } } |
<ignore_js_op>
5.将AndroidStudio编译出来的hook程序安装到手机 并将xposedInstaller框架安装到手机 打开xposed应用 在我们编写的模块后面打钩后点安装更新。
<ignore_js_op>
<ignore_js_op>
6.重启后打开模块运行查看log看到imei已经变成355888888888888说明hook成功。如果有没hook成功的同学可能环境没搭建好,重新来一次。
<ignore_js_op>
三.实战
安装并打开附件中的解锁程序apk发现程序需要输入密码才能解锁,接下来我将带你一起完成以下两个任务:
任务一
获得解锁码
任务二
解锁程序开启宝箱
要求:只能通过hook去开启宝箱,修改本地文件等视为无效。
任务一
1.用jeb打开解锁程序apk发现程序被简单混淆,我们找到MainActivity查看反编译代码
<ignore_js_op>
<ignore_js_op>
<ignore_js_op>
<ignore_js_op>
2.经过大致的分析我们知道解锁码为当前手机的androidid经过md5加密后与固定字符串hfdcxy1011进行拼接后再进行一次md5加密得到的值截取前6位。
这里我们有三种方式可以把这个解锁码打印出来
(1)我们知道第一个a方法是最后一层的加密 我们可以hook这个a方法把它的返回值打印出来 然后取其前6位为解锁码
(2)因为整个apk只有一处对substring的调用 我们可以hook系统函数substring把函数返回值打印出来
(3)通过分析知道第二个a方法为log打印的方法 我们可以Hook这个a方法的参数 把解锁码通过log打印出来
这里我给大家演示第一种hook方法
前面我们hook imei的时候已经说过了如果要hook一个方法要满足以下三个条件:
(1)方法的包名+类名
我们找打它为com.hfdcxy.android.by.test.a
(2)方法名
这里不用多提了吧,你要hook哪个方法,方法名就是哪个这里为a
(3)方法的参数类型
String
<ignore_js_op>
<ignore_js_op>
3.下面开始编写Hook代码
我们把Hook imei的那份代码复制粘贴后放到下面,按照格式修改成hook a方法的代码。还要过滤下包名防止xposed找不到包名对应的类报错 这里的包名是Manifest下的包名。
com.ss.android.ugc.aweme
<ignore_js_op>
<ignore_js_op>
HookMain类
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
public class HookMain implements IXposedHookLoadPackage { public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable { //固定格式 因为是系统函数所以无需过滤包名 findAndHookMethod( "android.telephony.TelephonyManager" , //要hook的包名+类名 lpparam.classLoader, //classLoader固定 "getDeviceId" , //要hook的方法名 //方法参数 没有就不填 new XC_MethodHook() { @Override //方法执行前执行 protected void beforeHookedMethod(MethodHookParam param) throws Throwable { } //方法执行后执行,改方法的返回值一定要在方法执行完毕后更改 protected void afterHookedMethod(MethodHookParam param) throws Throwable { param.setResult( "355888888888888" ); } } ); if (!lpparam.packageName.equals( "com.ss.android.ugc.aweme" )) //这里过滤一下包名 { return ; } Log.i( "Tiger_test" , "hook进入解锁程序" ); //Hook a方法 findAndHookMethod( "com.hfdcxy.android.by.test.a" , //要hook的包名+类名 lpparam.classLoader, //classLoader固定 "a" , //要hook的方法名 String. class , //方法的参数类型 这里为String类 new XC_MethodHook() { @Override //方法执行前执行 protected void beforeHookedMethod(MethodHookParam param) throws Throwable { } //方法执行后执行,改方法的返回值一定要在方法执行完毕后更改 protected void afterHookedMethod(MethodHookParam param) throws Throwable { Log.i( "Tiger_test" , "a方法的第一个参数为:" +param.args[ 0 ].toString()); //param.args[0]为方法的第一个参数,同理param.args[1]为第二个参数 Log.i( "Tiger_test" , "a方法的返回值为:" +param.getResult()); //方法的返回值只能放在afterHookedMethod中获取 //以下为修改 //param.args[0] = "235wtwerteq" //如果要修改第一个参数可以直接这样写 //param.setResult("7f769c0f91efd402a23d63627f48f03e"); //param.setResult修改方法的返回值 } } ); } } |
4.编译项目并安装到手机,xposed框架打钩模块,安装并更新重启后打开解锁程序随便输入一个解锁码点击解锁,可以看到a方法的参数和返回值已经被我们通过log打印出来了,因为a方法在程序中被调用了两次所以这里打印了两次。
我们只需看最后一次的返回值即可,取前6位可以看到我的手机的解锁码为4567d2
<ignore_js_op>
5.输入这个解锁码点击解锁 出现如图所示的界面说明解锁码正确 任务一获取解锁码也就完成了。
<ignore_js_op>
下面我们开看下任务二
1.任务二要求解锁程序开启宝箱,我们看到这个界面有两个按钮一个是充值1金币,另一个按钮是开启宝箱。测试发现这里我不断手点充值1金币把金币总量充值到100还是开启不了宝箱,提示金币不足,请充值。
<ignore_js_op>
2.我们继续分析代码,发现输入正确的解锁码后程序会跳转到DrawActivity类,这个类中有两个onClick 第一个是充值的onClick第二个是开启宝箱,分析发现只有金币大于9999时才能开启宝箱。
这里我们有两种方法去开启宝箱
(1)用手不断点击充值1金币,需要点9999次,这无疑是太耗费时间的,所以不可取。
(2)直接Hook第一个onClick里面的a方法把第三个参数改成10000即可开启宝箱。
<ignore_js_op>
<ignore_js_op>
<ignore_js_op>
3.编写hook代码
找到hook所需要的三个条件:
(1)方法的包名+类名
我们找打它为com.hfdcxy.android.by.test.b
(2)方法名
这里不用多提了吧,你要hook哪个方法,方法名就是哪个这里为a
(3)方法的参数类型 这里有三个
SharedPreferences
TextView
int
<ignore_js_op>
HookMain类
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
public class HookMain implements IXposedHookLoadPackage { public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable { //固定格式 findAndHookMethod( "android.telephony.TelephonyManager" , //要hook的包名+类名 lpparam.classLoader, //classLoader固定 "getDeviceId" , //要hook的方法名 //方法参数 没有就不填 new XC_MethodHook() { @Override //方法执行前执行 protected void beforeHookedMethod(MethodHookParam param) throws Throwable { } //方法执行后执行,改方法的返回值一定要在方法执行完毕后更改 protected void afterHookedMethod(MethodHookParam param) throws Throwable { param.setResult( "355888888888888" ); } } ); if (!lpparam.packageName.equals( "com.ss.android.ugc.aweme" )) //这里过滤一下包名 { return ; } Log.i( "Tiger_test" , "hook进入解锁程序" ); //Hook a方法 findAndHookMethod( "com.hfdcxy.android.by.test.a" , //要hook的包名+类名 lpparam.classLoader, //classLoader固定 "a" , //要hook的方法名 String. class , //方法的参数类型 这里为String类 new XC_MethodHook() { @Override //方法执行前执行,修改参数的地方 protected void beforeHookedMethod(MethodHookParam param) throws Throwable { } //方法执行后执行,修改方法的返回值的地方 protected void afterHookedMethod(MethodHookParam param) throws Throwable { Log.i( "Tiger_test" , "a方法的第一个参数为:" +param.args[ 0 ].toString()); //param.args[0]为方法的第一个参数,同理param.args[1]为第二个参数 Log.i( "Tiger_test" , "a方法的返回值为:" +param.getResult()); //方法的返回值只能放在afterHookedMethod中获取 //以下为修改 //param.args[0] = "235wtwerteq" //如果要修改第一个参数可以直接这样写 //param.setResult("7f769c0f91efd402a23d63627f48f03e"); //param.setResult修改方法的返回值 } } ); findAndHookMethod( "com.hfdcxy.android.by.test.b" , //要hook的包名+类名 lpparam.classLoader, //classLoader固定 "a" , //要hook的方法名 SharedPreferences. class , //方法的参数类型 TextView. class , //方法的参数类型 int . class , //方法的参数类型 new XC_MethodHook() { @Override //方法执行前执行,修改参数的地方 protected void beforeHookedMethod(MethodHookParam param) throws Throwable { param.args[ 2 ] = 10000 ; //注意修改参数要放在beforeHookedMethod里面写 Log.i( "Tiger_test" , "成功充值一万金币" ); } //方法执行后执行,修改方法的返回值的地方 protected void afterHookedMethod(MethodHookParam param) throws Throwable { } } ); } } |
4.编译xposed框架安装模块,打开解锁程序输入解锁码后点击充值1金币,然后点击开启宝箱。显示弹框开启成功,获得xposed新手称号。
<ignore_js_op>
<ignore_js_op>
<ignore_js_op>
四.总结
本节课我们首先了解了xpose的基本原理并一起搭建了xpose的hook环境。接下来我们又一起用xpose破解了解锁程序这个apk,通过对本节课的学习我想你对xpose已经有了一个全新的认识。环境搭建有问题的的同学可以直接把下面的课堂Demo导入到项目中去学习,课堂上面讲解的内容一定要多加练习,看不懂的要多百度,百度上面有很多关于xpose的教程,各种教程对比着来学习,总能理解的。
五.课后作业
1.hook本节课例子中的其他函数获取解锁码
2.hook修改极品美女找茬游戏中的金币余额为999
游戏下载链接
https://blog.csdn.net/ASSYIRAN/article/details/86014551
六.进阶
课堂作业完成后请参考以下两条链接,并自己编写对应模块练习里面的例子,完成对xpose的进阶。
http://www.cnblogs.com/gordon0918/p/6732100.html
https://bbs.pediy.com/thread-225190-1.htm
课堂Demo
链接:https://pan.baidu.com/s/1uTkofcZIORJeAscXIRaf6g
提取码:6vi6