你就是我的阳光 发表于 2024-3-7 10:08

某APP的sDjcSign加密

本帖最后由 你就是我的阳光 于 2024-3-7 10:24 编辑

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



2.反编译搜索




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



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


好消息,这就是一个aes加密,那我们先翻译一下代码,现在的代码不就是这样的么
tVar.a("sDjcSign", o.b(o.a(AES(sb2, "se35d32s63r7m23m"))));
那还有两个参数不知道干什么的对不对,重复刚才的操作,接着看o.a




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



果然啊,果然是RSA,就是从文件打开么,那就去找找这个文件


那这就很清晰明了了,现在的加密代码为
tVar.a("sDjcSign", o.b(RSA(AES(sb2, "se35d32s63r7m23m"))));
就剩一个o.b了。最后一个了,继续往下看


这个如果看不懂没关系,我们使用frida hook,正好这个sb2这个值也需要用frida去看一下对吧。
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()

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写。
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;
                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"))));
    }
}


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


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



这个是签到包

: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: https://daoju.qq.com/index.shtml
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
返回结果{
      "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": ""
}



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

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中的参数

/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中一致
终于到了最后一步,签到代码

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

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

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

大佬哪个app吗,想学习下。

你就是我的阳光 发表于 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

你就是我的阳光 发表于 2024-3-7 19:19
5o6M5LiK6YGT6IGa5Z+O

解密不出了

erichyx 发表于 2024-3-7 22:51

mhh 发表于 2024-3-7 22:12
解密不出了

掌上道聚城

corleone9 发表于 2024-3-8 06:21

学习了,谢谢

Xue1203 发表于 2024-3-8 08:07

学校了,感谢

sunshineinsky 发表于 2024-3-8 08:41

6666666,看看

你就是我的阳光 发表于 2024-3-8 13:40

mhh 发表于 2024-3-7 22:12
解密不出了

新版本加密应该是变了的,我一直没有更新,可以先按照我这个版本来一遍,在去看新版本
页: [1] 2 3 4 5
查看完整版本: 某APP的sDjcSign加密