吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3847|回复: 27
收起左侧

[Android 原创] 某k歌app缓存文件解密分析

  [复制链接]
red1y 发表于 2022-9-24 21:29

本文包括

说明:这个缓存文件的解密很简单,本文主要是为大家提供定位关键逻辑代码的一种思路

一、Java层定位缓存文件加载逻辑

  1. 前置工作

    • 这个apk很大,有19个dex文件,往jeb里加载之前要把jeb的可用内存设置的大一些,我设置了8G

    • 从启动Activity大致阅览一些,发现这个发布版包含了大量log日志记录的代码,并没有剔除

    • 因此可以使用查看应用log的方式定位我们所关注的逻辑代码

      LogUtil.i("SplashBaseActivity", "isDexActivityInRoot: id=" + v4 + ",numOfActivityes=" + v5 + ",topActivity=" + v6 + ",baseActivity=" + v1_2);
      LogUtil.i("SplashBaseActivity", "isDexActivityInRoot: rootActivity is special activity");
      LogUtil.e("SplashBaseActivity", "unexpected intent.");
      LogUtil.i("SplashBaseActivity", "relogin tag = " + v2_1);
  2. 查看应用日志

    • 打开DDMS,在安装AndroidStudio的时候会安装在SDKtools文件夹下,一个monitor.bat的脚本

      image-20220924173711324.png

    • 连接手机,运行app,查看应用进程号adb shell ps | findstr com.tencxxxt.kxxx,后面的是app的包名,linux使用grep过滤

      image-20220924174014933.png

    • DDMSlogcat窗口新建过滤器,根据进程pid过滤,此时已经可以看到app的运行日志了

      image-20220924174332129.png

  3. 观察缓存文件加载逻辑

    • 先播放一个音乐,音乐加载完成后就会在本地生成一个缓存文件

    • 把网络断掉,清空日志

    • 从新播放刚才的音乐

    • 此时加载缓存文件的逻辑已经在日志中记录下来了

      image-20220924174901641.png

    • 截取的部分日志

      09-24 17:44:37.433: I/RefactorDetailInfoController(10385): [, , 0]:[UI]QueryPayTaskStatusReq error:-1  msg:网络不可用, 请检查网络设置
      09-24 17:44:37.433: I/FeedAudioOperateController(10385): [, , 0]:[UI]mFeedPlayListener notifyHideLoading
      09-24 17:44:37.433: I/FeedAudioOperateView(10385): [, , 0]:[UI]hideLyricPage
      09-24 17:44:37.433: I/FeedAudioOperateController(10385): [, , 0]:[UI]mFeedPlayListener notifyHideLoading
      09-24 17:44:37.434: I/FeedAudioOperateView(10385): [, , 0]:[UI]hideLyricPage
      09-24 17:44:37.434: I/FeedMediaController(10385): [, , 0]:[UI]notifyPlaySongListChange actionType = [1]
      09-24 17:44:37.434: I/FeedMediaController(10385): [, , 0]:[UI]setCurrentPlay null
      09-24 17:44:37.434: I/WifiDialogUtil(10385): [, , 0]:[UI]call closeNoWifiDialog function
      09-24 17:44:37.435: I/MusicPlayer(10385): [, , 0]:[UI][MusicPlayerUtils] 无网络,忽略 ugc 获取作品信息步骤
      09-24 17:44:37.435: I/PlayManager(10385): [, , 0]:[UI]online song 忽然之间
      09-24 17:44:37.435: I/PlaySongInfoDbService(10385): [, , 0]:updatePlaySongList
      09-24 17:44:37.444: I/OpusMemCache(10385): [, , 0]:[UI]addMemCache, info: OpusCacheInfo{path='/storage/emulated/0/Android/data/com.tencent.karaoke/files/opus/-1991663251', bitrateLevel=48, vid='1021_e5e7eaee8e3718ba547e47ef80f9cdfd7c81bfac', cacheKey='1021_e5e7eaee8e3718ba547e47ef80f9cdfd7c81bfac_0'}
      09-24 17:44:37.444: I/PlayManager(10385): [, , 0]:[UI]can PlayOffline
      09-24 17:44:37.445: I/KaraPlayerService(10385): [, , 0]:[UI]updateCurrentPlaySong 18819739_1531398846_805
      09-24 17:44:37.445: I/OpusMemCache(10385): [, , 0]:[UI]addMemCache, info: OpusCacheInfo{path='/storage/emulated/0/Android/data/com.tencent.karaoke/files/opus/-1991663251', bitrateLevel=48, vid='1021_e5e7eaee8e3718ba547e47ef80f9cdfd7c81bfac', cacheKey='1021_e5e7eaee8e3718ba547e47ef80f9cdfd7c81bfac_0'}
      09-24 17:44:37.445: I/PlayManager(10385): [, , 0]:[UI]online song 忽然之间
      09-24 17:44:37.446: I/OpusMemCache(10385): [, , 0]:[UI]addMemCache, info: OpusCacheInfo{path='/storage/emulated/0/Android/data/com.tencent.karaoke/files/opus/-1991663251', bitrateLevel=48, vid='1021_e5e7eaee8e3718ba547e47ef80f9cdfd7c81bfac', cacheKey='1021_e5e7eaee8e3718ba547e47ef80f9cdfd7c81bfac_0'}
      09-24 17:44:37.446: I/PlayManager(10385): [, , 0]:[UI]can PlayOffline
      09-24 17:44:37.446: I/KaraPlayerService(10385): [, , 0]:[UI]playSong can startPlay
      09-24 17:44:37.447: I/lib_player:PlayProxy(10385): [, , 0]:[UI]setAudioProcesser: audioProcesser = h.w.n.j.u0.v.y0.d@84c18b
      09-24 17:44:37.447: I/lib_player:PlayProxy(10385): [, , 0]:[UI]setAudioProcesser: audioProcesser = h.w.n.j.u0.v.y0.g@800f268
      09-24 17:44:37.447: I/lib_player:PlayProxy(10385): [, , 0]:[UI]setTimeOut: timeOut = 5000
      09-24 17:44:37.447: I/lib_player:ExoPlayerBuilder(10385): [, , 0]:[UI]setBufferSize: set buffer size 1000
      09-24 17:44:37.447: I/lib_player:PlayProxy(10385): [, , 0]:[UI]buildPlayer: useSpeedLimit = false
      09-24 17:44:37.447: I/lib_player:ExoPlayerBuilder(10385): [, , 0]:[UI]buildPlayer: fromTag is 12
      09-24 17:44:37.448: I/lib_player:DefaultRenderersFactory(10385): Loaded Libgav1VideoRenderer.
      09-24 17:44:37.451: I/lib_player:ExoPlayerImpl(10385): Init 6b31681 [ExoPlayerLib/2.16.1] [platina, MI 8 Lite, Xiaomi, 29]
      09-24 17:44:37.451: I/lib_player:ExoPlayerImplInternal(10385): [, , 0]:[UI]init h.j.a.a.u1@3e21326 : create player PB 12
      09-24 17:44:37.452: D/AudioManager(10385): getStreamVolume isRestricted mode = 0
      09-24 17:44:37.455: I/lib_player:PlayProxy(10385): [, , 0]:[UI]setAudioStreamType: streamtype = 3
      09-24 17:44:37.455: I/lib_player(10385): [, , 0]:[UI]audioAttributes [eventTime=0.00, mediaPos=0.00, window=0, 2,0,1,1]
      09-24 17:44:37.455: I/KaraProxyPlayer:5f59b5a(10385): [, , 0]:[UI]createPlayer: playerType EXOPLAYER Player :526cd14
      09-24 17:44:37.455: I/MusicPlayer(10385): [, , 0]:[UI][MusicPlayerStateLiveData] music player state change to 2(PREPARING)
      09-24 17:44:37.455: I/KaraProxyPlayer:5f59b5a(10385): [, , 0]:[UI]initPlayer Player : 526cd14
      09-24 17:44:37.455: I/KaraProxyPlayer:5f59b5a(10385):  vid = [1021_e5e7eaee8e3718ba547e47ef80f9cdfd7c81bfac], playScene = [1], bitrateLevel = [48], hasEncrypted = [false], ugcId = [18819739_1531398846_805], sha1sum = [], ugcLoudness = [1.0], useSuperSound = [true], fmtID = [-1], cacheKey = [null], ktvDataInPlayer = [null], recyBufferSize = [0]
      09-24 17:44:37.456: I/OpusMemCache(10385): [, , 0]:[UI]addMemCache, info: OpusCacheInfo{path='/storage/emulated/0/Android/data/com.tencent.karaoke/files/opus/-1991663251', bitrateLevel=48, vid='1021_e5e7eaee8e3718ba547e47ef80f9cdfd7c81bfac', cacheKey='1021_e5e7eaee8e3718ba547e47ef80f9cdfd7c81bfac_0'}
      09-24 17:44:37.456: I/KaraProxyPlayer:5f59b5a(10385): [, , 0]:[UI]initPlayer: 业务没有传入验证的sha1值,不做验证,直接播放缓存
      09-24 17:44:37.456: I/lib_player:PlayProxy(10385): [, , 0]:[UI]setHasEncrypted: true
      09-24 17:44:37.456: I/lib_player:PlayProxy(10385): [, , 0]:[UI]setDataSource: filePath = /storage/emulated/0/Android/data/com.tencent.karaoke/files/opus/-1991663251
      09-24 17:44:37.456: I/lib_player:PlayProxy(10385): [, , 0]:[UI]setWakeMode: context = com.tencent.karaoke.KaraokeApplication@d2a531c, mode = 1
      09-24 17:44:37.457: I/lib_player:PlayProxy(10385): [, , 0]:[UI]prepareAsync
      09-24 17:44:37.457: I/lib_player(10385): [, , 0]:[UI]timeline [eventTime=0.00, mediaPos=0.00, window=0, periodCount=1, windowCount=1, reason=PLAYLIST_CHANGED
      09-24 17:44:37.457: I/lib_player(10385): [, , 0]:[UI]  period [?]
      09-24 17:44:37.457: I/lib_player(10385): [, , 0]:[UI]  window [?, seekable=false, dynamic=true]
      09-24 17:44:37.457: I/lib_player(10385): [, , 0]:[UI]]
      09-24 17:44:37.457: I/lib_player(10385): [, , 0]:[UI]mediaItem [eventTime=0.00, mediaPos=0.00, window=0, reason=PLAYLIST_CHANGED]
      09-24 17:44:37.458: I/lib_player(10385): [, , 0]:[UI]state [eventTime=0.00, mediaPos=0.00, window=0, BUFFERING]
      09-24 17:44:37.458: I/GlobalPlaySongManager(10385): [, , 0]:[UI]refreshPlaySongListAfterStartPlay -> mPlayingSongIdentif 
      09-24 17:44:37.458: I/MusicPlayer(10385): [, , 0]:[UI][MusicPlayer] 正在播放: UGC作品 忽然之间(18819739_1531398846_805), 下一首: UGC作品 横冲直撞(326668413_1634048914_780)
      09-24 17:44:37.459: I/DetailDataManager(10385): [, , 0]:[UI]loadVideoSizeFromUgcInfo: stream=0-0, norma=0-0
      09-24 17:44:37.459: I/DetailDataManager(10385): [, , 0]:[UI]loadVideoSizeFromUgcInfo cancel with empty
      09-24 17:44:37.459: I/RefactorPlayController(10385): [, , 0]:[UI]adjustVideoViewLayoutOnUiThread:videoHeight=1, videoWidth=1, isEffectTemplateShow=false
  4. 定位java代码

    • 从日志中找出自己认为比较关键的信息,然后在jeb里查找对应的

    • 我这里选在initPlayer Player :这一条,即初始化播放器

    • 搜索这个字符串,定位到相关代码处

      image-20220924175529966.png

    • 往下阅读处理流程可以看到出现了解密成功解密失败等字样

      image-20220924175743303.png

    • 跟进前面的判断函数

      image-20220924175905069.png

    • 成功定位到关键的加密类

      image-20220924175933773.png

