吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5350|回复: 42
收起左侧

[Android 原创] 某APP的sDjcSign加密

  [复制链接]
你就是我的阳光 发表于 2024-3-7 10:08
本帖最后由 你就是我的阳光 于 2024-3-7 10:24 编辑

最近沉迷游戏,每天要兑换交易牌有时候也会忘搞的签到全勤都没有了,一气之下做个自动化去。
1.抓包,经过多次抓包以后得到sDjcSign每次加密结果都不一样,初步怀疑RSA

image (6).png

2.反编译搜索

image (7).png


这结果简单明了啊,就两个,那第一个remove肯定不是了,那就看看第二个。
3.正式开始逆向之旅
image (8).png


可以看到这个sb2是上面sb.toString得来的,那具体内容不知道是什么一会用frIDA看看,我们先看o.a(sb2, "se35d32s63r7m23m")这个函数是干什么的。

image (9).png
好消息,这就是一个aes加密,那我们先翻译一下代码,现在的代码不就是这样的么
[Java] 纯文本查看 复制代码
tVar.a("sDjcSign", o.b(o.a(AES(sb2, "se35d32s63r7m23m"))));

那还有两个参数不知道干什么的对不对,重复刚才的操作,接着看o.a


image (10).png

这个o.a进来以后还调用了一个a,那就继续进这个a


image (11).png
果然啊,果然是RSA,就是从文件打开么,那就去找找这个文件

image (12).png
那这就很清晰明了了,现在的加密代码为
[Java] 纯文本查看 复制代码
tVar.a("sDjcSign", o.b(RSA(AES(sb2, "se35d32s63r7m23m"))));

就剩一个o.b了。最后一个了,继续往下看
image (13).png

这个如果看不懂没关系,我们使用frida hook,正好这个sb2这个值也需要用frida去看一下对吧。
[JavaScript] 纯文本查看 复制代码
function b() {
  Java.perform(function() {
    var i = Java.use("com.tencent.djcity.util.o");
    i.b.overload('[B').implementation = function (arg1) {
      console.log('b.arg1 = ' + ByteString.of(arg1).hex())
      // console.log('arg2 = ' + arg2)
      var ret = this.b(arg1)
      console.log('b.ret = ' + ret)
      return ret
    }
  });

}
function AES() {
  Java.perform(function() {
    var i = Java.use("com.tencent.djcity.util.o");
    i.a.overload('java.lang.String','java.lang.String').implementation = function (arg1,arg2) {
      console.log('AES.arg1 = ' + arg1)
      console.log('AES.arg2 = ' + arg2)
      var ret = this.a(arg1,arg2)
      console.log('AES.ret = ' + ByteString.of(ret).hex())
      return ret
    }
  });

}


function main() {
  AES()
  b()
}

main()
[Java] 纯文本查看 复制代码
AES.arg1 = 8C571443167953BCC67294D143B97FDA+5d73310d28a1a02929aea12667e64edb0dd356c592261249d47bf75e134efe2a+1709772579610+148
AES.arg2 = se35d32s63r7m23m
AES.ret = 778e7eb1fbad4a871b360bf31dbc5502b3d3c8fcfe60f34b6d7d6f7f1a91fabd04132463e13bc07364588f7a84118593bdb207adefd92823c81d0483319b8b88009c2905303978d2c8fa58480957eb005a73d5cb79a338b9c43fafe0de6c06bde254f1d3ce9d09a723d30c2337be860cb144864c4c69eeb190f1f19b4b6f3196
b.arg1 = 20b7bf49b0c8979e04a41474bc74d5acf6efa672a06fe78ea2e415bb929ea20e25a14316f7706e870664b95ba116b35448628b75c776188546b7291c27e05d7d963375c8a8a22e95ab6833a957e75a0f515f1b012f9e1ae6bf49c815c9b09afa1aabea8bb8df1651c073bec595cb235b6f3c6e0f385f291d242c6e2fd185af8d4336b33c2a5d24f7239a4a5d9587dc53acc686e0a2ede36c1d892d804f65d540474256902eca1fd7847fee066c5e2d70371b4e99015afb3c69b7dddfbeac87cc311654dd71ec220b54b84e76ccd46b08b16c71fce279f32a1d9cdb761a00383a8b17b6bb7b36602ffcc4fb2dd1b705530bdb379c3532d2cb22b6fbd89f0aabfa
b.ret = 20b7bf49b0c8979e04a41474bc74d5acf6efa672a06fe78ea2e415bb929ea20e25a14316f7706e870664b95ba116b35448628b75c776188546b7291c27e05d7d963375c8a8a22e95ab6833a957e75a0f515f1b012f9e1ae6bf49c815c9b09afa1aabea8bb8df1651c073bec595cb235b6f3c6e0f385f291d242c6e2fd185af8d4336b33c2a5d24f7239a4a5d9587dc53acc686e0a2ede36c1d892d804f65d540474256902eca1fd7847fee066c5e2d70371b4e99015afb3c69b7dddfbeac87cc311654dd71ec220b54b84e76ccd46b08b16c71fce279f32a1d9cdb761a00383a8b17b6bb7b36602ffcc4fb2dd1b705530bdb379c3532d2cb22b6fbd89f0aabfa

