使用的方法是以咕咚为跳板,先修改咕咚的计步数据然后上传微信。
其中:
l 使用的手机是 小米3+miui8首先要满足xposed可以运行的条件,其次系统一定要是android6.0+,原因会在后面详细说明
l 里面带了一些调试输出,用于查看过程中传感器数据的变化不需要的话可以把log.e全部删掉
l sensor.getName().equals("Step Counter")会由于权限问题报错eclipse代码左侧提示小图标点一下无视就好
package com.example.testxposedhook;
import static de.robv.android.xposed.XposedHelpers.findClass;
import java.lang.reflect.Field;
import android.hardware.Sensor;
import android.util.Log;
import android.util.SparseArray;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
public class Main implements IXposedHookLoadPackage {
private static int stepCount = 23333;//must lower than 98800
private static int acclCount = 1;//accelerate sensor count
@Override
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
// TODO Auto-generated method stub
if(!lpparam.packageName.equals("com.codoon.gps")){
return;
}
final Class> sensorEL = findClass("android.hardware.SystemSensorManager$SensorEventQueue", lpparam.classLoader);
XposedBridge.hookAllMethods(sensorEL, "dispatchSensorEvent", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
// ((float[]) param.args[1])[0] = ((float[]) param.args[1])[0] + 1168 * stepCount;
// stepCount++;
try{
//先拿manager
Field mfield = param.thisObject.getClass().getSuperclass().getDeclaredField("mManager");
mfield.setAccessible(true);
Object mManager= mfield.get(param.thisObject);
//再拿mmanager下的sensor
Field field = param.thisObject.getClass().getEnclosingClass().getDeclaredField("mHandleToSensor");
field.setAccessible(true);
int handle = (Integer) param.args[0];
Sensor sensor = ((SparseArray) field.get(mManager)).get(handle);
if(sensor.getName().equals("Step Counter"))//StepCounter sensor
{
Log.e("mytest","args = " + ((float[]) param.args[1])[0]+" "+ ((float[]) param.args[1])[1]+" "+ ((float[]) param.args[1])[2]+" "+ ((float[]) param.args[1])[3]);
((float[]) param.args[1])[0] = stepCount;
Log.e("mytest","sensor = " + sensor);
}
else{//accelerate sensor
if(acclCount%100>=80){
Log.e("mytest", "a loop end");
acclCount++;
return;
}
((float[]) param.args[1])[0] = 20;
((float[]) param.args[1])[1] = 10;
((float[]) param.args[1])[2] = 30;
acclCount++;
if(acclCount%20==0){
Log.e("mytest","running accelerate");
}
}
}
catch(Exception e){
Log.e("mytest",Log.getStackTraceString(e));
}
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
// TODO Auto-generated method stub
// super.afterHookedMethod(param);
Field mfield = param.thisObject.getClass().getSuperclass().getDeclaredField("mManager");
mfield.setAccessible(true);
Object mManager= mfield.get(param.thisObject);
//再拿mmanager下的sensor
Field field = param.thisObject.getClass().getEnclosingClass().getDeclaredField("mHandleToSensor");
field.setAccessible(true);
int handle = (Integer) param.args[0];
Sensor sensor = ((SparseArray) field.get(mManager)).get(handle);
if(!sensor.getName().equals("Step Counter"))
return;
Log.e("mytest", "after hook : "+((float[])param.args[1])[0]);
}
});
}
}
方法来源于陈浩的博客-《基于Xposed修改微信运动步数》
但发现有几个问题:
1.首先实测时小米3+miui8下微信并不能直接从手机获取计步器数据而咕咚获取的传感器数据不只是Step Counter,还有加速度传感器,因此判断咕咚是对两个传感器的数据进行了综合分析;
2.其次是android 5.X-到android 6.0+的变化使得原来获取sensor数据的部分报错无法获取
其中
Field field = param.thisObject.getClass().getEnclosingClass().getDeclaredField("sHandleToSensor");
field.setAccessible(true);
int handle = (Integer) param.args[0];
Sensor sensor = ((SparseArray) field.get(0)).get(handle);
XposedBridge.log("sensor = " + sensor);
这段代码不知道原作者有没有实测过,按照我的理解
Sensor sensor = ((SparseArray) field.get(0)).get(handle);
这段应该写成
Sensor sensor = ((SparseArray) field.get(null)).get(handle);
详细原因可以参照java.lang.reflect的api文档:Field api文档
Field.get 返回参数指定对象上此 Field表示的字段的值。如果该值是一个基本类型值,则自动将其包装在一个对象中。
因此0参数类型将为Integer,而写成null是可以的,因为在android5.X-的代码中sHandleToSensor的修饰符为final static。Filed.get在获取static型成员时可以直接使用null作为参数。
而在android6.0+的代码中SystemSensorManager有部分变动,相关代码结构变成了以下结构:
public class SystemSensorManager{
private final String mHandleToSensor="mHandleToSensor";//只是个示例实际这里的类型是SparseArray
static abstract class BasicQueue{
private final SystemSensorManager mManager=new SystemSensorManager();
}
static class SensorQueue extends BasicQueue{
public void dispatchSensorEvent(){}//这里是我们hook的地方所处的作用域
}
}
其中相比android5.X-的代码变化主要有以下两点
l sHandleToSensor改名为mHandleToSensor
l sHandleToSensor的修饰符由private static final变为private final
名字改了好说,直接在Filed生成时改一下获取名就可以了,由于static关键字被取消导致无法再通过空对象反射的方法获取sensor就比较麻烦了,通过观察SystemSensorManager的源码可以发现可以先获取SensorQueue的父类BasicQueue中的mManager然后用这个mManager来去获取mHandleToSensor。代码如下
//先拿manager
Field mfield = param.thisObject.getClass().getSuperclass().getDeclaredField("mManager");
mfield.setAccessible(true);
Object mManager= mfield.get(param.thisObject);
//再拿mmanager下的sensor
Field field = param.thisObject.getClass().getEnclosingClass().getDeclaredField("mHandleToSensor");
field.setAccessible(true);
int handle = (Integer) param.args[0];
Sensor sensor = ((SparseArray) field.get(mManager)).get(handle);
在修改计步器方面,最初是把所有的sensor的values[0]全部设为一个大数
((float[]) param.args[1])[0] = stepCount;//stepCount=23333
但是发现在咕咚实测的时候会变为每次只加3000步就停了,又联想到咕咚使用了两种传感器,而加速度传感器在之前也是用来做计步器的(详见xbase-《一个稳定好用的android计步器源代码》)。参考这篇文章猜测是用加速度传感器的三轴数据来判断运动状态,于是模拟了一个间歇性运动的过程
if(acclCount%100>=80){
Log.e("mytest", "a loop end");
acclCount++;
return;
}
((float[]) param.args[1])[0] = 20;
((float[]) param.args[1])[1] = 10;
((float[]) param.args[1])[2] = 30;
acclCount++;
if(acclCount%20==0){
Log.e("mytest","running accelerate");
}
其中三轴的数据刚开始用的全是8发现实测无效,随便改了一组可以了就没再研究咕咚具体的算法实现,有时间可以考虑逆向一下咕咚看一下具体的算法实现。至此整个功能就实现了。
附一张结果图,大写的23333: