当前位置: 首页 > news >正文

京东协议算法最新版

环境准备

1

com.jingdong.app.mall==11.6.4

入口定位

逆向分析,发现 params 里面有一个 sign 以及请求头里面有一个 jdgs

首先我们发现京东的 sign 是 32 位的,猜测其可能是 md5 之类的 hash 算法,既然是 hash 算法,那么就大概率会用到 getBytes 方法,我们首先 hook 一下 java.lang.String 的 getBytes 方法,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

// 获取 Java 的字符串

    const str = Java.use('java.lang.String');

    // 重载  getBytes 方法 (因为常用的 Hash 算法一般都会调用字符串的 getBytes 方法)

    str.getBytes.overload().implementation = function () {

        var response = this.getBytes()

        var str1 = this.toString();

        // 如果 string 里面包含了 functionId (其他关键字也可以,需要自己尝试)

        if (str1.indexOf("functionId") >= 0) {

            // 输出找到了字符串

            console.log("find string:", str1);

            // 打印一下堆栈

                console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));

        }

        return response;

    }

使用 firda 加载脚本后进入详情页就发现出现了以下堆栈

1

2

3

4

5

6

7

8

9

10

11

12

13

14

java.lang.Throwable

        at java.lang.String.getBytes(Native Method)

        at com.jd.security.jdguard.a.e.o(SourceFile:15)

        at com.jd.security.jdguard.a.e.i(SourceFile:3)

        at com.jd.security.jdguard.a.c.a(SourceFile:10)

        at com.jd.security.jdguard.a.c.b(SourceFile:4)

        at com.jingdong.common.guard.JDGuardHelper$1.genSign(SourceFile:1)

        at com.jingdong.jdsdk.network.toolbox.HttpSettingTool.doSignUsingJdGuard(SourceFile:10)

        at com.jingdong.jdsdk.network.toolbox.ParamBuilderForJDMall.setupParams(SourceFile:23)

        at com.jingdong.jdsdk.network.toolbox.HttpSettingTool.setupParams(SourceFile:2)

        at com.jingdong.jdsdk.network.toolbox.HttpGroupAdapter$RequestTask.run(SourceFile:14)

        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)

        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)

        at java.lang.Thread.run(Thread.java:919)

通过以上堆栈我们发现了疑似入口为:com.jingdong.common.guard.JDGuardHelper$1.genSign, jadx 源码如下:

1

2

3

public Map<String, String> genSign(URI uri, byte[] bArr, String str, String str2, boolean z) {

    return c.b(uri, bArr, str, str2, z);

}

因此我们再次 hook 验证,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

const jString = Java.use("java.lang.String");

let IJDGuardPlugin = Java.use("com.jingdong.common.guard.JDGuardHelper$1");

IJDGuardPlugin["genSign"].implementation = function (uri, bArr, str, str2, z) {

    let result = this["genSign"](uri, bArr, str, str2, z);

    let mapStr = JSONObject.$new(result).toString()

    console.log("======== IJDGuardPlugin.genSign is called ==========")

    console.log(`uri = ${uri}`)

    console.log(`bArr string = ${jString.$new(bArr)}`)

    console.log(`str  = ${str}`)

    console.log(`str2  = ${str2}`)

    console.log(`z  = ${z}`)

    console.log(`mapStr  = ${mapStr}\n\n`)

    return result;

};

1

2

3

4

5

6

7

8

9

10

11

12

13

14

uri = https://api.m.jd.com/client.action?functionId=getLegoWareDetailComment&lmt=0&clientVersion=11.6.4&build=98704&client=android&partner=tencent&oaid=1B4220DEA49487D7AF8DE6AF4C0F1F60F05819E2ADCC3CD2ACDF3C07D81BEDC6&eid=eidAf5088122acsfbxEYf1ulTs2pUn38cGRLh28RGgYgyPwz80gu9s7yfbXAvbZ1R70WQm1PENZaDGd6EF/munBEHRDWPW5sYgYPelPhS+vYueanoeJv&sdkVersion=29&lang=zh_CN&harmonyOs=0&networkType=wifi&uemps=2-2-2&ext=%7B%22prstate%22%3A%220%22%2C%22pvcStu%22%3A%221%22%2C%22cfgExt%22%3A%22%7B%5C%22privacyOffline%5C%22%3A%5C%220%5C%22%7D%22%7D&ef=1&ep=%7B%22hdid%22%3A%22JM9F1ywUPwflvMIpYPok0tt5k9kW4ArJEU3lfLhxBqw%3D%22%2C%22ts%22%3A1678345151525%2C%22ridx%22%3A-1%2C%22cipher%22%3A%7B%22area%22%3A%22CV83Cv8yDzu5XzK%3D%22%2C%22d_model%22%3A%22J05PUOnVU0O2CNKm%22%2C%22wifiBssid%22%3A%22dW5hbw93bq%3D%3D%22%2C%22osVersion%22%3A%22CJK%3D%22%2C%22d_brand%22%3A%22J25vUQn1cm%3D%3D%22%2C%22screen%22%3A%22CtO1EIenCNqm%22%2C%22uuid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%2C%22aid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%2C%22openudid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%7D%2C%22ciphertype%22%3A5%2C%22version%22%3A%221.2.0%22%2C%22appname%22%3A%22com.jingdong.app.mall%22%7D&st=1678346289851&sign=4bcf4d55a72e4023eebcf2d80c0bac2c&sv=111

bArr string = {"category":"12218;12221;13554","commentNum":3,"isNew":"0","newTitle":null,"shadowMainSku":"0","shieldCurrentComment":"1","shopId":null,"shopType":"0","sku":"10064651465940","venderId":"12715063","wareType":"0"}

str  = application/x-www-form-urlencoded; charset=UTF-8

str2  = Post

z  = true

mapStr  = {"jdgs":"{\"b1\":\"a4574221-958f-4752-ba5b-40e53bed1c17\",\"b2\":\"3.1.4-grey_0\",\"b3\":\"2.0\",\"b4\":\"eDIaHBTaPBKuc86zzA+aq9kltjVpsfYK9uL9uBZTn64tmAtrIPjdnnSaRU5oYnqGZrpwAfVY8PxsVG6T0BM07qPisYg5ffIXkrDYnM9W1bnZyY9uOnXSCSgwMt369HcipuC56Bh3OoP2\/8dGTfb1IJTRCj8s5o12Js+W4id6RKGen4q52iF74F+bl3fA5Zsl2Z3fg96JTVf7nAAIDvXiJBacMEMzWVBblzJMwjNENwZs2SvOB\/b6XSr5lrdlAtHgSytyL7ME5ftn+flvL2nJwH3CQ7AanNGaKFCaZPzjV0sU3+Q29NONh1SBjFDMGX\/PgA9QZmrQj14raPf7w7t\/QsdjjBPix+jbGr5KuPGOVaVBWSKhzh0s3Wkqhe5PqkjkYOUM3DAFGb7d1fNTXlZJUsI8Dsqin8a\/iXLb4RU=\",\"b5\":\"a49356adf69f2937aa764b661c2982d202ec6003\",\"b7\":\"1678346289856\",\"b6\":\"3e5485b2ee5c1e3a55e6ab35606728264d787a0f\"}"}

使用 firda 重新加载 js 脚本后发现其返回值居然是请求头里面的 jdgs,并不是我们需要的 sign,并且里面的参数,也就是 uri 里面包含了我们需要的 sign 参数,所以 sign 应该是在获取 jdgs 之前就生成了!

图片描述

图片描述

那么我们尝试定位上一级堆栈,也就是 com.jingdong.jdsdk.network.toolbox.HttpSettingTool.doSignUsingJdGuard ,源码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

public static void doSignUsingJdGuard(HttpSetting httpSetting, String str) {

    URI uri;

    if (TextUtils.isEmpty(httpSetting.getFunctionId()) || JDHttpTookit.getEngine().getJdGuardPlugin() == null || !JDHttpTookit.getEngine().getJdGuardPlugin().isEnable() || !JDHttpTookit.getEngine().getJdGuardPlugin().isInWhiteList(httpSetting.getFunctionId())) {

        return;

    }

    String str2 = httpSetting.isPost() ? UrlRequest.HTTP_METHOD_POST : UrlRequest.HTTP_METHOD_GET;

    boolean isPost = httpSetting.isPost();

    Map<String, String> map = null;

    String str3 = isPost ? ParamEncodeUtil.DEFAULT_CONTENT_TYPE : null;

    try {

        uri = new URI(httpSetting.getUrl());

    } catch (URISyntaxException unused) {

        uri = null;

    }

    try {

        map = JDHttpTookit.getEngine().getJdGuardPlugin().genSign(uri, !TextUtils.isEmpty(str) ? str.getBytes() : null, str3, str2, isPost);

    } catch (Throwable unused2) {

    }

    if (map == null || map.isEmpty()) {

        return;

    }

    Map<String, String> headerMap = httpSetting.getHeaderMap();

    if (headerMap == null || headerMap.isEmpty()) {

        headerMap = new HashMap<>();

    }

    headerMap.putAll(map);

    httpSetting.setHeaderMap(headerMap);

}

我们可以发现此时的 uri 里面就有的 sign ,因此我们继续定位上层 com.jingdong.jdsdk.network.toolbox.ParamBuilderForJDMall.setupParams ,源码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