我们先看AES.arg1这个是什么意思。这个要结合抓包看,当时我研究了很久这个是啥意思
8C571443167953BCC67294D143B97FDA,这个意思是openid,自己无法生成,系统生成给你的
5d73310d28a1a02929aea12667e64edb0dd356c592261249d47bf75e134efe2a,这个是你的sDeviceID,都是固定的。
1709772579610,这个很明显吧,时间戳148,这个嘛,忘记当时怎么找的了,但是测试的时候感觉所有都可以,那干脆就当他是随机数了。
在看b.arg1和b.ret,注意在frida的js文件中写的脚本,arg1是'[B',ret是string,那这个函数就是byte2string函数。4.复现加密我们从前面知道了这个参数的各个意思,因为他经过了一个读文件进行rsa加密嘛,所以我选择直接用java写。
[Java] 纯文本查看 复制代码
public class test {
    private static PublicKey RSA() {
        String baseDir = System.getProperty("user.dir");
        byte[] bArr;
        Exception e;
        try {
            InputStream open = new FileInputStream(baseDir + "\\djc_rsa_public_key_new.der");
            try {
                bArr = new byte[open.available()];
                do {
                    try {
                    } catch (Exception e2) {
                        e = e2;
                        System.out.println("Got exception while is -> bytearr conversion: " + e);
                        return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(bArr));
                    }
                } while (open.read(bArr) != -1);
            } catch (Exception e3) {
                e = e3;
                bArr = null;
            }
            return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(bArr));
        } catch (Exception e4) {
            System.out.println("cuole");
            return null;
        }
    }

    public static byte[] AES(String str, String str2) throws Exception {
        if (str == null) {
            return null;
        }
        Cipher instance = Cipher.getInstance("AES/ECB/PKCS5Padding");
        instance.init(1, new SecretKeySpec(str2.getBytes(StandardCharsets.UTF_8), "AES"));
        return instance.doFinal(str.getBytes(StandardCharsets.UTF_8));
    }

    public static byte[] toRSA(byte[] bArr) throws Exception {
        Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        instance.init(1, RSA());
        return instance.doFinal(bArr);
    }
    public static String bytesToHex(byte[] bytes) {
        StringBuilder buf = new StringBuilder(bytes.length * 2);
        for(byte b : bytes) { // 使用String的format方法进行转换
            buf.append(String.format("%02x", Integer.valueOf(b & 0xff)));
        }

        return buf.toString();
    }


    public static void main(){
         String time = String.valueOf(System.currentTimeMillis());
         System.out.println(bytesToHex3(toRSA(AES(openid + "+5d73310d28a1a02929aea12667e64edb0dd356c592261249d47bf75e134efe2a+" + time + "+" + new Random().nextInt(1000),"se35d32s63r7m23m"))));
    }
}


image (1).png
这时候我们得到了一条加密因为存在RSA了,我们需要发包验证才行,先对比一下长度对不对吧。

image (2).png
经过做个对比,长度是一样的,那我们直接复现一下签到协议吧,看看对不对

image (3).png

这个是签到包

