文章目录
一、前言
平头哥(ratel)是⼀个Android逆向分析⼯具套件,他提供⼀系列渐进式app逆向分析⼯具。同时平头哥也是⼀个app⼆次开发的沙箱环境,⽀持在免root环境下hook和重定义app功能。
项⽬地址: https://github.com/virjarRatel
二、初次体验
1、安装ratel手机端app
主要作用:可以方便查看app是否被感染成功
链接:https://pan.baidu.com/s/1eU2cEgW4pHOB08gcqDZDNQ 提取码:h7oo
安装效果图:
2、使⽤电脑端进⾏感染目标app
下载感染引擎:
链接:https://pan.baidu.com/s/13HqPPdqVL9LPrYFOHD-UBA 提取码:n3nj
⽬录结构
.
├── ratel.bat # windows上⽤来感染的脚本
├── ratel.sh # linux/mac 上⽤来感染的脚本
├── ratelConfig.properties
└── res
├── build_timestamp.txt
├── container-builder-repkg-2.0.0-SNAPSHOT-dex.jar
├── container-builder-repkg-2.0.0-SNAPSHOT.jar
├── hermes_bksv1_key
├── hermes_key
├── monthly_temp.txt
└── ratel_version.txt
运⾏脚本进⾏感染
# liunx
./retal.sh 目标app的路径
# windows
./ratel.bat 目标app的路径
需要被感染的目标app下载
链接:https://pan.baidu.com/s/1412ZQWa4frCAeyM1tCiuKQ 提取码:c10k
运⾏完成之后, 会得到⼀个 com.example.myapplication_1.0_1_ratel.apk
这个就是感染好了的⽂件, 可以直接安装到⼿机, 确保之前的app已经被卸载了, 否则可能会安装失败, 如果是debug下并且开启了testOnly, 需要用 adb install -t xxx.apk
进⾏安装。
打开ratel查看是否感染成功
3、开发⼀个平头哥插件
新建⼀个普通的Android项⽬,用于插件的基本模板
添加相关依赖
android {
.............
packagingOptions {
exclude 'META-INF/INDEX.LIST'
exclude 'META-INF/io.netty.versions.properties'
}
}
第三方包
dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
// ratel核⼼api
compileOnly 'com.virjar:ratel-api:1.3.6'
// ratel的扩展api,他⼀般是给逆向破解和爬⾍业务使⽤,如果您只是基于ratel做⼀个插件,那么⼤多数情况
api 'com.virjar:ratel-extersion:1.0.6'
// sekiro项⽬,他可以提供⻓链接RPC功能
api 'com.virjar:sekiro-api:1.0.3'
}
在Android清单⽂件 AndroidManifest.xml 添加如下内容
<application
.........
<meta-data android:name="xposedminversion" android:value="54"/>
<meta-data android:name="xposedmodule" android:value="true"/>
<meta-data android:name="xposeddescription" android:value="这个描述可以随便写, 会展示在插件列"/>
</application>
创建assets文件夹,在assets文件夹下添加文本xposed_init , 内容为hook的⼊⼝类的完整路径, 如果有多个,分到多⾏去写。
com.example.plugintest.HookEntry
# 如果有多个插件,分到多⾏去写
com.example.plugintest.HookEntry01
com.example.plugintest.HookEntry02
编写hook类
package com.example.plugintest;
import com.virjar.ratel.api.rposed.IRposedHookLoadPackage;
import com.virjar.ratel.api.rposed.callbacks.RC_LoadPackage;
public class HookEntry implements IRposedHookLoadPackage {
@Override
public void handleLoadPackage(RC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
System.out.println("包名是什么:" + lpparam.packageName);
}
}
将编写好的插件运⾏到app, 然后开启插件
控制台打印效果图:
三、更多应用
对com.example.myapplication
app包增加java代码如下:
public void MysteryBoxTest() throws Exception {
MysteryBox mysteryBox_1 = new MysteryBox();
mysteryBox_1.open();
String content_1 = mysteryBox_1.getContent();
MysteryBox mysteryBox_2 = new MysteryBox(10);
mysteryBox_2.open();
String content_2 = mysteryBox_2.getContent();
MysteryBox mysteryBox_3 = new MysteryBox(10, "泡泡玛特");
mysteryBox_3.open();
String content_3 = mysteryBox_2.getContent();
tvContent.setText("测试中.......");
int price = mysteryBox_3.price;
Field contentField = MysteryBox.class.getDeclaredField("brand");
contentField.setAccessible(true);
Object brand = contentField.get(mysteryBox_3);
tvContent.setText(String.valueOf(price) + " " + brand);
}
打包好的案例app下载:
链接:https://pan.baidu.com/s/15_DlY8yfw8XXiMScJ65R7g 提取码:abph
1、hook构造函数
以下代码主要工作:打印了构造函数的参数长度,修改了参数内容,拿到构造函数的实例,构造函数执行后的返回值(null)
①hook 无参数的构造函数
Class<?> MysteryBoxClass = lpparam.classLoader.loadClass("com.example.myapplication.MysteryBox");
RposedHelpers.findAndHookConstructor(MysteryBoxClass, new RC_MethodHook() {
// 调用构造方法之前
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Object[] objects = param.args;
System.out.println(TAG + "参数长度:" + objects.length);
super.beforeHookedMethod(param);
}
// 调用构造方法之后
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
}
});
②hook 1个参数的构造函数
RposedHelpers.findAndHookConstructor("com.example.myapplication.MysteryBox", lpparam.classLoader, int.class, new RC_MethodHook(){
// 调用构造方法之前
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Object[] objects = param.args;
// 修改参数
param.args[0] = 11111111;
System.out.println(TAG + "参数长度:" + objects.length);
super.beforeHookedMethod(param);
}
// 调用构造方法之后
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
}
});
③hook 2个参数的构造函数
RposedHelpers.findAndHookConstructor("com.example.myapplication.MysteryBox", lpparam.classLoader, int.class, String.class, new RC_MethodHook(){
// 调用构造方法之前
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Object[] objects = param.args;
// 修改参数
System.out.println(TAG + "原始参数1:" + param.args[0]);
System.out.println(TAG + "原始参数2:" + param.args[1]);
param.args[0] = 100;
param.args[1] = "耐克";
System.out.println(TAG + "参数长度:" + objects.length);
super.beforeHookedMethod(param);
}
// 调用构造方法之后
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
// 执行结束后拿到构造函数实例
System.out.println(TAG + "构造函数实例:" + param.thisObject);
// 执行结束后拿到构造函数返回值。为空
System.out.println(TAG + "构造函数返回值:" + param.getResult());
}
});
完整代码
package com.example.plugintest;
import com.virjar.ratel.api.rposed.IRposedHookLoadPackage;
import com.virjar.ratel.api.rposed.RC_MethodHook;
import com.virjar.ratel.api.rposed.RposedHelpers;
import com.virjar.ratel.api.rposed.callbacks.RC_LoadPackage;
public class HookEntry implements IRposedHookLoadPackage {
public String TAG = "pluginTest";
@Override
public void handleLoadPackage(RC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
System.out.println(TAG + "包名是什么:" + lpparam.packageName);
if (lpparam.packageName.equals("com.example.myapplication")){
System.out.println(TAG + "确认hook的app是:" + lpparam.packageName);
// hook 无参数的构造函数
// 得到类定义
Class<?> MysteryBoxClass = lpparam.classLoader.loadClass("com.example.myapplication.MysteryBox");
RposedHelpers.findAndHookConstructor(MysteryBoxClass, new RC_MethodHook() {
// 调用构造方法之前
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Object[] objects = param.args;
System.out.println(TAG + "参数长度:" + objects.length);
super.beforeHookedMethod(param);
}
// 调用构造方法之后
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
}
});
// hook 1个参数构造函数
RposedHelpers.findAndHookConstructor("com.example.myapplication.MysteryBox", lpparam.classLoader, int.class, new RC_MethodHook(){
// 调用构造方法之前
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Object[] objects = param.args;
// 修改参数
param.args[0] = 11111111;
System.out.println(TAG + "参数长度:" + objects.length);
super.beforeHookedMethod(param);
}
// 调用构造方法之后
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
}
});
// hook 2个参数构造函数
RposedHelpers.findAndHookConstructor("com.example.myapplication.MysteryBox", lpparam.classLoader, int.class, String.class, new RC_MethodHook(){
// 调用构造方法之前
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
Object[] objects = param.args;
// 修改参数
System.out.println(TAG + "原始参数1:" + param.args[0]);
System.out.println(TAG + "原始参数2:" + param.args[1]);
param.args[0] = 100;
param.args[1] = "耐克";
System.out.println(TAG + "参数长度:" + objects.length);
super.beforeHookedMethod(param);
}
// 调用构造方法之后
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
// 执行结束后拿到构造函数实例
System.out.println(TAG + "构造函数实例:" + param.thisObject);
// 执行结束后拿到构造函数返回值。为空
System.out.println(TAG + "构造函数返回值:" + param.getResult());
}
});
}
}
}
④打印效果:
app界面hook修改传参前
app界面hook修改传参后
2、hook属性
对于属性的hook可以用到如下方法:
属性值的获取 | 实例属性的修改 | 静态属性的修改 |
getObjectField | setObjectField | setStaticObjectField |
getBooleanField | setBooleanField | setStaticBooleanField |
getByteField | setByteField | setStaticByteField |
getCharField | setCharField | setStaticCharField |
getDoubleField | setDoubleField | setStaticDoubleField |
getFloatField | setFloatField | setStaticFloatField |
getIntField | setIntField | setStaticIntField |
getLongField | setLongField | setStaticLongField |
getShortField | setShortField | setStaticShortField |
简单的测试了几个方法如下:
package com.example.plugintest;
import com.virjar.ratel.api.rposed.IRposedHookLoadPackage;
import com.virjar.ratel.api.rposed.RC_MethodHook;
import com.virjar.ratel.api.rposed.RposedHelpers;
import com.virjar.ratel.api.rposed.callbacks.RC_LoadPackage;
public class HookFieldsEntry implements IRposedHookLoadPackage {
private static final String TAG= "pluginTest";
public void handleLoadPackage(RC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
System.out.println(TAG + "包名是什么:" + lpparam.packageName);
if (lpparam.packageName.equals("com.example.myapplication")){
System.out.println(TAG + "hook成功:" + lpparam.packageName);
RposedHelpers.findAndHookConstructor("com.example.myapplication.MysteryBox", lpparam.classLoader, new RC_MethodHook(){
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
// 拿到构造函数实例
Object box = param.thisObject;
// 拿到实例属性
Object content = RposedHelpers.getObjectField(box, "content");
System.out.println(TAG + "content hook成功:" + content);
// 拿到静态属性
Object BASE_PRICE = RposedHelpers.getStaticObjectField(box.getClass(), "BASE_PRICE");
System.out.println(TAG + "BASE_PRICE hook成功:" + BASE_PRICE);
// 修改实例属性
RposedHelpers.setIntField(box, "price", 10000);
RposedHelpers.setObjectField(box, "content", "耐克");
// 修改静态属性
RposedHelpers.setStaticIntField(box.getClass(), "BASE_PRICE", 200);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
}
});
}
}
}
3、hook⼀般函数 静态方法/实例方法
package com.example.plugintest;
import com.virjar.ratel.api.rposed.IRposedHookLoadPackage;
import com.virjar.ratel.api.rposed.RC_MethodHook;
import com.virjar.ratel.api.rposed.RposedHelpers;
import com.virjar.ratel.api.rposed.callbacks.RC_LoadPackage;
public class HookMethodsEntry implements IRposedHookLoadPackage {
private static final String TAG= "pluginTest";
public void handleLoadPackage(RC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
System.out.println(TAG + "包名是什么:" + lpparam.packageName);
if (lpparam.packageName.equals("com.example.myapplication")){
System.out.println(TAG + "hook成功:" + lpparam.packageName);
// hook 静态方法/实例方法 methodName 就是要hook的方法名
RposedHelpers.findAndHookMethod("com.example.myapplication.MysteryBox", lpparam.classLoader, "staticMethod", String.class, int.class, new RC_MethodHook(){
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
// 获取方法参数
System.out.println(TAG + "参数内容:" + param.args[0] + param.args[1]);
// 修改参数
param.args[0] = "小静被修改了";
param.args[1] = 10000;
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
// 获取方法返回值
Object res = param.getResult();
System.out.println(TAG + "方法返回值:" + res);
// 修改方法返回值
param.setResult("返回值被修改了");
}
});
// 内部类的处理 就多了一个$+内部类名称符号
RposedHelpers.findAndHookMethod("com.example.myapplication.MysteryBox$InnerClass", lpparam.classLoader, "innerClassMethod", String.class, int.class, new RC_MethodHook(){
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
// 获取方法参数
System.out.println(TAG + "参数内容:" + param.args[0] + param.args[1]);
// 修改参数
param.args[0] = "内部类修改了";
param.args[1] = 10000;
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
// 获取方法返回值
Object res = param.getResult();
System.out.println(TAG + "方法返回值:" + res);
// 修改方法返回值
param.setResult("返回值被修改了");
}
});
}
}
}