二、Hook加解密函数观察参数

  1. 这一步的目的是查看各函数的参数,因为目的是解密缓存文件,因此这里只关注解密函数

  2. 可以看到decrypt函数有三个重载,其中一个为native,另外两个参数个数不同;

    private native int decrypt(int arg1, ByteBuffer arg2, int arg3);
    public int decrypt(int arg6, byte[] arg7, int arg8);
    public int decrypt(int arg6, byte[] arg7, int arg8, int arg9);
  3. 两个Java层的函数最终也都调用了native的函数

  4. 可以自己hook一下java层的函数,看一下最终调用的是哪一个,以及参数特征

  5. 我这里测试,调用的是四个参数的decrypt,之后hook一下native的函数,看一下传给native的参数,ByteBuffer不会打印kkk

    [decrypt]
    arg1:  0
    arg3:  8
    [decrypt]
    arg1:  8
    arg3:  8
    [decrypt]
    arg1:  16
    arg3:  8
    [decrypt]
    arg1:  24
    arg3:  4
    [decrypt]
    arg1:  28
    arg3:  8
    [decrypt]
    arg1:  36
    arg3:  8
    [decrypt]
    arg1:  44
    arg3:  8
    [decrypt]
    arg1:  52
    arg3:  8
    [decrypt]
    arg1:  60
    arg3:  8
    [decrypt]
    arg1:  68
    arg3:  8
  6. 观察可以得出结论,这个类似一个流加解密,每次传给native8(除个别外)个字节解密,其他的参数是一些记录偏移的

