首页
社区
课程
招聘
[原创]数字猫小说阅读软件分析
发表于: 2021-2-27 16:10 13091

[原创]数字猫小说阅读软件分析

2021-2-27 16:10
13091

市面上很多小说阅读软件,这些小说软件美其名曰免费,实际上翻两页就是一大段广告,十分影响阅读体验,本篇文章通过数字猫小说软件入手,对其进行去广告以及相关算法分析。

软件没加壳,直接用JEB载入,通过搜索“AD”之类定位广告:
相关函数符号还没混淆:
isVideoRewardExpire判断看广告视频的阅读奖励是否过期,这里直接返回false。

还有一处也是类似的逻辑:

用apklab直接找到相关函数修改之

修改结果如下:

为了方便抓包,移除SSL证书绑定:

经过测试,APK还有签名校验,在so里面,直接把2该为1即可过掉该校验:

重打包,可以看到,广告已经去掉了:

光是去广告就完了吗?对于20年的书虫来说还不够,有时我想在kindle上阅读该小说,所以我决定做一个简陋的下载器。

有两个包十分关键,第一个包获取章节信息,包括章节名称和id等。可以看到请求都有签名sign字段。

第二个包是获取小说下载地址

下载过来是一个压缩包,打开发现里面都是以章节id命名的txt文件,很明显还加密过了:

思路很清晰了,先搞定sign算法,再搞定小说书籍解密算法。

先通过JEB定位sign算法的地方,发现这个函数最终还是调用了so里面的函数:

先用frida hook该函数,打印下日志,看看传参是啥,脚本如下:

很明显,传入的是HTTP的get或者post参数,参数key按升序排列。

为了分析sign算法,定位到了so里面的处理逻辑,把key append到字符串结尾,再求md5

这里我直接在memcpy打个断点,用IDA连上去查看key,当然也可以继续用frida hook。

好的,现在已经成功获取到sign的key,直接写出相关sign算法如下:

搞定了sign算法之后,开始分析小说数据的解密算法。
解密算法也在so里面,但是回调了java层里面的AES解密算法

java算法如下,把数据base64解密后,取前16个字节作为IV,传入的key直接作为AES-CBC的解密密钥进行解密:

这里继续用frida hook:

通过日志直接获取aes解密密钥,经过测试,不同的小说解密密钥都是一样的:

解密算法如下:

下载器源码如下,关键地方已经和谐处理:

效果如下:

通过以上分析,该软件基本的加固都没有,所以能很容易定位关键点,分析逻辑,而且在本人分析时候发现该软件阅读时间不够还能直接领取金币奖励,当然这是后话了。
好了就到这里了,我去看歪嘴龙王了

 
Java.perform(function () {
    var Encryption = Java.use('com.qimao.qmsdk.tools.encryption.Encryption');
    Encryption.sign.implementation = function () {
        var s = arguments[0];
        var r = this.sign(s)
        send('sign:    '+s+' result:    '+r);
        return r;
 
    }
});
Java.perform(function () {
    var Encryption = Java.use('com.qimao.qmsdk.tools.encryption.Encryption');
    Encryption.sign.implementation = function () {
        var s = arguments[0];
        var r = this.sign(s)
        send('sign:    '+s+' result:    '+r);
        return r;
 
    }
});
def getsign(param_dict):
    sign_key='d3dGiJ**********'
    before_sign = '';
    for key in sorted(param_dict):
        before_sign += f'{key}={str(param_dict[key])}'
    before_sign+=sign_key
    sign=MD5.new(before_sign.encode('utf8')).hexdigest()
    return sign
def getsign(param_dict):
    sign_key='d3dGiJ**********'
    before_sign = '';
    for key in sorted(param_dict):
        before_sign += f'{key}={str(param_dict[key])}'
    before_sign+=sign_key
    sign=MD5.new(before_sign.encode('utf8')).hexdigest()
    return sign
Java.perform(function () {
    var AESManager = Java.use('com.km.encryption.aes.AESManager');
    AESManager.decrypt.overload('java.lang.String', 'java.lang.String').implementation = function () {
        var s = arguments[0];
        var k = arguments[1];
        var r = this.decrypt(s,k)
        send('decrpt book:    '+s+' key:    '+k);
        return r;
 
    }
});
Java.perform(function () {
    var AESManager = Java.use('com.km.encryption.aes.AESManager');
    AESManager.decrypt.overload('java.lang.String', 'java.lang.String').implementation = function () {
        var s = arguments[0];
        var k = arguments[1];
        var r = this.decrypt(s,k)
        send('decrpt book:    '+s+' key:    '+k);
        return r;
 
    }
});
def decrypt_chapter(s):
    aes_key=b'242ccb**********'
    iv_enc_data=base64.b64decode(s)
    aes_iv=iv_enc_data[0:16]
    enc_data=iv_enc_data[16:]
    unpad = lambda s: s[:-ord(s[len(s) - 1:])]
    aes=AES.new(aes_key,AES.MODE_CBC,aes_iv)
    return unpad(aes.decrypt(enc_data))
def decrypt_chapter(s):
    aes_key=b'242ccb**********'
    iv_enc_data=base64.b64decode(s)
    aes_iv=iv_enc_data[0:16]
    enc_data=iv_enc_data[16:]
    unpad = lambda s: s[:-ord(s[len(s) - 1:])]
    aes=AES.new(aes_key,AES.MODE_CBC,aes_iv)
    return unpad(aes.decrypt(enc_data))
import requests
import base64
import string
import zipfile
import time
import json
import io
from Crypto.Hash import MD5
from Crypto.Cipher import AES
def getsign(param_dict):
    sign_key='d3dGiJ**********'
    before_sign = '';
    for key in sorted(param_dict):
        before_sign += f'{key}={str(param_dict[key])}'
    before_sign+=sign_key
    sign=MD5.new(before_sign.encode('utf8')).hexdigest()
    return sign
 