public static void setupParams(HttpRequest httpRequest) {

        String str;

        HttpSetting httpSetting = HttpSettingTool.setupBaseParams(httpRequest);

        HttpSettingTool.addGuardVerifyLmtCode(httpSetting);

        if (httpSetting.getCustomMapParam() == null || !httpSetting.getCustomMapParam().containsKey("uuid")) {

            str = "";

        else {

            str = httpSetting.getCustomMapParam().get("uuid");

            httpSetting.getCustomMapParam().remove("uuid");

        }

        if (TextUtils.isEmpty(str) && httpSetting.getCustomEncryptMapParam() != null && httpSetting.getCustomEncryptMapParam().containsKey("uuid")) {

            str = httpSetting.getCustomEncryptMapParam().get("uuid");

            httpSetting.getCustomEncryptMapParam().remove("uuid");

        }

        if (TextUtils.isEmpty(str)) {

            str = JDHttpTookit.getEngine().getStatInfoConfigImpl().getDeviceUUID(httpSetting.getFunctionId(), httpSetting.isEnableEncryptTransmission());

        }

        if (TextUtils.isEmpty(str)) {

            str = "unknow";

        }

        if (OKLog.D) {

            String str2 = TAG;

            OKLog.d(str2, "id:" + httpSetting.getId() + "- uuid -->> " + str);

        }

        String bodyParam = HttpSettingTool.getBodyParam(httpSetting);

        if (httpSetting.getType() == 6000) {

            if (RuntimeConfigHelper.advertiseStatDataEnable()) {

                HttpSettingTool.addStatQueryParam(httpSetting, bodyParam, str);

            }

        else {

            HttpSettingTool.addStatQueryParam(httpSetting, bodyParam, str);

        }

        HttpSettingTool.addCustomQueryParam(httpSetting);

        if (JDHttpTookit.getEngine().isNeedVerifySignature()) {

            JDHttpTookit.getEngine().getSignatureHandlerImpl().networkSettingsPreSignature();

            signature(httpSetting, bodyParam, str);

        }

        HttpSettingTool.doSignUsingJdGuard(httpSetting, bodyParam);

        JDHttpTookit.getEngine().getExternalDebugConfigImpl().addMockerIdName(httpSetting);

    }

发现这个函数是设置 httpSetting 的 参数的,貌似就是我们需要的!我们看一下有没有关键的代码!发现了这么一行代码:signature(httpSetting, bodyParam, str); ,很可疑,我们先来尝试 hook 验证加密入口是否正确,先尝试 hook 一下 com.jingdong.jdsdk.network.toolbox.ParamBuilderForJDMall.signature 这个函数吧,因为这个函数是没有返回值的,所以我们尝试输出一下调用前与调用后的 httpSetting 的 url 不就知道他有没有给 url 加上 sign 了吗?代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

Java.perform(() => {

    let ParamBuilderForJDMall = Java.use("com.jingdong.jdsdk.network.toolbox.ParamBuilderForJDMall");

    ParamBuilderForJDMall["signature"].implementation = function (httpSetting, str, str2) {

        console.log("========= ParamBuilderForJDMall.signature =========")

        console.log(`before httpSetting=${httpSetting}`)

        console.log(`before httpSetting uri=${httpSetting.getUrl()}`)

        console.log(`str=${str}`)

        console.log(`str2=${str2}`)

        this["signature"](httpSetting, str, str2);

        console.log(`after httpSetting=${httpSetting}`)

        console.log(`after httpSetting uri=${httpSetting.getUrl()`)

    };

})

图片描述

果然发现这就是关键入口,因为之前的 uri 里面是没有 sign 这个参数的,但是后面却有了,因此我们就追进去看看,发现了这么一行代码:String signature = JDHttpTookit.getEngine().getSignatureHandlerImpl().signature(JDHttpTookit.getEngine().getApplicationContext(), functionId, str, str2, property, versionName); ,继续跟进到 signature 里面去发现他只是一个接口,所以肯定有实现或者重载的地方!(这种情况,我们一般 hook 不到,因为位置不正确,实际调用的是其实现类的那个函数,不信你可以试试!)

那么我们应该如何找这个接口的实现呢?这边提供一个思路,因为实现的话肯定要使用同样的函数名,返回值以及参数,所以可以尝试直接搜索,例如当前函数定义为 String signature(Context context, String str, String str2, String str3, String str4, String str5);,那么我们就直接将这个作为关键字进行搜索(当然你也可以尝试查找用例,不过应该找不到)

通过搜索我们找到了如下结果:

图片描述

一个个去找就能发现真正的加密入口了:

1

2

3

4

@Override // com.jingdong.jdsdk.network.dependency.ISignatureHandler

public String signature(Context context, String str, String str2, String str3, String str4, String str5) {

    return BitmapkitUtils.getSignFromJni(context, str, str2, str3, str4, str5);

}

再继续跟进去就能发现这个是一个 native 函数 (其实从名字就能看出来),所以 sign 的 jni 入口为:com.jingdong.common.utils.BitmapkitUtils.getSignFromJni

环境准备

1

com.jingdong.app.mall==11.6.4

入口定位

逆向分析,发现 params 里面有一个 sign 以及请求头里面有一个 jdgs

首先我们发现京东的 sign 是 32 位的,猜测其可能是 md5 之类的 hash 算法,既然是 hash 算法,那么就大概率会用到 getBytes 方法,我们首先 hook 一下 java.lang.String 的 getBytes 方法,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

// 获取 Java 的字符串

    const str = Java.use('java.lang.String');

    // 重载  getBytes 方法 (因为常用的 Hash 算法一般都会调用字符串的 getBytes 方法)

    str.getBytes.overload().implementation = function () {

        var response = this.getBytes()

        var str1 = this.toString();

        // 如果 string 里面包含了 functionId (其他关键字也可以,需要自己尝试)

        if (str1.indexOf("functionId") >= 0) {

            // 输出找到了字符串

            console.log("find string:", str1);

            // 打印一下堆栈

                console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));

        }

        return response;

    }

使用 firda 加载脚本后进入详情页就发现出现了以下堆栈

1

2

3

4

5

6

7

8

9

10

11

12

13

14

java.lang.Throwable

        at java.lang.String.getBytes(Native Method)

        at com.jd.security.jdguard.a.e.o(SourceFile:15)

        at com.jd.security.jdguard.a.e.i(SourceFile:3)

        at com.jd.security.jdguard.a.c.a(SourceFile:10)

        at com.jd.security.jdguard.a.c.b(SourceFile:4)

        at com.jingdong.common.guard.JDGuardHelper$1.genSign(SourceFile:1)

        at com.jingdong.jdsdk.network.toolbox.HttpSettingTool.doSignUsingJdGuard(SourceFile:10)

        at com.jingdong.jdsdk.network.toolbox.ParamBuilderForJDMall.setupParams(SourceFile:23)

        at com.jingdong.jdsdk.network.toolbox.HttpSettingTool.setupParams(SourceFile:2)

        at com.jingdong.jdsdk.network.toolbox.HttpGroupAdapter$RequestTask.run(SourceFile:14)

        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)

        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)

        at java.lang.Thread.run(Thread.java:919)

通过以上堆栈我们发现了疑似入口为:com.jingdong.common.guard.JDGuardHelper$1.genSign, jadx 源码如下:

1

2

3

public Map<String, String> genSign(URI uri, byte[] bArr, String str, String str2, boolean z) {

    return c.b(uri, bArr, str, str2, z);

}

因此我们再次 hook 验证,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

const jString = Java.use("java.lang.String");

let IJDGuardPlugin = Java.use("com.jingdong.common.guard.JDGuardHelper$1");

IJDGuardPlugin["genSign"].implementation = function (uri, bArr, str, str2, z) {

    let result = this["genSign"](uri, bArr, str, str2, z);

    let mapStr = JSONObject.$new(result).toString()

    console.log("======== IJDGuardPlugin.genSign is called ==========")

    console.log(`uri = ${uri}`)

    console.log(`bArr string = ${jString.$new(bArr)}`)

    console.log(`str  = ${str}`)

    console.log(`str2  = ${str2}`)

    console.log(`z  = ${z}`)

    console.log(`mapStr  = ${mapStr}\n\n`)

    return result;

};

1

2

3

4

5

6

7

8

9

10

11

12

13

14

uri = https://api.m.jd.com/client.action?functionId=getLegoWareDetailComment&lmt=0&clientVersion=11.6.4&build=98704&client=android&partner=tencent&oaid=1B4220DEA49487D7AF8DE6AF4C0F1F60F05819E2ADCC3CD2ACDF3C07D81BEDC6&eid=eidAf5088122acsfbxEYf1ulTs2pUn38cGRLh28RGgYgyPwz80gu9s7yfbXAvbZ1R70WQm1PENZaDGd6EF/munBEHRDWPW5sYgYPelPhS+vYueanoeJv&sdkVersion=29&lang=zh_CN&harmonyOs=0&networkType=wifi&uemps=2-2-2&ext=%7B%22prstate%22%3A%220%22%2C%22pvcStu%22%3A%221%22%2C%22cfgExt%22%3A%22%7B%5C%22privacyOffline%5C%22%3A%5C%220%5C%22%7D%22%7D&ef=1&ep=%7B%22hdid%22%3A%22JM9F1ywUPwflvMIpYPok0tt5k9kW4ArJEU3lfLhxBqw%3D%22%2C%22ts%22%3A1678345151525%2C%22ridx%22%3A-1%2C%22cipher%22%3A%7B%22area%22%3A%22CV83Cv8yDzu5XzK%3D%22%2C%22d_model%22%3A%22J05PUOnVU0O2CNKm%22%2C%22wifiBssid%22%3A%22dW5hbw93bq%3D%3D%22%2C%22osVersion%22%3A%22CJK%3D%22%2C%22d_brand%22%3A%22J25vUQn1cm%3D%3D%22%2C%22screen%22%3A%22CtO1EIenCNqm%22%2C%22uuid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%2C%22aid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%2C%22openudid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%7D%2C%22ciphertype%22%3A5%2C%22version%22%3A%221.2.0%22%2C%22appname%22%3A%22com.jingdong.app.mall%22%7D&st=1678346289851&sign=4bcf4d55a72e4023eebcf2d80c0bac2c&sv=111

bArr string = {"category":"12218;12221;13554","commentNum":3,"isNew":"0","newTitle":null,"shadowMainSku":"0","shieldCurrentComment":"1","shopId":null,"shopType":"0","sku":"10064651465940","venderId":"12715063","wareType":"0"}

str  = application/x-www-form-urlencoded; charset=UTF-8

str2  = Post

z  = true

mapStr  = {"jdgs":"{\"b1\":\"a4574221-958f-4752-ba5b-40e53bed1c17\",\"b2\":\"3.1.4-grey_0\",\"b3\":\"2.0\",\"b4\":\"eDIaHBTaPBKuc86zzA+aq9kltjVpsfYK9uL9uBZTn64tmAtrIPjdnnSaRU5oYnqGZrpwAfVY8PxsVG6T0BM07qPisYg5ffIXkrDYnM9W1bnZyY9uOnXSCSgwMt369HcipuC56Bh3OoP2\/8dGTfb1IJTRCj8s5o12Js+W4id6RKGen4q52iF74F+bl3fA5Zsl2Z3fg96JTVf7nAAIDvXiJBacMEMzWVBblzJMwjNENwZs2SvOB\/b6XSr5lrdlAtHgSytyL7ME5ftn+flvL2nJwH3CQ7AanNGaKFCaZPzjV0sU3+Q29NONh1SBjFDMGX\/PgA9QZmrQj14raPf7w7t\/QsdjjBPix+jbGr5KuPGOVaVBWSKhzh0s3Wkqhe5PqkjkYOUM3DAFGb7d1fNTXlZJUsI8Dsqin8a\/iXLb4RU=\",\"b5\":\"a49356adf69f2937aa764b661c2982d202ec6003\",\"b7\":\"1678346289856\",\"b6\":\"3e5485b2ee5c1e3a55e6ab35606728264d787a0f\"}"}

使用 firda 重新加载 js 脚本后发现其返回值居然是请求头里面的 jdgs,并不是我们需要的 sign,并且里面的参数,也就是 uri 里面包含了我们需要的 sign 参数,所以 sign 应该是在获取 jdgs 之前就生成了!

图片描述

图片描述

那么我们尝试定位上一级堆栈,也就是 com.jingdong.jdsdk.network.toolbox.HttpSettingTool.doSignUsingJdGuard ,源码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

public static void doSignUsingJdGuard(HttpSetting httpSetting, String str) {

    URI uri;

    if (TextUtils.isEmpty(httpSetting.getFunctionId()) || JDHttpTookit.getEngine().getJdGuardPlugin() == null || !JDHttpTookit.getEngine().getJdGuardPlugin().isEnable() || !JDHttpTookit.getEngine().getJdGuardPlugin().isInWhiteList(httpSetting.getFunctionId())) {

        return;

    }

    String str2 = httpSetting.isPost() ? UrlRequest.HTTP_METHOD_POST : UrlRequest.HTTP_METHOD_GET;

    boolean isPost = httpSetting.isPost();

    Map<String, String> map = null;

    String str3 = isPost ? ParamEncodeUtil.DEFAULT_CONTENT_TYPE : null;

    try {

        uri = new URI(httpSetting.getUrl());

    } catch (URISyntaxException unused) {

        uri = null;

    }

    try {

        map = JDHttpTookit.getEngine().getJdGuardPlugin().genSign(uri, !TextUtils.isEmpty(str) ? str.getBytes() : null, str3, str2, isPost);

    } catch (Throwable unused2) {

    }

    if (map == null || map.isEmpty()) {

        return;

    }

    Map<String, String> headerMap = httpSetting.getHeaderMap();

    if (headerMap == null || headerMap.isEmpty()) {

        headerMap = new HashMap<>();

    }

    headerMap.putAll(map);

    httpSetting.setHeaderMap(headerMap);

}

我们可以发现此时的 uri 里面就有的 sign ,因此我们继续定位上层 com.jingdong.jdsdk.network.toolbox.ParamBuilderForJDMall.setupParams ,源码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

