Xpose学习笔记
Xpose原理控制zygote进程,通过替换/system/bin/app_precess程序控制zygote进程,使它在系统启动过程中加载Xposed framework的一个jar文件即XposedBridge.jar,从而完成对Zygote进程及创建的Dalvik/ART虚拟机的劫持,并且能够允许开发者独立替代任何class,例如framework本身,系统UI或者随意一个app。控制程序两
Xpose原理
控制zygote进程,通过替换/system/bin/app_precess程序控制zygote进程,使它在系统启动过程中加载Xposed framework的一个jar文件即XposedBridge.jar,从而完成对Zygote进程及创建的Dalvik/ART虚拟机的劫持,并且能够允许开发者独立替代任何class,例如framework本身,系统UI或者随意一个app。
控制程序两种方式,通过attach之后ptrace调试器,通过定制rom。
Xpose安装
Android5.0以后:
1.对手机root,刷入recovery。
下载对应zip补丁包。
重启手机安装xposedinstaller并授予root权限。
http://dl-xda.xposed.info/framework/
关于zip下载:
根据系统版本去下载。比如6以后就是ARM64。
以N5,6.0为例。
首先需要解锁bootloader,刷hammerhead的rom
如果出现Writing ‘userdata’ FAILED (remote: ‘Bogus size sparse and chunk header’)则先执行:flash-base.sh
然后将image-hammerhead-mmb29s.zip解压到某个文件夹,紧接着创建一个在文件夹中创建一个flash-image.bat,里面写入:
@echo off
::正常开机模式
::adb reboot bootloader
::reboot模式
fastboot reboot-bootloader
ping -n 15 127.0.0.1 >nul
::fastboot oem unlock
fastboot erase system
fastboot erase boot
fastboot erase recovery
fastboot erase cache
fastboot erase userdata
fastboot flash system system.img
fastboot flash boot boot.img
fastboot flash recovery recovery.img
fastboot flash cache cache.img
fastboot flash userdata userdata.img
pause
fastboot reboot
执行此bat,则刷机成功。
然后刷recovery,比如
C:\Users\ThinkPad>fastboot flash recovery C:\Users\ThinkPad\Desktop\n5_6.0\twrp-3.2.2-0-hammerhead.img
recovery 改为 boot,成功刷入twrp
进入recovery,n5这里直接手机选择进入(点击音量,到recovery再进入)。
然后拷入supersu和xposed。
再安装Xposed。
如果是高于xpose的版本,就要结合edxposed和magisk。
注意高版本9.0不要设置密码,不然twrp,recovery时要输入密码,而且输设置的也不对。
然后就是安装twrp->Magisk->EdXposed-SandHook->EdXposedManager。
Xposed插件开发之hook构造函数
Xposed插件编写
- 拷贝XposedBridgeApi.jar到新建工程的libs目录。
- 修改app目录下的build.gradle文件,在androidManifest.xml中增加Xpose的相关内容。
- 新建hook类,编写hook代码
- 新建assets文件夹,在assets目录下新建文件xposed_init,在里面写上hook类的完整路径。
2.Xposed插件编写
<meta-data android:name="xposedmodule"
android:value="true"/>
<meta-data android:name="xposeddescription"
android:value="模块描述"/>
<meta-data android:name="xposedminversion"
android:value="54"/>
新建hook类,编写hook代码
新建assets文件夹,在assets目录下新建文件xposed_init,在里面写上hook类的完整路径
res文件夹下新建assets文件夹,在assets目录下新建文件xposed_init,在里面写上hook类的完整路径,里面内容为实现了IXposedHookLoadPackage接口的hook类的完整类名(可以有多个,每一个hook实现类一行)
xposed的hook构造函数
首先是通过jadx确定包名,类名,方法名,接着使用
两个,如下,第一个需要类型,可以通过classload加载。第二个需要传入类名,ClassLoader。
然后紧接着的参数为遍参的构造函数参数,最后一个为修改构造函数的回调函数。
public static Unhook findAndHookConstructor(Class<?> clazz, Object... parameterTypesAndCallback) {
if (parameterTypesAndCallback.length != 0 && parameterTypesAndCallback[parameterTypesAndCallback.length - 1] instanceof XC_MethodHook) {
XC_MethodHook callback = (XC_MethodHook)parameterTypesAndCallback[parameterTypesAndCallback.length - 1];
Constructor<?> m = findConstructorExact(clazz, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
return XposedBridge.hookMethod(m, callback);
} else {
throw new IllegalArgumentException("no callback defined");
}
}
public static Unhook findAndHookConstructor(String className, ClassLoader classLoader, Object... parameterTypesAndCallback) {
return findAndHookConstructor(findClass(className, classLoader), parameterTypesAndCallback);
}
另外MethodHookParam参数里,获取类的返回值是this.object,不是result。
// public Object thisObject;
// public Object[] args;
// private Object result = null;
以下是hook各种参数构造函数的代码实例:
public class HookConstructors implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)
throws Throwable {
Log.i("Xposed01",loadPackageParam.packageName);
XposedBridge.log("app packagename"+loadPackageParam.packageName);
if (loadPackageParam.packageName.equals("com.yrq.xposedhook01")) {
XposedBridge.log("hookConstructors" + loadPackageParam.packageName);
ClassLoader classLoader = loadPackageParam.classLoader;
Class StudentClass=classLoader.loadClass("com.yrq.xposedhook01.Student");//
//Student()
XposedHelpers.findAndHookConstructor(StudentClass, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
XposedBridge.log("com.yrq.XposedHook01.Student() is called!Before HookConstructor");
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
XposedBridge.log("com.yrq.XposedHook01.Student() is called!after HookConstructor");
}
});
//public Student(String name2)
// public Object thisObject;
// public Object[] args;
// private Object result = null;
XposedHelpers.findAndHookConstructor(StudentClass, String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
java.lang.Object[] argobjarry=param.args;
String name=(String)argobjarry[0];
XposedBridge.log("com.yrq.XposedHook01.Student(String) is called!Before HookConstructor"+" student name is "+name);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
XposedBridge.log("com.yrq.XposedHook01.Student(String) is called!Before HookConstructor");
}
});
// public Student(String name,String id)
// {
// this.name = name;
// this.id = id;
// }
XposedHelpers.findAndHookConstructor(StudentClass, String.class, String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
java.lang.Object[] argobjarry=param.args;
String name=(String)argobjarry[0];
String id=(String)argobjarry[1];
XposedBridge.log("com.yrq.XposedHook01.Student(String,String) is called!Before HookConstructor"+" student name is "+name+" id is"+id);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
XposedBridge.log("com.yrq.XposedHook01.Student(String,String) is called!Before HookConstructor");
}
});
// public Student(String name,String id,int age)
// {
// this.name = name;
// this.id = id;
// this.age = age;
// }
/* XposedHelpers.findAndHookConstructor(StudentClass, String.class, String.class,int.class , new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
java.lang.Object[] argobjarry=param.args;
String name=(String)argobjarry[0];
String id=(String)argobjarry[1];
int age=(int)argobjarry[2];
XposedBridge.log("com.yrq.XposedHook01.Student(String,String,int) is called!Before HookConstructor"+" student name is "+name+" id is"+id+"age "+age);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
XposedBridge.log("com.yrq.XposedHook01.Student(String,String,int) is called!Before HookConstructor");
}
});
*/
XposedHelpers.findAndHookConstructor("com.yrq.xposedhook01.Student",loadPackageParam.classLoader,String.class, String.class,int.class , new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
java.lang.Object[] argobjarry=param.args;
int age=(int)argobjarry[2];
argobjarry[1]="9547";
argobjarry[0]="gecong";
String name=(String)argobjarry[0];
String id=(String)argobjarry[1];
XposedBridge.log("com.yrq.XposedHook01.Student(String,String,int) is called!Before HookConstructor"+" student name is "+name+" id is"+id+"age "+age);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
XposedBridge.log("com.yrq.XposedHook01.Student(String,String,int) is called!Before HookConstructor");
}
});
}
}
}
Xposed插件开发之修改属性
前面的方法是通过hook类的构造函数,修改变量,现在是通过类或者对象,修改类或者对象的属性。
- 使用java反射修改属性
- 使用Xposed的API修改属性
第一种生成的hook类也是运行在当前进程的用户空间的,跟app自己的dex也都是在一起的,所以可用反射。
注意点如果使用第一种使用java反射修改属性,非public时修改前不设置取消权限修改会报错,第二种使用Xposed的API修改属性则正常
teacherField.setAccessible(true);
课时4:Xposed插件开发之hook一般函数
- 一般Java函数的hook
- 内部类中函数的hook
- 匿名内部类函数的hook
- 类中JNI函数的hook
都可用findAndHookMethod。
Xposed插件开发之主动调用类函数
- 使用java反射完成对类函数的调用
- 使用Xposed的API完成对类函数的调用
使用java反射完成对类函数的调用
对于类中静态函数,直接调用即可
对于非静态函数需要先得到实例,再调用
静态函数主动调用
对于类中静态函数,关于public和private的Xposed主动调用区别是private要关闭权限检查即
.setAccessible(true);
public class HookActiveInvoke implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)
throws Throwable {
Log.i("Xposed01",loadPackageParam.packageName);
XposedBridge.log("app packagename"+loadPackageParam.packageName);
if (loadPackageParam.packageName.equals("com.yrq.xposedhook01")) {
XposedBridge.log("YRQ" + loadPackageParam.packageName);
//使用反射完成对静态函数的调用
// public static String publicstaticfunc(String arg1,int arg2){
// String result=privatestaticfunc("privatestaticfunc",99);
// Log.i("Xposed","publicstaticfunc is called");
// return arg1+"---"+arg2+"---"+result;
// }
ClassLoader classLoader=loadPackageParam.classLoader;
Class StudentCLass=classLoader.loadClass("com.yrq.xposedhook01.Student");
Method publicstaticfunc_Method=StudentCLass.getDeclaredMethod("publicstaticfunc",String.class,int.class);
publicstaticfunc_Method.invoke(null,"publicstaticfunc_Method InvokeByXposed",100);
// private static String privatestaticfunc(String arg1,int arg2){
// Log.i("Xposed","privatestaticfunc is called");
// return arg1+"---"+arg2;
// }
Method privatestaticfunc_Method=StudentCLass.getDeclaredMethod("privatestaticfunc",String.class,int.class);
privatestaticfunc_Method.setAccessible(true);
privatestaticfunc_Method.invoke(null,"privatestaticfunc_Method InvokeByXposed",101);
}
}
}
非静态函数主动调用
有2种思路,自己调用构造函数创建一个对象,调用函数(这里可以有两种操作:使用反射或者XposedAPI)。
- 另外就是hook函数,这里可以hook两种函数 可以hook构造函数,在构造函数结束时thisObject就得到对象
- 者hook对象被调用的一般函数,得到此时的对象。
自己调用构造函数创建一个对象,调用函数
使用反射自己创建对象方式
使用反射,得到对象后调用
//非静态需要得到对象然后调用
Method publicfunc_Method=StudentCLass.getDeclaredMethod("publicfunc",String.class,int.class);
//要得到对象,就通过反射,得到构造函数,调用创建实例对象
//public Student(String name,String id)
// {
// this.name = name;
// this.id = id;
// }
Constructor StuCon=StudentCLass.getDeclaredConstructor(String.class,String.class);
Object StuObj=StuCon.newInstance("InstanceByXposed","112233");
publicfunc_Method.invoke(StuObj,"publicfuncInvockedByXposed",332211);
// private String privatefunc(String arg1,int arg2){
// Log.i("Xposed","privatefunc is called!---"+arg1+"---"+arg2);
// return arg1+"---"+arg2;
// }
Method privatefunc_Method=StudentCLass.getDeclaredMethod("privatefunc",String.class,int.class);
privatefunc_Method.setAccessible(true);
String privatefunc_result= (String) publicfunc_Method.invoke(StuObj,"privatefuncInvockedByXposed",333333);
XposedBridge.log("privatefunc activeInvokeByXposed"+privatefunc_result);
使用Xposed的API操作自己创建对象方式
XposedHelpers.callStaticMethod()来调用,有两个此函数,区别为有一个有指定参数列表,实现重载的函数调用
java.lang.Class<?>[]parameterTypes={String.class,int.class};
XposedHelpers.callStaticMethod(StudentCLass,"publicstaticfunc",parameterTypes,"XposedHelpers.callStaticMethod",101);
Object objStudentByXposed=XposedHelpers.newInstance(StudentCLass,"XposedHelpers.newInstance","444444");
String result1=(String)XposedHelpers.callMethod(objStudentByXposed,"publicfunc","publicfunc is call by XposedHelpers.callMethod",123);
XposedBridge.log("publicfunc XposedHelpers.callMethod result----"+result1);
}
}
通过hook以有函数方式
这里是hook构造函数或者hook某一个类的非静态函数
假如是已经有对象了,已经实例化了。一种思路是hook构造函数,就可以通过参数拿到thisobject
XposedHelpers.findAndHookConstructor(StudentCLass, String.class, String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
XposedBridge.log("Student(String,String) is called beforeHookMethod");
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Object cstudent=param.thisObject;
String result3=(String)XposedHelpers.callMethod(cstudent,"privatefunc","cstudent privatefunc",8888);
XposedBridge.log(result3);
}
});
另一种思路是观察,如果都调用了某个非静态函数,就对这个函数Hook,就可以得到他的对象,然后调用对象的其他函数。
XposedHelpers.findAndHookMethod(StudentCLass, "getNickname", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Object obj =param.thisObject;
XposedHelpers.callMethod(obj,"publicfunc","publicfunc is callec by getNickname ",7766);
XposedBridge.log("getNickname beforeHookedMethod->"+obj);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
Object obj =param.thisObject;
XposedHelpers.callMethod(obj,"publicfunc","publicfunc is callec by getNickname ",7766);
XposedBridge.log("getNickname afterHookedMethod->"+obj);
}
});
Xposed插件开发之加壳APP的Hook处理
因为app的Application类中的attachBaseContext和onCreate这两个函数是最先获取执行权进行代码执行的,加壳APP一般是在这两个函数进行对原dex的初始化。
这里StubApp就依旧还有这两个函数。
这里就是利用反射进行各种操作,从这里hook就知道加载了哪些类。
(以下要正常工作,app中classloader不能直接继承classloader)我们可以看到PathClassLoader继承自BaseDexClassLoader,其中有个属性
private final DexPathList pathList;
这里DexPathList类种有个属性
private Element[] dexElements;
记录了所有加载的dex
每一个dex都有一个dexFile对象
Dexfile如下:
立面有很多API如获取类列表:getClassNameList(df.mCookie);这里要拿到mCookie
下面就是代码流程:
public class HookJava implements IXposedHookLoadPackage {
public void GetClassLoaderClasslist(ClassLoader classloader){
//首先通过classloader的反射拿到pathlist, private final DexPathList pathList;
XposedBridge.log("start dealwith classloader:"+classloader);
Object pathListObj=XposedHelpers.getObjectField(classloader,"pathList");
//接下来拿private Element[] dexElements;
Object[] dexElementsObj=(Object[])XposedHelpers.getObjectField(pathListObj,"dexElements");
//每一个dexElements数组成员都拥有dexfile对象。
for(Object i:dexElementsObj){
//对数组进行遍历,取出每个dexfile对象
Object dexFileObj=XposedHelpers.getObjectField(i,"dexFile");
//通过dexfile的此APIgetClassNameList(df.mCookie);获取类列表
Object mCookieObj=XposedHelpers.getObjectField(dexFileObj,"mCookie");
//private static native String[] getClassNameList(Object cookie);
Class DexFileClass=XposedHelpers.findClass("dalvik.system.DexFile",classloader);
String[] classList=(String[])XposedHelpers.callStaticMethod(DexFileClass,"getClassNameList",mCookieObj);
for(String classname:classList){
XposedBridge.log(dexFileObj+"---"+classname);
}
}
XposedBridge.log("end dealwith classloader:"+classloader);
}
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)
throws Throwable {
Log.i("HookJava",loadPackageParam.packageName);
XposedBridge.log("HookJava app packagename"+loadPackageParam.packageName);
if (loadPackageParam.packageName.equals("com.kanxue.xposedhook01")) {
XposedBridge.log("YRQ" + loadPackageParam.packageName);
//public static Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) {
// return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback);
// }
// public static Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) {
// if (parameterTypesAndCallback.length != 0 && parameterTypesAndCallback[parameterTypesAndCallback.length - 1] instanceof XC_MethodHook) {
// XC_MethodHook callback = (XC_MethodHook)parameterTypesAndCallback[parameterTypesAndCallback.length - 1];
// Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
// return XposedBridge.hookMethod(m, callback);
// } else {
// throw new IllegalArgumentException("no callback defined");
// }
// }
ClassLoader classLoader=loadPackageParam.classLoader;
XposedBridge.log("loadPackageParam.classLoader->"+classLoader);
GetClassLoaderClasslist(classLoader);
ClassLoader parent=classLoader.getParent();
while(parent!=null){
XposedBridge.log("parent->"+parent);
//因为bootclassloader不是继承自baseclassloader,所以要过滤掉它
if(parent.toString().contains("BootClassLoader")){
} else{
GetClassLoaderClasslist(parent);
}
parent=parent.getParent();
}
/*
* 2021-01-04 05:46:27.788 4568-4568/com.kanxue.xposedhook01 I/EdXposed-Bridge: YRQcom.kanxue.xposedhook01
2021-01-04 05:46:27.789 4568-4568/com.kanxue.xposedhook01 I/EdXposed-Bridge: loadPackageParam.classLoader->dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.kanxue.xposedhook01-zeef8np0YDodFo6lhfYTyw==/base.apk"],nativeLibraryDirectories=[/data/app/com.kanxue.xposedhook01-zeef8np0YDodFo6lhfYTyw==/lib/arm64, /system/lib64, /vendor/lib64]]]
2021-01-04 05:46:27.789 4568-4568/com.kanxue.xposedhook01 I/EdXposed-Bridge: parent->java.lang.BootClassLoader@69a83b6
* */
Class StuClass=classLoader.loadClass("com.kanxue.xposedhook01.Student");
Class StuClassByXposed=XposedHelpers.findClass("com.kanxue.xposedhook01.Student",classLoader);
//public static String publicstaticfunc(String arg1,int arg2){
// String result=privatestaticfunc("privatestaticfunc",99);
//
// return arg1+"---"+arg2+"---"+result;
// }
// private static String privatestaticfunc(String arg1,int arg2){
// return arg1+"---"+arg2;
// }
// public String publicfunc(String arg1,int arg2){
// String result=privatefunc("privatefunc",96);
// return arg1+"---"+arg2+"---"+result;
// }
// private String privatefunc(String arg1,int arg2){
// return arg1+"---"+arg2;
// }
XposedHelpers.findAndHookMethod(StuClass, "publicstaticfunc",String.class,int.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Object[] objectarray=param.args;
String arg0=(String)objectarray[0];
int arg1=(int)objectarray[1];
objectarray[0]="changebyxpsoedjava";
objectarray[1]=10086;
XposedBridge.log("beforeHookedMethod publicstaticfunc->arg0:"+arg0+"---arg1:"+arg1);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
String result=(String)param.getResult();
param.setResult("changebyxposed->afterHookedMethod");
XposedBridge.log("beforeHookedMethod publicstaticfunc->result:"+result);
}
});
Class StudentPerson=XposedHelpers.findClass("com.kanxue.xposedhook01.Student$person",classLoader);
XposedHelpers.findAndHookMethod(StudentPerson,"getName", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
XposedBridge.log("beforeHookedMethod->getName"+param.args[0]);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
XposedBridge.log("afterHookedMethod->getName"+param.getResult());
}
});
}
}
}
下面就是结果:
01-04 23:43:42.212 3434-3434/com.kanxue.xposedhook01 I/HookJava: com.kanxue.xposedhook01
01-04 23:43:42.212 3434-3434/com.kanxue.xposedhook01 I/Xposed: HookJava app packagenamecom.kanxue.xposedhook01
01-04 23:43:42.212 3434-3434/com.kanxue.xposedhook01 I/Xposed: YRQcom.kanxue.xposedhook01
01-04 23:43:42.212 3434-3434/com.kanxue.xposedhook01 I/Xposed: loadPackageParam.classLoader->dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.kanxue.xposedhook01-1/base.apk"],nativeLibraryDirectories=[/data/app/com.kanxue.xposedhook01-1/lib/arm, /vendor/lib, /system/lib]]]
01-04 23:43:42.212 3434-3434/com.kanxue.xposedhook01 I/Xposed: start dealwith classloader:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.kanxue.xposedhook01-1/base.apk"],nativeLibraryDirectories=[/data/app/com.kanxue.xposedhook01-1/lib/arm, /vendor/lib, /system/lib]]]
01-04 23:43:42.221 3434-3434/com.kanxue.xposedhook01 I/Xposed: /data/app/com.kanxue.xposedhook01-1/base.apk---com.qihoo.util.Configuration
01-04 23:43:42.221 3434-3434/com.kanxue.xposedhook01 I/Xposed: /data/app/com.kanxue.xposedhook01-1/base.apk---com.qihoo.util.DtcLoader
01-04 23:43:42.221 3434-3434/com.kanxue.xposedhook01 I/Xposed: /data/app/com.kanxue.xposedhook01-1/base.apk---com.qihoo.util.QHDialog
01-04 23:43:42.221 3434-3434/com.kanxue.xposedhook01 I/Xposed: /data/app/com.kanxue.xposedhook01-1/base.apk---com.qihoo.util.ᵢˋ
01-04 23:43:42.221 3434-3434/com.kanxue.xposedhook01 I/Xposed: /data/app/com.kanxue.xposedhook01-1/base.apk---com.qihoo.util.ᵢˎ
01-04 23:43:42.221 3434-3434/com.kanxue.xposedhook01 I/Xposed: /data/app/com.kanxue.xposedhook01-1/base.apk---com.qihoo.util.ᵢˏ
01-04 23:43:42.221 3434-3434/com.kanxue.xposedhook01 I/Xposed: /data/app/com.kanxue.xposedhook01-1/base.apk---com.stub.StubApp
01-04 23:43:42.222 3434-3434/com.kanxue.xposedhook01 I/Xposed: end dealwith classloader:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.kanxue.xposedhook01-1/base.apk"],nativeLibraryDirectories=[/data/app/com.kanxue.xposedhook01-1/lib/arm, /vendor/lib, /system/lib]]]
01-04 23:43:42.222 3434-3434/com.kanxue.xposedhook01 I/Xposed: parent->dalvik.system.PathClassLoader[DexPathList[[dex file "/data/dalvik-cache/xposed_XResourcesSuperClass.dex", dex file "/data/dalvik-cache/xposed_XTypedArraySuperClass.dex"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
01-04 23:43:42.223 3434-3434/com.kanxue.xposedhook01 I/Xposed: start dealwith classloader:dalvik.system.PathClassLoader[DexPathList[[dex file "/data/dalvik-cache/xposed_XResourcesSuperClass.dex", dex file "/data/dalvik-cache/xposed_XTypedArraySuperClass.dex"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
01-04 23:43:42.223 3434-3434/com.kanxue.xposedhook01 I/Xposed: /data/dalvik-cache/xposed_XResourcesSuperClass.dex---xposed.dummy.XResourcesSuperClass
01-04 23:43:42.223 3434-3434/com.kanxue.xposedhook01 I/Xposed: /data/dalvik-cache/xposed_XTypedArraySuperClass.dex---xposed.dummy.XTypedArraySuperClass
01-04 23:43:42.223 3434-3434/com.kanxue.xposedhook01 I/Xposed: end dealwith classloader:dalvik.system.PathClassLoader[DexPathList[[dex file "/data/dalvik-cache/xposed_XResourcesSuperClass.dex", dex file "/data/dalvik-cache/xposed_XTypedArraySuperClass.dex"],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
01-04 23:43:42.223 3434-3434/com.kanxue.xposedhook01 I/Xposed: parent->java.lang.BootClassLoader@ff4dd1
可见并没有Student类,因为在attachBaseContext和onCreate.
onCreate之间进行的壳操作,onCreate之后已经解密,所以hook此函数onCreate。
此函数是onCreate在StubApp类里。
在hook了onCreate函数之后,为了获取classlaoder,需要同过一步步反射,需要找到framework的ActivityThread。
注意,入过没有获取成功类列表,说明BaseDexClassLoader非继承ClassLoader ,所以没有private final DexPathList pathList参数
public static Object invokeStaticMethod(String class_name,
String method_name, Class[] pareTyple, Object[] pareVaules) {
try {
Class obj_class = Class.forName(class_name);
Method method= obj_class.getMethod(method_name, pareTyple);
return method.invoke(null, pareVaules);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static Object getFieldOjbect(String class_name, Object obj,
String filedName) {
try {
Class obj_class = Class.forName(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static ClassLoader getClassloader(){
ClassLoader resultClassloader=null;
Object currentActivityThread=invokeStaticMethod("android.app.ActivityThread","currentActivityThread",new Class[]{},new Object[]{});
Object mBoundApplication=getFieldOjbect("android.app.ActivityThread",currentActivityThread,"mBoundApplication");
Application mInitalApplication=(Application)getFieldOjbect("android.app.ActivityThread",currentActivityThread,"mInitalApplication");
Object loadedApkInfo=getFieldOjbect("android.app.ActivityThread$AppBindData",mBoundApplication,"info");
Application mApplication = (Application)getFieldOjbect("android.app.LoadedApk",loadedApkInfo,"mApplication");
resultClassloader=mApplication.getClassLoader();
以下就是完整代码
package com.yrq.xposed01;
import android.app.Application;
import android.os.Bundle;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class HookJava implements IXposedHookLoadPackage {
public static Object invokeStaticMethod(String class_name,
String method_name, Class[] pareTyple, Object[] pareVaules) {
try {
Class obj_class = Class.forName(class_name);
Method method= obj_class.getMethod(method_name, pareTyple);
return method.invoke(null, pareVaules);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static Object getFieldOjbect(String class_name, Object obj,
String filedName) {
try {
Class obj_class = Class.forName(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static ClassLoader getClassloader(){
ClassLoader resultClassloader=null;
Object currentActivityThread=invokeStaticMethod("android.app.ActivityThread","currentActivityThread",new Class[]{},new Object[]{});
Object mBoundApplication=getFieldOjbect("android.app.ActivityThread",currentActivityThread,"mBoundApplication");
Application mInitalApplication=(Application)getFieldOjbect("android.app.ActivityThread",currentActivityThread,"mInitalApplication");
Object loadedApkInfo=getFieldOjbect("android.app.ActivityThread$AppBindData",mBoundApplication,"info");
Application mApplication = (Application)getFieldOjbect("android.app.LoadedApk",loadedApkInfo,"mApplication");
resultClassloader=mApplication.getClassLoader();
return resultClassloader;
}
public void GetClassLoaderClasslist(ClassLoader classloader){
//首先通过classloader的反射拿到pathlist, private final DexPathList pathList;
XposedBridge.log("start dealwith classloader:"+classloader);
Object pathListObj=XposedHelpers.getObjectField(classloader,"pathList");
//接下来拿private Element[] dexElements;
Object[] dexElementsObj=(Object[])XposedHelpers.getObjectField(pathListObj,"dexElements");
//每一个dexElements数组成员都拥有dexfile对象。
for(Object i:dexElementsObj){
//对数组进行遍历,取出每个dexfile对象
Object dexFileObj=XposedHelpers.getObjectField(i,"dexFile");
//通过dexfile的此APIgetClassNameList(df.mCookie);获取类列表
Object mCookieObj=XposedHelpers.getObjectField(dexFileObj,"mCookie");
//private static native String[] getClassNameList(Object cookie);
Class DexFileClass=XposedHelpers.findClass("dalvik.system.DexFile",classloader);
String[] classList=(String[])XposedHelpers.callStaticMethod(DexFileClass,"getClassNameList",mCookieObj);
for(String classname:classList){
XposedBridge.log(dexFileObj+"---"+classname);
}
}
XposedBridge.log("end dealwith classloader:"+classloader);
}
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)
throws Throwable {
Log.i("HookJava",loadPackageParam.packageName);
XposedBridge.log("HookJava app packagename"+loadPackageParam.packageName);
if (loadPackageParam.packageName.equals("com.kanxue.xposedhook01")) {
XposedBridge.log("YRQ" + loadPackageParam.packageName);
//public static Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) {
// return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback);
// }
// public static Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) {
// if (parameterTypesAndCallback.length != 0 && parameterTypesAndCallback[parameterTypesAndCallback.length - 1] instanceof XC_MethodHook) {
// XC_MethodHook callback = (XC_MethodHook)parameterTypesAndCallback[parameterTypesAndCallback.length - 1];
// Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
// return XposedBridge.hookMethod(m, callback);
// } else {
// throw new IllegalArgumentException("no callback defined");
// }
// }
ClassLoader classLoader=loadPackageParam.classLoader;
XposedBridge.log("loadPackageParam.classLoader->"+classLoader);
GetClassLoaderClasslist(classLoader);
ClassLoader parent=classLoader.getParent();
while(parent!=null){
XposedBridge.log("parent->"+parent);
//因为bootclassloader不是继承自baseclassloader,所以要过滤掉它
if(parent.toString().contains("BootClassLoader")){
} else{
GetClassLoaderClasslist(parent);
}
parent=parent.getParent();
}
//Class StubAppClass=XposedHelpers.findClass("com.stub.StubApp",loadPackageParam.classLoader);
//Method[] methods=StubAppClass.getDeclaredMethods();
// for(Method i:methods){
// XposedBridge.log("com.stub.StubApp->"+i);
// }
//hook onCreate,获取student
XposedHelpers.findAndHookMethod("com.stub.StubApp", loadPackageParam.classLoader, "onCreate", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
XposedBridge.log("com.stub.StubApp->onCreate"+"onCreate beforeHookedMethod");
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
XposedBridge.log("com.stub.StubApp->onCreate"+"onCreate afterHookedMethod");
ClassLoader finalClassLoader=getClassloader();
XposedBridge.log("finalClassLoader->"+finalClassLoader);
GetClassLoaderClasslist(finalClassLoader);
Class StuClass=finalClassLoader.loadClass("com.kanxue.xposedhook01.Student");
Class StudentPerson=finalClassLoader.loadClass("com.kanxue.xposedhook01.Student$person");
XposedHelpers.findAndHookMethod(StuClass, "publicstaticfunc",String.class,int.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Object[] objectarray=param.args;
String arg0=(String)objectarray[0];
int arg1=(int)objectarray[1];
objectarray[0]="changebyxpsoedjava";
objectarray[1]=10086;
XposedBridge.log("beforeHookedMethod publicstaticfunc->arg0:"+arg0+"---arg1:"+arg1);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
String result=(String)param.getResult();
param.setResult("changebyxposed->afterHookedMethod");
XposedBridge.log("beforeHookedMethod publicstaticfunc->result:"+result);
}
});
//Class StudentPerson=XposedHelpers.findClass("com.kanxue.xposedhook01.Student$person",finalClassLoader);
XposedHelpers.findAndHookMethod(StudentPerson,"getpersonname", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
XposedBridge.log("beforeHookedMethod->getpersonname"+param.args[0]);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
XposedBridge.log("afterHookedMethod->getName"+param.getResult());
}
});
}
});
Class StuClass=classLoader.loadClass("com.kanxue.xposedhook01.Student");
Class StuClassByXposed=XposedHelpers.findClass("com.kanxue.xposedhook01.Student",classLoader);
}
}
}
就跟fart中通过classloader获取所有加载的类。
对多dex的处理
加壳其实是为对多dex处理的特例。加固时是可以通过反射拿到修正后的classloader。
多dex就不一定在修正的classloader当中了,可能在其他classloader中。
实例
如此APP:loaddex,这里发现加载资源的函数loaddex并未经过如加壳搬的修复,只是动态加载了。
1.dex如图
做了操作之后,就可以看到
01-06 14:08:43.614 3081-3081/com.kanxue.loaddex I/Xposed: beforeHookedMethod,dexpath is /data/user/0/com.kanxue.loaddex/cache/1.dex,optimizedDirectory is /data/user/0/com.kanxue.loaddex/app_opt_dex,librarySearchPath is /data/user/0/com.kanxue.loaddex/app_lib_path
相关代码
package com.yrq.xposed01;
import android.app.Application;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import dalvik.system.DexClassLoader;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class HookDynamicDex implements IXposedHookLoadPackage {
public static Field getClassField(ClassLoader classloader, String class_name, String filedName) {
try {
Class obj_class = classloader.loadClass(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
return field;
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
public static Object getClassFieldObject(ClassLoader classloader, String class_name, Object obj, String filedName) {
try {
Class obj_class = classloader.loadClass(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
Object result = null;
result = field.get(obj);
return result;
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
public static Object invokeStaticMethod(String class_name,String method_name, Class[] pareTyple, Object[] pareVaules) {
try {
Class obj_class = Class.forName(class_name);
Method method= obj_class.getMethod(method_name, pareTyple);
return method.invoke(null, pareVaules);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static Object getFieldOjbect(String class_name, Object obj, String filedName) {
try {
Class obj_class = Class.forName(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static ClassLoader getClassloader(){
ClassLoader resultClassloader=null;
Object currentActivityThread=invokeStaticMethod("android.app.ActivityThread","currentActivityThread",new Class[]{},new Object[]{});
Object mBoundApplication=getFieldOjbect("android.app.ActivityThread",currentActivityThread,"mBoundApplication");
Application mInitalApplication=(Application)getFieldOjbect("android.app.ActivityThread",currentActivityThread,"mInitalApplication");
Object loadedApkInfo=getFieldOjbect("android.app.ActivityThread$AppBindData",mBoundApplication,"info");
Application mApplication = (Application)getFieldOjbect("android.app.LoadedApk",loadedApkInfo,"mApplication");
resultClassloader=mApplication.getClassLoader();
return resultClassloader;
}
public void GetClassLoaderClasslist(ClassLoader classloader){
//首先通过classloader的反射拿到pathlist, private final DexPathList pathList;
XposedBridge.log("start dealwith classloader:"+classloader);
Object pathListObj=XposedHelpers.getObjectField(classloader,"pathList");
//接下来拿private Element[] dexElements;
Object[] dexElementsObj=(Object[])XposedHelpers.getObjectField(pathListObj,"dexElements");
//每一个dexElements数组成员都拥有dexfile对象。
for(Object i:dexElementsObj){
//对数组进行遍历,取出每个dexfile对象
Object dexFileObj=XposedHelpers.getObjectField(i,"dexFile");
//通过dexfile的此APIgetClassNameList(df.mCookie);获取类列表
Object mCookieObj=XposedHelpers.getObjectField(dexFileObj,"mCookie");
//private static native String[] getClassNameList(Object cookie);
Class DexFileClass=XposedHelpers.findClass("dalvik.system.DexFile",classloader);
String[] classList=(String[])XposedHelpers.callStaticMethod(DexFileClass,"getClassNameList",mCookieObj);
for(String classname:classList){
XposedBridge.log(dexFileObj+"---"+classname);
}
}
XposedBridge.log("end dealwith classloader:"+classloader);
}
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)
throws Throwable {
Log.i("HookDexClassLoader",loadPackageParam.packageName);
XposedBridge.log("HookDynamicDex->app packagename"+loadPackageParam.packageName);
if (loadPackageParam.packageName.equals("com.kanxue.loaddex")) {//包名
// public DexClassLoader(String dexPath, String optimizedDirectory,
// String librarySearchPath, ClassLoader parent) {
// super(dexPath, null, librarySearchPath, parent);
// }
XposedHelpers.findAndHookConstructor(DexClassLoader.class, String.class, String.class, String.class, ClassLoader.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
//拿到DEX加载的路经,就是DexClassLoader第一个参数
Object array[] = param.args;
String dexpath=(String)array[0];
String optimizedDirectory=(String)array[1];
String librarySearchPath=(String)array[2];
XposedBridge.log("beforeHookedMethod,dexpath is "+dexpath+",optimizedDirectory is "+optimizedDirectory+",librarySearchPath is "+librarySearchPath);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
//当构造函数结束时,拿到当前dex依附的DexClassLoader
DexClassLoader dexClassLoader=(DexClassLoader)param.thisObject;
XposedBridge.log("afterHookedMethod,dexClassLoader is "+dexClassLoader);
GetClassLoaderClasslist(dexClassLoader);
//public boolean testcontent(String content) {
XposedHelpers.findAndHookMethod("com.kanxue.test02.TestClass", dexClassLoader, "testcontent", String.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
//change result
param.setResult(true);
}
});
}
});
}
}
}
Hook SO文件中的函数
我们想要Hook SO中函数,首先思路是hook加载so的函数,在so里编辑hook代码(通过一定hook手段如inlineHook,gothook等实现),让后将其放在3个关键位置执行。如so中:最先加载so的init()里,JNI_OnLoad(),init_array。
我们就需要些Xposed代码先再JAVA层java.lang.Runtime(之前是打算hookjava.lang.System.loadLibrary,不成功,Xposed作者有解释,可以去看项目介绍,所以最后hook了更底层的函数才成功),在此函数加载native-lib时调用hookso的C++代码如下:
Xpsed的JAVA代码:
package com.yrq.xposed01;
import android.util.Log;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class XposedHookSo implements IXposedHookLoadPackage {
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam)
throws Throwable {
Log.i("XposedHookSo",loadPackageParam.packageName);
XposedBridge.log("app packagename"+loadPackageParam.packageName);
if (loadPackageParam.packageName.equals("com.yrq.xposedhookso")) {
XposedBridge.log("XposedHookSo" + loadPackageParam.packageName);
//首先完成system类的loadlibrary的hook
XposedHelpers.findAndHookMethod("java.lang.Runtime", loadPackageParam.classLoader, "loadLibrary", String.class,ClassLoader.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
String soname=(String)param.args[0];
XposedBridge.log("beforeHookedMethod Runtime.load("+soname+")");
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
String soname=(String)param.args[0];
XposedBridge.log("afterHookedMethod Runtime.load("+soname+")");
if(soname.contains("native-lib")){
System.load("/data/data/tmp/libnative-lib.so");
}
}
});
}
}
}
hookso.so代码主体如下,记得配置ndk汇编语言种类:
代码参考的github为:
https://github.com/ele7enxxh/Android-Inline-Hook
#include <jni.h>
#include <string>
#include <android/log.h>
#include <dlfcn.h>
extern "C" {
#include "include/inlineHook.h"
}
void *(*old_strstr)(char *,char*) = nullptr;
void * new_strstr(char* arg0,char* arg1)
{
__android_log_print(4,"hookso","strstr is called,arg1:,%s,arg2:%s",arg0,arg1);
if(strcmp(arg1,"hookso")==0){
int a=1;
return &a;
}else{
void* result= old_strstr(arg0, arg1);
return result;
}
}
void starthooklibc() {
//低版本中,通过dlopen()函数获取handle
void* libchandle=dlopen("libc.so",RTLD_NOW);
void* strstr_addr=dlsym(libchandle,"strstr");
if (registerInlineHook((uint32_t) strstr_addr, (uint32_t) new_strstr, (uint32_t **) &old_strstr) != ELE7EN_OK) {
return ;
}
if (inlineHook((uint32_t) strstr_addr) == ELE7EN_OK) {
__android_log_print(4,"hookso","hook libc.so->strstr success");
}
}
extern "C" void _init(void){
starthooklibc();
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_yrq_hookso_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
将生成的so修改为代码命名的hookso.so的名字再放进去。
yrq@ubuntu:~/Desktop/xposed/app-debug.apk_FILES/lib/armeabi-v7a$ adb push libnative-lib.so /sdcard
libnative-lib.so: 1 file pushed. 0.8 MB/s (116316 bytes in 0.148s)
yrq@ubuntu:~/Desktop/xposed/app-debug.apk_FILES/lib/armeabi-v7a$ adb shell
shell@hammerhead:/ $ su
root@hammerhead:/ # cd /data/data/
root@hammerhead:/data/data # mkdir tmp
root@hammerhead:/data/data # chmod 777 tmp/
root@hammerhead:/data/data # cd tmp
cp /sdcard/libnative-lib.so . <
root@hammerhead:/data/data/tmp # ls
libnative-lib.so
root@hammerhead:/data/data/tmp # ls -il
82613 -rw------- root root 116316 2021-01-07 00:31 libnative-lib.so
chmod 777 libnative-lib.so <
root@hammerhead:/data/data/tmp # ls -il
82613 -rwxrwxrwx root root 116316 2021-01-07 00:31 libnative-lib.so
root@hammerhead:/data/data/tmp #
结果就从hook前的:
01-06 17:59:34.820 5098-5098/com.yrq.xposedhookso I/xposedhookso: not find xposedhookso
01-06 17:59:34.820 5098-5098/com.yrq.xposedhookso I/xposedhookso: not find aaaaaaaaaaaaaaaaaab
变成了
01-07 00:41:01.125 3265-3265/com.yrq.xposedhookso I/xposedhookso: find xposedhookso
01-07 00:41:01.125 3265-3265/com.yrq.xposedhookso I/xposedhookso: find aaaaaaaaaaaaaaaaaab
关于64位的支持
可以看到高低版本安卓9.0,6.0参数出现区别
所以需要hook函数时要注意参数变化。
并且不能使用之前的inlinehook需要使用如sandhook之类的其他框架,这里使用sandhook。
编写
64为so文件,在次文件的init函数中完成hook,这里不用init原因是sandhook框架在init时还未初始化完全,在init_array里有操作,而init_array晚于init执行。
这里方法是:
- 在init_array里添加hook代码,优先级设置低。
- 在JNI_Onload里设置。
###流程
将sandhook的nativehook代码拷入so项目。
注意修改编译配置
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
}
}
代码如下:
#include <jni.h>
#include <string>
#include "sandhook_native.h"
#include "../../../../../../Android/Sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/jni.h"
#include "utils/log.h"
#include <android/log.h>
void *(*old_strstr)(char *,char*) = nullptr;
void * new_strstr(char* arg0,char* arg1)
{
__android_log_print(4,"hooksoArm64","strstr is called,arg1:,%s,arg2:%s",arg0,arg1);
if(strcmp(arg1,"hookso")==0){
int a=1;
return &a;
}else{
void* result= old_strstr(arg0, arg1);
return result;
}
}
extern "C" void starthooklibc(){
//首先获取libc中的strstr
//32位libc在/system/lib
//64位在/system/lib64
//如果为64位
if(sizeof(void*)==8){
const char* libcPath="/system/lib64/libc.so";
old_strstr= reinterpret_cast<void *(*)(char *, char *)>(SandInlineHookSym(libcPath, "strstr",
reinterpret_cast<void *>(new_strstr)));
} else{
}
}
extern "C" void _init(void){
LOlogGD("go into JNI_OnLoad");
}
extern "C" jint JNICALL JNI_Onload(JavaVM* vm,void* reserved){
LOGD("go into JNI_OnLoad");
starthooklibc();
return JNI_VERSION_1_6;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_yrq_xposedhooksobysandhook_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
主动调用so函数
首先想主动调用的函数在ida里可以看见在模块中偏移是871C,注意thumb指令奇数。
原so
plugins {
id 'com.android.application'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.yrq.xposedhookso"
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk{
abiFilters 'armeabi-v7a'
}
externalNativeBuild {
cmake {
cppFlags "-std=c++11"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.10.2"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
hook代码so库中相关代码:
#include <jni.h>
#include <string>
#include "sandhook_native.h"
#include "../../../../../../Android/Sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/jni.h"
#include "utils/log.h"
#include <android/log.h>>
void *(*old_strstr)(char *,char*) = nullptr;
void * new_strstr(char* arg0,char* arg1)
{
__android_log_print(4,"hooksoArm64","strstr is called,arg1:,%s,arg2:%s",arg0,arg1);
if(strcmp(arg1,"hookso")==0){
int a=1;
return &a;
}else{
void* result= old_strstr(arg0, arg1);
return result;
}
}
extern "C" void starthooklibc(){
//首先获取libc中的strstr
//32位libc在/system/lib
//64位在/system/lib64
//如果为64位
if(sizeof(void*)==8){
const char* libcPath="/system/lib64/libc.so";
old_strstr= reinterpret_cast<void *(*)(char *, char *)>(SandInlineHookSym(libcPath, "strstr",
reinterpret_cast<void *>(new_strstr)));
} else{
}
}
//bool testhook(const char* content){
void activecalltesthook(){
typedef int (*testhook)(const char* a1);
testhook testhookfunction= nullptr;
//得到函数地址
void* libnativebase=SandGetModuleBase("libnative-lib.so");
unsigned long tmpaddr=(unsigned long)libnativebase+0x871C;
void* testhookaddr= reinterpret_cast<void *>(tmpaddr);
testhookfunction= reinterpret_cast<testhook>(testhookaddr);
int result1=testhookfunction("aaaaaaaaaaaaaaso");
LOGD("testhookfunction(aaaaaaaaaaaaaaso);return %d",result1);
int result2=testhookfunction("aaaaaabbbbbbbbbb");
LOGD("testhookfunction(aaaaaabbbbbbbbbb);return %d",result2);
}
extern "C" void _init(void){
LOGD("go into _init");
}
extern "C" jint JNICALL JNI_OnLoad(JavaVM* vm,void* reserved){
LOGD("go into JNI_OnLoad");
starthooklibc();
activecalltesthook();
return JNI_VERSION_1_6;
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_yrq_xposedhooksobysandhook_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
Xposed指纹检测与简单定制反检测
Xposed指纹
- Xposed插件管理:XposedInstaller
- Xposed对函数Hook的管理:java函数变native函数。
- Xposed框架有大量API
- Xposed框架的特点文件等。
Fdex2
loadclass()和forName()区别是后者加载类会初始化。
要想完成dump dex,要拿到dex中某个类的class对象可以通过hook classLoader(这里是Xposed参数中的classLoader),得到其中一个class,就可以dumpdex。
想要拿到实际的classloader,要通过已经加载正常app时,进行反射,所以可以进入正常线程休眠一段时间,这时就是所需classloader。
想要对目标app进程动态修改,1是ptrace-attach,2是加载到app所在进程空间中。
下面就是利用Fdex2的代码:
package com.yrq.xposed01;
import android.app.Application;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class XposedFdex2 implements IXposedHookLoadPackage {
Class Dex;
Method Dex_getBytes;
Method getDex;
String packagename;
public static Field getClassField(ClassLoader classloader, String class_name, String filedName) {
try {
Class obj_class = classloader.loadClass(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
return field;
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
public static Object getClassFieldObject(ClassLoader classloader, String class_name, Object obj, String filedName) {
try {
Class obj_class = classloader.loadClass(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
Object result = null;
result = field.get(obj);
return result;
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
public static Object invokeStaticMethod(String class_name,String method_name, Class[] pareTyple, Object[] pareVaules) {
try {
Class obj_class = Class.forName(class_name);
Method method= obj_class.getMethod(method_name, pareTyple);
return method.invoke(null, pareVaules);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static Object getFieldOjbect(String class_name, Object obj, String filedName) {
try {
Class obj_class = Class.forName(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static ClassLoader getClassloader(){
ClassLoader resultClassloader=null;
Object currentActivityThread=invokeStaticMethod("android.app.ActivityThread","currentActivityThread",new Class[]{},new Object[]{});
Object mBoundApplication=getFieldOjbect("android.app.ActivityThread",currentActivityThread,"mBoundApplication");
Application mInitalApplication=(Application)getFieldOjbect("android.app.ActivityThread",currentActivityThread,"mInitalApplication");
Object loadedApkInfo=getFieldOjbect("android.app.ActivityThread$AppBindData",mBoundApplication,"info");
Application mApplication = (Application)getFieldOjbect("android.app.LoadedApk",loadedApkInfo,"mApplication");
resultClassloader=mApplication.getClassLoader();
return resultClassloader;
}
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
initReflect();
packagename=lpparam.packageName;
XposedBridge.log("设定包名:"+packagename);
if(lpparam.packageName.equals("com.example.classloadertest")){
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
XposedBridge.log("sleep over start dump dex");
ClassLoader classloader =getClassloader();
try {
Class MainActivityClass=classloader.loadClass("com.example.classloadertest.MainActivity");
XposedBridge.log("sleep over start dump dex by class:"+MainActivityClass);
dumpdexbyClass(MainActivityClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}).start();
}
if ((!lpparam.packageName.equals(packagename))||packagename==null) {
XposedBridge.log("当前程序包名与设定不一致或者包名为空");
return;
}
XposedBridge.log("目标包名:"+lpparam.packageName);
//想要拿到实际的classloader,要通过已经加载正常app时,进行反射
// String str = "java.lang.ClassLoader";
// String str2 = "loadClass";
//
// XposedHelpers.findAndHookMethod(str, lpparam.classLoader, str2, String.class, Boolean.TYPE, new XC_MethodHook() {
// protected void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable {
// super.afterHookedMethod(param);
// Class cls = (Class) param.getResult();
// dumpdexbyClass(cls);
//
// }
// } );
}
public void dumpdexbyClass(Class cls) throws InvocationTargetException, IllegalAccessException {
if (cls == null) {
//XposedBridge.log("cls == null");
return;
}
String name = cls.getName();
XposedBridge.log("当前类名:" + name);
byte[] bArr = (byte[]) Dex_getBytes.invoke(getDex.invoke(cls, new Object[0]), new Object[0]);
if (bArr == null) {
XposedBridge.log("数据为空:返回");
return;
}
XposedBridge.log("开始写数据");
String dex_path = "/data/data/" + packagename + "/" + packagename + "_" + bArr.length + ".dex";
XposedBridge.log(dex_path);
File file = new File(dex_path);
if (file.exists()) return;
writeByte(bArr, file.getAbsolutePath());
}
//初始化函数,通过反射,获取2个API
public void initReflect() {
try {
Dex = Class.forName("com.android.dex.Dex");
Dex_getBytes = Dex.getDeclaredMethod("getBytes", new Class[0]);
getDex = Class.forName("java.lang.Class").getDeclaredMethod("getDex", new Class[0]);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
//保存拿到dex的内容
public void writeByte(byte[] bArr, String str) {
try {
OutputStream outputStream = new FileOutputStream(str);
outputStream.write(bArr);
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
XposedBridge.log("文件写出失败");
}
}
}
结果如下,已经打出想要的dex:
我们在脱壳操作中,想要获取dex_file的beign_和size_。dex_file是有虚函数的。
为了方便,与通用最好找导出的函数,返回值是dex_file这种。
另外一种方法为把系统的libart拖出来,比如n5就在/system/lib/libart.so中在导出表查找参数或返回值有DexFile。
art::verifier::MethodVerifier::VerifyClass(art::Thread *, art::DexFile const*, art::Handle<art::mirror::DexCache>, art::Handle<art::mirror::ClassLoader>, art::DexFile::ClassDef const*, bool, std::string *)
_ZN3art8verifier14MethodVerifier11VerifyClassEPNS_6ThreadEPKNS_7DexFileENS_6HandleINS_6mirror8DexCacheEEENS7_INS8_11ClassLoaderEEEPKNS4_8ClassDefEbPNSt3__112basic_stringIcNSG_11char_traitsIcEENSG_9allocatorIcEEEE
.text:00364B0C _ZN3art8verifier14MethodVerifier11VerifyClassEPNS_6ThreadEPKNS_7DexFileENS_6HandleINS_6mirror8DexCacheEEENS7_INS8_11ClassLoaderEEEPKNS4_8ClassDefEbPNSt3__112basic_stringIcNSG_11char_traitsIcEENSG_9allocatorIcEEEE
再去查看源码
或者以下几个函数都可以找到dexFile
_ZN3art11PrettyFieldEjRKNS_7DexFileEb
_ZN3art11ClassLinker11ResolveTypeERKNS_7DexFileEtPNS_6mirror5ClassE
_ZN3art11ClassLinker22LoadSuperAndInterfacesENS_6HandleINS_6mirror5ClassEEERKNS_7DexFileE
因为app插件是注入到app进程空间,所以在一个地址空间,所以这种时候app相当于这就是我们自己编写的app,插件是都可以对其进行访问修改。现在要做的是拿到要hook函数地址,借助hook框架,进行hook
参考资料
看雪视频
更多推荐
所有评论(0)