从Android视角看数据爬虫
简介
从Android客户端视角看数据爬虫,主要分为以下几个步骤
1、通过反编译等手段,hook住对应app的网络请求库,得到对应的URL
2、根据hook的点打印出当时的header和参数
3、模拟APP请求,发起对应的数据请求
如何实现
通过反编译和Hook获取url
这个爬虫的重点步骤,通过此步骤才能的到对应的URL,难易程度取决于APP对自己的保护程度。主要从以下几种情况来说明:
1、使用了OkHttp,但未自己进行封装或修改的:
直接通过hook OkHttp的addInterceptoer方法即可随意往里添加自己定义的interceptoer方法,由于此方法会将整个chain(请求链)作为参数,因此我们添加到最后即可获取所有的请求信息并打印出来。
现在大部分的私人app或者小公司app通过此方法即可轻松获取url、header和参数等信息。
2、使用了OkHttp,但对其中的实现进行了修改:
此情况如果没有混淆,可以找到修改后添加Interrupter的方法,并进行hook方法插入。其余同方案1。
如果混淆了,可以先找到添加Interrupter的方法,但过程比较麻烦,需要好很多经历。或同方案3.
3、对于自己封装或者对OkHttp修改比较大的,Android可以直接HOOK网络底层,直接hook到请求和数据返回的地方,获取对应的url和参数。
此方法比较麻烦,不同的Android版本,对于请求的封装并不相同。
使用XPosed来hook请求,获取URL和参数
hook到OkHttp的buid方法,然后将自定义的interceptor添加到interceptors中。
XposedHelpers.findAndHookMethod(instanceClazz, "build", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
synchronized (OkHttpHook.class){
if (hasHooked){
return;
}
hasHooked = true;
LOGGER.d("正在运行build方法,开始hook");
List interceptors = (List) XposedHelpers.getObjectField(param.thisObject, "interceptors");
if (interceptors != null){
//如果获取到了interceptors
LOGGER.d("interceptors = "+interceptors.size());
Object logInterceptor = getLoggingInterceptor();
if (logInterceptor != null){
//获取到了interceptor
LOGGER.d("获取到Log的interceptor");
interceptors.add(logInterceptor);
}
LOGGER.d("interceptors = "+interceptors.size());
LOGGER.e("has been hooked succeed!");
}
}
}
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
}
});
/**
* 获取jar包中的interceptor
* @return
*/
private Object getLoggingInterceptor() {
String jarPath = FileUtils.PATH_FILE_DIR + File.separator + FileUtils.FILE_NAME_INTERCEPTOR;
LOGGER.d("当前jarPath="+jarPath);
File file = new File(jarPath);
if (file.exists()){
LOGGER.d("存在interceptor的jar文件");
}
DexClassLoader dexClassLoader = new DexClassLoader(jarPath, FileUtils.PATH_OUT_DIR, null, mClassLoader);
try {
mHttpLoggingInterceptor = dexClassLoader.loadClass("com.alzzz.interceptor.AlzInterceptor");
mLoggerClass = dexClassLoader.loadClass("com.alzzz.interceptor.AlzInterceptor$Logger");
if (mLoggerClass != null && mHttpLoggingInterceptor != null) {
LOGGER.e("获取到jar包中的拦截器");
return InitInterceptor();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
具体的interceptor随自己心愿,想写什么写什么,类需要实现Interceptor接口,方法中可以获取chain对象,这个能做的事情就有很多了。拿到url和参数很轻松。
模拟请求,获取数据
使用第二步获取到的url和header及params,进行模拟数据请求。如果此时的request鉴权通过,即可绕过获取数据。
https证书问题
以上方案,除了模拟请求都可以完全绕过证书,不受证书控制。Android手机在root后使用XPose可以绕过证书鉴权,获取请求数据。
怎么防御
从客户端出发
1、监听进程,判断是否有Xpose的运行进程,如果有则直接弹出提示,不让对方使用。
现在市面上很多APP都做了这项处理,但是,不能完全防御,因为XPosed可以hook自己把自己进程也变了
2、加壳
使用第三代的加密方式,对自己的apk进行加密,大大加强反编译和hook难度。
XPosed可以hook到Android系统然后将转成的SO库从系统中导出出来,加大反编译成本,而且会替换掉部分代码,是的可读性更差。
3、使用更加恶心的混淆方式0o0o00这样的加密方式,大大降低可读性。
从Server出发
1、除了信安的鉴权方式外,为每个接口都增加业务鉴权。
对比与闲鱼,每次请求闲鱼会不断修改
wua=HHnB_xFKMPecU%2BxPA%2FNwVHVrwPQq7O83RzZawsAN%2FgEb2fGizfJ%2FWZU6SC%2BxxVPx4VI1K0noc2g8gpji27DUg9uleZNA169x2z8zyYSc8RaeBRaXSAszUK90P%2FI%2BkH5mVXD%2F%2B<br>
x-sign=ab23180010e22f50c705640041863ba451bb884b3cd0aad85f<br>
每次请求都会进行修改,这里就可以推断出,每次鉴权都会根据一个双方协定好的加密方式,对每次请求的唯一标示进行了加密。例如:
时间戳+用户id+关键某几个过程中不可修改的参数+私钥(或对称加密方式的随机串)
进行了一次加密。Server根据这次请求唯一的加密鉴权,确定是否返回数据。
成本:每次请求都需要进行加密串的生成,确认是对哪些参数进行加密。
2、如果请求就是从客户端发起的,直接是模拟的客户端真实的点击
该情况,很难避免,只能加大其成本,如果间隔频率太小的请求,划入到异常请求,如果达到某个量级则对deviceId进行封禁。