某音乐软件的KGS格式——解码

起因:

某音乐软件里面有个功能是“一起听”,只要房主播放VIP歌曲,房间内的用户都可以听这首歌。

 

因为之前有过抓包获取到一些特殊途径下载VIP歌曲,而且下载是MP3格式(不是VIP用户下载歌曲的有效期的kgm结尾的加密歌曲)

 

所以我以为这里应该可能也是MP3格式的歌曲。

 

抓包

通过雷电Android模拟器,配合root+EdXposed+TrustMeAlready(TrustMeAlready一开始不行,最后发现需要设置作用域才行😂)

1725449160648

TrustMeAlready是绕过ssl证书验证(很多请求是https的,要证书验证的,抓包工具是抓不到的),抓包工具是Burp Suite社区版(具体忘了还要不要导出Burp Suite的证书安装到模拟器里面,应该是要的,如果要的话,百度找教程)

Burp suite代理设置里面

1725449509016

要选ALL interfaces,因为模拟器是局域网的

模拟器设置WiFi代理

 

打开概念版音乐软件(一开始是某音乐软件找到这个“一起听”功能的,但要登录。后面发现概念版不用登录🤫)

1725449866229

然后选“众乐房”就行,选一首VIP歌曲,普通歌曲本来就不用VIP下载的。

通过排查很轻松定位到:

1725450155060

 

一开始看返回的数据,extName为MP3,还以为就是MP3格式。用普通播放器播放失败。

后面以为是kgm这样的加密方法,因为这个音乐软件只喜欢改后缀比如:kgtemp,kgm什么格式,但都是同一套加密方法,工具可以解密(已经有工具可以解kgm的加密),用工具解码,解码失败。

 

找解码算法过程

百度找不到kgs有关的信息,应该是比较新的加密方法。

后面试试kgm入手,看看kgm的解密方法,应该就和kgs差不多的。(之前抓包,发现这个音乐软件很多加密的都类似的,比如请求url里面的signature字段的算法)

 

后面找到这篇文章:https://www.cnblogs.com/KMBlog/p/6877752.html

发现这里说1024是多出的,后面发现kgm确实比MP3多出了1024字节,kgs没有多出,kgs就是和mp3大小一模一样。

 

大概看了这篇文章,发现这里并没有使用1024字节的内容,通过规律找到解密算法的。

 

通过这个思路,我对kgs进行找规律,但没有找到。

 

百度对kgm的算法也只有这篇文章而已。

 

后面去GitHub找之前的kgm解密工具,看看有没有算法介绍。

有代码没有介绍,最重要的是它是go语言和ts的😭,看不懂一点。

 

没有解密教程介绍也好啊,起码是python代码我会看懂一点。找到的代码几乎都go、ts、rs的

 

后面不知道怎么找到一个python的代码,但它是一个库takiyasha

https://pypi.org/project/takiyasha/0.3.4/

 

大概分析了一下,已经能从takiyasha提取出kgm的算法了。

1725452876251

 

大致就是这样的,

