0. Xposed框架简介
关于Xposed框架相信大家应该不陌生了,他是Android中Hook技术的一个著名的框架,而Xposed框架是免费的而且还是开源的,本文主要介绍如何通过这个框架来进行系统方法的拦截功能,比如我们开发过程中,对于一些测试环境很难模拟,特别是测试同学有时候像随机改变设备的imei,mcc等信息用来模拟不同测试场景,这时候如果可以去修改系统的这个值的话对于测试来说就非常方便了,其实这些在网上已经有很多类似的小工具了,下面就来详细的讲解如何使用这个框架。
1. 编写模块功能
环境搭建好了,下面就开始操作了,上面安装的那个工具其实是一个模块管理器,我们如果想做一些hook操作还得自己编写模块也就是应用程序,然后把这个模块安装到设备中,这个工具就可以检测出来了,会提示你加载这模块然后在重启设备,模块功能就有效果了。那么下面来看一下如何编写一个Xposed模块呢?
第一步:新建一个Android项目,导入Xposed工具包
这里一定要注意,不能使用libs文件夹而是lib文件夹,如果这里使用了libs文件夹的话,在安装成功模块之后重启会发现Hook是失败的,通过打印tag为xposed的日志信息会发现这样的错误:java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
注意:在Eclipse中,如果把工具包放到libs文件中,默认是加入到编译路径中的,同时在编译出来的程序中也是包含了这个工具包中的所有类,而对于其他非libs文件夹,我们添加工具包之后在add buildpath之后只是做到了工程引用工具包的功能,而最终并不会把这个工具包包含到程序中的。
第二步:编写模块代码
模块代码编写还是比较简单的,我们只要新建一个实现IXposedHookLoadPackage接口的类,然后在handleLoadPackage回调方法中进行拦截操作即可,而具体的拦截操作是借助XposedHelpers.findAndHookMethod方法和XposedBridge.hookMethod方法实现的,这两个方法也是比较简单的,从参数含义可以看到,主要是Hook的类名和方法名,然后还有一个就是拦截的回调方法,一般是拦截之前做什么的一个beforeHookedMethod方法和拦截之后做什么的一个afterHookedMethod方法。
- public class Main implements IXposedHookLoadPackage {
//hook 一个具体的函数
private void hook_method(String className, ClassLoader classLoader, String methodName,
Object... parameterTypesAndCallback){
try {
XposedHelpers.findAndHookMethod(className, classLoader, methodName, parameterTypesAndCallback);
} catch (Exception e) {
XposedBridge.log(e);
}
}
//hook 相同函数名的函数
private void hook_methods(String className, String methodName, XC_MethodHook xmh){
try {
Class<?> clazz = Class.forName(className);
for (Method method : clazz.getDeclaredMethods())
if (method.getName().equals(methodName)
&& !Modifier.isAbstract(method.getModifiers())
&& Modifier.isPublic(method.getModifiers())) {
XposedBridge.hookMethod(method, xmh);
}
} catch (Exception e) {
XposedBridge.log(e);
}
}
@Override
public void handleLoadPackage(final LoadPackageParam lpp) throws Throwable{
Log.i("jw", "pkg:"+lpp.packageName);
//hook 系统函数getDeviceId 函数,并将返回值设置为jiangwei
hook_method("android.telephony.TelephonyManager", lpp.classLoader, "getDeviceId", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Log.i("jw", "hook getDeviceId...");
Object obj = param.getResult();
Log.i("jw", "imei args:"+obj);
param.setResult("jiangwei");
}
});
//定位
hook_methods("android.location.LocationManager", "getLastKnownLocation", new XC_MethodHook(){
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Log.i("jw", "hook getLastKnownLocation...");
Location l = new Location(LocationManager.PASSIVE_PROVIDER);
double lo = -10000d; //经度
double la = -10000d; //纬度
l.setLatitude(la);
l.setLongitude(lo);
param.setResult(l);
}
});
hook_methods("android.location.LocationManager", "requestLocationUpdates", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Log.i("jw", "hook requestLocationUpdates...");
if (param.args.length == 4 && (param.args[0] instanceof String)) {
LocationListener ll = (LocationListener)param.args[3];
Class<?> clazz = LocationListener.class;
Method m = null;
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().equals("onLocationChanged")) {
m = method;
break;
}
}
try {
if (m != null) {
Object[] args = new Object[1];
Location l = new Location(LocationManager.PASSIVE_PROVIDER);
double lo = -10000d; //经度
double la = -10000d; //纬度
l.setLatitude(la);
l.setLongitude(lo);
args[0] = l;
m.invoke(ll, args);
}
} catch (Exception e) {
XposedBridge.log(e);
}
}
}
});
}
}
对于IXposedHookLoadPackage这个接口和回调方法,我们可以知道,应该是拦截系统中所有应用的运行信息,这里传递回来的一个LoadPackageParam参数类型就是包括了Hook应用的具体信息,我们可以打印应用的包名就可以看到效果了。
注意:如果你想Hook一个类的具体方法,那么就必须要清楚的了解到这个方法的相信信息:参数类型和个数,返回类型等。因为在拦截的过程中必须要对这个方法进行分析,比如得到方法参数来进行具体参数修改,返回值信息来进行返回值修改,这里看到了获取imei值的方法是一个无参数的返回字符串类型的方法,那么如果要拦截他的返回值,就需要修改他的返回值使用setResult方法即可。所以从这里可以看到不管是你hook系统的方法,还是日后去hook第三方应用的具体类方法,第一步都得了解到你hook对象的具体信息,关于系统方法咋们可以通过查看源码来得到信息,而对于第三方应用的话那么只能借助反编译技术了,比如修改游戏金币功能,你必须先反编译游戏知道修改金币的类和具体方法才可行。
这里我不仅Hook了系统的imei信息,也简单的Hook了系统的地理位置信息,在Android中获取经纬度信息有三种方式,这里为了演示简单,用了GPS定位功能,一般获取经纬度信息的代码主要是两处:
一处是初始化的时候调用getLastKnowLocation方法获取最后一次系统中的地理位置信息
//获取地理位置管理器
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
//获取Location
Location location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
if(location!=null){//不为空,显示地理位置经纬度showLocation(location);}
还有一处就是监听地理位置变化的回调接口中的onLocationChanged回调方法:
/**
* LocationListern监听器
* 参数:地理位置提供器、监听位置变化的时间间隔、位置变化的距离间隔、LocationListener监听器
*/
LocationListener locationListener = new LocationListener() {
@Override
public void onStatusChanged(String provider, int status, Bundle arg2) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
@Override
public void onLocationChanged(Location location) {
//如果位置发生变化,重新显示
showLocation(location);
}
};
所以如果想Hook系统的地理位置信息进行拦截,那么就需要操作这两处代码了,而他们有一个区别就是,第一处是通过返回值得到的,第二处是通过回调方法中的参数得到的。下面来看一下具体的Hook代码:
Hook第一处代码比较简单,直接构造一个假的Location对象然后设置返回值即可。
//定位
hook_methods("android.location.LocationManager", "getLastKnownLocation", new XC_MethodHook(){
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Log.i("jw", "hook getLastKnownLocation...");
Location l = new Location(LocationManager.PASSIVE_PROVIDER);
double lo = -10000d; //经度
double la = -10000d; //纬度
l.setLatitude(la);
l.setLongitude(lo);
param.setResult(l);
}
});
Hook第二处代码有点复杂,需要先找到添加位置监听的方法requestLocationUpdates,然后通过反射得到这个回调对象,找到具体的回调方法,然后在进行操作,因为回调方法是通过参数把Location对象传递回来的,所以这里需要修改参数值。
hook_methods("android.location.LocationManager", "requestLocationUpdates", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Log.i("jw", "hook requestLocationUpdates...");
if (param.args.length == 4 && (param.args[0] instanceof String)) {
LocationListener ll = (LocationListener)param.args[3];
Class<?> clazz = LocationListener.class;
Method m = null;
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().equals("onLocationChanged")) {
m = method;
break;
}
}
try {
if (m != null) {
Object[] args = new Object[1];
Location l = new Location(LocationManager.PASSIVE_PROVIDER);
double lo = -10000d; //经度
double la = -10000d; //纬度
l.setLatitude(la);
l.setLongitude(lo);
args[0] = l;
m.invoke(ll, args);
}
} catch (Exception e) {
XposedBridge.log(e);
}
}
}
});
好了,到这里我们就编写好了Hook系统的imei值和地理位置信息的模块了。
第三步:添加模块入口
这一步是非常重要的,也是最容易忘记的,就是要告诉Xposed框架一个模块中Hook的入口,这里可以看到模块的入口是Main类,所以需要在模块的assets中添加一个xposed_init文件:
这里的内容很简单,就是模块入口类的全称名称即可:
第四步:添加模块的额外信息
最后一步就是需要在模块的AndroidManifest.xml文件添加额外信息,具体包括模块的描述信息,版本号等:
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="Hook系统信息" />
<meta-data
android:name="xposedminversion"
android:value="30" />
经过上面四步之后咋们就完成了模块的定义了,最后咋们为了验证我们Hook的结果,在新建一个Activity类,在内部调用一下系统的获取imei方法以及位置信息方法,并且显示在屏幕中:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
locationTxt = (TextView)findViewById(R.id.location);
imeiTxt = (TextView)findViewById(R.id.imei);
//获取地理位置管理器
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
//获取Location
Location location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
if(location!=null){
//不为空,显示地理位置经纬度
showLocation(location);
}
//监视地理位置变化
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 3000, 1, locationListener);
TelephonyManager telephonyManager = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
String imei = telephonyManager.getDeviceId();
imeiTxt.setText("imei:"+imei);
}
2. 运行模块
这时候看到,激活成功之后,会提示你再次重启设备才能生效,所以这里可以看到每次如果有新的模块或者是模块代码有更新了,比如这样:
都是需要重启设备,模块才能生效的,这一点还是有点蛋疼的和麻烦的。然后咋们重启设备之后,在运行我们的模块代码看看效果:
从这显示结果看到了,Hook成功了,在没有Hook之前的效果是:
这时候咋们在来看一下打印的日志信息:
看到了,百度地图在获取我们设备的imei和位置信息,当然这是符合正常情况的,从这里可以看到,我们还可以利用这个技术来观察设备中有哪些应用在获取设备的一些隐私数据。
3.实际用途
本文主要是介绍了Xposed框架的基本使用以及一个简单作用,但是在实际过程中,这个框架是非常有用的,比如在文章开头就说到了,我们可以通过修改系统的一些信息来帮助测试模拟复杂的测试环境,但是这个框架现在用的最广泛的当属破解了,这个也是我们后续讲解的重点,用这个框架咋们可以进行应用的脱壳,游戏的外挂等。