public static void setupParams(HttpRequest httpRequest) {

        String str;

        HttpSetting httpSetting = HttpSettingTool.setupBaseParams(httpRequest);

        HttpSettingTool.addGuardVerifyLmtCode(httpSetting);

        if (httpSetting.getCustomMapParam() == null || !httpSetting.getCustomMapParam().containsKey("uuid")) {

            str = "";

        else {

            str = httpSetting.getCustomMapParam().get("uuid");

            httpSetting.getCustomMapParam().remove("uuid");

        }

        if (TextUtils.isEmpty(str) && httpSetting.getCustomEncryptMapParam() != null && httpSetting.getCustomEncryptMapParam().containsKey("uuid")) {

            str = httpSetting.getCustomEncryptMapParam().get("uuid");

            httpSetting.getCustomEncryptMapParam().remove("uuid");

        }

        if (TextUtils.isEmpty(str)) {

            str = JDHttpTookit.getEngine().getStatInfoConfigImpl().getDeviceUUID(httpSetting.getFunctionId(), httpSetting.isEnableEncryptTransmission());

        }

        if (TextUtils.isEmpty(str)) {

            str = "unknow";

        }

        if (OKLog.D) {

            String str2 = TAG;

            OKLog.d(str2, "id:" + httpSetting.getId() + "- uuid -->> " + str);

        }

        String bodyParam = HttpSettingTool.getBodyParam(httpSetting);

        if (httpSetting.getType() == 6000) {

            if (RuntimeConfigHelper.advertiseStatDataEnable()) {

                HttpSettingTool.addStatQueryParam(httpSetting, bodyParam, str);

            }

        else {

            HttpSettingTool.addStatQueryParam(httpSetting, bodyParam, str);

        }

        HttpSettingTool.addCustomQueryParam(httpSetting);

        if (JDHttpTookit.getEngine().isNeedVerifySignature()) {

            JDHttpTookit.getEngine().getSignatureHandlerImpl().networkSettingsPreSignature();

            signature(httpSetting, bodyParam, str);

        }

        HttpSettingTool.doSignUsingJdGuard(httpSetting, bodyParam);

        JDHttpTookit.getEngine().getExternalDebugConfigImpl().addMockerIdName(httpSetting);

    }

发现这个函数是设置 httpSetting 的 参数的,貌似就是我们需要的!我们看一下有没有关键的代码!发现了这么一行代码:signature(httpSetting, bodyParam, str); ,很可疑,我们先来尝试 hook 验证加密入口是否正确,先尝试 hook 一下 com.jingdong.jdsdk.network.toolbox.ParamBuilderForJDMall.signature 这个函数吧,因为这个函数是没有返回值的,所以我们尝试输出一下调用前与调用后的 httpSetting 的 url 不就知道他有没有给 url 加上 sign 了吗?代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

Java.perform(() => {

    let ParamBuilderForJDMall = Java.use("com.jingdong.jdsdk.network.toolbox.ParamBuilderForJDMall");

    ParamBuilderForJDMall["signature"].implementation = function (httpSetting, str, str2) {

        console.log("========= ParamBuilderForJDMall.signature =========")

        console.log(`before httpSetting=${httpSetting}`)

        console.log(`before httpSetting uri=${httpSetting.getUrl()}`)

        console.log(`str=${str}`)

        console.log(`str2=${str2}`)

        this["signature"](httpSetting, str, str2);

        console.log(`after httpSetting=${httpSetting}`)

        console.log(`after httpSetting uri=${httpSetting.getUrl()`)

    };

})

图片描述

果然发现这就是关键入口,因为之前的 uri 里面是没有 sign 这个参数的,但是后面却有了,因此我们就追进去看看,发现了这么一行代码:String signature = JDHttpTookit.getEngine().getSignatureHandlerImpl().signature(JDHttpTookit.getEngine().getApplicationContext(), functionId, str, str2, property, versionName); ,继续跟进到 signature 里面去发现他只是一个接口,所以肯定有实现或者重载的地方!(这种情况,我们一般 hook 不到,因为位置不正确,实际调用的是其实现类的那个函数,不信你可以试试!)

那么我们应该如何找这个接口的实现呢?这边提供一个思路,因为实现的话肯定要使用同样的函数名,返回值以及参数,所以可以尝试直接搜索,例如当前函数定义为 String signature(Context context, String str, String str2, String str3, String str4, String str5);,那么我们就直接将这个作为关键字进行搜索(当然你也可以尝试查找用例,不过应该找不到)

通过搜索我们找到了如下结果:

图片描述

一个个去找就能发现真正的加密入口了:

1

2

3

4

@Override // com.jingdong.jdsdk.network.dependency.ISignatureHandler

public String signature(Context context, String str, String str2, String str3, String str4, String str5) {

    return BitmapkitUtils.getSignFromJni(context, str, str2, str3, str4, str5);

}

再继续跟进去就能发现这个是一个 native 函数 (其实从名字就能看出来),所以 sign 的 jni 入口为:com.jingdong.common.utils.BitmapkitUtils.getSignFromJni

图片描述

我们回到 jdgs 加密入口处,继续跟进代码就能发现其实际也是一个 native 函数,简单讲一下步骤

1

2

3

4

5

6

1. 首先定位的入口为:com.jingdong.common.guard.JDGuardHelper$1.genSign

2. 进入 c.b,关键代码为 eVar.i();

3. 进入 eVar.i,发现 jdgs 就是 而 o 是 o(h())

4. 进入 o 函数,发现了 com.jd.security.jdguard.b.d().A(bArr);

5. 进入 com.jd.security.jdguard.b.d().A 发现了 Bridge.main(101, new Object[]{bArr, u(), s(), w(), q()});

6. 进入 main 发现 public static native Object[] main(int i2, Object[] objArr); 也就是入口了

接口分析

sign

入口为:com.jingdong.common.utils.BitmapkitUtils.getSignFromJni

使用通用的办法找到 so 文件和函数签名:so 文件 libjdbitmapkit.so, 函数签名:getSignFromJni(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; , hook 一下分析一下参数吧!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

Java.perform(() => {

    let BitmapkitUtils = Java.use("com.jingdong.common.utils.BitmapkitUtils");

    BitmapkitUtils["getSignFromJni"].implementation = function (context, str, str2, str3, str4, str5) {

        console.log("========= getSignFromJni =========")

        console.log(` context=${context}`)

        console.log(` str=${str}`)

        console.log(` str2=${str2}`)

        console.log(` str3=${str3}`)

        console.log(` str4=${str4}`)

        console.log(` str5=${str5}`)

        let result = this["getSignFromJni"](context, str, str2, str3, str4, str5);

        console.log(` result=${result}`)

        return result;

    };

})

图片描述

  • context:就是 android.context.Context
  • str:也就是 functionId
  • str2:是 body 字符串
  • str3:一直是 f9f40175bf4c6710, 猜测可能是设备码或者 secretKey 之类的东西吧(搜索抓包发现其为一个 uuid, 那就是设备码之类的 )
  • str4:设备类型,我的是 android
  • str5:看起来是版本号
  • result:就是加密好的 sign 和时间戳以及一个 sv, 所以猜测加密用上了这些

jdgs

入口为:com.jd.security.jdguard.core.Bridge.main

使用通用的办法找到 so 文件和函数签名:so 文件 libjdg.so, 函数签名:main(I[Ljava/lang/Object;)[Ljava/lang/Object; , hook 一下分析一下参数吧!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Java.perform(() => {

    let Bridge = Java.use("com.jd.security.jdguard.core.Bridge");

    Bridge["main"].implementation = function (i2, objArr) {

        console.log("======== Bridge.main ========")

        console.log(`i2=${i2}`)

        console.log(`objArr=${objArr}`)

        let result = this["main"](i2, objArr);

        console.log(`result=${result}`)

        return result;

    };

})

  • i2101, 固定的数字
  • objArr
    • objArr[0]uri 转为 byte 数组
    • objArr[1]:一串和设备相关的字符串
    • objArr[2]eid 还是什么东西
    • objArr[3]:1.0
    • objArr[4]:83
  • result:请求头里面的 jdgs

这个参数暂时先不分析了,我们本文主要分析 sign ,如果有需要后续再补充分布步骤,先记录一下参数示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

i2: 101

objArr[0]: POST /client.action bef=1&bfa06501b64b4672e3b8645fd0ad54401e4786bd9efebc784fa483cf99bec2fd=&build=98704&client=android&clientVersion=11.6.4&ef=1&eid=eidAf5088122acsfbxEYf1ulTs2pUn38cGRLh28RGgYgyPwz80gu9s7yfbXAvbZ1R70WQm1PENZaDGd6EF%2FmunBEHRDWPW5sYgYPelPhS+vYueanoeJv&ep=%7B%22hdid%22%3A%22JM9F1ywUPwflvMIpYPok0tt5k9kW4ArJEU3lfLhxBqw%3D%22%2C%22ts%22%3A1678345151525%2C%22ridx%22%3A-1%2C%22cipher%22%3A%7B%22area%22%3A%22CV83Cv8yDzu5XzK%3D%22%2C%22d_model%22%3A%22J05PUOnVU0O2CNKm%22%2C%22wifiBssid%22%3A%22dW5hbw93bq%3D%3D%22%2C%22osVersion%22%3A%22CJK%3D%22%2C%22d_brand%22%3A%22J25vUQn1cm%3D%3D%22%2C%22screen%22%3A%22CtO1EIenCNqm%22%2C%22uuid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%2C%22aid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%2C%22openudid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%7D%2C%22ciphertype%22%3A5%2C%22version%22%3A%221.2.0%22%2C%22appname%22%3A%22com.jingdong.app.mall%22%7D&ext=%7B%22prstate%22%3A%220%22%2C%22pvcStu%22%3A%221%22%2C%22cfgExt%22%3A%22%7B%5C%22privacyOffline%5C%22%3A%5C%220%5C%22%7D%22%7D&functionId=wareBusiness&harmonyOs=0&lang=zh_CN&lmt=0&networkType=wifi&oaid=1B4220DEA49487D7AF8DE6AF4C0F1F60F05819E2ADCC3CD2ACDF3C07D81BEDC6&partner=tencent&scval=10062561918391&sdkVersion=29&sign=68049b09fa2cb6111f9ab0209ea9412e&st=1678348533650&sv=100&uemps=2-2-2 转为 byte 数组

objArr[1]: sdm845|-|OnePlus6|-|-|-|-|-|-|-|-|-|-|-|-|-|-§§0|1§§-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-§§-|115717931008§§-|-|1.0|-§§0|1|-|-|-|-|-|-|-|-|-§§OnePlus/OnePlus6/OnePlus6:10/QKQ1.190716.003/2107162030:user/release-keys/|-|-|1678344292938|-§§-|-|-|-|-|-|-

objArr[2]: eidAf5088122acsfbxEYf1ulTs2pUn38cGRLh28RGgYgyPwz80gu9s7yfbXAvbZ1R70WQm1PENZaDGd6EF/munBEHRDWPW5sYgYPelPhS+vYueanoeJv

objArr[3]: 1.0

objArr[4]: 83

返回值:{"b1":"a4574221-958f-4752-ba5b-40e53bed1c17","b2":"3.1.4-grey_0","b3":"2.0","b4":"poW6/4yQ4JTEcYoerj5XATijmNYCBHcNjt++g5Q4O+l6+FjqoOp+0969nSqdVuQ6OYidCZIgcjC5HDYYgMu7jfPc+9qv092hwdGQxZK4GlClVizD6jznVAOvOvMYNkLQ3FjogO+BAMTJQvk8EPD7vWXJm8uvve8RVv1HkFs5RZ3zXSBNp3M2OyVX1hIZ9/CRBsu49MSJLIjUmz1fWvi47BZ+IlWC78KJMHb33i9I9YwzGtGnZCtIA3Sj+SzBiIIAS7B9dNfqYkjHf3FzDMcx3F0QznG+VyIw/qw727A7tulxd+5kky8tIG73Yb59KDmoEXrMlzDFEh9YGutpUKhBz3bTFp/uMp9MM1I5oKMoURMMAf7BK4ZWocY/wEWwq6Chh+rrlbZYpwJ/zCg3nxk/xmdIgvJ85+Ses0BYy+49","b5":"231497376118012d822698e78658dc91d6fa9a63","b7":"1678348533659","b6":"eeb029f8f6aadee6c235acd9eaf8447e6825f478"}

Unidbg

这边提供一个通用的模板,可以自己填充

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

package 这个写当前包名;

import com.github.unidbg.AndroidEmulator;

import com.github.unidbg.arm.backend.Unicorn2Factory;

import com.github.unidbg.linux.android.AndroidEmulatorBuilder;

import com.github.unidbg.linux.android.AndroidResolver;

import com.github.unidbg.linux.android.dvm.AbstractJni;

import com.github.unidbg.linux.android.dvm.DalvikModule;

import com.github.unidbg.linux.android.dvm.DvmClass;

import com.github.unidbg.linux.android.dvm.VM;

import com.github.unidbg.memory.Memory;

import java.io.File;

public class className extends AbstractJni {

    private final AndroidEmulator emulator;

    private final VM vm;

    // 包名

    private final String packageName = "包名";

    // apk 地址

    private final String packagePath = "apk 地址";

    // so 名称, 要去掉 lib 和  .so

    private final String libraryName = "so 名称, 要去掉 lib 和  .so";

    // jni 类名

    private final String jniClassName = "jni 类名";

    // 调试信息

    private final Boolean verbose = true;

    // jni 模块

    private final DvmClass Module;

    public className() {

        // 实例化一个模拟器

        emulator = AndroidEmulatorBuilder

                .for32Bit()

                .addBackendFactory(new Unicorn2Factory(true))

                .setProcessName(packageName)

                .build();

        Memory memory = emulator.getMemory();

        memory.setLibraryResolver(new AndroidResolver(23));

        vm = emulator.createDalvikVM(new File(packagePath));

        vm.setJni(this);

        vm.setVerbose(verbose);

        DalvikModule dm = vm.loadLibrary(libraryName, true);

        Module = vm.resolveClass(jniClassName);

        dm.callJNI_OnLoad(emulator);

    }

    public static void main(String[] args) {

        className ins = new className();

    }

}

sign

先套用一下模板,然后运行看看

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

package com.jingdong.app.mall;

import com.github.unidbg.AndroidEmulator;

import com.github.unidbg.arm.backend.Unicorn2Factory;

import com.github.unidbg.linux.android.AndroidEmulatorBuilder;

import com.github.unidbg.linux.android.AndroidResolver;

import com.github.unidbg.linux.android.dvm.AbstractJni;

import com.github.unidbg.linux.android.dvm.DalvikModule;

import com.github.unidbg.linux.android.dvm.DvmClass;

import com.github.unidbg.linux.android.dvm.VM;

import com.github.unidbg.memory.Memory;

import java.io.File;

public class signUtils extends AbstractJni {

    private final AndroidEmulator emulator;

    private final VM vm;

    // 包名

    private final String packageName = "com.com.jingdong.app.mall";

    // apk 地址

    private final String packagePath = "unidbg-android/src/test/resources/jd.apk";

    // so 名称, 要去掉 lib 和  .so

    private final String libraryName = "jdbitmapkit";

    // jni 类名

    private final String jniClassName = "com.jingdong.common.utils.BitmapkitUtils";

    // 调试信息

    private final Boolean verbose = true;

    // jni 模块

    private final DvmClass Module;

    public signUtils() {

        // 实例化一个模拟器

        emulator = AndroidEmulatorBuilder

                .for32Bit()

                .addBackendFactory(new Unicorn2Factory(true))

                .setProcessName(packageName)

                .build();

        Memory memory = emulator.getMemory();

        memory.setLibraryResolver(new AndroidResolver(23));

        vm = emulator.createDalvikVM(new File(packagePath));

        vm.setJni(this);

        vm.setVerbose(verbose);

        DalvikModule dm = vm.loadLibrary(libraryName, true);

        Module = vm.resolveClass(jniClassName);

        dm.callJNI_OnLoad(emulator);

    }

    public static void main(String[] args) {

        signUtils ins = new signUtils();

    }

}

  • com/jingdong/common/utils/BitmapkitUtils->a:Landroid/app/Application;

1

2

java.lang.UnsupportedOperationException: com/jingdong/common/utils/BitmapkitUtils->a:Landroid/app/Application;

    at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)

发现报错了,是因为缺少环境问题,可以看到最底层的报错堆栈在 com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField,大概意思就是要找com/jingdong/common/utils/BitmapkitUtils这个类的 a 字段,但是找不到,所以报了上述的错误,那么我们就需要重写 getStaticObjectField 这个方法,将环境补充完毕,步骤如下:

首先我们先点进源码里面看看作者自己的实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

@Override

public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {

    switch (signature) {

        case "android/content/Context->TELEPHONY_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.TELEPHONY_SERVICE);

        case "android/content/Context->WIFI_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.WIFI_SERVICE);

        case "android/content/Context->CONNECTIVITY_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.CONNECTIVITY_SERVICE);

        case "android/content/Context->ACCESSIBILITY_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.ACCESSIBILITY_SERVICE);

        case "android/content/Context->KEYGUARD_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.KEYGUARD_SERVICE);

        case "android/content/Context->ACTIVITY_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.ACTIVITY_SERVICE);

        case "android/content/Context->LOCATION_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.LOCATION_SERVICE);

        case "android/content/Context->WINDOW_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.WINDOW_SERVICE);

        case "android/content/Context->SENSOR_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.SENSOR_SERVICE);

        case "android/content/Context->UI_MODE_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.UI_MODE_SERVICE);

        case "android/content/Context->DISPLAY_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.DISPLAY_SERVICE);

        case "android/content/Context->AUDIO_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.AUDIO_SERVICE);

        case "java/lang/Void->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Void");

        case "java/lang/Boolean->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Boolean");

        case "java/lang/Byte->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Byte");

        case "java/lang/Character->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Character");

        case "java/lang/Short->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Short");

        case "java/lang/Integer->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Integer");

        case "java/lang/Long->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Long");

        case "java/lang/Float->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Float");

        case "java/lang/Double->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Double");

    }

    throw new UnsupportedOperationException(signature);

}

