折腾京东APP签名-获取wskey相关签名参数

折腾京东APP签名-获取wskey相关签名参数

orzlee
2021-09-06 / 23 评论 / 5,710 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2022年02月02日,已超过784天没有更新,若内容或图片失效,请留言反馈。

9BJQUycoguMsovYxJ84M.png

前言

前几天随手撸了个自动上传京东open token(open token其实就是用户cookie)的脚本(有兴趣可以看看:京东cookie自动更新维护),虽然可以自动上传了,但是每天都得去打开京东APP才行,而且两次打开时间不能超过24小时,不然open token就过期了。这方法怎么看都不是很省事!

最近一直在找替代方法,发现Zy143L大佬脚本中提到wskey可以申请open token,wskey有效期应该比较长,我使用好几天之前抓包到的wskey依然可以获取到open token。wskey在APP抓包中经常看到,使用wskey的请求里面都有签名,感觉签名方法应该是一样的。通过genToken的接口获取open token,那个签名方法虽然短时间可以重复使用(请求只需要变动wskey就好,签名没有加入wskey做计算),但是还不清楚有效期,如果一直靠抓包获取签名的话那比上面直接抓包open token还恶心!

分析

wskey很重要,京东APP很多敏感接口都是使用wskey,不要泄露!!!

既然可以通过wskey获取open token,wskey有效期有比较长,那么它是一个很好代替每天上传open token的方法。

我的思路:

  1. 不定期通过手机app抓包wskey上传到服务器,服务器接收到后写入wskeys.list。
  2. 每天定时通过wskey换取open token(open token 24小时有效期,应该每天获取两次比较保险),并且写入cookies.list。
  3. 目前还不清楚wskey有效期,所以在第二步中wskey过期及时发送通知以便重新上传wskey。

经过一下午折腾,确实可行。但是看Zy143L大佬源码发现genToken接口中的签名是请求coding.net返回的(应该是大佬自建部署的吧!)!这个动作引起了我的好奇心,没有公布签名算法,究竟是何等高级的玩意儿?请求没有任何数据,就一个get请求即可返回所有签名参数,难道签名参与计算的值都是固定的吗?

这类资料比较少,不过还是有乐于分享的大佬。找到几篇文章:

对于我来说难度比较大,涉及知识面广,不过如果有信心那就没问题。

我总结了一下经过步骤:

  1. 首先得搭建一个android开发环境。
  2. 下载京东安卓APP,apk文件可以当作压缩文件打开,从lib里面提取出libjdbitmapkit.so。
  3. ida修改libjdbitmapkit.so文件,需要修改一些地方屏蔽错误。
  4. 使用unidbg载入libjdbitmapkit.so,调用签名方法签名。
  5. 打包编译,部署到服务器。

步骤虽然简单,但是操作过程对于一个从来没有接触过andriod、汇编的人来说每一步都掉坑!!!