[Java] 纯文本查看 复制代码
:method: POST
:path: /ams/ame/amesvr?ameVersion=0.3&sServiceType=dj&iActivityId=11117&sServiceDepartment=djc&set_info=newterminals&&appSource=android&appVersion=148&ch=10010&sDeviceID=5d73310d28a1a02929aea12667e64edb0dd356c592261249d47bf75e134efe2a&osVersion=Android-29&p_tk=1713520954&sVersionName=v4.7.2.0
:authority: comm.ams.game.qq.com
:scheme: https
user-agent: TencentDaojucheng=v4.7.2.0&appSource=android&appVersion=148&ch=10010&sDeviceID=5d73310d28a1a02929aea12667e64edb0dd356c592261249d47bf75e134efe2a&firmwareVersion=10&phoneBrand=Xiaomi&phoneVersion=MI+8&displayMetrics=1080 * 2115&cpu=AArch64 Processor rev 13 (aarch64)&net=wifi&sVersionName=v4.7.2.0&plNo=133
charset: UTF-8
referer: [url=https://daoju.qq.com/index.shtml]https://daoju.qq.com/index.shtml[/url]
content-type: application/x-www-form-urlencoded
content-length: 910
accept-encoding: gzip
cookie: djc_appSource=android; djc_appVersion=148; acctype=qc; appid=1101958653; openid=8C571443167953BCC67294D143B97FDA; access_token=1348438E26324A8A76C19222FF57A062

djcRequestId=5d73310d28a1a02929aea12667e64edb0dd356c592261249d47bf75e134efe2a-1709772046351-874&appVersion=148&sign_version=1.0&ch=10010&iActivityId=11117&sDjcSign=0ff184ac0b2b6b7c0ee76f7daf9c4f6bbafd7d1610d58eee92add748bd1af18e644ba8d2766393017c928e0f1193d19b8c5d7d08c2a6071e229c2a62cd8b0434ec5b0d068b9f0cbe880fb74580408632eb2f28f5daec685aa37d7685fd5b2cfb42bea5615d86396c9f128096292b10e4ad5077caaa6bd1642d03ebb5f1e8f9e9b982ec96415d24b335a73f5ad09d4859fb6af45b404e71db7a4fbda9e0a49862192a35935cdbab75612eea8707dafb2f2380f3e2a5e67587f162b2bb3456ca00a63c4b12c47d7ad59914d3910d4f83ca6462d48a4030a66ac695255d5f6de1bc2077383ec6bbe00f52f7a881365977228658401a0f256417b04e7785d258b1ee&sDeviceID=5d73310d28a1a02929aea12667e64edb0dd356c592261249d47bf75e134efe2a&p_tk=1713520954&month=202403&osVersion=Android-29&iFlowId=96939&sVersionName=v4.7.2.0&sServiceDepartment=djc&sServiceType=dj&appSource=android&g_tk=1842395457

返回结果
[Java] 纯文本查看 复制代码
{
        "flowRet": {
                "iRet": "0",
                "sMsg": "MODULE OK",
                "iAlertSerial": "0",
                "sLogSerialNum": "AMS-DJ-0307084047-hYkHMd-11117-96939"
        },
        "modRet": {
                "msg": "\u7b7e\u5230\u6210\u529f",
                "sMsg": "\u7b7e\u5230\u6210\u529f",
                "action_id": "7932",
                "ret": "0",
                "iRet": "0"
        },
        "ret": "0",
        "msg": ""
}

image (4).png


我们看这个包吧。先看post body

[Java] 纯文本查看 复制代码
djcRequestId=5d73310d28a1a02929aea12667e64edb0dd356c592261249d47bf75e134efe2a-1709772046351-874
&appVersion=148
&sign_version=1.0
&ch=10010
&iActivityId=11117
&sDjcSign=0ff184ac0b2b6b7c0ee76f7daf9c4f6bbafd7d1610d58eee92add748bd1af18e644ba8d2766393017c928e0f1193d19b8c5d7d08c2a6071e229c2a62cd8b0434ec5b0d068b9f0cbe880fb74580408632eb2f28f5daec685aa37d7685fd5b2cfb42bea5615d86396c9f128096292b10e4ad5077caaa6bd1642d03ebb5f1e8f9e9b982ec96415d24b335a73f5ad09d4859fb6af45b404e71db7a4fbda9e0a49862192a35935cdbab75612eea8707dafb2f2380f3e2a5e67587f162b2bb3456ca00a63c4b12c47d7ad59914d3910d4f83ca6462d48a4030a66ac695255d5f6de1bc2077383ec6bbe00f52f7a881365977228658401a0f256417b04e7785d258b1ee
&sDeviceID=5d73310d28a1a02929aea12667e64edb0dd356c592261249d47bf75e134efe2a
&p_tk=1713520954
&month=202403
&osVersion=Android-29
&iFlowId=96939
&sVersionName=v4.7.2.0
&sServiceDepartment=djc
&sServiceType=dj
&appSource=android
&g_tk=1842395457

djcRequestId:这个我们不就发现这不就是AES加密的时候那个参数么。在外面拼接一下Openid在做一层RSA不就得到sDjcSign。剩下的参数p_tk同样也是根据openid还有access_token得来的,我们要是想实现签到,那就先抓包,得到openid,access_token,p_tk。好像这几个有效期是三个月。再看url中的参数

[Java] 纯文本查看 复制代码
/ams/ame/amesvr?ameVersion=0.3
&sServiceType=dj
&iActivityId=11117
&sServiceDepartment=djc
&set_info=newterminals
&&appSource=android
&appVersion=148
&ch=10010
&sDeviceID=5d73310d28a1a02929aea12667e64edb0dd356c592261249d47bf75e134efe2a
&osVersion=Android-29
&p_tk=1713520954
&sVersionName=v4.7.2.0

url中没有什么特别的参数,就是p_tk要跟post body中一致
终于到了最后一步,签到代码

[Java] 纯文本查看 复制代码
public class test {
    private static PublicKey RSA() {
        String baseDir = System.getProperty("user.dir");
        byte[] bArr;
        Exception e;
        try {
            InputStream open = new FileInputStream(baseDir + "\\djc_rsa_public_key_new.der");
            try {
                bArr = new byte[open.available()];
                do {
                    try {
                    } catch (Exception e2) {
                        e = e2;
                        System.out.println("Got exception while is -> bytearr conversion: " + e);
                        return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(bArr));
                    }
                } while (open.read(bArr) != -1);
            } catch (Exception e3) {
                e = e3;
                bArr = null;
            }
            return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(bArr));
        } catch (Exception e4) {
            System.out.println("cuole");
            return null;
        }
    }

    public static byte[] AES(String str, String str2) throws Exception {
        if (str == null) {
            return null;
        }
        Cipher instance = Cipher.getInstance("AES/ECB/PKCS5Padding");
        instance.init(1, new SecretKeySpec(str2.getBytes(StandardCharsets.UTF_8), "AES"));
        return instance.doFinal(str.getBytes(StandardCharsets.UTF_8));
    }

    public static byte[] toRSA(byte[] bArr) throws Exception {
        Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding");
        instance.init(1, RSA());
        return instance.doFinal(bArr);
    }
    public static String bytesToHex(byte[] bytes) {
        StringBuilder buf = new StringBuilder(bytes.length * 2);
        for(byte b : bytes) { // 使用String的format方法进行转换
            buf.append(String.format("%02x", Integer.valueOf(b & 0xff)));
        }

        return buf.toString();
    }
    public static String sendPost() {
//        获取年月
        Calendar instance = Calendar.getInstance();
        String year = String.valueOf(instance.get(Calendar.YEAR));
        int month = instance.get(Calendar.MONTH) + 1;
        String months = "";
        if (month<=9){
            months = "0" + month;
        }
        String months1 = String.valueOf(year + months);
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            String time = String.valueOf(System.currentTimeMillis());
            URL realUrl = new URL("https://comm.ams.game.qq.com/ams/ame/amesvr?ameVersion=0.3&sServiceType=dj&iActivityId=11117&sServiceDepartment=djc&set_info=newterminals&w_ver=20&w_id=149&appSource=android&appVersion=148&ch=10010&sDeviceID=36faffea6eade625598fd1074a63f01d4bdf26e4d42a62689277877f55428e0b&osVersion=Android-29&p_tk=" + p_tk +"&sVersionName=v4.7.2.0");
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("user-agent", "TencentDaojucheng=v4.7.2.0&appSource=android&appVersion=148&ch=10010&sDeviceID=36faffea6eade625598fd1074a63f01d4bdf26e4d42a62689277877f55428e0b&firmwareVersion=10&phoneBrand=Xiaomi&phoneVersion=MI+8&displayMetrics=1080 * 2029&cpu=AArch64 Processor rev 13 (aarch64)&net=wifi&sVersionName=v4.7.2.0&plNo=133");
            conn.setRequestProperty("charset", "UTF-8");
            conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
            conn.setRequestProperty("cookie", "djc_appSource=android; djc_appVersion=148; acctype=qc; appid=1101958653; " + open_access);
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            // 发送请求参数
            String param ="djcRequestId=36faffea6eade625598fd1074a63f01d4bdf26e4d42a62689277877f55428e0b-"+time+"-"+new Random().nextInt(1000)+"&appVersion=148&sign_version=1.0&ch=10010&iActivityId=11117&sDjcSign=" + bytesToHex3(toRSA(AES(openid + "+36faffea6eade625598fd1074a63f01d4bdf26e4d42a62689277877f55428e0b+" + time + "+" + new Random().nextInt(1000),"se35d32s63r7m23m"))) + "&sDeviceID=36faffea6eade625598fd1074a63f01d4bdf26e4d42a62689277877f55428e0b&p_tk=" + p_tk + "&month=" + months1 +"&osVersion=Android-29&iFlowId=96939&sVersionName=v4.7.2.0&sServiceDepartment=djc&sServiceType=dj&appSource=android&g_tk=1842395457";
//            System.out.println(param);
            out.print(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送 POST 请求出现异常!"+e);
            e.printStackTrace();
        }
        //使用finally块来关闭输出流、输入流
        finally{
            try{
                if(out!=null){
                    out.close();
                }
                if(in!=null){
                    in.close();
                }
            }
            catch(IOException ex){
                ex.printStackTrace();
            }
        }
        return result;
    }
    public static String unicodeDecode(String string) {
        Pattern pattern = Pattern.compile("(\\\\u(\\p{XDigit}{4}))");
        Matcher matcher = pattern.matcher(string);
        char ch;
        while (matcher.find()) {
            ch = (char) Integer.parseInt(matcher.group(2), 16);
            string = string.replace(matcher.group(1), ch + "");
        }
        return string;
    }

    public static void main(){
         System.out.println(unicodeDecode(sendPost()));
    }
}

