妈了个巴子的,白瞎我两天时间,气死
闪照储存位置 首先得找到闪照的文件储存的位置,这个过程相当繁琐,不再列举,直接把缓存的储存位置贴在下面
1/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即可
1234567891011121314151617181920212223242526272829303132333435363738394041 /** * 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("文件不存在"); } }
加密方法,有解密就顺手把加密放出来吧
123456789101112131415161718192021222324252627282930/** * 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("文件不存在"); }}
获取密钥的方式,计算方式有很多种,我只不过是为了测试程序可行性临时写了一个
12345678910111213141516/** * 根据参数生成密钥 * @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[],,,不过它一定是必要的
123456789public 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方法调用了它,继续向上查找
123456789101112131415161718192021222324public 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参数的值
123String paramString = paramMessageForPic.md5bundle.putString("md5", paramString);Log.d("xxin", paramString);
当点击聊天记录中的闪照时,LogCat中输出了它的md5值“5AB73AA4439EB210F65D2115C887A191”
通过这个md5,也就是key参数值,根据上面的思路,编写key获取方法
1234567891011121314151617181920/** * 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对加密的文件进行解密
12345public 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签名,然后拿来解密闪照。。。