三、分析还原Native层解密算法

  1. IDA看一下对应的函数,这个就很简单,根据算出来的偏移取密码表里的字节和原始字节异或就得到了明文

    __int64 __fastcall Java_com_tencent_karaoke_audiobasesdk_KaraMediaCrypto_decrypt(__int64 a1, __int64 a2, int start, __int64 a4, int size)
    {
     __int64 data; // x1
     __int64 result; // x0
     __int64 i; // x9
     int offset; // w16
     int v11; // w16
     int v12; // w18
     int v13; // w16
    
     data = (*(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 1840LL))(a1, a4);
     result = (unsigned int)size;
     if ( (start & 0x80000000) != 0 )
       return 4294967294LL;
     if ( size > 0 )
     {
       i = 0LL;
       do
       {
         if ( ((start + i) & 0x8000000000000000LL) != 0 )
         {
           offset = 0;
         }
         else
         {
           offset = start + i;
           if ( start + i >= 0x8000 )
             offset %= 0x7FFF;
         }
         v11 = offset * offset;
         v12 = v11 + 80923;
         v13 = v11 + 81178;
         if ( v12 >= 0 )
           v13 = v12;
         *(_BYTE *)(data + i++) ^= byte_7191359B20[v12 - (v13 & 0xFFFFFF00)]; // 和密码表对应的字节映射
       }
       while ( size != (_DWORD)i );
     }
     return result;
    }
  2. python照着写一遍,把密码表copy下来就行了

  3. python实现的时候可以发现很多if条件都触发不了,因此可以自己精简一下

