zoukankan      html  css  js  c++  java
  • 破解一号店的心得

    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()

    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,再启脚本。

         5.String res = "http://localhost:8004/sign?p1="+DecipherUtil.key(keyword, page)+"&p2="+p2;这一行代码就是起到在本地开个端口调用那个加密方法的作用,端口号无所谓,只要不占用其他的进程就好。如果我们不是在本地调用,而是丢到服务器上的话,那就要在服务器上弄个手机模拟器,然后启动frida,执行python脚本进行远程调用,步骤同上,这样的话我们的ip跟端口号就要变了。

  • 相关阅读:
    高德引擎构建及持续集成技术演进之路
    Java的传值调用
    Java中真的只有值传递么?
    Centos7启动防火墙时报错Failed to start IPv4 firewall with iptables
    Redis入门(四)-Java操作Redis
    SecureCRT远程连接The remote system refused the connection问题
    [需求设计]从一个小需求感受Redis的独特魅力
    Oracle报错ORA-12516 TNS:listener could not find available handler with matching protocol stack
    编译Netty源码遇到的一些问题-缺少io.netty.util.collection包
    Linux使用alias自定义命令自定义快捷键
  • 原文地址:https://www.cnblogs.com/shitechnology/p/13633453.html
Copyright © 2011-2022 走看看