image (5).png
我今天已经签到过了,所以提示签到过了,剩下的请求都是一样的,就是体力活搬砖,这个app没有涉及到so,没有涉及到抓包检测,反调试也没有,加密也不是那么难,很适合练手啊。

免费评分

参与人数 11威望 +2 吾爱币 +110 热心值 +10 收起 理由
hu15823 + 1 谢谢@Thanks!
fengbolee + 2 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
imumu1239 + 1 + 1 谢谢@Thanks!
抱书人人 + 1 + 1 谢谢@Thanks!
nnzhs + 1 + 1 谢谢@Thanks!
楠宝大帝 + 1 + 1 热心回复!
allspark + 1 + 1 用心讨论,共获提升!
西枫游戏 + 1 很好的一个例子,赞了爱了
正义天下 + 1 + 1 谢谢@Thanks!
Hslim + 1 + 1 我很赞同!
正己 + 2 + 100 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

520CAI 发表于 2024-3-10 20:48
为什么我弄成青龙脚本输出的sDjcSign是这样的:
PE88On/RFVIrhmPJsDi5SnTokmCTnyiJDtnUdqYjXbX//SBF+I2AwJ8ZsTiDJ0c+aDszMLQsUrxrD+u10B7VLkwffHZYbyvhE4MfYL8SeQbwJYvXKSsj8/RRXLp4DErZ1RlW11vyd8VEr7wWvw8DVqvdXNKeX3ODU/qgvv54ImXHSSQRIM4w5bidajG4PYokF48msxLtNUrm4Z8iXwBoBWmmM0G9KzkjhbBAuJbBRfw+/f+vt4q0+Lgpy3YU6fF+Y4WnpywwM/bUF7LuG+CFtEOvbykSL2C+b13FyCTmVeX5G2eOzjpqXZOE2J2GvkACIckMZuAoHeZQBDfF4zuzyg==

就想自动签个到,就这么难
yan_97 发表于 2024-4-15 06:07
之前有弄过,但是发现返回虽然也是提示签到成功或者已签到,实际去app看   没有签。  不知道你的是不是这个情况
mhh 发表于 2024-3-7 18:49
 楼主| 你就是我的阳光 发表于 2024-3-7 19:19
mhh 发表于 2024-3-7 18:49
大佬哪个app吗,想学习下。

5o6M5LiK6YGT6IGa5Z+O
zjh889 发表于 2024-3-7 21:14
这东西不错,谢谢大师!
mhh 发表于 2024-3-7 22:12

解密不出了
erichyx 发表于 2024-3-7 22:51

掌上道聚城
corleone9 发表于 2024-3-8 06:21
学习了,谢谢
Xue1203 发表于 2024-3-8 08:07
学校了,感谢
sunshineinsky 发表于 2024-3-8 08:41
6666666,看看
 楼主| 你就是我的阳光 发表于 2024-3-8 13:40

新版本加密应该是变了的,我一直没有更新,可以先按照我这个版本来一遍,在去看新版本
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-14 08:52

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表