四、解密缓存文件测试

  • 正常播放

    image-20220924181039908.png

免费评分

参与人数 12威望 +1 吾爱币 +33 热心值 +10 收起 理由
junjia215 + 1 + 1 谢谢@Thanks!
固水 + 1 + 1 我很赞同!牛皮,虽然看不懂
huo99 + 1 谢谢@Thanks!
daiqing + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
RUO + 2 + 1 有点硬核,谢谢分享
axin0529 + 1 + 1 热心回复!
坚持啊啊 + 1 + 1 我很赞同!
爱飞的猫 + 2 mp4 后缀可以考虑替换为 m4a 或 aac
wanfon + 1 + 1 热心回复!
imumu1239 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
hxd97244 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
正己 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

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

 楼主| red1y 发表于 2022-9-26 22:38
chendipang 发表于 2022-9-25 23:40
大佬问个小问题,为啥我的真机用DDMS输出的日志全在一行不会换行显示出来

这个我也不清楚,应该是一条log占一行的
taoxwl666 发表于 2022-9-25 16:29
alonelycute 发表于 2022-9-25 23:21
chendipang 发表于 2022-9-25 23:40
大佬问个小问题,为啥我的真机用DDMS输出的日志全在一行不会换行显示出来
wfys66 发表于 2022-9-25 23:47
来看看666666
我缘以为 发表于 2022-9-26 08:16
66666666666技术佬
htpidk 发表于 2022-9-26 18:18
非常感谢楼主分享
Pro111 发表于 2022-9-26 20:44
可以可以
焕墨如烟9817 发表于 2022-9-26 20:52
高级操作!
hk9186 发表于 2022-9-26 21:16
点赞 局外人
看一下
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

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

GMT+8, 2024-4-25 08:54

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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