过大坑纪要

  • Exception in thread "main" java.lang.IllegalStateException: Illegal JNI version: 0xffffffff,这个问题坑了我好久,起初我以为自己环境有问题,因为根本找不到什么资料。其实就是so文件问题,如果你没有使用unidbg-0.9.1版本的话会出现这个错误,引发错误的根本原因就是先按借鸡生蛋之某电商App签名so的使用(三)对so文件打补丁,如果按上文推荐的文章顺序阅读那么就会被卡住,建议全部读一遍后再动手。
    error-1.png

  • 打补丁使用的ida软件,对于汇编是一脸懵逼的,借鸡生蛋之某电商App签名so的使用(三)一文中把修改位置写得很明白了,但是ida真心不会用,其中包括安装ida插件:

    1. keystone中下载msi安装文件,Version 0.9.1有提供32位和64位装一个就行。安装过程一定要勾选安装pthon2.7,我就取消了,最后插件无法使用,还是自己手动去安装的,如果你也是手动安装记得安装six模块:pip install six

    2. keypatch代码复制,到ida plugins目录建一个新文件Keypatch.py,把代码复制进去。

    3. idaOptions-General-Disassembly-Number of opcode bytes设置成16,以十六进制显示字节码:
      ida-option.png

    4. 使用keypatch插件修改sourceDir部分,JNI_OnLoad部分使用ida功能Edit-Patch program-Change byte就好了。000037CC 04 25 C0 F2 01 05 MOVS R5, #0x10004这段我是用keypatch一直报错,浪费一两个小时,使用Patch program替换中间字节就行了:
      ida-patch-bytes.png
      修改摘要:

       00002D08 40 F0 40 81 BNE.W loc_2F8C      -> 00002D08 40 E1 B loc_2F8C
       00002F8C 6F F0 02 00 MOV R0, #0xFFFFFFFD -> 00002F8C 4F F0 01 00 MOV.W R0, \#1
       000037C8 FF F7 96 F9 BL check_status     -> 000037C8 FF F7 96 F9 BL check_status
      
       000037C8 FF F7 96 F9 BL check_status
       000037CC 80 46 MOV R8, R0
       000037CE 00 28 CMP R0, \#0
       000037D0 67 D1 BNE loc_38A2
        -> 
       000037C8 FF F7 96 F9 BL check_status
       000037CC 04 25 C0 F2 01 05 MOVS R5, #0x10004
       000037D2 2A E0 B loc_382A;
  • 打包一定要看Unidbg使用指南(一),所有操作都是在test文件夹完成的,不然会缺少包,装又装不上!打包也要载入test文件夹!

搭建服务

如果你能坚持到这步就已经成功了,只要能获取到签名参数想怎么玩就怎么玩!我还是用http服务比较顺手,网上找了个java socket代码一顿改!!!

JD app sign 加密参数破解 - unidbg文中代码基础上修改,加入socket模拟http:

