從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進行封禁。