本帖最后由 不温卜火 于 2022-4-9 17:31 编辑
妈了个巴子的,白瞎我两天时间,气死 闪照储存位置 首先得找到闪照的文件储存的位置,这个过程相当繁琐,不再列举,直接把缓存的储存位置贴在下面
| /storage/emulated/0/Android/data/com.tencent.mobileqq/Tencent/MobileQQ/chatpic/chatimg/
|
在这个路径的文件夹下,有许多的子文件夹,子文件夹中的文件,就是闪照图片加密后的文件了 读取加密文件 以十六进制读取加密后的文件,可以看到字节流以“ENCRYPT:”开头
推己及人,如果是我开发这个程序,绝对会在读取加密文件的数据流时,在代码中判断与”ENCRYPT:”.getBytes(“UTF-8”)相同的流,那么也就是说在smali代码中很大可能通过”ENCRYPT:”这个字符串找到相应的解密方法
果然,在com.tencent.mobileqq.utils.DESUtils类中找到了它,顺便发现了闪照文件的加密方式,DES加密
那么可以推断,查看闪照时,程序读取加密文件的字节流并删除掉”ENCRYPT:”,然后通过Key进行DES解密,先编写DES解密方法吧 DES解密和加密 DES是一种过时的加密方式,不够安全,不过用在这里好像恰到好处。。。 解密方法,只要向方法中传入需要解密的文件路径和解密的Key即可 - /**
- * DES加密文件解密
- * @param filePath 加密文件的路径
- * @param key 密钥
- */
- private static void decrypt(String filePath,Key key) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
- File fromFile = new File(filePath); //已经加密的文件
- File toFile = new File(filePath + "_decrypt.png"); //解密的文件的路径
- Cipher cipher = Cipher.getInstance("DES");
- cipher.init(Cipher.DECRYPT_MODE,key); //使用密钥解密
- //加密文件文件头字节流
- byte[] encryptBytes = "ENCRYPT:".getBytes("UTF-8");
- if (fromFile.exists()){
- InputStream inputStream = new FileInputStream(fromFile);
- OutputStream outputStream = new FileOutputStream(toFile);
- CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream,cipher);
- byte[] buffer = new byte[1024];
- int len;
- int i = 0;
- while ((len = inputStream.read(buffer)) >= 0){
- // 输出流中跳过加密文件头
- if (i == 0){
- cipherOutputStream.write(buffer, encryptBytes.length, len-encryptBytes.length);
- }
- else {
- cipherOutputStream.write(buffer, 0, len);
- }
- i++;
- }
- inputStream.close();
- outputStream.close();
- cipherOutputStream.close();
- }
- else {
- System.out.println("文件不存在");
- }
- }
复制代码
加密方法,有解密就顺手把加密放出来吧
- /**
- * DES加密文件
- * @param filePath 需要加密的文件的路径
- * @param key 密钥
- */
- private static void encrypt(String filePath,Key key) throws IOException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
- File fromFile = new File(filePath); //未加密的文件
- File toFile = new File(filePath + "_encrypt"); //加密的文件
- Cipher cipher = Cipher.getInstance("DES");
- cipher.init(Cipher.ENCRYPT_MODE,key); //使用密钥加密
- if (fromFile.exists()){
- InputStream inputStream = new FileInputStream(fromFile);
- OutputStream outputStream = new FileOutputStream(toFile);
- CipherInputStream cipherInputStream = new CipherInputStream(inputStream,cipher);
- byte[] buffer = new byte[1024];
- int len;
- while ((len = cipherInputStream.read(buffer)) > 0) {
- outputStream.write(buffer, 0, len);
- }
- inputStream.close();
- outputStream.close();
- cipherInputStream.close();
- }
- else {
- System.out.println("文件不存在");
- }
- }
复制代码
获取密钥的方式,计算方式有很多种,我只不过是为了测试程序可行性临时写了一个
- /**
- * 根据参数生成密钥
- * @param encodeKey key字符串参数
- * @return key对象
- */
- private static Key getKey(String encodeKey){
- KeyGenerator key = null;
- try {
- key = KeyGenerator.getInstance("DES");
- key.init(new SecureRandom(encodeKey.getBytes()));
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- }
- assert key != null;
- return key.generateKey();
- }
复制代码
万事具备,只欠解密的Key 获取Key对象 通过这个输入流可以看到,它是在进行DES解密,并通过SecretKeySpec创建DES解密的Key对象;
在this(a(paramArrayOfByte),”DES”)中,虽然不知道a(paramArrayOfByte)是什么玩意儿,传入一个byte[]再返回一个byte[],,,不过它一定是必要的
- public static byte[] a(byte[] paramArrayOfbyte) {
- byte[] arrayOfByte = new byte[8];
- if (8 > paramArrayOfbyte.length) {
- System.arraycopy(paramArrayOfbyte, 0, arrayOfByte, 0, paramArrayOfbyte.length);
- } else {
- System.arraycopy(paramArrayOfbyte, 0, arrayOfByte, 0, 8);
- }
- return arrayOfByte;
- }
复制代码
然后去找a(paramArrayOfByte)所用到的paramArrayOfByte,它是方法的第三个参数,向上查找,发现当前类中重载的a方法调用了它,继续向上查找
- public static void a(String str, String str2) {
- try {
- if (!a(str)) {
- long currentTimeMillis = System.currentTimeMillis();
- File file = new File(str);
- long length = file.length() / 1024;
- File file2 = new File(str + ".tmp");
- if (file2.exists()) {
- file2.delete();
- }
- //调用
- a(file, file2, str2.getBytes("UTF-8"));
- FileUtils.copyFile(file2, file);
- file2.delete();
- if (QLog.isDevelopLevel()) {
- QLog.d("DESUtil", 4, "DES Encrypt filePath:" + str + ",key:" + str2 + ",costTime:" + (System.currentTimeMillis() - currentTimeMillis) + ",fileSize:" + length + "KB");
- }
- } else if (QLog.isDevelopLevel()) {
- QLog.d("DESUtil", 2, "encrypt had encrypt,file:" + str);
- }
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- }
复制代码
在com.tencent.mobileqq.dating.HotChatFlashPicActivity的子类HotChatFlashPicActivity$5中调用了这个方法,不过依然没有拿到key的计算方式,继续向上查找
在HotChatFlashPicActivity的A()方法中,发现返回了this.t,并且doOnCreate()中有this.t的赋值this.t = getIntent().getStringExtra(“md5”);,所以可以猜测,key的字符串参数是某字符串的md5加密值或者文件的md5唯一值,如果是前者还好,是后者就凉了
然后又经过一系列查找。。。发现在com.tencent.mobileqq.activity.aio.item.FlashPicItemBuilder类中向其put了md5参数
在这里插入log输出md5参数的值
- String paramString = paramMessageForPic.md5
- bundle.putString("md5", paramString);
- Log.d("xxin", paramString);
复制代码
当点击聊天记录中的闪照时,LogCat中输出了它的md5值“5AB73AA4439EB210F65D2115C887A191”
通过这个md5,也就是key参数值,根据上面的思路,编写key获取方法
- /**
- * QQ闪照加密的KEY获取方式
- * @param md5 key参数值
- * @return key对象
- */
- private static Key getQQKey(String md5) throws UnsupportedEncodingException, NoSuchPaddingException, NoSuchAlgorithmException {
- SecretKeySpec secretKeySpec = new SecretKeySpec(a(md5.getBytes("UTF-8")), "DES");
- return secretKeySpec;
- }
- private static byte[] a(byte[] bytes) {
- byte[] bytes1 = new byte[8];
- if (8 > bytes.length){
- System.arraycopy(bytes, 0, bytes1, 0, bytes.length);
- }
- else {
- System.arraycopy(bytes, 0, bytes1, 0, 8);
- }
- return bytes1;
- }
复制代码
调用key获取方法得到key,并使用key对加密的文件进行解密
- public static void main(String[] args) throws NoSuchPaddingException, IOException, NoSuchAlgorithmException, InvalidKeyException {
- String md5 = "5AB73AA4439EB210F65D2115C887A191"; //原文件的md5值是DES加密文件时使用的密钥值,妈的,初步推测这个md5值从服务器获取
- Key qqKey = getQQKey(md5);
- decrypt("C:\\Users\\30335\\Desktop\\闪照加密\\Cache_-f5ed123ea2f6cdd_fp",qqKey); //解密
- }
复制代码
目测解密成功
[ 
不过,如果多点几张不同的闪照,会发现md5值并不固定,所以它一定有一个计算方式: md5值获取 从上面知道,md5值是计算key对象用到的key参数值,key对象是解密闪照的密钥;md5值不固定,不同的图片的闪照有不同的md5值,但是同一张图片的闪照的md5值在任何情况下永远固定,不禁让人发起深思。。。这个md5不会是原图片文件签名的md5吧
妈的,是的,这个md5值是验证文件唯一性的值,字节流不同的文件有不同的md5值,并不是通过其它什么计算得出,且这个md5值是在对方发送闪照时一并发送,作为闪照的接收端只能从服务器发送的数据中接收
说简单直白点,要有原图的MD5,才能把加密后的图片解密成原图;只有原图,才能得到解密用的MD5;话说回来,都有原图了,我还解他妈的加密干什么 当然,也可以直接问对方要原图MD5签名
A:妹子可以发张照片看吗?
B:[闪照]
A:妹子可以提供下闪照原图的MD5签名吗,我解密下,谢谢。
B:阴阳怪气什么啊,普信男真下头 天无绝人之路,也可以像上面那样向原安装包中注入log,以使在点击闪照时在logcat中输出原图的md5签名,然后拿来解密闪照。。。
最后:简单点来说就是通过软件直接解密闪照是不可能的!除非修改QQ安装包。
|