重写这个方法,补充实现,我们查看 jadx 发现 a 这个字段是一个 android.app.Application (你也可以通过签名看出来),所以我们需要在 unidbg 里面构造出来。

在 unidbg 里面,resolveClass是一个可变参数方法,它的第二个参数是可变参数,用于进一步描述我们所构造的这个DvmClass的父类和接口。

拿目前需要补充的 android.app.Application 为例,通过搜索资料可知,Application 继承与 ContextWrapper, 而 ContextWrapper 又继承与 Context,所以在 unidbg 里面需要实现一个 Application,那么可以使用以下嵌套代码:

1

2

3

DvmClass Context = vm.resolveClass("android/content/Context");

DvmClass ContextWrapper = vm.resolveClass("android/content/ContextWrapper", Context);

DvmClass Application = vm.resolveClass("android/app/Application", ContextWrapper);

补环境代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

@Override

    public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {

        switch (signature) {

            case "com/jingdong/common/utils/BitmapkitUtils->a:Landroid/app/Application;": {

                DvmClass Context = vm.resolveClass("android/content/Context");

                DvmClass ContextWrapper = vm.resolveClass("android/content/ContextWrapper", Context);

                // 使用 android/app/Application 发现会报错: com.github.unidbg.arm.backend.BackendException

                // 百度了一下发现这个错误是找不到 methodID

                // 因此可以使用 android/app/Activity 代替, 因为它也可以获取 Application 对象

                DvmClass Application = vm.resolveClass("android/app/Activity", ContextWrapper);

                return Application.newObject(signature);

            }

        }

        return super.getStaticObjectField(vm, dvmClass, signature);

    }

  • android/content/pm/ApplicationInfo->sourceDir:Ljava/lang/String;

1

2

java.lang.UnsupportedOperationException: android/content/pm/ApplicationInfo->sourceDir:Ljava/lang/String;

    at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)

取 ApplicationInfo 也就是 apk 的存放位置 可以使用 adb 进手机查看一下

图片描述

所以 app 在这里: /data/app/com.jingdong.app.mall-Icn20e0xHLzrVHPBwOirRA==/bask.apk

1

2

3

4

5

6

7

8

9

@Override

