揭祕-Android刷量有多容易
做網際網路開發的同學可能對流量這個詞很熟悉,
在網際網路行業中對一個產品的質量有一些關鍵指標,比如日活DAU,比如次日留存,點選率。
往往評估一個產品的變現能力會通過日活來計算。
因此也就誕生了一些灰色產業,專門在日活上做文章。
而作為金主(廣告主)來說,如何判斷一個產品的真實日活就顯得非常重要了。
行業潛規則裡,你自己家給的日活資料都要打個折。。除非能提供一些第三方的統計資料。
今天要說的是一個設計不完善的DAU系統有多容易偽造資料。
刷量手段
現在市面上做Android灰色產業的技術手段離不開這幾種
· 協議破解
· 偽造使用者操作
· 偽造使用者資料
我們以其中"偽造使用者資料"這一點為例子來展示一下如何通過技術手段實現用一個手機讓一個app日活過百的。
一個正常的日活
首先我挑選了一個國外的小眾SDK,類似於友盟這種有日活統計功能的。
下面是在我開始刷量之前的資料,
記住哦,從這裡開始我都只用了兩臺手機。

可以看到DAU在3/6之前不超過5(當然這裡面超過2的那些是google play上的其他真實使用者)。
開始破解
首先需要確定的是它用哪個指標來進行使用者唯一性的判斷,
這裡我通過抓包分析它的介面資料如下,
{ "advertiser_id": "966694c4-05fa-4020-a39c-ad3bced26b62", "carrier": "", "custom_id": "", "screen_height": 1920, "screen_width": 1080, "limit_tracking": false, "ln": "zh", "locale": "CN", "device_brand": "Xiaomi", "device_model": "MI MAX", "device_type": "tablet", //省略無關資料 "device_time": 1526006628156, "controller_version": "1.0.9.16", "user_metadata": {}, "device_audio": false, "test_mode": false, "guid": "395fce37-55c3-410e-bdb8-7d20fad3c14e", "guid_key": "395fce37-55c3-410e-bdb8-7d20fad3c14eDUBu6wJ27y6xs7VWmNDw67DD" }
乍看下來沒發現什麼特別的欄位,起碼沒有我想象中應該出現的 device_id這種欄位。
但是我發現了一個熟悉的資料模式
"advertiser_id": "966694c4-05fa-4020-a39c-ad3bced26b62",
做過SDK相關的同學會知道,這個欄位跟UUID一模一樣,
那麼這個UUID是從哪裡來的呢?
進一步猜測
到這裡開始陷入僵局,接下來有兩種可能性,
· sdk在app第一次啟動的時候生成了UUID
· sdk在上報資料的時候從GP取的UUID
做過海外的同學可能瞭解,GP本身也有一個AdvertisingIdClient.Info,可以獲取裝置唯一性識別碼,而這個編碼跟上面介面中的資料長的也是一樣的。
如果是第二種可能的話,要破解就比較麻煩了。
那麼我們從簡單的第一個可能下手。
如果把gms service刪了會怎樣?這樣就能排除 gp的干擾了。
在這個思路下,我把手機的 GMS框架刪掉後重新請求了資料,
這時候的介面資料變成下這樣了,
{ "advertiser_id": "", "device_id": "97b4cf89a32c3ddb08c1ee0be43d827b129a134b", }
上面的介面資料只列出了前後的差異,並不是只有這兩條哦。
可以看到原本的跟UUID一模一樣的欄位變空了,取而代之的是多了個 device_id欄位,
也就是說,
這個SDK 在國內環境下(沒有谷歌的環境)用了另一套機制用來確定裝置唯一性,
然而這裡再次陷入僵局。
這一長串字元是什麼玩意?
山窮水盡疑無路
這個時候開始基本就是靠猜了,
一般用來確定裝置唯一性的資料有這個幾個
· UUID
· android_id(通過 Setttings獲取)
· IMEI/MEID等移動裝置唯一編號
對於上面這幾種可能來說,比較可能的是第二第三個資料,
第一個為什麼被我排除了呢,
因為考慮到 device_id 應該是經過了加密,而UUID加密不出來這樣的欄位,所以我們可以先從第二個可能性開始。
接下來就是確定它的加密方式了!
一般常見的加密方式,不外乎 md5/sha1這些,可以先暴力猜測一下,頂多20分鐘就可以知道結果。
5分鐘後。。。
運氣不錯,當嘗試到 android_id + sha1 組合的時候就得到了介面中的 device_id資料。
資料有了,接下來的思路就是用 Xposed框架來 hook獲取 android_id的介面了,
相對於上面的破解過程,Xposed的程式碼非常簡單,
private void hookAndroidID(XC_LoadPackage.LoadPackageParam lpparam, final DeviceInfo info) { XposedBridge.log("[methodhook] hookAndroidID()"); XposedHelpers.findAndHookMethod(Settings.Secure.class.getName(), lpparam.classLoader, "getString", ContentResolver.class, String.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { XposedBridge.log("[afterHookedMethod] hookAndroidID: " + (String)info.getAndroidId()); param.setResult((String)"whatever you want"); super.afterHookedMethod(param); } }); }
在hook了這個方法之後,每次sdk要去拿 android_id,都會被替換成我們設定進去的假資料。
偽造後的日活
下面來看看在經過破解之後的資料吧,

看看這暴漲100倍的日活。
如何防禦刷量
其實關於破解和加密一直都是魔高一丈道高一尺的博弈,
並沒有能夠完全無解的加密邏輯,有的只是無限提高破解代價的邏輯。
對於上面給出的例子來說,其實只要在介面和唯一性資料的選擇上給出更好的方案就可以避免被刷量了。
比如介面資料加密,不要用 device_id這種顯眼的欄位,
比如用下發祕鑰的方式去和 android_id一起加密,這樣即使拿到其中一個,也猜測不出來加密演算法。
總而言之,Android 的刷量思路基本就跟上面所說的這樣,
但我不鼓勵大家去惡意刷量,希望在平時開發中在敏感資料的設計上多繞幾個彎,這樣能避免被其他人利用。