原文件与key与mask_v2_pre_def与mask_v2异或,最后异或的东西再与自己的低4位异或。(这里的低位异或与之前找到的算法有点相关:https://www.cnblogs.com/KMBlog/p/6877752.html,可以看出还是同一个东西改改就行了)

 

这里的用到了key,就是文件开头的1024字节的内容,后面才发现这个教程https://www.cnblogs.com/KMBlog/p/6877752.html已经是17年的了,音乐软件应该就是改算法了,也就是现在的算法,也就是python代码所示的。

 

可以分析一波:

其实是固定的,也就是key特殊而已。

 

而且python代码里面,不止一个kgm一个解码算法

1725453708133

 

这里有两个不同的头部,后面在GitHub里面找,找到VPR的解释,

1725453765124

其实是这个app的下载VIP歌曲的加密方法

 

如果是VPR的话,就是最后再进行一步异或

1725453827855

😒经典改改就能用了

 

看懂了代码,我发现VPR就是一个头,如果把kgs也是一个头的事🤣,直接试一试。发现不行😒,看来它大改了。

 

没办法,代码已经这样了,kgs没有key的,大概率也不是这个算法,或者key不是简单的歌曲hash

现在的想法就是通过这个python代码的提示,逆向音乐软件,找到他们怎么逆向出这个python解码过程。说不定kgs的解码过程就在附近。🤣

 

最好的提示就是

1725454243892

这些是在算法里面用到的,就必须存储下来的。

通过小脚本:

可以快速找apk里面哪个文件有这些内容

 

找不到😒,奇怪

1725454621043

这些可以找到,VPR需要对应软件才行

奇怪

必要的内容文件不存储可能吗?(当时没有经历那么多,没想到可能会是算法生成的,存储的这些可能已经是生成后的,必然找不到)

 

后面发现python代码还打开一个文件,90多kb的kgm.v2.mask,奇怪,用010打开发现好像是个压缩包,用压缩包打开,解压之后70多M,离谱,压缩率这么高,010打开,发现一堆重复的字节,难怪。我在想如果一个算法要存储这么大的内容可能吗?不管了,都试一下,用脚本分别找90kb的内容和70M的内容,发现还是找不到。

😒,奇怪

 

没思路,只能又去GitHub找找,rs就是rs,看不懂ChatGPT问问,被ChatGPT搞到脑溢血。😐

 

后面还真找到一些东西

https://github.com/parakeet-rs/

就在刚刚 2024/9/4 21点09分,访问这个网站,已经把源码删掉了,就两周前而已,它的文档也没了

1725455398454

😭😭😭😭😭😭😭😭😭

这个教程对我非常的重要,非常重要的一个转折点啊,突然就没了😭😭😭😭😭😭😭😭😭

😭😭😭😭😭😭😭😭😭

😭😭😭😭😭😭😭😭😭

 

如果我晚一点发现这个,就可能做不出kgs解码算法。

它这个文档有多牛逼呢?

它提到很多音乐软件的解密算法。

其中它里面提到了,kgm的解密算法,1024里面其实有两个key的,

python代码里面提到了第二个key:

1725455682191

写着的注释是未解决,说明python的算法和现在大多数算法go、rs那些代码都是不完整的,不是官方的,应该是另类的求解方法。

而且第二个key是会进行验证的

1725455905456

(因为文档没了,还好之前我跟着文档的思路弄了一下,可以找到文档的一些内容)

这个图片的内容是libjengine.so里面的,python代码里面提到的头

1725455977179

和ida里面的内容是对应的,这个是1024字节的头部的识别

1725456037054

这个标识是告诉解码算法这个kgm文件,还是VPR文件,第一个字节是0014h的03是代表算法的版本,这个版本是第三个版本的加密算法,文档 里面提到了四个算法,它还预发现有个版本4的算法,但没有投入使用。🐂🐂🐂🐂

001Ch到002Bh是第一个key,002Ch到003B是第二个key。第二个key好像是用来验证的,验证是否得到ida图片里面的0x38 0x85 0xED ... 这些的。

 

它自己写了一个算法,就是和官方一样的,还进行第二个key的验证,而且是不用kgm.v2.mask这个东西。

 

关于这个东西,文档里面也提到一点,它说一开始他的解法也是通过爆破字典表,分解字典表。就说了这个,虽然没有直接提到kgm.v2.mask,但我感觉就是这个。(后面不知道在哪个github里面看到一个kgm.v2.mask完整版几个G😂,好像是这个https://github.com/jixunmoe/kugou-crypto

 

文档给了一个代码就是分解字典表的,我现在不记得了,因为代码了,但我在另一个GitHub代码里面看到过,几个table可以生成kgm.v2.mask

 

python代码:

这个应该不是原代码,我是看它的代码,自己写了一个,发现就是kgm.v2.mask的内容。😂,当然只是看了前560个字节,后面不敢说。

 

我通过这两个table也没找到对应的代码,可能这两个也不是最终的官方算法,只是将kgm.v2.mask进一步压缩了而已。

 

文档里面只是提到这些,并介绍了4个版本的解密算法,代码有的,rs的,看不懂,它的使用需要key,这个代码不提供key,提供官方的key,就能正确解密。我又通过另类的方法在GitHub找到相关的key,确实可以正确解密,不用kgm.v2.mask。而且好像还找到了版本4的key,版本4的key有两个,很大的。我一开始看到有个新的算法,我就感觉应该是kgs的。就直接用来测试,发现不行。😒(真的牛逼啊,这么难破)

我现在怀疑它们预知的版本4解密算法可能不对,可能不需要1024字节的,我的kgs就是没有1024。我一直陷入了版本4算法就是kgs解密算法。

后面我觉得可能它们文档是对的,我只能通过这样去验证:

我直接找到一个版本,有“一起听”功能的,也就是能播放kgs的版本,又没有版本4的key的(版本4的key也是在libjengine.so里面的🤫),还真的找到了😭,具体哪个版本忘了,大概是10.4.5吧。

 

这个文档,真正给我的东西不是解密算法,而是我知道了,目前它们的解密算法不是真正的官方代码的,我不能再从这里入手了。目前真正知道kgm的解密算法应该就是这个文档了。🐂🐂🐂🐂🐂🐂🐂

 

新的开始

我也学它们的通过破解字典,这种加密方法无非就是生成一个字典,一一对应还原而已么(一开始不是很懂,问ChatGPT,说什么AES对称加密是可以原来多少字节加密之后也是多少字节的,我服了,真的脑溢血😒,但不可否认有些时候确实能给点提示的。比如signature算法,和软件里面有些对称加密,非对称加密,都是它给的代码,网页版的代码都解不出的,它的代码可以,还是可以的👌)

 

从上面的python代码里面

 

1725458027518

 

可以看到,就key是特殊而已,其他可以mask_v2_pre_def^mask_v2就是一个新的数而已,最后的低4位异或,也是可以提取出来的,相当于src ^ key1 ^ key2 ^ 公共。

 

通过观察两个不同的kgs,但MP3头部格式一样的,MP3头部格式为:ID3 (49 44 33)

 

它们的ID3编码之后是不一样的,证明确实用的特殊key,不可能是统一的key。不是简单一个表加密。

 

一开始我是用kgm来测试的,先看看自己能不能把kgm搞出点什么东西,能不能做出kgm.v2.mask的字典。

 

做了一堆发现kgm与原mp3异或,kgm ^ mp3可以得到一个一直循环的东西。

有一定规律的循环:

1725458494369

后期不是,但起码知道点东西了。src ^ key1 ^ key2 ^ 公共 ^ src = key1 ^ key2 ^ 公共,它们之间有点规律。

 

其实不是一开始就发现有规律的,一开始是我是kgs^mp3,得到的东西,什么规律都没有。

后面去试试kgm^mp3才发现4个字节搜索是有一定规律的。后面我又回去kgs^mp3还是不行,然后我提出了一个假设:

kgs = src ^ key特殊 ^ key公共 (先排除其他方法,比如循环位移,后面才发现有这样的方法。因为上面的python的,文档,我已经知道这个音乐软件的加密方法就是表,就是这个表怎么弄出来而已,排除了ChatGPT乱说的AES什么加密😒,所以有了这个假设)

所以我尝试,kgs1 ^ kgs2 = key特殊1 ^ key特殊2,这些key肯定是算法生成的,要生成一个key,肯定用到循环,用到循环说明某些规律是不变的,只是值变了而已;kgs1 ^ kgs2本来也不行,因为我一开始用的kgs1和kgs2大小是差不多的文件,后面又随便试了这两首歌,

后面又试了几首歌,本以为是两首歌的大小差别越大越好,其实并不是,但大小越接近越不好,只能说刚好碰对这两首歌,让我找到一些规律。

1725502754381

大概每隔3f1就是一个循环,问题就在这里,只有简单9个是满足这个规律的。说明大部分不是这样的。后来我就对这些有规律进行了一些分析

看附件。

对仅有的这里规律进行了总结,后面换两首歌进行^,看看满不满足,答案是否定的。不满足😒,说明还是不对

ps(不是现在发现的,是之后发现):

这里可以提前说一下,为什么这里会出现一些规律

我们可以看看3f1到7e2之间的数据有什么,

kgs2:

1725503490198

kgs1:

1725503502021

其实正确的加密方法是:

kgs = 循环左移(src ^ key特殊 ^ key公共)

我做的假设是 = src ^ key特殊 ^ key公共

看源文件MP3这里的数据几乎都是相同的,它们的开头是相同的,所以循环左移不影响,这样就能得到上面的规律出现,就是key1特殊 ^ key2特殊的情况。其他位置的数据不一样,所以就没有这些规律。

 

回归正题

当时这里发现规律是不通用的,就放弃了。

 

漫游中。。。

之后一直在探索找到音乐的加密算法在哪里

Java so文件都看了遍

 

其中有个比较重要的点,就是软件获取kgs是直接播放了,找不到对应的代码插桩,后面尝试了一下,抓包篡改kgs的链接,改成不同的kgs,发现

1725504162079

有弹窗!


ps:

我一开始就对MP3的hash有关注,可惜kgm里面的key和hash没有关系,导致我对hash的重要性下降了一点。为什么会关注这个hash,因为可能kgs的加密用到hash。可能hash就是特殊key。可以从几个方面看出,首先就是kgs获取的请求,里面没有什么字段,就

1725450155060

hash和mixsongid是唯一和kgs挂钩的,不管你换roomid,获取到kgs的文件是一样的,所以和roomid无关,mixsongid也是无关的,但hash是获取歌曲的id,还不能排除。其实kgs的url链接后面那段有点像base64编码的,解码可以看到:

1725520286785

mp3?,直接用明文请求是失败的,前面的a79..6e4这个应该是签名,后面篡改就是请求失败——没有权限。

这里可以看到后面有个参数hash,所以感觉这个会有点问题,可能真的是用hash进行加密文件的。

后面尝试搜索了一下这条链接,发现以前的这个音乐软件,这个链接可以随便下载歌曲的,就是后面封了,现在看这个,只是加了一个签名和base64编码而已😂。


最终能定位到Java代码,但需要反向查找调用该函数的函数。

搞很久,几乎看了调用的它的方法,其中有个东西还是有点东西的

package com.kugou.common.filemanager.downloadengine

的Engine类,(很早之前我就看过这个类了,因为这个类里面有这些函数:encodeFile,enableID3Fetcher,因为在此之前,我有搜索decode,encode的关键字看看能不能找到加密解密算法,enableID3Fetcher是因为我用ID3的关键字,因为MP3的头部就有ID3😭😂)

 

 

再次看到这个Engine类,感觉有点不简单。

 

smali代码插桩弄到人麻,因为调用弹窗的函数外部的调用的函数很多,我要确定是和Engine类相关的才行。

 

后面受不了,我找找看有没有Android调试的工具(插桩是初中就会的,一直以来也没接触过Android逆向了,不知道现有的方法有什么😂,所以现在还是这样用。调试工具的话ida一直不行,会报错,百度这个错误说是反调试,而且也没有教程详细教一下怎么绕过反调试😂,所以看看现有的技术有没有动态调试)

 

找到这几个东西,jeb、frida、jadx都说能调试。

先试了jeb,jeb一直报错,反编译不了apk😒,一开始以为试jeb版本太旧了,换了新的也不行,经常报错,大概率是内存不足,😒,因为小apk就行,这个音乐软件有100多M来着。

 

后面先试了frida,发现这个东西有点东西,但唯一就是输入和输出而已(但后面就是靠这个东西)

 

(frida这里学到很多东西,因为frida最新的版本是通过进程号来hook的,不是包名,一开始不理解,每次我都要ps -A看看音乐软件的进程,后面发现一直hook不了那个函数,我配合着插桩,插桩信息都输出了,还是hook不了,后面找到这个教程:https://blog.csdn.net/weixin_51111267/article/details/135509890,“多个进程”好家伙,确实有点道理,我用ps -A | grep kugou(Android虚拟机,adb),还真的发现有个com.kugou.android.lite.support,原本包名应该是com.kugou.android.lite,也有这个进程,但多了这个support进程,换了这个进程pid,居然可以hook到了🐂🐂🐂🐂🐂,这里还学到了,hook so文件的话要用真实机,虚拟机是不行的,原文不知道是哪篇文章说的了:https://bbs.125.la/thread-14603946-1-1.html,这里也说了用真机。原文章是说虚拟机的架构不是arm架构,hook不到so的,所以用真机,这两个知识很重要)

 

因为现在我想知道这个弹窗是调用的,发现frida有扩展工具,objection,objection可以直接看这个函数的调用栈,好东西😍

 

看了这个弹窗,发现的它调用的的最终函数居然是个线程😒

 

又没了线索。

 

又试了jadx,起码这个东西能轻松编译了apk,但是它的编译后的Java代码很多是缺失的,还没MT管理器好用😂,毕竟免费的,唯一的好处是它可以一键生成xposed和frida的代码。而且调试,只能调试主进程的,我要调试的是support进程,怎么都调不了。😒

 

调试又告别一段落了。

后面突发奇想,试试objection看看,看看有没有调用Engine类的encodeFile,enableID3Fetcher函数。

就是进入”一起听“播放一首歌就能触发。

1725520905672

encodeFile,enableID3Fetcher都没有触发,只有这些。

后面逐个函数去看它们的调用栈,研究了好久。

这些函数都是so调用的

其实这里可以看出c和b可能是回传的消息的,也可能是回传下载的数据Java解密,具体看了c和b的Java代码,大概就是回传消息的,看看解密成功还是失败。

一套完整的流程就是上面的那样了,所以是否解密,就是在这些函数里面,或者也不在(不确定),怎么看这个函数名称就是下载而已,没有提到解密。而且对这些函数内部进行大致看了一下,没有kgs关键字(主要是一开始我认为你要解密kgs,kgs又区别于kgm等加密算法,已知一共有4种加密算法,而且每个kgs没有相同的内容,怎么进行判断是解kgs还是kgm?所以我猜想kgs字符串应该是要的;;;;现在我解出了算法,我还是不知道怎么判断的,我大概猜一下是,用4种加密算法的验证,验证失败就是kgs了,或者MemoryOnly后面会提到😒😒😒😒)

 

对这些函数研究了很久,因为有些函数不是简单几行代码,涉及了很多东西,下载什么功能的。

 

还是没研究出什么。

 

后面尝试从这里篡改,让其下载其他kgs,看看能不能解密成功。如果这里解密成功了,能播放了,就说明下载的过程中进行了解密,所以还有研究的意义。

 

只有frida能做到了,对startDownload函数进行分析,

它的参数居然是Java类,还好frida能对类进行操作,只是,DownloadFileInfo和DownloadOption这两个,有点混淆,还好没有完全混淆,它的get_函数暴露参数的含义,

经过一系列的测试,发现DownloadOption没有什么用,默认参数就行。

DownloadFileInfo的Urls有用,但和抓包拦截篡改实现的功能是一样的,其实P2PHash和FileHash都是MP3的hash,我尝试篡改这个,还是不行,难道不是hash加密解密的????其实这些字段还有几个可以留意一下:

Encryption、MemoryOnly、FileSize。

 

又做了一堆的测试,后面发现,那个完整的流程,在startDownload之前,还有几个函数,其中addDownload有个参数就是DownloadFileInfo,看看addDownload,发现确实也有一下字段,但还没有urls,我尝试修改P2PHash、FileHash,好像不行(忘记当时是为什么了,太久远了😂),后面想,直接拦截去掉就好了,让其没有调用addDownload就行,然后再篡改startDownload的hash就行,没想到,居然可以!!!!

逆天!

非常重要的转折点,起码给了我希望,第一次能用控制"一起听"播放我指定的kgs。这个的成功说明了一些东西,kgs和hash应该是有关,startDownload可能一边下载一边解密,因为还有一种方法传回Java解密也可能的。


后面我把downloadFileInfo的参数尽量的删除,保留重要的东西,得出一些内容(测试了很多东西,看了很多代码得到的结论):

Key:是随机生成的,用于与Java这边交互的,和KGFile这个类有关。

FilePath:保存文件的路径。

Urls:kgs下载链接。

P2PHash:mp3的hash FileHash:mp3的hash

FileSize:mp3的大小 Encryption:是否加密保存,就是如果保存在本地,是否使用加密,就是已知4种加密方法的第三的加密方法,就是目前常用的kgm这种加密 MemoryOnly:是否是放在内存里面,其实就是不保存文件,留在内存就行。


已知这些内容,其实可以直接用篡改下载特定的kgs了,而且保存下来的是mp3格式了。

后面发现frida可以直接调用startDownload,直接下载。

 


如果要addDownload保留,还能篡改hash的,其实可以直接对addDownload的参数进行修改就行(没试过,但应该可以的),但当时我逆向Java的时候发现,它们有个公共赋值的类,com.kugou.common.filemanager.entity.c,反正addDownload的参数就是从这里拿的,所以我会拦截这个类的$init,篡改,这个有点牛的,一个参数解释都没有,我通过测试得到几个关键点:

用文档打开这些代码注释会和参数对应的,主要就是着几个,其实是分别对应Encryption、MemoryOnly的。

注:这里我其实忘了,startDownload的参数的Encryption、MemoryOnly篡改能不能起作用了,具体忘了,应该是起作用的,不起作用就拦截entity类吧😂😂😂😂。不拦截addDownload反正hash篡改是不起作用的,因为addDownload有hash就不会用startDownload的参数。


其实按照上面那个完整的流程,b,c函数是传递状态的,由于我没有对它们进行处理,其实是篡改不了歌曲的,还是要拦截b,c,拦截就行,不需要进行什么处理,b,c的上一层函数是callbackOnDownloadStateChanged和callbackOnDownloadStatus,也可以拦截它们,我拦截这两个,其中它们的参数那个类是有点内容的:

其中的那个ErrorDetail,会有用。callbackOnDownloadStateChanged和callbackOnDownloadStatus的原型参数是Object的,所以这里用到了强转成对应的类。


 

Android应用

后面直接想做一个Android应用(为什么是Android应用,一开始我以为so文件都是能直接调用的,打ctf打多了,不太了解,后面才发现,Android和Linux生成的so文件都是不一样的,因为它们的架构不一样,也就是cpu的指令不一样,不能直接调用运行,因为音乐软件是Android的,so是Android的,只能用Android来运行调用它,本来看frida的一本书,里面提到有个软件可以模拟运行Android的so文件的函数的——AndroidNativeEmu,底层是Unicorn,大概查了一下好像不是所以的函数都能直接调用,好像只用JNI的,刚好startDownload这些函数都不是JNI的,而且它的教程很少,我都不知道怎么安装开始用,还复杂😂😂,所以想到直接做个Android应用),调用so里面的startDownload,就能下载了,就不用知道它的解密算法。😂

代码在附件。

能成功引用了libjengine.so文件,它好像会动态注册与Java的函数绑定,因为好像没有使用JNI来调用的,还好

会报错,会告诉你,少哪些函数没有定义,就要在Java里面先定义,我直接把Engine这个类的native函数都复制过来了。

虽然能引用了,但调用startDownload,返回的是false,用frida的会返回true,而且也没成功下载,我想了一下,可能是少了网络请求权限和存储权限,但加上也不行(Android不懂,百度加上的,好像还分Android版本的,Android7以上又要什么什么,没搞懂,后面也没弄了,可能没这么简单,可能还要与Java这边交互,或者调用其他函数的,但没有提示了,也做不了。)

 

用frida发现Xposed的这些也是用hook技术的,我就想,要不就做个Xposed插件吧,反正frida代码已经写好了,应该都差不多的,用插件下载也是可以的。

 

Xposed的教程还算简单,就是都是那个人教程😂,而且那个jar包已经下载不了,找这个资源找了半天,而且配置文件也配了半天,都不知道谁的能成功,混合弄了,能成功就行。

 

直接给代码,具体为什么这样写,也找不到对应的博客了,就这样写才能成功运行hook下载歌曲。

 

一些参考文献:

https://51wlb.top/xposed-plugin/

https://www.kancloud.cn/a6260362/study-android-and-web/1646986

jar:

https://github.com/dennishucd/xposed7/blob/master/lib/api-82.jar

 

其中:MultiprocessSharedPreferences

是直接拿了大佬的代码的,这个作用就是,我们做的插件可以做一些配置更改,比如我要下载歌曲,我给了个输入框,起码每次hook拿到的数据都是输入框的内容啊,要不然就不能随便下载对应的歌曲,不可能每次下首歌都有重新写apk下载😂😂😂,其实就是进程间的通信,Xposed软件会调用你给它的xposed_init入口函数,其实这个函数是Xposed调用的,不是你的Android调用的,不能直接用Android的方法传递参数,网上其实也有一些其他方法进行通信的,本质就是进程间的通信。我一开始是想用文件来通信就行,简单,但发现存储权限有问题,只能拿大佬的代码了,这个代码的原理也是文件通信,共享文件的内容。

https://blog.csdn.net/adzcsx2/article/details/107087300?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ECtr-1-107087300-blog-113703039.235%5Ev43%5Epc_blog_bottom_relevance_base1&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ECtr-1-107087300-blog-113703039.235%5Ev43%5Epc_blog_bottom_relevance_base1&utm_relevant_index=1

附件

 

意外发现

我还是在测试那个两个不同进程怎么进行通信(不是上面那个通信),是:

我想在com.kugou.android.lite监听一个搜索事件,然后直接调用com.kugou.android.lite.support的startDownload函数(这样就不用每次都要进去“一起听”听一首歌了,目前的Xposed都是要进去“一起听”听一首VIP歌才能下载,因为这个Xposed不像frida一样能直接调用函数的,它只能拦截某个函数进行篡改,就这些简单的篡改frida直接留空就行,它这里不行,需要做很多东西才行,挺离谱的两个差这么多😂😂😂,相当于重写了frida的代码了),发现loadPackageParam不能共享,就是每个if都是一个进程的意思(好像,不是很懂,但测试出来的感觉就是这样),当前if里面的loadPackageParam就是这个进程信息而已不能调用其他进程的信息,就是不能直接调用com.kugou.android.lite.support的startDownload函数,因为上面那个可以共享信息,但这个是对象,也是不能共享的。😒😒😒

 

弄着弄着,突然有个点子:

现在我已经能控制它播放指定的歌曲,我是不是可以自定义这个歌曲,做一些特殊的歌曲,让其进行解密,这样会不会解出加密算法的可能性就更高。

 

要篡改kgs的下载链接,直接用python做一个web服务器就行了。

这样局域网下载歌曲就行。

离谱就离谱在,这个东西不知道为什么没有成功下载,一开始我没发现,后面才发现,flask输出的日志是我浏览器请求下载而已,不是“一起听”请求下载的。然后我发现它还是能下载歌曲,我直接人傻了😂😂😂,我说怎么可能有玄学,我一直测试排除,做了一堆测试,终于发现,

DownloadFileInfo的urls只要为空,P2PHash不为空,就能用另一个方式下载歌曲,还是直接mp3的,不是kgs😂😂😂😂,后面想用代理来抓包看看是什么请求,发现一用代理这个下载就失败,会出现p2pOnly...的错误,上面提到的ErrorDetail里面出现的提示,其实这里猜了个大概,应该是p2p原理下载的歌曲,后面又做了一些测试,用wireshark,wireshark是不设代理的所以能正常请求,用wireshark大概测试了一下,发现这个歌曲请求是udp格式的,不是tcp,数据算明文,但请求看不出来,它的服务器地址不知道怎么来的,每次的请求服务器地址不一样,ip反查也查不到是什么域名,dns也没解析(因为dns没有解析,现在我的猜测就是,应该用一个https请求获取到p2p服务器IP,然后通过udp与其交互获取歌曲信息,因为我目前没有办法wireshark获取https请求明文,因为,好像只能是拦截浏览器的,其他应用的获取不到ssl密钥的。还不是很懂😂😂😂,应该就是加密获取的服务器地址,我都把wireshark抓到包都看了,都找不到p2p服务器ip,而且udp的请求格式也没解密出来,就知道了,客户端和服务器端发送的第一个字节是固定的而已。为什么它没有请求我局域网服务器呢,可能一开始的局域网服务器的host是127.0.0.1,局域网访问不了,因为后面解密算法确实是用了自定义歌曲来破解的)这个方法下载歌曲有点厉害的,目前最厉害的,几乎所有音乐。

 

因为这样,我直接将Xposed插件的代码改了,不是下载kgs,而是直接下载MP3歌曲了。😂😂😂

 

反解密

弄完了上面的东西,我还是想解密kgs,继续那个想法,用特殊歌曲进行解密。

 

局域网服务器host要设置为0.0.0.0才能被访问下载歌曲。

 

然后需要拦截entity类,因为我要篡改hash(我不太记得,为什么我不用拦截addDownload,反正我一直拦截entity篡改而已。😂😂😂)

 

这样就能定制特殊的歌曲,特殊hash进行解密kgs。

这里其实还有个问题,如果是解密的文件播放不了,会把解密的文件删除的,我不知道它是哪个函数删除的,按照上面的哪个完整的调用过程,我拦截过stop和delete都没能阻止它删除文件。后面发现Linux的chmod也不能阻止它删除,后面ChatGPT给了一个chattr指令,这个可以,用了这个连root都删除不了,这样就能保留解密算法生成的文件了,这里肯定也要MemoryOnly关闭的。(这个命令不是所有Android设备都有,反正我实体机没有,模拟器可能也是安装了mask,xposed这些才有这个命令的,可能)

我先用了全00和全11的制作了文件大小5099740字节的歌曲,hash用的32个0,

发现00得到的解密歌曲还是全00,全11的话变成了奇奇怪怪的东西,证明应该是起作用了。一开始我以为全11和hash全00生成这个会不会就是key了😊,后面发现不是。

后面又做了一系列的测试,01的歌曲10的歌ff的歌,hash全0,全1,全f的。

反正就做一堆的测试,我还是用了之前的假设:

kgs = src ^ key特殊1 ^ key公共

 

所以我得到单个文件看不出什么信息,我直接用两个kgs进行^,我用了src一样的、hash为0和为1的kgs进行了^,理应得到: key特殊1^key特殊2

 

好家伙,得到一堆有规律的东西:

1725590585426

给我整兴奋了,感觉离真相不远了。😘😘😘😘😘😘

 

有3F1的规律,看来之前的那个很少的3f1规律还是有点用的,就是不像这个这么规律,已经确定是3F1一次循环了。

 

特殊key肯定是3f1一次循环了,其实后面得到了这个也没什么进展,因为换了key之后虽然3f1还有,单它们的值不一样了。

 

后面突然想到:

可以是hash一样的01歌曲和key一样的10歌曲^,这样理应得到 src1 ^ src2,01 ^ 10=11,应该全是11的

1725590939667

全是88?,不是11?

 

看看88和11的二进制,会不会有什么规律(第一篇kgm博客给的提示,几乎都是二进制进行一个规律变化的)

 

1725591032578

好家伙,应该就是保留溢出的移位,11是原字节的话,就是原字节进行保留溢出的右移一位(后面知道这个叫循环右移),按照对称逻辑,原文件应该是经过了循环左移(这里注意,我一开始以为是源文件循环左移,但我忘了,这个11不是src是 src ^ key特殊 ^ key公共,所以是循环左移(src ^ key特殊 ^ key公共),加密完才位移,左移它们一个整体,后面我就因为这个弄错了,搞了很久没对)

 

这样就好办了,更新一下假设,

kgs = 循环左移(src ^ key特殊1 ^ key公共)

 

我已经知道了会进行循环左移,我把循环右移加上,看看kgs^kgs会得到什么东西,后面发现还是没有规律,因为key的特殊,如果能解出特殊key就能得到公共key了(为什么不是解出公共key,因为不可能拿到公共key的,^会消掉,而且就算拿到公共key,可能每个特殊key又不一样,我又不可能每次下载一首歌来这里做一系列操作拿到这个特殊key?那还不如直接让其下载解密了。😒😒😒😒)。

 

又停滞了一会儿,但发现关键东西

通过^是得不到特殊key的,只能通过找到它的生成算法。之前那么多逆向都找不到,没办法啊。

你猜怎么着,我能控制了hash和url,我尝试将hash弄成错误的,比如超出f,超出32个,不足32个(其实之前也弄过,得到我能控制hash的时候就试过了。)发现报错了!!!

hash不正确,尝试从libjengine.so(为什么一直围绕它,一开始是所以so都会去尝试的,后面逆向发现startDownload这些函数就是在libjengine.so里面的,所以一定先考虑这个so)看看,有没有这个关键字,还真有,

1725606585677

伪代码逻辑,这个sub_87624(&v8, "invalid hash:'%s'", v4);应该就是传递到java的函数,按照这个if逻辑,要执行

loc_42EE4才知道sub_87624要不要执行,看看loc_42EE4(32位的看不到伪代码,不懂为什么,64位可以看,可以用64位看看伪代码逻辑)

64位

1725606940561

loc_42EE4还是有很多函数的,通过一个一个排查,我定位到这个sub_5E3B0函数

因为

1725607015566

它给了我两个提示,一个是dword_17772C,是一堆字节数据,最有可能用来生成key的,0x3f1就是key^key的规律循环。

 

好家伙,好像看到希望了。

也不一定是这个函数,只是像而已。可以用frida拦截看看,这里要用实体机,因为实体机才能拦截so函数,

这里有几个难点我还没解决,但巧用函数参数的方法,得到了我想要的(其中一个难点,就是函数内部的变量获取不到,

ida给的:

1725608019564

这些寄存器偏移,this.context.sp可以获取sp,但感觉还是没拿到对应的值。

)。

首先sub_5E3B0函数有两个参数,通过分析伪代码

1725607358730

a1会进行一个运算,看看是不是16长度(这里看到16,我有点乱,按道理应该是32啊?算了,这里应该还是hash)

1725607403934

伪代码开始:v2 = a2,然后有这个代码

1725607542357

a2的作用应该就是存储这个算法的结果的地址,起始地址。

 

这里还有个函数:

1725607797593

一开始我以为这里还要进行一些列的运算,后面经过测试排查,发现这个对生成key没有影响,不知道它在干什么。

 

frida代码在附件。


通过hook可以知道,a1确实是hash值,只不过不是明文存储,是把hash值当成字节,所以两个字符变成一共字节,所以hash的长度就是16了。


 

最重要的代码就是

这里是获取算计算的结果,初看到这个结果是伤心的,因为和自己期待的不一样。

我一开始想得到是 这个算法是得到 key特殊^key公共,我一直拿着这个值与生成的值对比,找规律,没一个线索。(灰色界面这个是kgs ^ src得到的,就是key特殊^key公共,白色界面应该只是key特殊)

1725608389302

1725608402839

两个值都不一样,(上面这个hash是全0的),后面又弄了个全1的,

1725608483671

1725608712546

还是不一样,就在我准备放弃的时候,我想到了,可能这个算法就是生成特殊key的而已,怎么验证呢,可以用 key特殊1 ^ key公共 ^ key特殊2 ^ key公共 = key特殊1 ^ key特殊2 。

0x68 ^ 0x3e = 0x0b ^ 0xa7, 这里不等,但其实是等的,差一个循环位移,上面有个地方讲到,我混淆了循环位移的位置了,应该是kgs = 循环左移(src ^ key特殊1 ^ key公共),但我弄错成kgs = 循环左移(src) ^ key特殊1 ^ key公共,但这里我看出来了,看到它们差了一个位移

1725608875140

后面多测试了几个,就是这样了,证明这个函数就是生成特殊key的😭😭😭😭,开心死了!!!!

 

开始痛苦

已经找到了函数,现在就是要把它算法还原出来,一开始以为找了就能轻松还原了,经过不懈努力,研究这个伪代码,终于写出了第一版python代码,看附件

还是不对生成的与获取到不一样,完全不一样,能跑是能跑了(直接模仿伪代码是不对的)

 

摆了一段时间,因为想靠看懂伪代码直接写出来,但有些操作确实想不到它在干嘛(我是密码学小白)。后面又想着直接用Android写一个程序调用这个函数得到对应的key,后面发现Android能调用的函数只能是jni的,不能随便调用(ctf玩多了)。后面又找了很多其他版本的音乐软件,看看它们的伪代码好不好理解。

 

基本都很难理解。

 

突然想到一个,Windows版本的,win版本的也有“一起听”功能,但它很隐藏,不小心发现的。而且win版的话,它是dll啊,我可以python直接调用了,就算不会还原🤣🤣。

 

很快通过小脚本获取到对应的dll(因为它会用到dword_17772C的字节生成key,直接查找是否存在这里面的字节就能定位到对应的dll和函数了),netcore.dll,大小不大,挺好的,就算还原不了,直接python调用是可以接受的。

1725610001399

发现调用这些字节有多个函数,看来看去sub_100F6F84最像,因为有3f1。这里函数是非常简洁,大概率是能还原出算法,首先先验证,是不是这个函数生成特殊key的先。

 

用python直接调用so的函数😒😒😒😒,发现它居然不行,说不支持32位。查了一下,发现我的python版本是64位,这个音乐软件的dll是32位的。需要我python版本也是32位??????,那还怎么玩,找了找看看这个软件有没有64位版本,发现没有。看来还是得还原出算法。

 

发现frida也可以hook dll(牛🐂🐂🐂)的。

结合https://blog.51cto.com/lilongsy/11202791这篇和官网的例子,写出win版的脚本,起码先确保这个算法是计算特殊key的(这里为什么一定需要验证呢,因为这里的算法大致一样,但while循环这里它循环的条件是1024,在apk里面的是256而已🤣,很难评啊,所以有必要验证一下)

 

这里hook和Android一样的,通过任务管理找到pid,发现有好多,我就随便试了一下,试中了KGService。(随便试都行,不对会报错的🤫这个挺好的)

 

好家伙,我的脚本还是官方的,但获取到的参数不是我想要的。

1725610900010

这个函数有三个参数

1725610971666

a1应该是长度hash长度,

a2应该是hash值

1725611103510

a3应该是算法计算的返回值

1725611139724

用脚本输出的都对不上

后面ChatGPT给了点提示

igned int __usercall sub_100F6F84@(signed int a1@, int *a2@, int a3)

a1参数放在edx寄存器,a2放在ecx寄存器?

试了一下,确实可以,但a3怎么拿?a3是值,我要验证这个值是不是和apk的一样?

 

后面发现

memcpy((void *)a3, &Src, 0x3F1u);

a3是通过变量src赋值的,src是变量,

1725611358187

可以通过这些寄存器ebp进行偏移,但就是没成功过,这个ida给的地址应该还是有点问题的,或者是我不太懂😂😂,因为我输出的长度是3f1,后面减少一点,发现能正常输出了,但输出的内容和apk里面的输出不太一样。本来想放弃了,但想了一下,如果偏移不对,这些内容不是开头???,没想到还真是,通过搜索发现就是特殊key,只是偏移地址不对,输入的内容是后面的数据了,不是开头😂😂,完美。

 

终于,又有点动力了。这个伪代码简洁,可以试试写成python代码,第二版(代码已经丢失,可能我最终代码直接覆盖了)

 

第二版还是运行不了,因为这个伪代码不是像表面的那么简单。(通过调试才发现很多细节需要注意,其实调试的时候发现伪代码确实也没毛病,但直接照着伪代码写出程序,我是办不到的😂😂)

 

不到怎么办了。

 

后面尝试很多的调试ida,jadx等,从Android开始(其实逆向一开始就已经尝试过调试了,就是不行,应该有反调),但后面实体机出现还没试过,试了一下,发现还是不行(实体机frida可以hook so,所以我以为实体机可以调试)

 

后面想起来,Windows版本的是可以调试的,之前在弄那个signature的时候试过ida调试没有报错,但调试不到对应的函数😂,现在想可能是pid的问题。

 

所以,尝试一下ida调试win版,还是KGService进程啊,ida不报错的,什么进程都会调试,但调不到断点程序🤣。发现真的可以,舒服了。

1725612310777

完整版注释忘记保存了,但不重要了,直接看我的python代码就行(上面那个图片的注释是有点问题的,v16那里,反正看python代码就行了),我的代码就是按照调试流程来的。其实以为照着调试流程来就行了,但还真的会少了一下细节,因为我不可能完整key流程都看完的,我肯定通过几个熟悉一下他在干嘛,然后编写代码。而且流程也很搞,一些地方不开汇编代码还真的不知道,比如图片中的v16那里是+v7的么,我以为就是偏移v7的值而已么,没想到是v7*4 (汇编有写),但其实仔细研究伪代码也没错,&v16已经是地址了,就是双字节就是4个字节,就是4的倍数偏移(说白了我C语言指针不行啊🤣🤣🤣)。对了,调试的时候看这些变量是看寄存器的,鼠标放到变量会提示是哪个寄存器,寄存器可以从右边的寄存器窗口看到。具体也没什么好说的了,反正就是看懂一点伪代码的意思,先自己按照这个思路算出下一步的结果,再和调试的结果比对,如果对了应该就是你理解的那样。

痛苦啊,好不容易写完跟着流程跑完,写完了,但还是有几个坑:

dword_10173570[(unsigned __int8)(v9 + (v6 ^ v10))],dword_10173570就是256的一个数组,(v9 + (v6 ^ v10))是可能超出的,跑程序的时候就提示,伪代码这里也写了强转8位,2^8=256刚好,应该是这个意思,python就这样写了(offer + (selfxor ^ one)) & 0xFF

 

selfxor = ((tv11 >> 25) ^ (selfxor << 7)) ,用特殊key没发现,用普通key发现,这里居然会扩展位🤣,明明是^居然还能扩展了😒😒不太懂了,所以后面写成了selfxor = ((tv11 >> 25) ^ (selfxor << 7)) & 0xffffffff

 

好了跑出来了,开头都没有什么问题(和apk计算的进行人工对比),拉到末尾,发现有点不对,但很多都对,就是位置和位数不一样,奇怪,很奇怪。不知道是不是就是这个算法和apk的算法就是有点不一样的,还是哪里代码写错了?但我感觉不可能代码错了,因为这个算法其实是用到之前生成的内容再进行生成的,如果有个值不一样,可能结果会差别很大吧,现在看这个结果差不多啊,就一些奇奇怪怪的地方而已。

 

不管了,先拿这个key去试试,看看能不能解出公共key,发现不对啊,好像我的假设是错的???我尝试了两个hash不同的,kgs ^ key特殊 = key公共,两个不同的kgs,得到key公共应该是一样的啊,但不一样啊。

 

迷茫,奇迹

要不就是我的代码是错的,要就是两个key生成算法确实有点问题,要不就是我的假设出错了。后面我想了,我用的特殊key进行调试的,可能确实有些地方理解不太对。又做了一些测试,没发现什么问题。

 

后面去吃饭的时候,仔细研究这些代码,有哪些是可能会出错的,

bytes_list = [(hex_num >> (8 * i)) & 0xFF for i in range((hex_num.bit_length() + 7) // 8)]

这里我想的意思是 0x12 能分解成 0x00 0x00 0x00 0x12的,但试了一下这个代码居然会出错,只生成0x12而已,难怪,可能是这里的问题,后面改成了bytes_list = [(hex_num >> (8 * i)) & 0xFF for i in range(4)]

两个代码都是chatgpt写的,我没这么强,第一个代码被它害惨了。

 

终于对了,每一个都对应上了。😭😭

 

开始解出公共key,发现还是出现问题,然后就是上面提到的那个问题,应该是kgs = 循环左移(src ^ key特殊1 ^ key公共),但我弄错成kgs = 循环左移(src) ^ key特殊1 ^ key公共。后面发现这个问题,成功解出两个不同kgs,相同的公共key,证明假设是对的。

 

已经可以解密了,但公共key的大小决定你能解密多大的kgs。

 

后面想了一下,我可以制造一个很大的公共key,这样就行了。因为我能制造特殊歌曲,直接制造一个500M的歌曲。进行分离公共key(其实我有想过公共key可能也会是算法生成的,应该会循环的。)确实是有规律的,生成500M的公共key (很慢) ,用010打开,发现

1725621117493

0x1f21400循环一次,大约32M的大小。还行,比500M好多了。就这样了,能解密kgs,已经基本不可能找到公共key的算法了,而且这个公共key算法可能不止一个,可能是 key1 ^ key2 ^ key3...这样的,32M已经可以接受了。

 

大佬可以尝试解出公共key的算法(这里特殊key已经知道这个函数产生的,看看它被哪里调用应该就能找到公共key的算法了😂猜的)。或者想办法压缩一下32M。😘

 

结束

没有一步是浪费的,需要经过这么多的观察和思考,才可能找到解法。

能成功一大因素是坚持,一直在尝试,一直失败,一直不放弃,几乎有可能的情况都有去尝试了。

还好不是两个特殊key,否则真的不好解出。

还有调试,没有调试也不可能解出。几乎都崩溃了,照着伪代码一次都没成功,还好win版能调试。😭😭

有点幸运吧,能知道自己创造特殊歌曲,还有“invalid hash”的提示,这个非常重要,没有这个还真不一定找这个算法。

那个p2p下载歌曲的也有个提示,可能能缩短寻找时间,后面再说了,感觉这个有点难🤣

挺好的,学到挺多新东西的。

 

一些环境

frida 16.4.8 frida-tools 12.5.0

objection 1.11.0

takiyasha 0.3.4

雷电模拟器 Android9.0(64位) root

真机 oppo 1107 (root)

某音乐软件 apk (10.9版本、10.4.5版本)

某音乐概念版(3.5.2版本)

某音乐win版本(10.2.50.25708)

frida-server-16.4.10-windows-x86_64.exe.xz

frida-server-16.4.8-android-x86_64.xz

frida-server-16.0.0-android-arm.xz

JustTrustMe_20190516.apk

TrustMeAlready-v1.11-release.apk

Parakeet.win32.zip

android-studio-ide-191.6010548-windows.exe 3.5.0.0

burpsuite_community_windows-x64_v2024_2_1_5.exe

Wireshark-4.2.3-x64.exe

Python 3.11.4

jeb-demo-4.2.0

jadx-gui-1.5.0-with-jre-win

IDA_Pro_v7.0

 

参考文献:

https://blog.51cto.com/lilongsy/11202791

https://zhangqf.com/en/docs/frida-so-hook/

https://kuizuo.cn/docs/frida-so-hook/

https://blog.csdn.net/weixin_51111267/article/details/131010916

https://blog.csdn.net/adzcsx2/article/details/107087300?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ECtr-1-107087300-blog-113703039.235%5Ev43%5Epc_blog_bottom_relevance_base1&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ECtr-1-107087300-blog-113703039.235%5Ev43%5Epc_blog_bottom_relevance_base1&utm_relevant_index=1

https://51wlb.top/xposed-plugin/

https://github.com/dennishucd/xposed7/blob/master/lib/api-82.jar

https://www.kancloud.cn/a6260362/study-android-and-web/1646986

https://blog.csdn.net/weixin_44236034/article/details/134210873

https://www.cnblogs.com/JipengYe/p/10044715.html

https://blog.csdn.net/wenzhi20102321/article/details/137080360

https://blog.csdn.net/khq1013/article/details/133678832

https://blog.csdn.net/xiru9972/article/details/131016673

https://blog.csdn.net/qq_39609284/article/details/135005689

https://www.52pojie.cn/thread-1031123-1-1.html

https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458298594&idx=1&sn=4feaaf7959a89c3299fdd569535a60f6&chksm=b181986886f6117e64fffa8ff2e84b9df14a94541b5eb5259fd25957ed2cee2dbcbb76695bff&scene=27

https://www.alilinet.com/site/96.html

https://blog.csdn.net/SXXYNHHXX/article/details/139045769

https://blog.csdn.net/weixin_38819889/article/details/122698360

https://blog.csdn.net/weixin_39190897/article/details/115582853

https://blog.csdn.net/zzxx191z/article/details/139898668

https://blog.csdn.net/weixin_51100340/article/details/139493081

https://blog.csdn.net/qq_41525018/article/details/125141194

https://blog.csdn.net/qq_65474192/article/details/138916083

https://www.52pojie.cn/thread-1598242-1-1.html

https://www.52pojie.cn/forum.php?mod=viewthread&tid=742250

https://blog.csdn.net/freeking101/article/details/105910877/

https://blog.csdn.net/kinghzking/article/details/137071826

https://blog.csdn.net/kinghzking/article/details/137071768

https://www.cnblogs.com/lxh2cwl/p/14842544.html

https://blog.csdn.net/freakishfox/article/details/78289293

https://blog.csdn.net/kinghzking/article/details/137008446

https://www.cnblogs.com/Only-xiaoxiao/p/17294561.html#

https://juejin.cn/post/7363078360786485287#heading-3

https://www.jianshu.com/p/5e54aca2cfca

https://blog.csdn.net/gap12521/article/details/139912208

https://www.cnblogs.com/KMBlog/p/6877752.html

https://www.cnblogs.com/wawahaha/p/4657381.html

附件