public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {

    switch (signature) {

        case "android/content/pm/ApplicationInfo->sourceDir:Ljava/lang/String;": {

            return new StringObject(vm, "/data/app/com.jingdong.app.mall-Icn20e0xHLzrVHPBwOirRA==/bask.apk");

        }

    }

    return super.getObjectField(vm, dvmObject, signature);

}

  • com/jingdong/common/utils/BitmapkitZip->unZip(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[B

1

2

java.lang.UnsupportedOperationException: com/jingdong/common/utils/BitmapkitZip->unZip(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[B

    at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:432)

这个补的时候有一个参数,可以先输出一下参数,看看都是什么

1

2

3

arg0--  /data/app/com.jingdong.app.mall-Icn20e0xHLzrVHPBwOirRA==/bask.apk

arg1--  META-INF/

arg2--  .RSA

所以他找的好像是 apk 里面 META-INF 文件夹里面的 xxx.RSA , 我们只要使用压缩文件打开 apk 就可以看到一个叫做 JINGDONG.RSA 的文件,补全一下即可!

图片描述

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

@Override

public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {

    switch (signature) {

        case "com/jingdong/common/utils/BitmapkitZip->unZip(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[B": {

            StringObject apkPath = varArg.getObjectArg(0);

            StringObject directory = varArg.getObjectArg(1);

            StringObject filename = varArg.getObjectArg(2);

            if (

                    "/data/app/com.jingdong.app.mall-Icn20e0xHLzrVHPBwOirRA==/bask.apk".equals(apkPath.getValue())

                            && "META-INF/".equals(directory.getValue())

                            && ".RSA".equals(filename.getValue())

            ) {

                byte[] data = vm.unzip("META-INF/JINGDONG.RSA");

                return new ByteArray(vm, data);

            }

        }

    }

    return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);

}

  • sun/security/pkcs/PKCS7-><init>([B)V

1

2

java.lang.UnsupportedOperationException: sun/security/pkcs/PKCS7-><init>([B)V

    at com.github.unidbg.linux.android.dvm.AbstractJni.newObject(AbstractJni.java:741)

图片描述

写完之后提示要捕捉异常,因此就捕捉一下,虽然我也不懂为什么

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

@Override

public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {

    switch (signature) {

        case "sun/security/pkcs/PKCS7-><init>([B)V": {

            PKCS7 pkcs7 = null;

            try {

                byte[] bArray = (byte[]) varArg.getObjectArg(0).getValue();

                pkcs7 = new PKCS7(bArray);

            } catch (ParsingException e) {

                e.printStackTrace();

            }

            DvmClass pkcs7Class = vm.resolveClass("sun/security/pkcs/PKCS7");

            return pkcs7Class.newObject(pkcs7);

        }

    }

    return super.newObject(vm, dvmClass, signature, varArg);

}

  • sun/security/pkcs/PKCS7->getCertificates()[Ljava/security/cert/X509Certificate;

1

2

java.lang.UnsupportedOperationException: sun/security/pkcs/PKCS7->getCertificates()[Ljava/security/cert/X509Certificate;

    at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:921)

1

2

3

4

5

6

7

8

9

10

11

12

@Override

public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {

    switch (signature) {

        case "sun/security/pkcs/PKCS7->getCertificates()[Ljava/security/cert/X509Certificate;": {

            PKCS7 pkcs7 = (PKCS7) dvmObject.getValue();

            X509Certificate[] x509Certificates = pkcs7.getCertificates();

            return ProxyDvmObject.createObject(vm, x509Certificates);

        }

    }

    return super.callObjectMethod(vm, dvmObject, signature, varArg);

}

  • com/jingdong/common/utils/BitmapkitZip->objectToBytes(Ljava/lang/Object;)[B

1

2

java.lang.UnsupportedOperationException: com/jingdong/common/utils/BitmapkitZip->objectToBytes(Ljava/lang/Object;)[B

    at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:432)

发现他找的是一个 objectToBytes 方法,但是找不到,所以我们直接去 jadx 里面搜索一下,便得到了结果

图片描述

图片描述

可以直接复制出来使用,去除报错,导入一些包就可以了!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

public static byte[] objectToBytes(Object obj) {

    try {

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);

        objectOutputStream.writeObject(obj);

        objectOutputStream.flush();

        byte[] byteArray = byteArrayOutputStream.toByteArray();

        objectOutputStream.close();

        byteArrayOutputStream.close();

        return byteArray;

    } catch (IOException e2) {

        return null;

    }

}

然后再继续补环境

1

2

3

case "com/jingdong/common/utils/BitmapkitZip->objectToBytes(Ljava/lang/Object;)[B": {

    return new ByteArray(vm, objectToBytes(varArg.getObjectArg(0).getValue()));

}

接着运行发现居然不报错了,因此我们就写一个获取 sign 的函数尝试调用一下!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public void getSign() {

    DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);

    String arg1 = "getLegoWareDetailComment";

    String arg2 = "{\"category\":\"12473;12480;12515\",\"commentNum\":3,\"isNew\":\"0\",\"newTitle\":null,\"shadowMainSku\":\"0\",\"shieldCurrentComment\":\"1\",\"shopId\":null,\"shopType\":\"0\",\"sku\":\"11653586880\",\"venderId\":\"644185\",\"wareType\":\"0\"}";

    String arg3 = "f9f40175bf4c6710";

    String arg4 = "android";

    String arg5 = "11.6.4";

    DvmObject<?> ret = module.callStaticJniMethodObject(emulator, "getSignFromJni(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", vm.addLocalObject(context), arg1, arg2, arg3, arg4, arg5

    );

    System.out.println("================================================");

    System.out.println(ret.getValue());

    System.out.println("================================================");

}

图片描述

好的,又开始报错了,咱继续补环境

  • java/lang/StringBuffer-><init>()V

1

2

java.lang.UnsupportedOperationException: java/lang/StringBuffer-><init>()V

    at com.github.unidbg.linux.android.dvm.AbstractJni.newObjectV(AbstractJni.java:791)

1

2

3

4

5

6

7

8

9

10

11

12

@Override

public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {

    switch (signature) {

        case "java/lang/StringBuffer-><init>()V": {

            StringBuffer stringBuffer = new StringBuffer();

            return vm.resolveClass("java/lang/StringBuffer").newObject(stringBuffer);

        }

    }

    return super.newObjectV(vm, dvmClass, signature, vaList);

}

  • java/lang/StringBuffer->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

1

2

java.lang.UnsupportedOperationException: java/lang/StringBuffer->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)

1

2

3

4

5

6

7

8

9

10

11

12

13

@Override

public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {

    switch (signature) {

        case "java/lang/StringBuffer->append(Ljava/lang/String;)Ljava/lang/StringBuffer;": {

            StringBuffer stringBuffer = (java.lang.StringBuffer) dvmObject.getValue();

            stringBuffer.append(vaList.getObjectArg(0).getValue());

            DvmClass StringBuffer = vm.resolveClass("java/lang/StringBuffer");

            return StringBuffer.newObject(stringBuffer);

        }

    }

    return super.callObjectMethodV(vm, dvmObject, signature, vaList);

}

  • java/lang/Integer->toString()Ljava/lang/String;

1

2

java.lang.UnsupportedOperationException: java/lang/Integer->toString()Ljava/lang/String;

    at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)

1

2

3

4

5

6

case "java/lang/Integer->toString()Ljava/lang/String;": {

    Integer value = (Integer) dvmObject.getValue();

    return vm.resolveClass("java/lang/String").newObject(value.toString());

}

  • java/lang/StringBuffer->toString()Ljava/lang/String;

1

2

java.lang.UnsupportedOperationException: java/lang/StringBuffer->toString()Ljava/lang/String;

    at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)

1

2

3

4

5

case "java/lang/StringBuffer->toString()Ljava/lang/String;": {

    StringBuffer value = (StringBuffer) dvmObject.getValue();

    return vm.resolveClass("java/lang/String").newObject(value.toString());

}

最后发现环境补完了,可以直接得到 sign 了!

图片描述

jdgs

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

package com.jingdong.app.mall;

import com.github.unidbg.AndroidEmulator;

import com.github.unidbg.arm.backend.Unicorn2Factory;

import com.github.unidbg.linux.android.AndroidEmulatorBuilder;

import com.github.unidbg.linux.android.AndroidResolver;

import com.github.unidbg.linux.android.dvm.*;

import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;

import com.github.unidbg.memory.Memory;

import java.io.File;

public class jdgs extends AbstractJni {

    private final AndroidEmulator emulator;

    private final VM vm;

    // 包名

    private final String packageName = "com.com.jingdong.app.mall";

    // apk 地址

    private final String packagePath = "unidbg-android/src/test/resources/jd.apk";

    // so 名称, 要去掉 lib 和  .so

    private final String libraryName = "jdg";

    // jni 类名

    private final String jniClassName = "com.jd.security.jdguard.core.Bridge";

    // 调试信息

    private final Boolean verbose = true;

    // jni 模块

    private final DvmClass module;

    public jdgs() {

        // 实例化一个模拟器

        emulator = AndroidEmulatorBuilder

                .for32Bit()

                .addBackendFactory(new Unicorn2Factory(true))

                .setProcessName(packageName)

                .build();

        Memory memory = emulator.getMemory();

        memory.setLibraryResolver(new AndroidResolver(23));

        vm = emulator.createDalvikVM(new File(packagePath));

        vm.setJni(this);

        vm.setVerbose(verbose);

        DalvikModule dm = vm.loadLibrary(libraryName, true);

        module = vm.resolveClass(jniClassName);

        dm.callJNI_OnLoad(emulator);

    }

    public static void main(String[] args) {

        jdgs ins = new jdgs();

        ins.getJdgs();

    }

    public void getJdgs() {

        Integer i2 = 101;

        String arr1 = "/client.action bef=1&bfa06501b64b4672e3b8645fd0ad54401e4786bd9efebc784fa483cf99bec2fd=&build=98704&client=android&clientVersion=11.6.4&ef=1&eid=eidAf5088122acsfbxEYf1ulTs2pUn38cGRLh28RGgYgyPwz80gu9s7yfbXAvbZ1R70WQm1PENZaDGd6EF%2FmunBEHRDWPW5sYgYPelPhS+vYueanoeJv&ep=%7B%22hdid%22%3A%22JM9F1ywUPwflvMIpYPok0tt5k9kW4ArJEU3lfLhxBqw%3D%22%2C%22ts%22%3A1678345151525%2C%22ridx%22%3A-1%2C%22cipher%22%3A%7B%22area%22%3A%22CV83Cv8yDzu5XzK%3D%22%2C%22d_model%22%3A%22J05PUOnVU0O2CNKm%22%2C%22wifiBssid%22%3A%22dW5hbw93bq%3D%3D%22%2C%22osVersion%22%3A%22CJK%3D%22%2C%22d_brand%22%3A%22J25vUQn1cm%3D%3D%22%2C%22screen%22%3A%22CtO1EIenCNqm%22%2C%22uuid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%2C%22aid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%2C%22openudid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%7D%2C%22ciphertype%22%3A5%2C%22version%22%3A%221.2.0%22%2C%22appname%22%3A%22com.jingdong.app.mall%22%7D&ext=%7B%22prstate%22%3A%220%22%2C%22pvcStu%22%3A%221%22%2C%22cfgExt%22%3A%22%7B%5C%22privacyOffline%5C%22%3A%5C%220%5C%22%7D%22%7D&functionId=wareBusiness&harmonyOs=0&lang=zh_CN&lmt=0&networkType=wifi&oaid=1B4220DEA49487D7AF8DE6AF4C0F1F60F05819E2ADCC3CD2ACDF3C07D81BEDC6&partner=tencent&scval=10062561918391&sdkVersion=29&sign=68049b09fa2cb6111f9ab0209ea9412e&st=1678348533650&sv=100&uemps=2-2-2";

        String arr2 = "sdm845|-|OnePlus6|-|-|-|-|-|-|-|-|-|-|-|-|-|-§§0|1§§-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-§§-|115717931008§§-|-|1.0|-§§0|1|-|-|-|-|-|-|-|-|-§§OnePlus/OnePlus6/OnePlus6:10/QKQ1.190716.003/2107162030:user/release-keys/|-|-|1678344292938|-§§-|-|-|-|-|-|-";

        String arr3 = "eidAf5088122acsfbxEYf1ulTs2pUn38cGRLh28RGgYgyPwz80gu9s7yfbXAvbZ1R70WQm1PENZaDGd6EF/munBEHRDWPW5sYgYPelPhS+vYueanoeJv";

        String arr4 = "1.0";

        String arr5 = "83";

        Object[] arr = new Object[]{arr1.getBytes(), arr2, arr3, arr4, arr5};

        DvmObject<?> ret = module.callStaticJniMethodObject(emulator, "main(I[Ljava/lang/Object;)[Ljava/lang/Object;", i2, ProxyDvmObject.createObject(vm, arr));

        System.out.println("================================================");

        System.out.println(ret.getValue());

        System.out.println("================================================");

    }

}

模拟 jdgs 的时候不知道为什么会报这样一个错误,并且也没有堆栈,因为还是小白,暂时不知道如何解决
目前会导致获取到的结果拿不到,以后有思路了再来完善这一块!希望有大佬能指导一下,万分感谢!!

图片描述

我们回到 jdgs 加密入口处,继续跟进代码就能发现其实际也是一个 native 函数,简单讲一下步骤

1

2

3

4

5

6

1. 首先定位的入口为:com.jingdong.common.guard.JDGuardHelper$1.genSign

2. 进入 c.b,关键代码为 eVar.i();

3. 进入 eVar.i,发现 jdgs 就是 而 o 是 o(h())

4. 进入 o 函数,发现了 com.jd.security.jdguard.b.d().A(bArr);

5. 进入 com.jd.security.jdguard.b.d().A 发现了 Bridge.main(101, new Object[]{bArr, u(), s(), w(), q()});

6. 进入 main 发现 public static native Object[] main(int i2, Object[] objArr); 也就是入口了

接口分析

sign

入口为:com.jingdong.common.utils.BitmapkitUtils.getSignFromJni

使用通用的办法找到 so 文件和函数签名:so 文件 libjdbitmapkit.so, 函数签名:getSignFromJni(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; , hook 一下分析一下参数吧!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

Java.perform(() => {

    let BitmapkitUtils = Java.use("com.jingdong.common.utils.BitmapkitUtils");

    BitmapkitUtils["getSignFromJni"].implementation = function (context, str, str2, str3, str4, str5) {

        console.log("========= getSignFromJni =========")

        console.log(` context=${context}`)

        console.log(` str=${str}`)

        console.log(` str2=${str2}`)

        console.log(` str3=${str3}`)

        console.log(` str4=${str4}`)

        console.log(` str5=${str5}`)

        let result = this["getSignFromJni"](context, str, str2, str3, str4, str5);

        console.log(` result=${result}`)

        return result;

    };

})

图片描述

  • context:就是 android.context.Context
  • str:也就是 functionId
  • str2:是 body 字符串
  • str3:一直是 f9f40175bf4c6710, 猜测可能是设备码或者 secretKey 之类的东西吧(搜索抓包发现其为一个 uuid, 那就是设备码之类的 )
  • str4:设备类型,我的是 android
  • str5:看起来是版本号
  • result:就是加密好的 sign 和时间戳以及一个 sv, 所以猜测加密用上了这些

jdgs

入口为:com.jd.security.jdguard.core.Bridge.main

使用通用的办法找到 so 文件和函数签名:so 文件 libjdg.so, 函数签名:main(I[Ljava/lang/Object;)[Ljava/lang/Object; , hook 一下分析一下参数吧!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

Java.perform(() => {

    let Bridge = Java.use("com.jd.security.jdguard.core.Bridge");

    Bridge["main"].implementation = function (i2, objArr) {

        console.log("======== Bridge.main ========")

        console.log(`i2=${i2}`)

        console.log(`objArr=${objArr}`)

        let result = this["main"](i2, objArr);

        console.log(`result=${result}`)

        return result;

    };

})

  • i2101, 固定的数字
  • objArr
    • objArr[0]uri 转为 byte 数组
    • objArr[1]:一串和设备相关的字符串
    • objArr[2]eid 还是什么东西
    • objArr[3]:1.0
    • objArr[4]:83
  • result:请求头里面的 jdgs

这个参数暂时先不分析了,我们本文主要分析 sign ,如果有需要后续再补充分布步骤,先记录一下参数示例

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

i2: 101

objArr[0]: POST /client.action bef=1&bfa06501b64b4672e3b8645fd0ad54401e4786bd9efebc784fa483cf99bec2fd=&build=98704&client=android&clientVersion=11.6.4&ef=1&eid=eidAf5088122acsfbxEYf1ulTs2pUn38cGRLh28RGgYgyPwz80gu9s7yfbXAvbZ1R70WQm1PENZaDGd6EF%2FmunBEHRDWPW5sYgYPelPhS+vYueanoeJv&ep=%7B%22hdid%22%3A%22JM9F1ywUPwflvMIpYPok0tt5k9kW4ArJEU3lfLhxBqw%3D%22%2C%22ts%22%3A1678345151525%2C%22ridx%22%3A-1%2C%22cipher%22%3A%7B%22area%22%3A%22CV83Cv8yDzu5XzK%3D%22%2C%22d_model%22%3A%22J05PUOnVU0O2CNKm%22%2C%22wifiBssid%22%3A%22dW5hbw93bq%3D%3D%22%2C%22osVersion%22%3A%22CJK%3D%22%2C%22d_brand%22%3A%22J25vUQn1cm%3D%3D%22%2C%22screen%22%3A%22CtO1EIenCNqm%22%2C%22uuid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%2C%22aid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%2C%22openudid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%7D%2C%22ciphertype%22%3A5%2C%22version%22%3A%221.2.0%22%2C%22appname%22%3A%22com.jingdong.app.mall%22%7D&ext=%7B%22prstate%22%3A%220%22%2C%22pvcStu%22%3A%221%22%2C%22cfgExt%22%3A%22%7B%5C%22privacyOffline%5C%22%3A%5C%220%5C%22%7D%22%7D&functionId=wareBusiness&harmonyOs=0&lang=zh_CN&lmt=0&networkType=wifi&oaid=1B4220DEA49487D7AF8DE6AF4C0F1F60F05819E2ADCC3CD2ACDF3C07D81BEDC6&partner=tencent&scval=10062561918391&sdkVersion=29&sign=68049b09fa2cb6111f9ab0209ea9412e&st=1678348533650&sv=100&uemps=2-2-2 转为 byte 数组

objArr[1]: sdm845|-|OnePlus6|-|-|-|-|-|-|-|-|-|-|-|-|-|-§§0|1§§-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-§§-|115717931008§§-|-|1.0|-§§0|1|-|-|-|-|-|-|-|-|-§§OnePlus/OnePlus6/OnePlus6:10/QKQ1.190716.003/2107162030:user/release-keys/|-|-|1678344292938|-§§-|-|-|-|-|-|-

objArr[2]: eidAf5088122acsfbxEYf1ulTs2pUn38cGRLh28RGgYgyPwz80gu9s7yfbXAvbZ1R70WQm1PENZaDGd6EF/munBEHRDWPW5sYgYPelPhS+vYueanoeJv

objArr[3]: 1.0

objArr[4]: 83

返回值:{"b1":"a4574221-958f-4752-ba5b-40e53bed1c17","b2":"3.1.4-grey_0","b3":"2.0","b4":"poW6/4yQ4JTEcYoerj5XATijmNYCBHcNjt++g5Q4O+l6+FjqoOp+0969nSqdVuQ6OYidCZIgcjC5HDYYgMu7jfPc+9qv092hwdGQxZK4GlClVizD6jznVAOvOvMYNkLQ3FjogO+BAMTJQvk8EPD7vWXJm8uvve8RVv1HkFs5RZ3zXSBNp3M2OyVX1hIZ9/CRBsu49MSJLIjUmz1fWvi47BZ+IlWC78KJMHb33i9I9YwzGtGnZCtIA3Sj+SzBiIIAS7B9dNfqYkjHf3FzDMcx3F0QznG+VyIw/qw727A7tulxd+5kky8tIG73Yb59KDmoEXrMlzDFEh9YGutpUKhBz3bTFp/uMp9MM1I5oKMoURMMAf7BK4ZWocY/wEWwq6Chh+rrlbZYpwJ/zCg3nxk/xmdIgvJ85+Ses0BYy+49","b5":"231497376118012d822698e78658dc91d6fa9a63","b7":"1678348533659","b6":"eeb029f8f6aadee6c235acd9eaf8447e6825f478"}

Unidbg

这边提供一个通用的模板,可以自己填充

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

package 这个写当前包名;

import com.github.unidbg.AndroidEmulator;

import com.github.unidbg.arm.backend.Unicorn2Factory;

import com.github.unidbg.linux.android.AndroidEmulatorBuilder;

import com.github.unidbg.linux.android.AndroidResolver;

import com.github.unidbg.linux.android.dvm.AbstractJni;

import com.github.unidbg.linux.android.dvm.DalvikModule;

import com.github.unidbg.linux.android.dvm.DvmClass;

import com.github.unidbg.linux.android.dvm.VM;

import com.github.unidbg.memory.Memory;

import java.io.File;

public class className extends AbstractJni {

    private final AndroidEmulator emulator;

    private final VM vm;

    // 包名

    private final String packageName = "包名";

    // apk 地址

    private final String packagePath = "apk 地址";

    // so 名称, 要去掉 lib 和  .so

    private final String libraryName = "so 名称, 要去掉 lib 和  .so";

    // jni 类名

    private final String jniClassName = "jni 类名";

    // 调试信息

    private final Boolean verbose = true;

    // jni 模块

    private final DvmClass Module;

    public className() {

        // 实例化一个模拟器

        emulator = AndroidEmulatorBuilder

                .for32Bit()

                .addBackendFactory(new Unicorn2Factory(true))

                .setProcessName(packageName)

                .build();

        Memory memory = emulator.getMemory();

        memory.setLibraryResolver(new AndroidResolver(23));

        vm = emulator.createDalvikVM(new File(packagePath));

        vm.setJni(this);

        vm.setVerbose(verbose);

        DalvikModule dm = vm.loadLibrary(libraryName, true);

        Module = vm.resolveClass(jniClassName);

        dm.callJNI_OnLoad(emulator);

    }

    public static void main(String[] args) {

        className ins = new className();

    }

}

sign

先套用一下模板,然后运行看看

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

package com.jingdong.app.mall;

import com.github.unidbg.AndroidEmulator;

import com.github.unidbg.arm.backend.Unicorn2Factory;

import com.github.unidbg.linux.android.AndroidEmulatorBuilder;

import com.github.unidbg.linux.android.AndroidResolver;

import com.github.unidbg.linux.android.dvm.AbstractJni;

import com.github.unidbg.linux.android.dvm.DalvikModule;

import com.github.unidbg.linux.android.dvm.DvmClass;

import com.github.unidbg.linux.android.dvm.VM;

import com.github.unidbg.memory.Memory;

import java.io.File;

public class signUtils extends AbstractJni {

    private final AndroidEmulator emulator;

    private final VM vm;

    // 包名

    private final String packageName = "com.com.jingdong.app.mall";

    // apk 地址

    private final String packagePath = "unidbg-android/src/test/resources/jd.apk";

    // so 名称, 要去掉 lib 和  .so

    private final String libraryName = "jdbitmapkit";

    // jni 类名

    private final String jniClassName = "com.jingdong.common.utils.BitmapkitUtils";

    // 调试信息

    private final Boolean verbose = true;

    // jni 模块

    private final DvmClass Module;

    public signUtils() {

        // 实例化一个模拟器

        emulator = AndroidEmulatorBuilder

                .for32Bit()

                .addBackendFactory(new Unicorn2Factory(true))

                .setProcessName(packageName)

                .build();

        Memory memory = emulator.getMemory();

        memory.setLibraryResolver(new AndroidResolver(23));

        vm = emulator.createDalvikVM(new File(packagePath));

        vm.setJni(this);

        vm.setVerbose(verbose);

        DalvikModule dm = vm.loadLibrary(libraryName, true);

        Module = vm.resolveClass(jniClassName);

        dm.callJNI_OnLoad(emulator);

    }

    public static void main(String[] args) {

        signUtils ins = new signUtils();

    }

}

  • com/jingdong/common/utils/BitmapkitUtils->a:Landroid/app/Application;

1

2

java.lang.UnsupportedOperationException: com/jingdong/common/utils/BitmapkitUtils->a:Landroid/app/Application;

    at com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField(AbstractJni.java:103)

发现报错了,是因为缺少环境问题,可以看到最底层的报错堆栈在 com.github.unidbg.linux.android.dvm.AbstractJni.getStaticObjectField,大概意思就是要找com/jingdong/common/utils/BitmapkitUtils这个类的 a 字段,但是找不到,所以报了上述的错误,那么我们就需要重写 getStaticObjectField 这个方法,将环境补充完毕,步骤如下:

首先我们先点进源码里面看看作者自己的实现

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

@Override

public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {

    switch (signature) {

        case "android/content/Context->TELEPHONY_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.TELEPHONY_SERVICE);

        case "android/content/Context->WIFI_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.WIFI_SERVICE);

        case "android/content/Context->CONNECTIVITY_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.CONNECTIVITY_SERVICE);

        case "android/content/Context->ACCESSIBILITY_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.ACCESSIBILITY_SERVICE);

        case "android/content/Context->KEYGUARD_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.KEYGUARD_SERVICE);

        case "android/content/Context->ACTIVITY_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.ACTIVITY_SERVICE);

        case "android/content/Context->LOCATION_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.LOCATION_SERVICE);

        case "android/content/Context->WINDOW_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.WINDOW_SERVICE);

        case "android/content/Context->SENSOR_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.SENSOR_SERVICE);

        case "android/content/Context->UI_MODE_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.UI_MODE_SERVICE);

        case "android/content/Context->DISPLAY_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.DISPLAY_SERVICE);

        case "android/content/Context->AUDIO_SERVICE:Ljava/lang/String;":

            return new StringObject(vm, SystemService.AUDIO_SERVICE);

        case "java/lang/Void->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Void");

        case "java/lang/Boolean->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Boolean");

        case "java/lang/Byte->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Byte");

        case "java/lang/Character->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Character");

        case "java/lang/Short->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Short");

        case "java/lang/Integer->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Integer");

        case "java/lang/Long->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Long");

        case "java/lang/Float->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Float");

        case "java/lang/Double->TYPE:Ljava/lang/Class;":

            return vm.resolveClass("java/lang/Double");

    }

    throw new UnsupportedOperationException(signature);

}

