1. 程式人生 > 實用技巧 >破解一號店的心得

破解一號店的心得

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,再啟指令碼。