...
public static void service(final Socket socket, final JingDong jd) {
        InputStream inSocket;
        try {
            //获取HTTP请求头
            inSocket = socket.getInputStream();
            int size = inSocket.available();
            byte[] buffer = new byte[size];
            inSocket.read(buffer);
            String request = new String(buffer);
            System.out.println("ClientBrowser:\n" + request + "\n"
                    + "------------------------------------------------------------------");
            String httpCode = "200 OK";
            String result = "";
            if (request.length() > 0) {
                try {
                    JSONObject data = JSON.parseObject(request.substring(request.lastIndexOf("\r\n")).trim());
                    if (data != null) {
                        String uuid = data.getString("uuid");
                        String functionId = data.getString("functionId");
                        try {
                            String body = new URLCodec().decode(data.getString("body"));
                            String version = data.getString("version");
                            if (!uuid.isEmpty() && !functionId.isEmpty() && !body.isEmpty() && !version.isEmpty()) {
                                result = convert(jd.runJni(uuid, functionId, body, version));
                                System.out.println(result);
                                System.out.println(convert(result));
                            }
                        } catch (DecoderException e) {
                            e.printStackTrace();
                        }
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                    result = "{\"code\": \"400\", \"message\": \"Invalid param\"}";
                    httpCode = "400 Bad Request";
                }
            }
            //将响应头发送给客户端
            String responseFirstLine = "HTTP/1.1 " + httpCode + "\r\n";
            String responseHead = "Content-Type:application/json\r\n";
            OutputStream outSocket = socket.getOutputStream();
            System.out.println("ServerResponse:\n" + responseFirstLine + "\n" + responseHead + "\n" + result + "\n"
                    + "--------------------------------------------------------------------");
            System.out.println("current thread:" + Thread.currentThread().getName());
            System.out.println("threads count:" + Thread.activeCount());
            outSocket.write(responseFirstLine.getBytes());
            outSocket.write(responseHead.getBytes());
            outSocket.write("\r\n".getBytes());
            outSocket.write(result.getBytes());
            outSocket.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws IOException {
        //獲取參數,服務監聽地址和端口
        //java -Daddress=0.0.0.0 -Dport=9999 -jar unidbg-android.jar
        address = System.getProperty("address", "127.0.0.1");
        port = Integer.parseInt(System.getProperty("port", "8668"));
        threadCount = Integer.parseInt(System.getProperty("thread", "10"));
        //綫程池
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
        final ServerSocket serverSocket;
        final JingDong jd = new JingDong();
        try {
            //建立服务器Socket,监听客户端请求
            serverSocket = new ServerSocket(port, 50, InetAddress.getByName(address));
            System.out.println("Server is running on " + serverSocket.getLocalSocketAddress());
            //死循环不间断监听客户端请求
            while (true) {
                final Socket socket = serverSocket.accept();
                System.out.println("biuld a new tcp link with client,the cient address:" +
                        socket.getInetAddress() + ":" + socket.getPort());
                //并发处理HTTP客户端请求
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        service(socket, jd);
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            jd.destroy();
            executorService.shutdown();
        }
    }
...

网上七拼八凑折腾出来这么个玩意,使用:

java -Daddress=127.0.0.1 -Dport=8668 -DthreadCount=5 -jar xxx.jar

address:绑定地址,port:绑定端口,threadCount:线城数量。

请求参数:

//没有限制请求方法,GET POST随意,以下是请求body
{"uuid":"随机字符串,随机生成就行了","functionId":"接口(genToken)","body":"URI编码的json,不要带任何多余东西","version":"安卓版本"}

//返回结果
{
    "uuid": "9d53afe389f6ae5f",
    "st": "1630922926986",
    "sign": "0a9b72a4fc6ffd5d799642dd295623f8",
    "sv": "111"
}

然后外面再套一层nginx反代,或者和自己jd_script同服务器,内网请求。你要是绑定0.0.0.0暴漏出去也行!

结语

为了个签名折腾了好久,整个签名使用了functionId,body,uuid,client,clientVersion五个参数,client基本可以固定android了。使用ida汇编长了不少知识。

至此应该是不用在操心京东登陆方面的问题了,只要使用京东APP全部都自动更新了!

0
取消
扫码打赏
支付金额随意哦!

评论 (23)

取消
  1. 头像
    KiNaki
    Windows 10 · Google Chrome

    可能JD 觉得大数据风控就够了 导致最新版的JD算法还是没变

    回复
    1. 头像
      orzlee 作者
      Linux · Google Chrome
      @ KiNaki

      我觉得不可能大改,除非有更安全高效的算法!再一个无论怎么变都可以被逆向的,只要算法是安全的就没有必要花费太多精力在这上面!

      回复
  2. 头像
    DD
    Windows 10 · Google Chrome

    Sign计算是计算出来了,但提交后返回的是{"code":"600","echo":"signature verification failed"}”,这是什么问题呢

    回复
    1. 头像
      orzlee 作者
      Linux · Google Chrome
      @ DD

      签名算法变了!

      回复
      1. 头像
        DD
        Windows 10 · Google Chrome
        @ orzlee

        原来如此,新的签名算法你研究出来了吗

        回复
        1. 头像
          orzlee 作者
          Linux · Google Chrome
          @ DD

          https://github.com/Zy143L/wskey 有现成的用!

          回复
  3. 头像
    DBOT
    Linux · Google Chrome

    大佬,你自己算SIGN的API可以分享下吗~~

    回复
    1. 头像
      orzlee 作者
      Linux · Google Chrome
      @ DBOT

      签名算法已经变了。https://github.com/Zy143L/wskey 这个项目里面有,看代码!

      回复
      1. 头像
        DBOT
        Linux · Google Chrome
        @ orzlee

        (agent) [408443] Arguments com.jingdong.common.utils.BitmapkitUtils.getSignFromJni(com.jingdong.app.mall.JDApp@944bdee, getShopHomeActivityInfo, {"RNVersion":"0.59.9","abtest":"","cid":"17105374","cname":"米面粮油","complexSource":"app-productDetail","lat":"25.074035","latWs":"25.074263","lng":"101.499347","lngWs":"101.499748","shopId":"11317694","source":"app-s,"sourceRpc":"shop_app_home_allProduct","venderId":"11578734"}, f7b5b779d8eed0ef, android, 10.3.5)
        (agent) [408443] Return Value: st=1643911743825&sign=57ba1527014b975f01df1ae53845942e&sv=101
        (agent) [408443] Return Value: st=1643911743826&sign=86fd7056104f2b85a2ba68ee24888e3b&sv=122

        大佬我目前到这一步,getSignFromJni函数算出来的不行了吗?
        主要想 有了SGIN 配合监听 谢谢 京东活动的脚本

        回复
        1. 头像
          orzlee 作者
          Linux · Google Chrome
          @ DBOT

          这个应该是没变,但是在这个基础上加强了!

          回复
          1. 头像
            DBOT
            Linux · Google Chrome
            @ orzlee

            我先试试大佬 有问题和你探讨~~~~

            回复
            1. 头像
              mayuu酱重症患者
              Android Oreo · Google Chrome
              @ DBOT

              时隔三个多月,挖下,打扰了,老哥表情

              不知老哥你之后研究完jd app的签名算法了么??才入坑逆向不到一个月,有些急于求成,近一段时间拿jd 来练手,才翻到大佬的这篇文章,继而看到老哥你在2月初发的评论提问。。。

              如果老哥之后解决了“getSignFromJni函数”这部分算不出来的问题的话,麻烦分享下最新的解决方法呗;亦或者老哥你将研究出的解决方法分享到你的个人博客 或你的github 个人仓库的话,麻烦指个路呗

              回复
              1. 头像
                orzlee 作者
                Linux · Google Chrome
                @ mayuu酱重症患者

                我没有研究签名算法了,因为有大佬分享了他做好的api!签名算法也不是我逆向出来的,文章中有提到我参考的文章,最新的他也逆向出来了,因该是变种base64!具体你可以参考他的文章。

                回复
  4. 头像
    DOD
    Windows 10 · Google Chrome

    你好 这边我也是出现了和你一样的问题
    Exception in thread "main" java.lang.IllegalStateException: Illegal JNI version: 0xffffffff
    so文件也是按照大佬的步骤改的 需要添加的环境也添加了 老是报这个错

    回复
    1. 头像
      orzlee 作者
      Linux · Google Chrome
      @ DOD

      要对so文件打补丁

      回复
  5. 头像
    phoenix
    Windows 10 · Google Chrome

    你好,你最后Unidbg使用的还是0.9.1版本是吧?最新版是不是还是不行?

    回复
  6. 头像
    phoenix
    Windows 10 · Google Chrome

    你好,patch好的so文件方便发一下吗?我老是改不对

    回复
  7. 头像
    DJD
    Windows 10 · Google Chrome

    大佬,Convert method 代码可以贴而一下吗

    回复
  8. 头像
    KIKI
    MacOS · Google Chrome

    能否透露下app版本..我拿10.X的改..ida里面改完了..但是实际还是会报错

    回复
    1. 头像
      orzlee 作者
      Linux · Google Chrome
      @ KIKI

      我都是下最新版本

      回复
  9. 头像
    ee
    Windows 10 · Google Chrome

    最新的版本10.1.6 有新的变化了吗?怎么总是通不过

    回复
    1. 头像
      orzlee 作者
      Linux · Google Chrome
      @ ee

      什么东西版本10.1.6?

      回复
  10. 头像
    sos
    Windows 10 · Google Chrome

    大佬,可以给份服务器源码吗

    回复