安卓Native逆向之MOO\QQ音乐解密( .bkcflac,bkcmp3文件解密)
- 1、背景
- 2、Java层逆向
- 3、Native层逆向
- 4、Java实现
1、背景
本文写于2021年1月5日,解密算法适用于目前最新版的MOO加密和QQ音乐加密。之前加密方式和目前加密方式并不完全相同
支持的格式
MOO音乐 :.bkcflac,bkcmp3
QQ音乐:.qmcflac,.qmc0,qmc2,qmc3,
2018年鹅厂推出了一款名为MOO的音乐APP,设计风格独特,采用QQ音乐曲库,可以看作是QQ音乐的轻奢款。可以是因为设计风格太过独特,MOO音乐只在一些小圈子里流行,一直不温不火。
不过从2019年末开始,MOO音乐就开始免费送VIP,到现在持续了一年多。由于MOO音乐曲库的曲库和QQ音乐的曲库一致,所以,相当于免费送绿钻。
条件也很容易达到,每日听够一个小时的歌,就送一天的VIP,有了VIP后,就可以将喜欢的音乐下载下来。
但不幸的是,MOO音乐下载的文件是加密的,目前最新的加密格式是
.bkcflac,bkcmp3
2、Java层逆向
用jadx打开MOO音乐,经过一番分析,确定了文件的解密是在这里做的(搜decrypt分析一下)
具体来说,在这个函数里面读取了本地加密文件,然后在
PayProcessor.d(i, bArr, read);
这个函数里面解密。追到内部,发现是个native函数,可以知道加密函数在
libpay_encrypt.so里面实现
public class PayProcessor {
private static final String TAG = "PayProcessor";
private static boolean hXR = true;
private static native int native_decrypt(int i, byte[] bArr, int i2);
private static native int native_encrypt(int i, byte[] bArr, int i2);
static {
try {
System.loadLibrary("pay_encrypt");
b.a.i(TAG, "[static initializer] load success", new Object[0]);
} catch (Throwable th) {
b.a.e(TAG, "static initializer", th);
}
}
public static int c(int i, byte[] bArr, int i2) {
if (hXR) {
return native_encrypt(i, bArr, i2);
}
b.a.e(TAG, "[encrypt] sLoad = false", new Object[0]);
return -1;
}
public static int d(int i, byte[] bArr, int i2) {
if (hXR) {
return native_decrypt(i, bArr, i2);
}
b.a.e(TAG, "[encrypt] sLoad = false", new Object[0]);
return -1;
}
}
3、Native层逆向
把MOO安装包解压后 ,我们可以在lib\armeabi-v7a目录下找到这个so文件
用IDA Pro打开这个so文件,我们可以定位带加密函数
最终可以定位到这里,加密函数的逻辑很简单,对整个文件的每个字节与一个字典进行异或操作,解密也是用的同一个函数。例如 假设某个数据为a,字典里的值为b,加密后的数据为c
那么加密的过程就是 c=a^b
解密的过程就是a=c^b
当然,在MOO音乐这里,还是做了一些额外的操作,IDA的伪代码里已经写得很清楚,不多作介绍了
4、Java实现
将上面我们逆向后得到的数据,翻译成Java代码
public class FileDecrypt {
private static final int[] map={ 0x77,0x48,0x32,0x73,0xDE,0xF2,0xC0,0xC8,0x95,0xEC,0x30,0xB2,0x51,0xC3,0xE1,0xA0,0x9E,0xE6,0x9D,0xCF,0xFA,0x7F,0x14,0xD1,0xCE,0xB8,0xDC,0xC3,0x4A,0x67,0x93,0xD6,0x28,0xC2,0x91,0x70,0xCA,0x8D,0xA2,0xA4,0xF0,8,0x61,0x90,0x7E,0x6F,0xA2,0xE0,0xEB,0xAE,0x3E,0xB6,0x67,0xC7,0x92,0xF4,0x91,0xB5,0xF6,0x6C,0x5E,0x84,0x40,0xF7,0xF3,0x1B,2,0x7F,0xD5,0xAB,0x41,0x89,0x28,0xF4,0x25,0xCC,0x52,0x11,0xAD,0x43,0x68,0xA6,0x41,0x8B,0x84,0xB5,0xFF,0x2C,0x92,0x4A,0x26,0xD8,0x47,0x6A,0x7C,0x95,0x61,0xCC,0xE6,0xCB,0xBB,0x3F,0x47,0x58,0x89,0x75,0xC3,0x75,0xA1,0xD9,0xAF,0xCC,8,0x73,0x17,0xDC,0xAA,0x9A,0xA2,0x16,0x41,0xD8,0xA2,6,0xC6,0x8B,0xFC,0x66,0x34,0x9F,0xCF,0x18,0x23,0xA0,0xA,0x74,0xE7,0x2B,0x27,0x70,0x92,0xE9,0xAF,0x37,0xE6,0x8C,0xA7,0xBC,0x62,0x65,0x9C,0xC2,8,0xC9,0x88,0xB3,0xF3,0x43,0xAC,0x74,0x2C,0xF,0xD4,0xAF,0xA1,0xC3,1,0x64,0x95,0x4E,0x48,0x9F,0xF4,0x35,0x78,0x95,0x7A,0x39,0xD6,0x6A,0xA0,0x6D,0x40,0xE8,0x4F,0xA8,0xEF,0x11,0x1D,0xF3,0x1B,0x3F,0x3F,7,0xDD,0x6F,0x5B,0x19,0x30,0x19,0xFB,0xEF,0xE,0x37,0xF0,0xE,0xCD,0x16,0x49,0xFE,0x53,0x47,0x13,0x1A,0xBD,0xA4,0xF1,0x40,0x19,0x60,0xE,0xED,0x68,9,6,0x5F,0x4D,0xCF,0x3D,0x1A,0xFE,0x20,0x77,0xE4,0xD9,0xDA,0xF9,0xA4,0x2B,0x76,0x1C,0x71,0xDB,0,0xBC,0xFD,0xC,0x6C,0xA5,0x47,0xF7,0xF6,0,0x79,0x4A,0x11};
public static boolean decrypt(String fullPath){
try {
String sign="";
if(fullPath.contains(".bkc")){
sign=".bkc";
}else if(fullPath.contains(".qmc")){
sign=".qmc";
}else {
return false;
}
String decryptFilePath=fullPath.replace(sign,".");
String postFix=decryptFilePath.substring(decryptFilePath.indexOf("."));
if(postFix.equals(".0")||postFix.equals(".2")||postFix.equals(".3")){
decryptFilePath.replace(postFix,".mp3");
}
File file=new File(fullPath);
FileInputStream is=new FileInputStream(file);
FileOutputStream fout=new FileOutputStream(decryptFilePath);
byte[] b=new byte[8192];
int len=0;
long total=0;
while ((len=is.read(b))!=-1){
for (int i=0;i<len;i++){
b[i]^=encMap(total+i);
}
fout.write(b,0,len);
total+=len;
}
is.close();
fout.close();
return true;
}catch (Exception e){
e.printStackTrace();
}
return false;
}
private static int encMap(long len){
int pos= (int) (len%0x7fff);
return map[(pos*pos+80923)%256];
}
最后
欢迎关注我的微信公众号:从来不想