重写这个方法,补充实现,我们查看 jadx 发现 a 这个字段是一个 android.app.Application (你也可以通过签名看出来),所以我们需要在 unidbg 里面构造出来。

在 unidbg 里面,resolveClass是一个可变参数方法,它的第二个参数是可变参数,用于进一步描述我们所构造的这个DvmClass的父类和接口。

拿目前需要补充的 android.app.Application 为例,通过搜索资料可知,Application 继承与 ContextWrapper, 而 ContextWrapper 又继承与 Context,所以在 unidbg 里面需要实现一个 Application,那么可以使用以下嵌套代码:

1

2

3

DvmClass Context = vm.resolveClass("android/content/Context");

DvmClass ContextWrapper = vm.resolveClass("android/content/ContextWrapper", Context);

DvmClass Application = vm.resolveClass("android/app/Application", ContextWrapper);

补环境代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

@Override

    public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {

        switch (signature) {

            case "com/jingdong/common/utils/BitmapkitUtils->a:Landroid/app/Application;": {

                DvmClass Context = vm.resolveClass("android/content/Context");

                DvmClass ContextWrapper = vm.resolveClass("android/content/ContextWrapper", Context);

                // 使用 android/app/Application 发现会报错: com.github.unidbg.arm.backend.BackendException

                // 百度了一下发现这个错误是找不到 methodID

                // 因此可以使用 android/app/Activity 代替, 因为它也可以获取 Application 对象

                DvmClass Application = vm.resolveClass("android/app/Activity", ContextWrapper);

                return Application.newObject(signature);

            }

        }

        return super.getStaticObjectField(vm, dvmClass, signature);

    }

  • android/content/pm/ApplicationInfo->sourceDir:Ljava/lang/String;

