PicaComic介面分析手記
好久沒有更新技術類文章了,不過其實我也有慢慢在寫幾篇文章,然而它們依舊躺在草稿箱。剛好群裡討論寫個Pica客戶端,於是我就來分析下Pica的介面吧。
拆開來一看,竟然沒有混淆……build.gradle改改也沒多大成本吧,雖然給我省事就是了。
抓包得知,Pica的介面使用signature頭以校驗。所以首先要找出signature的計算方法。由於,Pica採用了okhttp3,所以signature的計算邏輯八成會寫在interceptor裡。搜尋addInterceptor,果然只有一處,出現在com.picacomic.fregata.networks.RestClient的構造器裡。
// 區域性變數名稱與部分邏輯有所調整 Request request = chain.request(); String nonce = UUID.randomUUID().toString().replace("-", ""); String path = request.url().toString().replace("https://picaapi.picacomic.com/", ""); String timeStamp = System.currentTimeMillis() / 1000L + PreferenceHelper.getTimeDifference(var1) + ""; String signature = MyApplication.getInstance().getStringCon(new String[]{"https://picaapi.picacomic.com/", path, timeStamp, nonce, request.method(), "C69BAF41DA5ABD1FFEDC6D2FEA56B", RestClient.version, RestClient.buildVersion});
於是順藤摸瓜,看com.picacomic.fregata.MyApplication的getStringCon方法。
public String getStringCon(String[] strs) { if (this.generateSignature == null) { this.generateSignature = new GenerateSignature(); } String rawParams = ""; for (String str : strs) { rawParams += str + ", "; } PrintLog.PrintErrorLog(TAG, "RAW parameters = " + rawParams); String concatParam = this.getStringConFromNative(strs); PrintLog.PrintErrorLog(TAG, "CONCAT parameters = " + concatParam); String concatKey = this.getStringSigFromNative(); PrintLog.PrintErrorLog(TAG, "CONCAT KEY = " + concatKey); return this.generateSignature.getSignature(concatParam, concatKey); }
getStringConFromNative和getStringSigFromNative兩個方法都是native的,那暫且放一遍,先把Java層的getSignature看完。不過邏輯其實也不難猜到,就是轉小寫後HmacSHA256。所以重點還應該是在native層。不過有點挺有趣的,getStringSigFromNative很好理解,但getStringConFromNative怎麼看都像是拼接字串。在這上面做文章還是沒怎麼見過,有意思。
lib名是libJniTest,很優秀。丟進IDA,很順利。找到函式,很順利,畢竟靜態註冊。F5,依舊很順利。嘛,看到lib名也應該能想到的。首先分析Java_com_picacomic_fregata_MyApplication_getStringConFromNative,依舊是當初分析B站App的套路,改型別、重新命名,然後分析邏輯。其實也什麼好分析,首先從陣列中取出對應下標的字串然後GetStringUTFChars,然後就是主要的拼接邏輯。
拼接很直接,就是這個用於判斷的repack_chk_and_genKey10(原名genKey10)需要分析一哈。通過分析,發現這個函式是用來校驗apk簽名的。
然而校驗失敗返回false後,getStringConFromNative依舊可以拼接,而且會變更拼接的方式。至於為什麼這麼做,筆者暫且被蒙在吉他裡。
接下來分析Java_com_picacomic_fregata_MyApplication_getStringSigFromNative。
……
心涼半截。看看彙編。
沒辦法,按照esp的偏移慢慢算咯。當然傻傻的一個個字元改也確實沒效率,所以簡單處理一下資料,寫個py jio本。
char_dic = {0x9: 110, 0x2D: 107, 0x37: 97, 0x3B: 107, 0x40: 114, 0x46: 114, 0xC: 83, 0x14: 85, 0x19: 76, 0x1B: 82, 0x27: 67, 0x28: 69, 0x2C: 75, 0x33: 90, 0x45: 67, 0xD: 57, 0x16: 56, 0x1F: 57, 0x21: 52, 0x23: 51, 0x2F: 55, 0x38: 53, 0x42: 49} int_dic = {0x17: "zf", 0x29: "sl", 0x39: "zk", 0x1D: "PM", 0x31: "BY", 0x35: "BA", 0x0F: "lG", 0x11: "ts", 0x3C: "RB", 0x3E: "L7"} if __name__ == "__main__": offset = 0x8 org_str = "~*}$#,$-\").=$)\",,#/-.'%(;$[,|@/&(#\"~%*!-?*\"-:*!!*,$\"%.&'*|%/*,*" ls = list(org_str) for pos, ch in char_dic.items(): ls[pos - offset] = chr(ch) for pos, ch in int_dic.items(): ls[pos - offset] = ch[0] ls[pos - offset + 1] = ch[1] print("".join(ls))
這放飛自我的編碼風格……嘛,總之算是跑出了結果。另外,校驗失敗同樣會返回另一個key。
簡單彙總一下signature的計算方法:
- 拼接請求路徑(不包含/)+當前時間戳+nonce+請求方法+”C69BAF41DA5ABD1FFEDC6D2FEA56B”
- 轉小寫
- 計算其HmacSHA256,金鑰為”~n}$S9$lGts=U)8zfL/R.PM9;4[3|@/CEsl~Kk!7?BYZ:BAa5zkkRBL7r|1/*Cr”
其中nonce生成的java實現為UUID.randomUUID().toString().replace(“-“, “”);,常見的隨機串生成方式。實現時生成任意32長隨機字串即可。
總體感覺就是有安全意識但是做的很不夠。不過講字串拼接的邏輯放在native層很有趣,而且native層的處理較B站早期的實現也更完善。改天測試下另一種signature有啥不同。