破解一號店的心得
1、豌豆莢下載最新版一號店,版本7.0.3,(下載老版本貌似會強制升級)。
2、通過fiddler抓包,這裡我抓了一個根據關鍵字搜尋產品的包。
請求頭跟請求體大致如上,通過傳送http請求發現,返回的json在幾分鐘內會失效,這時候想到裡面有跟時間相關的加密引數來對請求進行校驗。在改變引數的情況下,找到了影響引數。發現url的字尾有一個sign,這時候引數找到了,再通過jadx-gui這個軟體來看原始碼想要找到這個引數是怎麼進行加密的。因為影響結果的引數跟sign有關,所以搜尋跟sign有關的方法,並通過vs code來除錯看整個過程有沒有走這個方法。
通過frida來hook,這個在前面的部落格裡有寫到,這裡就不再說了。下面是hook的程式碼
function hook1(){ /** * .overload() .overload('[B') .overload('[B', '[B') .overload('java.util.Map', 'java.lang.String', 'java.lang.String') */ var sign = Java.use('com.jingdong.jdsdk.network.toolbox.GatewaySignatureHelper');//類名 sign.signature.implementation= function(a,b,c){ console.log('**************start*******************') console.log('加密前:'+a) console.log('加密前b:'+b) console.log('加密前c:'+c) var res = this.signature(a,b,c) console.log('加密後:'+res); console.log('----------------end----------------') console.log(' ') console.log(' ') return res; } } function main(){ Java.perform(function(){ hook1(); }) } setImmediate(main);
這裡有幾個要注意的點,一個類裡面會有很多個相同名字的方法,但是我們hook的時候就需要hook具體的某一個方法就可以,所以如果有兩個以上的同名方法,需要overload一下,參考註釋裡的程式碼;還有就是原始碼中該方法傳了幾個引數,那這裡hook的話就需要傳幾個引數。
在原始碼中找想要的方法也是需要技巧的,一般來說,如果我們直接搜sign那麼可能會出現幾千條跟這個有關的程式碼,所以我們可以加上一個雙引號,這樣就會大大的篩選了結果,方便我們去hook。通過篩選結果可以看到就三十來條,然後點進去,找到是哪個類的哪個方法,將其拷到上面的hook程式碼中,通過手機搜尋關鍵字看控制檯有無結果輸出。(命令frida -U com.thestore.main-l Hook.js)
然後就是很難受的發現這裡面的所有方法都不走....後來通過&sign來搜尋,終於找到了那個加密方法,發現有三個引數影響加密結果,進一步解析發現secretKey是個定值,所有判定只有url跟body進行加密。
通過hook確定了在通過關鍵字搜尋產品的時候,確實走了這個方法進行了加密,但是需要進一步確認,我最終拿到返回的資料,是不是跟我抓包時候的請求結構一樣,是不是可以通過發請求拿到真資料,是不是跟我在APP搜尋返回展示的資料一樣。所以這時候需要遠端呼叫一下,然後本地寫個測試類看下控制檯輸出的結果是不是我想要的。python指令碼如下(指令碼名rpc2.py)
from flask import Flask from flask import request import frida import hashlib import requests import time import json import chardet app = Flask(__name__) def on_message(message, data): if message['type'] == 'send': print(message) else: print(message) script = None def begin(): global script process = frida.get_remote_device().attach('com.thestore.main') # process = frida.get_device_manager().get_device("127.0.0.1:21503").attach('com.thestore.main') with open("rpc2.js",'r',encoding='utf-8') as js: jscode=js.read() script = process.create_script(jscode) script.on('message', on_message) script.load()#載入指令碼完畢 print('1. 載入指令碼完畢,成功獲取script物件.....') app.run(debug=True, port=8004)# 啟動服務 @app.route('/sign', methods=['GET']) def waimai_function(): p1 = request.args.get("p1")#根據加密方法來確定傳幾個參 p2 = request.args.get("p2") print("p1是"+p1) print("p2是"+p2) res = script.exports.wirelesscode(p1,p2)#在這裡傳值,剩下就是開服務的問題了 # res = '{wirelesscode:"'+res+'"}' print(res) return res if __name__ == "__main__": begin()
js程式碼如下(名字為rpc2.js)
rpc.exports = { wirelesscode: function (p1,p2) { var result = '' Java.perform(function () { var sign = Java.use('com.jingdong.jdsdk.network.toolbox.GatewaySignatureHelper');//類名 console.log('來了老弟---'+p1+'-------'+p2); console.log('傳進來的引數:'+p1); console.log('傳進來的引數:'+p2); var c = 'f9b37e4b28e84c169f9d503baaa23b6c';//定值 result = sign.signature(p1,p2,c); console.log(result); }); return result; } };
本地測試類程式碼如下
public class TestKeyword { public static void main(String[] args) throws Exception { String keyword = "潔面乳"; int page = 1; String p2 = URLEncoder.encode("{\"addrFilter\":\"1\",\"apolloId\":\"b3fbf56db4484f42bb8464b94374d5a0\"," + "\"apolloSecret\":\"af200ce75e1a4e188eca5fa90907f4a7\",\"articleEssay\":\"1\"," + "\"deviceidTail\":\"\",\"insertArticle\":\"1\",\"insertScene\":\"1\"," + "\"insertedCount\":\"0\",\"isCorrect\":\"1\",\"keyword\":\""+keyword+"\"," + "\"latitude\":\"0.0\",\"longitude\":\"0.0\",\"newMiddleTag\":\"1\"," + "\"newVersion\":\"3\",\"oneBoxMod\":\"1\",\"orignalSearch\":\"1\"," + "\"orignalSelect\":\"1\",\"page\":\""+page+"\",\"pageEntrance\":\"1\"," + "\"pagesize\":\"10\",\"pvid\":\"\",\"sdkClient\":\"plugin_android\"," + "\"sdkName\":\"search\",\"sdkVersion\":\"1.0.0\",\"searchVersionCode\":\"0\"," + "\"showShopTab\":\"yes\",\"showStoreTab\":\"1\",\"stock\":\"1\"}","utf-8"); String res = "http://localhost:8004/sign?p1="+DecipherUtil.key(keyword, page)+"&p2="+p2; String url = HttpBase.get(res, "utf-8").getResult(); String body = "body="+p2+"&"; Map<String,String> header = new HashMap(); header.put("Charset", "UTF-8"); header.put("Connection", "keep-alive"); header.put("Cache-Control", "no-cache"); header.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); header.put("Content-Length", "927"); header.put("Host", "api.m.jd.com"); header.put("User-Agent", "okhttp/3.12.1"); try { String result = PostUtil.post(url, header, body); System.out.println(result); } catch (ConnectException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
util類程式碼如下
public class DecipherUtil { public static String key(String keyword,int page) throws Exception { String time = System.currentTimeMillis()+""; String p1 = URLEncoder.encode("http://api.m.jd.com/api?appid=member_yhd&functionId=yhd_nsearch" + "&t="+time+"&clientVersion=7.0.3&build=703&client=yhd_android&d_brand=Coolpad" + "&d_model=N3C&osVersion=7.1.1&screen=1344*720&partner=jingdong" + "&lang=zh_CN&uuid=352118197740800-00281a339ed5&area=2_2825_0_0" + "&networkType=wifi&wifiBssid=unknown","utf-8"); String p2 = URLEncoder.encode("{\"addrFilter\":\"1\",\"apolloId\":\"b3fbf56db4484f42bb8464b94374d5a0\"," + "\"apolloSecret\":\"af200ce75e1a4e188eca5fa90907f4a7\",\"articleEssay\":\"1\"," + "\"deviceidTail\":\"\",\"insertArticle\":\"1\",\"insertScene\":\"1\"," + "\"insertedCount\":\"0\",\"isCorrect\":\"1\",\"keyword\":\""+keyword+"\"," + "\"latitude\":\"0.0\",\"longitude\":\"0.0\",\"newMiddleTag\":\"1\"," + "\"newVersion\":\"3\",\"oneBoxMod\":\"1\",\"orignalSearch\":\"1\"," + "\"orignalSelect\":\"1\",\"page\":\""+page+"\",\"pageEntrance\":\"1\"," + "\"pagesize\":\"10\",\"pvid\":\"\",\"sdkClient\":\"plugin_android\"," + "\"sdkName\":\"search\",\"sdkVersion\":\"1.0.0\",\"searchVersionCode\":\"0\"," + "\"showShopTab\":\"yes\",\"showStoreTab\":\"1\",\"stock\":\"1\"}","utf-8"); return p1; } }
其實通過hook那個方法就可以知道,參與加密的無非是搜尋的關鍵字,時間最多加上一個翻頁引數,主要就是看它加密前是怎麼拼接的。一般看到body裡面有很多個%,就會很自然的想到編碼解碼。以前碰到的那些編碼解碼也就是隻有漢字進行編碼,但一號店這個是全部都參與了編碼。
整個過程中要注意的幾個點:
1.在整個hook過程以及本地測試的時候,真機或者模擬器都必須連線電腦,且需要開啟一號店APP,如果hook過程中報有關frida的錯,可能是未給最高許可權,也可能是沒有轉發,這時候把這倆步驟重新弄一遍再啟動frida即可。
2.上面的python指令碼,如果有依賴未下載的話,啟動也是會報錯的,匯入依賴的命令pip install 包名。
3.有時候frida啟動了,一號店這個APP也打開了,但是執行指令碼的時候會報錯,顯示說frida.InvalidArgumentError: device not found,就是我們process=frida.get_devices_manager()這行程式碼寫錯了,換成上面那行就行了,這個也不是不能用這個方法來寫,主要是有時候會出現第一次連線的時候連線不上,之後第二次,第三次就可以了。保險起見用上面那行程式碼。
4. 還會出現服務啟動了,python指令碼也可以執行,但是會出現APP閃退,再次開啟就打開不了了。這時候需要將手機或者模擬器重啟,然後要重新轉發,啟動frida,再開啟一號店APP,再啟指令碼。