吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 9187|回复: 12
收起左侧

[Python 原创] 【原创源码】【python】ncm解密

  [复制链接]
漁滒 发表于 2020-7-19 22:24
本帖最后由 aiai 于 2020-7-19 22:27 编辑

参考https://github.com/ix64/unlock-music发布的js代码,改写为python代码,并做少量修改


运行结果解密py目录下所有ncm文件
[Python] 纯文本查看 复制代码
import base64
import json
import os
from mutagen.id3 import ID3, TIT2, TPE1, TALB
from Crypto.Cipher import AES

def getUint32(dataView, offset):
    dataView = list(dataView[offset:offset+4])[::-1]
    u16 = ''
    for each in dataView:
        u16 += hex(each)[-2:]
    u16 = u16.replace('x', '0')
    return int(u16, 16)
    
def getKeyData(dataView, offset):
    keyLen = getUint32(dataView, offset)
    offset += 4
    cipherText = list(dataView[offset:offset+keyLen])
    for i in range(0, len(cipherText)):
        cipherText[i] = cipherText[i] ^ 0x64
    offset += keyLen
    cryptor = AES.new(key=bytes.fromhex('687a4852416d736f356b496e62617857'), mode=AES.MODE_ECB)
    plainText = cryptor.decrypt(bytes(cipherText))
    return {"offset": offset, "data": list(plainText)[17:-14]}
    
def getKeyBox(keyData):
    box = []
    for i in range(0, 256):
        box.append(i)
    keyDataLen = len(keyData)
    j = 0
    for i in range(0, 256):
        j = (box[i] + j + keyData[i % keyDataLen]) & 0xff
        box[i], box[j] = box[j], box[i]
    boxmap = []
    for i in range(0, 256):
        i = (i + 1) & 0xff;
        si = box[i]
        sj = box[(i + si) & 0xff]
        boxmap.append(box[(si + sj) & 0xff])
    return boxmap 
    
def getMetaData(dataView, offset):
    metaDataLen = getUint32(dataView, offset)
    offset += 4
    cipherText = list(dataView[offset:offset+metaDataLen])
    for i in range(0, len(cipherText)):
        cipherText[i] = cipherText[i] ^ 0x63
    offset += metaDataLen
    cryptor = AES.new(key=bytes.fromhex('2331346C6A6B5F215C5D2630553C2728'), mode=AES.MODE_ECB)
    plainText = cryptor.decrypt(base64.b64decode(bytes(cipherText)[22:])).decode()
    labelIndex = plainText.find(":")
    result = json.loads(plainText[labelIndex+1:].encode().replace(b'\x07', b'').decode())
    return {'data': result, 'offset': offset}
    
def GetFileInfo(artist, title, filenameNoExt, separator):
    newArtist = ""
    newTitle = ""
    filenameArray = filenameNoExt.split(separator)
    if len(filenameArray) > 1:
        newArtist = filenameArray[0].strip()
        newTitle = filenameArray[1].strip()
    elif len(filenameArray) == 1:
        newTitle = filenameArray[0].strip()
    if type(artist) == 'string' and artist:
        newArtist = artist
    if type(title) == 'string' and title:
        newArtist = title
    return {'artist': newArtist, 'title': newTitle}

def DetectAudioExt(data, fallbackExt):
    if data[:8] == '664C6143':
        return 'flac'
    elif data[:6] == '494433':
        return 'mp4'
    elif dat[:8] == '4F676753':
        return 'ogg'
    elif data[4:12] == '66747970':
        return 'm4a'
    elif data[:32] == '3026B2758E66CF11A6D900AA0062CE6C':
        return 'wma'
    elif data[:8] == '52494646':
        return 'wav'
    else:
        return 'mp3'

def decryptncm(filepath):
    outputpath = './unlockmusic/wangyiyun/'
    if not os.path.exists(outputpath):
        os.makedirs(outputpath)
    ncm = {}
    ncm['fileext'] = filepath.split('.')[-1]
    ncm['filename'] = filepath[:-(len(ncm['fileext'])+1)]
    with open(filepath, 'rb') as f:
        ncm['filebuffer'] = f.read()
    keyDataObj = getKeyData(ncm['filebuffer'], 10)
    keyBox =  getKeyBox(keyDataObj['data'])
    musicMetaObj = getMetaData(ncm['filebuffer'], keyDataObj['offset']);
    musicMeta = musicMetaObj['data']
    audioOffset = musicMetaObj['offset']+getUint32(ncm['filebuffer'], musicMetaObj['offset']+5)+13
    audioData = list(ncm['filebuffer'][audioOffset:])
    lenAudioData = len(audioData)
    for i in range(0, lenAudioData):
        audioData[i] ^= keyBox[i & 0xff];
    if not 'album' in musicMeta.keys():
        musicMeta['album'] = ''
    artists = []
    if musicMeta['artist']:
        for each in musicMeta['artist']:
             artists.append(each[0])
    info = GetFileInfo(' & '.join(artists), musicMeta['musicName'], ncm['filename'], '-')
    if not artists:
        artists.append(info['artist'])
    if not 'format' in musicMeta.keys():
        musicMeta['format'] = DetectAudioExt(audioData[:32].hex().upper(), "mp3");
    outputpath = outputpath+musicMeta['musicName']+"."+musicMeta['format']
    with open(outputpath, 'wb') as f:
        f.write(bytes(audioData))
    if musicMeta['format'] == 'mp3':
        audio = ID3(outputpath)
        audio.update_to_v23()
        audio.delete()
        audio['TIT2'] = TIT2(encoding=3, text=[info['title']])
        audio['TPE1'] = TPE1(encoding=3, text=[artists[0]])
        audio['TALB'] = TALB(encoding=3, text=[musicMeta['album']])
        audio.save()
    print(musicMeta['musicName']+"."+musicMeta['format']+'解密完成')

def main():
    for each in os.listdir(os.getcwd()):
        if '.ncm' in each:
            decryptncm(each)
    a = input('所有音乐解密完成')
    
if __name__ == "__main__":
    main()



已知问题
1.自身语法写的比较低级,影响运行速度
2.当解密文件过多时,时间偏长

免费评分

参与人数 4吾爱币 +9 热心值 +4 收起 理由
qzs004 + 1 谢谢@Thanks!
ymhld + 1 + 1 看不懂也要顶
画中画。 + 1 + 1 我很赞同!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

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

 楼主| 漁滒 发表于 2021-2-24 23:03
青衫桑 发表于 2021-2-24 21:45
话说解密算法是怎么发现的

我是参考的github项目,这个项目应该是逆向客户端得到的算法
88rui 发表于 2020-8-21 14:12
好走不送 发表于 2020-9-1 21:45
skysniper 发表于 2020-9-19 23:04
真大神,膜拜中
Y3T4 发表于 2021-2-20 16:29
下个Py试试代码
xixicoco 发表于 2021-2-20 23:51
好的,网易云
vagrantear 发表于 2021-2-23 15:28
还是很强的
MOEYU_VANILLA 发表于 2021-2-24 20:54
支持一下
青衫桑 发表于 2021-2-24 21:45
话说解密算法是怎么发现的
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-11-25 07:24

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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