1

2

java.lang.UnsupportedOperationException: android/content/pm/ApplicationInfo->sourceDir:Ljava/lang/String;

    at com.github.unidbg.linux.android.dvm.AbstractJni.getObjectField(AbstractJni.java:171)

取 ApplicationInfo 也就是 apk 的存放位置 可以使用 adb 进手机查看一下

图片描述

所以 app 在这里: /data/app/com.jingdong.app.mall-Icn20e0xHLzrVHPBwOirRA==/bask.apk

1

2

3

4

5

6

7

8

9

@Override

public DvmObject<?> getObjectField(BaseVM vm, DvmObject<?> dvmObject, String signature) {

    switch (signature) {

        case "android/content/pm/ApplicationInfo->sourceDir:Ljava/lang/String;": {

            return new StringObject(vm, "/data/app/com.jingdong.app.mall-Icn20e0xHLzrVHPBwOirRA==/bask.apk");

        }

    }

    return super.getObjectField(vm, dvmObject, signature);

}

  • com/jingdong/common/utils/BitmapkitZip->unZip(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[B

1

2

java.lang.UnsupportedOperationException: com/jingdong/common/utils/BitmapkitZip->unZip(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[B

    at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:432)

这个补的时候有一个参数,可以先输出一下参数,看看都是什么

1

2

3

arg0--  /data/app/com.jingdong.app.mall-Icn20e0xHLzrVHPBwOirRA==/bask.apk

arg1--  META-INF/

arg2--  .RSA

所以他找的好像是 apk 里面 META-INF 文件夹里面的 xxx.RSA , 我们只要使用压缩文件打开 apk 就可以看到一个叫做 JINGDONG.RSA 的文件,补全一下即可!

图片描述

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

@Override

public DvmObject<?> callStaticObjectMethod(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {

    switch (signature) {

        case "com/jingdong/common/utils/BitmapkitZip->unZip(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)[B": {

            StringObject apkPath = varArg.getObjectArg(0);

            StringObject directory = varArg.getObjectArg(1);

            StringObject filename = varArg.getObjectArg(2);

            if (

                    "/data/app/com.jingdong.app.mall-Icn20e0xHLzrVHPBwOirRA==/bask.apk".equals(apkPath.getValue())

                            && "META-INF/".equals(directory.getValue())

                            && ".RSA".equals(filename.getValue())

            ) {

                byte[] data = vm.unzip("META-INF/JINGDONG.RSA");

                return new ByteArray(vm, data);

            }

        }

    }

    return super.callStaticObjectMethod(vm, dvmClass, signature, varArg);

}

  • sun/security/pkcs/PKCS7-><init>([B)V

1

2

java.lang.UnsupportedOperationException: sun/security/pkcs/PKCS7-><init>([B)V

    at com.github.unidbg.linux.android.dvm.AbstractJni.newObject(AbstractJni.java:741)

图片描述

写完之后提示要捕捉异常,因此就捕捉一下,虽然我也不懂为什么

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

@Override

public DvmObject<?> newObject(BaseVM vm, DvmClass dvmClass, String signature, VarArg varArg) {

    switch (signature) {

        case "sun/security/pkcs/PKCS7-><init>([B)V": {

            PKCS7 pkcs7 = null;

            try {

                byte[] bArray = (byte[]) varArg.getObjectArg(0).getValue();

                pkcs7 = new PKCS7(bArray);

            } catch (ParsingException e) {

                e.printStackTrace();

            }

            DvmClass pkcs7Class = vm.resolveClass("sun/security/pkcs/PKCS7");

            return pkcs7Class.newObject(pkcs7);

        }

    }

    return super.newObject(vm, dvmClass, signature, varArg);

}

  • sun/security/pkcs/PKCS7->getCertificates()[Ljava/security/cert/X509Certificate;

1

2

java.lang.UnsupportedOperationException: sun/security/pkcs/PKCS7->getCertificates()[Ljava/security/cert/X509Certificate;

    at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethod(AbstractJni.java:921)

1

2

3

4

5

6

7

8

9

10

11

12

@Override

public DvmObject<?> callObjectMethod(BaseVM vm, DvmObject<?> dvmObject, String signature, VarArg varArg) {

    switch (signature) {

        case "sun/security/pkcs/PKCS7->getCertificates()[Ljava/security/cert/X509Certificate;": {

            PKCS7 pkcs7 = (PKCS7) dvmObject.getValue();

            X509Certificate[] x509Certificates = pkcs7.getCertificates();

            return ProxyDvmObject.createObject(vm, x509Certificates);

        }

    }

    return super.callObjectMethod(vm, dvmObject, signature, varArg);

}

  • com/jingdong/common/utils/BitmapkitZip->objectToBytes(Ljava/lang/Object;)[B

1

2

java.lang.UnsupportedOperationException: com/jingdong/common/utils/BitmapkitZip->objectToBytes(Ljava/lang/Object;)[B

    at com.github.unidbg.linux.android.dvm.AbstractJni.callStaticObjectMethod(AbstractJni.java:432)

发现他找的是一个 objectToBytes 方法,但是找不到,所以我们直接去 jadx 里面搜索一下,便得到了结果

图片描述

图片描述

可以直接复制出来使用,去除报错,导入一些包就可以了!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

public static byte[] objectToBytes(Object obj) {

    try {

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);

        objectOutputStream.writeObject(obj);

        objectOutputStream.flush();

        byte[] byteArray = byteArrayOutputStream.toByteArray();

        objectOutputStream.close();

        byteArrayOutputStream.close();

        return byteArray;

    } catch (IOException e2) {

        return null;

    }

}

然后再继续补环境

1

2

3

case "com/jingdong/common/utils/BitmapkitZip->objectToBytes(Ljava/lang/Object;)[B": {

    return new ByteArray(vm, objectToBytes(varArg.getObjectArg(0).getValue()));

}

接着运行发现居然不报错了,因此我们就写一个获取 sign 的函数尝试调用一下!

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public void getSign() {

    DvmObject<?> context = vm.resolveClass("android/content/Context").newObject(null);

    String arg1 = "getLegoWareDetailComment";

    String arg2 = "{\"category\":\"12473;12480;12515\",\"commentNum\":3,\"isNew\":\"0\",\"newTitle\":null,\"shadowMainSku\":\"0\",\"shieldCurrentComment\":\"1\",\"shopId\":null,\"shopType\":\"0\",\"sku\":\"11653586880\",\"venderId\":\"644185\",\"wareType\":\"0\"}";

    String arg3 = "f9f40175bf4c6710";

    String arg4 = "android";

    String arg5 = "11.6.4";

    DvmObject<?> ret = module.callStaticJniMethodObject(emulator, "getSignFromJni(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", vm.addLocalObject(context), arg1, arg2, arg3, arg4, arg5

    );

    System.out.println("================================================");

    System.out.println(ret.getValue());

    System.out.println("================================================");

}

图片描述

好的,又开始报错了,咱继续补环境

  • java/lang/StringBuffer-><init>()V

1

2

java.lang.UnsupportedOperationException: java/lang/StringBuffer-><init>()V

    at com.github.unidbg.linux.android.dvm.AbstractJni.newObjectV(AbstractJni.java:791)

1

2

3

4

5

6

7

8

9

10

11

12

@Override

public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {

    switch (signature) {

        case "java/lang/StringBuffer-><init>()V": {

            StringBuffer stringBuffer = new StringBuffer();

            return vm.resolveClass("java/lang/StringBuffer").newObject(stringBuffer);

        }

    }

    return super.newObjectV(vm, dvmClass, signature, vaList);

}

  • java/lang/StringBuffer->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

1

2

java.lang.UnsupportedOperationException: java/lang/StringBuffer->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

    at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)

1

2

3

4

5

6

7

8

9

10

11

12

13

@Override

public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {

    switch (signature) {

        case "java/lang/StringBuffer->append(Ljava/lang/String;)Ljava/lang/StringBuffer;": {

            StringBuffer stringBuffer = (java.lang.StringBuffer) dvmObject.getValue();

            stringBuffer.append(vaList.getObjectArg(0).getValue());

            DvmClass StringBuffer = vm.resolveClass("java/lang/StringBuffer");

            return StringBuffer.newObject(stringBuffer);

        }

    }

    return super.callObjectMethodV(vm, dvmObject, signature, vaList);

}

  • java/lang/Integer->toString()Ljava/lang/String;

1

2

java.lang.UnsupportedOperationException: java/lang/Integer->toString()Ljava/lang/String;

    at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)

1

2

3

4

5

6

case "java/lang/Integer->toString()Ljava/lang/String;": {

    Integer value = (Integer) dvmObject.getValue();

    return vm.resolveClass("java/lang/String").newObject(value.toString());

}

  • java/lang/StringBuffer->toString()Ljava/lang/String;

1

2

java.lang.UnsupportedOperationException: java/lang/StringBuffer->toString()Ljava/lang/String;

    at com.github.unidbg.linux.android.dvm.AbstractJni.callObjectMethodV(AbstractJni.java:416)

1

2

3

4

5

case "java/lang/StringBuffer->toString()Ljava/lang/String;": {

    StringBuffer value = (StringBuffer) dvmObject.getValue();

    return vm.resolveClass("java/lang/String").newObject(value.toString());

}

最后发现环境补完了,可以直接得到 sign 了!

图片描述

jdgs

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

package com.jingdong.app.mall;

import com.github.unidbg.AndroidEmulator;

import com.github.unidbg.arm.backend.Unicorn2Factory;

import com.github.unidbg.linux.android.AndroidEmulatorBuilder;

import com.github.unidbg.linux.android.AndroidResolver;

import com.github.unidbg.linux.android.dvm.*;

import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;

import com.github.unidbg.memory.Memory;

import java.io.File;

public class jdgs extends AbstractJni {

    private final AndroidEmulator emulator;

    private final VM vm;

    // 包名

    private final String packageName = "com.com.jingdong.app.mall";

    // apk 地址

    private final String packagePath = "unidbg-android/src/test/resources/jd.apk";

    // so 名称, 要去掉 lib 和  .so

    private final String libraryName = "jdg";

    // jni 类名

    private final String jniClassName = "com.jd.security.jdguard.core.Bridge";

    // 调试信息

    private final Boolean verbose = true;

    // jni 模块

    private final DvmClass module;

    public jdgs() {

        // 实例化一个模拟器

        emulator = AndroidEmulatorBuilder

                .for32Bit()

                .addBackendFactory(new Unicorn2Factory(true))

                .setProcessName(packageName)

                .build();

        Memory memory = emulator.getMemory();

        memory.setLibraryResolver(new AndroidResolver(23));

        vm = emulator.createDalvikVM(new File(packagePath));

        vm.setJni(this);

        vm.setVerbose(verbose);

        DalvikModule dm = vm.loadLibrary(libraryName, true);

        module = vm.resolveClass(jniClassName);

        dm.callJNI_OnLoad(emulator);

    }

    public static void main(String[] args) {

        jdgs ins = new jdgs();

        ins.getJdgs();

    }

    public void getJdgs() {

        Integer i2 = 101;

        String arr1 = "/client.action bef=1&bfa06501b64b4672e3b8645fd0ad54401e4786bd9efebc784fa483cf99bec2fd=&build=98704&client=android&clientVersion=11.6.4&ef=1&eid=eidAf5088122acsfbxEYf1ulTs2pUn38cGRLh28RGgYgyPwz80gu9s7yfbXAvbZ1R70WQm1PENZaDGd6EF%2FmunBEHRDWPW5sYgYPelPhS+vYueanoeJv&ep=%7B%22hdid%22%3A%22JM9F1ywUPwflvMIpYPok0tt5k9kW4ArJEU3lfLhxBqw%3D%22%2C%22ts%22%3A1678345151525%2C%22ridx%22%3A-1%2C%22cipher%22%3A%7B%22area%22%3A%22CV83Cv8yDzu5XzK%3D%22%2C%22d_model%22%3A%22J05PUOnVU0O2CNKm%22%2C%22wifiBssid%22%3A%22dW5hbw93bq%3D%3D%22%2C%22osVersion%22%3A%22CJK%3D%22%2C%22d_brand%22%3A%22J25vUQn1cm%3D%3D%22%2C%22screen%22%3A%22CtO1EIenCNqm%22%2C%22uuid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%2C%22aid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%2C%22openudid%22%3A%22ZtvwDNKnDzVsZtHtDtcnCK%3D%3D%22%7D%2C%22ciphertype%22%3A5%2C%22version%22%3A%221.2.0%22%2C%22appname%22%3A%22com.jingdong.app.mall%22%7D&ext=%7B%22prstate%22%3A%220%22%2C%22pvcStu%22%3A%221%22%2C%22cfgExt%22%3A%22%7B%5C%22privacyOffline%5C%22%3A%5C%220%5C%22%7D%22%7D&functionId=wareBusiness&harmonyOs=0&lang=zh_CN&lmt=0&networkType=wifi&oaid=1B4220DEA49487D7AF8DE6AF4C0F1F60F05819E2ADCC3CD2ACDF3C07D81BEDC6&partner=tencent&scval=10062561918391&sdkVersion=29&sign=68049b09fa2cb6111f9ab0209ea9412e&st=1678348533650&sv=100&uemps=2-2-2";

        String arr2 = "sdm845|-|OnePlus6|-|-|-|-|-|-|-|-|-|-|-|-|-|-§§0|1§§-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-|-§§-|115717931008§§-|-|1.0|-§§0|1|-|-|-|-|-|-|-|-|-§§OnePlus/OnePlus6/OnePlus6:10/QKQ1.190716.003/2107162030:user/release-keys/|-|-|1678344292938|-§§-|-|-|-|-|-|-";

        String arr3 = "eidAf5088122acsfbxEYf1ulTs2pUn38cGRLh28RGgYgyPwz80gu9s7yfbXAvbZ1R70WQm1PENZaDGd6EF/munBEHRDWPW5sYgYPelPhS+vYueanoeJv";

        String arr4 = "1.0";

        String arr5 = "83";

        Object[] arr = new Object[]{arr1.getBytes(), arr2, arr3, arr4, arr5};

        DvmObject<?> ret = module.callStaticJniMethodObject(emulator, "main(I[Ljava/lang/Object;)[Ljava/lang/Object;", i2, ProxyDvmObject.createObject(vm, arr));

        System.out.println("================================================");

        System.out.println(ret.getValue());

        System.out.println("================================================");

    }

}

模拟 jdgs 的时候不知道为什么会报这样一个错误,并且也没有堆栈,因为还是小白,暂时不知道如何解决
目前会导致获取到的结果拿不到,以后有思路了再来完善这一块!希望有大佬能指导一下,万分感谢!!

图片描述

相关文章:

京东协议算法最新版

环境准备 1 com.jingdong.app.mall11.6.4 入口定位 逆向分析&#xff0c;发现 params 里面有一个 sign 以及请求头里面有一个 jdgs 首先我们发现京东的 sign 是 32 位的&#xff0c;猜测其可能是 md5 之类的 hash 算法&#xff0c;既然是 hash 算法&#xff0c;那么就大概率…...

软考系统架构设计师案例分析知识汇总

软件架构风格 △△△ 软件架构风格是描述某一类特定应用领域中软件系统组织方式和惯用方式。组织方式描述了系统的组成构件和这些构件的组织方式,惯用模式则反映众多系统共有的结构和语义。 面向对象架构风格的特征是将数据表示和基本操作封装在对象中。这种模式的构件是对象…...

MyBatis-plus 代码生成器

具体代码 application.yaml server:port: 8081 #自定义端口号spring:datasource:url: jdbc:mysql://localhost:3306/itcast?useUnicodetrue&characterEncodingutf-8&serverTimezoneGMT%2B8username: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driver…...

运维常识——网络

内网&#xff0c;公网IP 内网IP为专网IP 因为网络资源&#xff08;IP地址不够&#xff0c;所以引出来了内网IP和IPv6&#xff09; 内网IP和公网IP之分是为了减缓IP地址不够使用的情况 一般设置代理服务器 设置两张网卡 一张对外一张对内 内部主机将数据转发到内网卡&#…...

《研发效能(DevOps)工程师》课程简介(一)丨IDCF

为贯彻落实《关于深化人才发展体制机制改革的意见》&#xff0c;推动实施人才强国战略&#xff0c;促进专业技术人员提升职业素养、补充新知识新技能&#xff0c;实现人力资源深度开发&#xff0c;推动经济社会全面发展&#xff0c;根据《中华人民共和国劳动法》有关规定&#…...

OMV 介绍及安装

# Time: 2023/11/02 #Author: Xiaohong # 运行电脑: Lenovo X201I (Intel(R) Core(TM) i3 CPU M 370 2.40GHz) # 功能: OMV 介绍及安装 导图 若OMV6 安装Extras 插件失败&#xff0c;可以参考 OMV6 安装Extras 插件失败的解决方法...

JAVA 实现PDF转图片(spire.pdf.free版)

1.引入jar包 导入方法1&#xff1a; 手动引入。将Free Spire.PDF for Java下载到本地&#xff0c;解压&#xff0c;找到lib文件夹下的Spire.PDF.jar文件。在IDEA中打开如下界面&#xff0c;将本地路径中的jar文件引入Java程序&#xff1a; 导入方法2&#xff1a;如果您想通过…...

高效学习工具之AnkiMobile新手入门指南(ios端,包括ipad、ihpone设备)————创建、使用、备份、设置参数、相关资料

文章目录 0 背景0.1 闭环学习0.2 什么是anki0.3 anki践行者经验分享 1 开始使用1.1 导入1.2 创建空白组1.3 创建卡片1.3.1 利用anki创建卡片的两种方法1.3.2 复习材料分类 1.4 筛选&#xff08;做减法&#xff0c;拆分学习&#xff08;做子卡牌集合&#xff09;&#xff09;&am…...

LiveMeida视频接入网关

一、产品简介 视频接入网关主要部署在视频存储节点或视频汇聚节点&#xff0c;面向不同用户&#xff0c;主要用于对接不同厂家、不同型号的摄像机设备&#xff0c;获取摄像机视频后&#xff0c;以统一标准的视频格式和传输协议&#xff0c;将视频推送至上层联网/应用平台。可广…...

我和云栖有个约会

文章目录 云栖大会体验与感受大模型的体验感受 对大会的期待 云栖大会 云栖大会是是阿里巴巴集团主办的年度技术盛会&#xff0c;是云计算、大数据、人工智能等前沿技术产业发展的见证者、参与者和推动者。2023年的云栖大会于10月31日在杭州开幕&#xff0c;吸引了全球的技术专…...

模拟官网编写自定义Grafana Dashboard

前言 我们想编写自定义的Dashboard&#xff0c;类似于官网那样下载的Dashboard&#xff0c;并且能移值到机器主机&#xff0c;如何实现了&#xff1f; ## 官网dashboard https://grafana.com/grafana/dashboards/ 编写 先在虚拟机写好Dashboard 然后下载。json文件如下: {…...

组件局部注册和全局注册

普通组件的注册使用-局部注册 1.特点&#xff1a; 只能在注册的组件内使用 2.实现效果 3.步骤&#xff1a; 创建.vue文件&#xff08;三个组成部分&#xff09;在使用的组件内先导入再注册&#xff0c;最后使用 4.使用方式&#xff1a; 当成html标签使用即可 <组件名&…...

【数据结构】模拟实现stack

namespace my_stack {//适配器模式/配接器template <class T,class Containervector<T>>class stack {public:void push(const T& val){_con.push_back(val);}void pop(){_con.pop_back();}const T& top(){return _con.back();}size_t size(){return _con.…...

Hive创建分区表并插入数据

业务中经常会遇到这种需求&#xff1a;数据每天全量更新&#xff0c;但是要求月底将数据单独保存一份以供后期查询某月节点的信息。这时就要考虑用到Hive的分区表实现&#xff0c;即按照月份创建分区表&#xff0c;相当于新的月份数据保存在新表&#xff0c;进而实现保存了历史…...

虚拟机防火墙关闭教程

虚拟机防火墙关闭教程 连接数据库请求超时 教程...

《研发效能(DevOps)工程师》课程简介(二)丨IDCF

为贯彻落实《关于深化人才发展体制机制改革的意见》&#xff0c;推动实施人才强国战略&#xff0c;促进专业技术人员提升职业素养、补充新知识新技能&#xff0c;实现人力资源深度开发&#xff0c;推动经济社会全面发展&#xff0c;根据《中华人民共和国劳动法》有关规定&#…...

EViews| 基础操作 备战下周机考

目录 一、创建工作文件 1、非时间序列数据 2、时间序列数据 二、导入数据 1、导入数据 2、保存数据组合或方程结果 三、估计回归模型 1、估计回归模型 2、回归结果名词解读 四、检验模型设定错误 1、检验是否遗漏变量 2-1、检验是否加入了不相干变量 2-2、惩罚新增…...

Web安全系列——敏感信息泄露与加密机制

一、前言 数字化时代&#xff0c;越来越多的数据正在被传输到Web应用程序中&#xff0c;这其中不乏个人或机构的敏感信息。 如果Web应用程序未采取正确的加密机制&#xff0c;这些信息可能会遭到窃取或篡改&#xff0c;从而使用户数据或机构的财产受到威胁。 二、加密机制失…...

【kubernetes】k8s对象

文章目录 1、什么是kubernetes对象2、如何描述kubernetes对象3、服务器端字段验证 1、什么是kubernetes对象 在k8s系统中&#xff0c;kubernetes 对象是持久化的实体&#xff0c;kubernetes使用这些实体去表示 整个集群的状态,具体而言&#xff0c;他们描述了以下信息&#xf…...

关注云栖大会的感受:从工业大脑到全面AI时代的进化

前言 自2009年的地方网站峰会到如今的云栖大会&#xff0c;这个盛大的科技盛事已经走过了一个多十年的漫长历程。这个会议见证了中国科技行业的崛起&#xff0c;也记录了技术的不断演化。而对我来说&#xff0c;首次接触云栖大会是在2020年&#xff0c;当年大会迁移到线上&…...

设计模式和设计原则回顾

设计模式和设计原则回顾 23种设计模式是设计原则的完美体现,设计原则设计原则是设计模式的理论基石, 设计模式 在经典的设计模式分类中(如《设计模式:可复用面向对象软件的基础》一书中),总共有23种设计模式,分为三大类: 一、创建型模式(5种) 1. 单例模式(Sing…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

React19源码系列之 事件插件系统

事件类别 事件类型 定义 文档 Event Event 接口表示在 EventTarget 上出现的事件。 Event - Web API | MDN UIEvent UIEvent 接口表示简单的用户界面事件。 UIEvent - Web API | MDN KeyboardEvent KeyboardEvent 对象描述了用户与键盘的交互。 KeyboardEvent - Web…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

【git】把本地更改提交远程新分支feature_g

创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

C++ 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件&#xff0c;这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下&#xff0c;实现高效测试与快速迭代&#xff1f;这一命题正考验着…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝

目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为&#xff1a;一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...

基于Java+VUE+MariaDB实现(Web)仿小米商城

仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意&#xff1a;运行前…...