def decrypt_chapter(s):
    aes_key=b'242ccb**********'
    iv_enc_data=base64.b64decode(s)
    aes_iv=iv_enc_data[0:16]
    enc_data=iv_enc_data[16:]
    unpad = lambda s: s[:-ord(s[len(s) - 1:])]
    aes=AES.new(aes_key,AES.MODE_CBC,aes_iv)
    return unpad(aes.decrypt(enc_data))
 
def qm_get(url,params,headers=None):
    if not headers:
        headers = {
        'app-version':'50810',
        'platform':'android',
        'reg':'0',
        'AUTHORIZATION':'',
        'application-id':'com.****.reader',
        'net-env':'1',
        'channel':'unknown',
        'qm-params':'',
        }
    headers.update({"sign": getsign(headers)})
    params.update({"sign": getsign(params)})
    res = requests.get(url,params=params,headers=headers,verify=False)
    return str(res.text)
def get_book_download_url(bookid):
 
    download_url = 'https://api-bc.****.com/api/v1/book/download'
    params = {
      'source' : '1',
      'type' : '1',
      'id':bookid,
    }
    book_dl_info=json.loads(qm_get(download_url,params))
    return book_dl_info['data']['link']
def get_chapter_list(bookid):
    chapter_url = 'https://api-ks.****.com/api/v1/chapter/chapter-list'
    params = {
      'is_all_update' : '0',
      'id':bookid,
    }
    chapter_list=json.loads(qm_get(chapter_url,params))
    return chapter_list['data']['chapter_lists']
 
 
def get_book_data(link):
    res=requests.get(link,verify=False)
    return res.content
 
 
 
if __name__=="__main__":
 
    logo='''
 
    '''
    print(logo)
 
    book_id=198241
    with open(str(book_id)+'.txt','wb+') as fd:
        download_link=get_book_download_url(book_id)
        chapter_list=get_chapter_list(book_id)
        chapter_list.sort(key=lambda x:x['chapter_sort'])
        #print(chapter_list)
        book_data=get_book_data(download_link)
        book_file = io.BytesIO(book_data)
        with zipfile.ZipFile(book_file) as zip_ref:
            for chapter_info in chapter_list:
                chapter_id=chapter_info['id']
                with zip_ref.open(chapter_id+'.txt', "r") as fo:
                    data=fo.read()
                    chapter_data=decrypt_chapter(data)
                    print(chapter_data)
                    fd.write(chapter_info['title'].encode('utf8')+b'\r\n'+chapter_data)
 
 
    print('------------------------------------------------------------------------------------------------')
    print('[+]解密完成!!!!!')
import requests
import base64
import string
import zipfile
import time
import json
import io
from Crypto.Hash import MD5
from Crypto.Cipher import AES
def getsign(param_dict):
    sign_key='d3dGiJ**********'
    before_sign = '';
    for key in sorted(param_dict):
        before_sign += f'{key}={str(param_dict[key])}'
    before_sign+=sign_key
    sign=MD5.new(before_sign.encode('utf8')).hexdigest()
    return sign
 
def decrypt_chapter(s):
    aes_key=b'242ccb**********'
    iv_enc_data=base64.b64decode(s)
    aes_iv=iv_enc_data[0:16]
    enc_data=iv_enc_data[16:]

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 9
支持
分享
最新回复 (14)
雪    币: 26399
活跃值: (63262)
能力值: (RANK:135 )
在线值:
发帖
回帖
粉丝
2

感谢分享!666~~

最后于 2021-2-27 16:22 被Editor编辑 ,原因:
2021-2-27 16:21
0
雪    币: 2040
活跃值: (4950)
能力值: ( LV13,RANK:278 )
在线值:
发帖
回帖
粉丝
3

2021-2-27 16:35
0
雪    币: 295
活跃值: (920)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
sign_key='******c651gSQ8w1' 哈哈哈
2021-2-27 16:37
1
雪    币: 2089
活跃值: (3933)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
seven猫占领了各种应用商店阅读应用的广告位,盈利是真的靠广告的吗?
2021-2-27 19:24
0
雪    币: 10
活跃值: (774)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
这是用啥软件抓的包,界面看的比charles和fiddler舒服多了
2021-3-6 15:32
0
雪    币: 10856
活跃值: (14644)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
7
Runope 这是用啥软件抓的包,界面看的比charles和fiddler舒服多了
fiddler anywhere
2021-3-6 18:43
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
没找到到签名检验的so
2021-3-7 13:50
0
雪    币: 588
活跃值: (367)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
是怎么定位到sign算法的地方?
2021-3-30 19:49
0
雪    币: 453
活跃值: (129)
能力值: (RANK:0 )
在线值:
发帖
回帖
粉丝
10
问题是没啥好看的东西.
2021-3-30 22:14
0
雪    币: 229
活跃值: (203)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
11
感谢wmsuper师傅分享自己的研究成果
2021-4-25 15:17
0
雪    币: 187
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
能否告知下这个是哪个app
2021-4-26 11:55
0
雪    币: 1985
活跃值: (1800)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
感谢分析~  请问楼主操作的版本是啥版本呢
2021-4-27 13:42
0
雪    币: 10856
活跃值: (14644)
能力值: ( LV13,RANK:400 )
在线值:
发帖
回帖
粉丝
14
淡定小胖子 感谢分析~ 请问楼主操作的版本是啥版本呢
5.8.10
2021-4-27 13:50
0
雪    币: 0
活跃值: (532)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
大佬 那听书功能能实现吗
2021-4-30 17:48
0
游客
登录 | 注册 方可回帖
返回
//