吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2682|回复: 35
收起左侧

[Android 原创] 沪江开心词场APP词书接口和数据破解

  [复制链接]
uusama 发表于 2024-11-6 10:32
本帖最后由 uusama 于 2024-11-8 23:18 编辑

说明

上一篇沪江小D(现APP已不能使用)的破解,主要思路也是Fiddler抓接口,然后jadx反编译apk包,根据接口定位反编译代码,分析代码得到数据解密方式,最后使用python实现解密算法得到解密秘钥和文本。

检查了一下,之前是爬取了沪江开心词场所有的日语单词书,json格式,包括读音例句释义等详细信息,还有沪江小D上爬取的13000多个日语单词,另外还爬了其他两个APP上的日语N5-N1语法和N5-N1真题,同时存了json文件和数据库db,看下如果有兄弟要学日语需要的话可以在百度盘下载,压缩后100M左右:https://pan.baidu.com/s/1NS5ApZykRn7WoBRLXTuAaA&pwd=uuuu,不过建议自己上手试试看。

另外两个APP(羊驼日语和芥末日语,不确定是否还活着)的破解也是类似的思路,有个还涉及到接口VIP校验。

哈哈,为了过个N1找资料我也是爱上了逆向。:lol

准备工具

相关工具以及破解的apk包可以通过百度云获取: https://pan.baidu.com/s/1CjdDkXgVGJpMToXuc_8SRQ,提取码uurr

实现的整个源码可以参考github,其中实现了沪江词书的解密和爬虫。: https://github.com/youyouzh/PythonPractice/blob/master/spider/word/crawler/hujiang_crawler.py

词书查询下载接口

打开沪江开心词场,注册登录后,添加词书,这儿选择日语词书 -> 查看更多,可以筛选,然后点击其中一本词书添加。抓包接口如下:

ci-query.png

其中第一个接口GET /v3/book/search_by_tag根据tag来搜索,尝试在postman中构造这个请求,连登录态都不需要,没有任何校验,直接可以查寻词书,返回值也没有做任何加密,其中就有词书id。

看第二个接口GET /v3/user/me/book/13216/resource用于获取词书文件下载地址,同样在postman中构造这个请求,这个请求需要token登录态才可以,没有其他限制。

注意到返回值json中显然有词书文件压缩包的地址,而且看接下来的请求是下载2109011636.xml.zip后缀的词书,其他的词书估计没用到先不管。

访问词书压缩包下载地址https://c2g.hjfile.cn/tools_book_package/13216/2109011636.xml.zip,可以直接下载zip文件,没有登录态和其他安全检查。

压缩包下载以后,解压,可以看到其中的文件列表,但是提取文件的时候显示需要密码。

ci-unzip-password.png

词书压缩文件密码算法破解

压缩包加密码正常操作,要不然别人也太容易爬取它的词书了。别想着暴力破解压缩密码啥的,那太慢了,直接反编译apk,看代码里面怎么解压缩的。

打开jadx,然后反编译沪江开心词场的apk包。

这儿的搜索就很有技巧了,我们的目的是要找出压缩包的解压密码,可以通过压缩包下载路径搜索,但是压缩包下载路径是从接口GET /v3/user/me/book/13216/resource动态获取的,然而搜索/v3/user/me/book/没有结果,试着搜索/user/me/book/也不行,搜索user/me/book/终于有结果了。搜索的时候要多试一试,找一些区分度大的字符串搜索,这样找到目标代码的概率会比较大。

ci-jadx-search-book.png

看搜索结果,使用都是在UserBookAPI的类中,猜测这个类就是词书下载处理类。

UserBookAPI类中搜索resource很容易定位到接口处理的代码:

ci-jadx-request-book.png

其中BookResourceResultList结构很明显了,就是词书资源接口的返回结果,这个方法就是一个http request的简单实现,返回值的处理在requestCallback回调中,但是此处的类型RequestCallback是一个抽象处理类,其中没有解压缩包的具体实现,为了弄清楚requestCallback具体是什么,我们搜索这个方法的调用,看这个参数是怎么传进来的。

ci-jadx-call.png

直接搜索方法名m30808c,可以看到结果有3个,点击第一个进入可以看到创建了一个RequestCallback的匿名实现类,分析其中的实现并没有找到解压的处理。

这就是我为什么说搜索很有技巧了,上面的步骤其实是在搜索查询词书资源并处理的代码,一般认为下载完之后应该会立即解压缩,所以分析下载后的代码应该很容找到解压缩才对,然而实际上却很费劲,因为程序的解压缩可以放在异步线程里面,或者加一个观察者来实现,解压缩的代码有可能和下载的代码不在一个地方。

那么换一个思路,一般解压缩我们想到的英文单词是unzip,而且在实际开发过程中,对于解压操作容易出错,我们一般会在解压缩前后打印日志,而日志字符串是没有混淆的。那么我们不妨直接搜索unzip,很少人打印日志用中文,应为写代码是英文,如果打印日志用中文要频繁切换输入法。

ci-jadx-unzip.png

搜索结果很多,可以大概浏览一下,很多类名都没有混淆,其中词书相关的类比如BookResManager,点进去一眼就可以发现是调用UnzipProcessor的静态方法实现解压缩。看类名就知道是专门处理解压缩的。

ci-jadx-password-param.png

UnzipProcessor这个类其中解压的实现逻辑,很容易看到密码的赋值unzipModel.unzipPwd = bookRes.m40584j();语句,接下来研究bookRes.m40584j();的实现即可知道密码是怎么得到的,点进这个方法,可以看到密码的构造过程。

ci-jadx-password-build.png

其中逻辑很简单,首先判断this.f33179i是否为空,为空直接返回空字符串也就是没有密码,否则调用一个加密工具类中的方法EncodeUtils.m39475b(valueOf),在进入这个加密工具类之前,先弄清楚输入参数是什么,也就是this.f33179i的值。

ci-jadx-zip-md5.png

可以看到this.f33179i是在父类BookResource中定义,注意在其上面的注解@DatabaseField(m23752a = "zip_new_version"),这个字段应该是版本号,千万不要看错看成下面的注解@DatabaseField(m23752a = "zip_md5")!!!查询词书资源那个接口GET /v3/user/me/book/13216/resource返回值中有一个version字段,这儿还不能直接确定(虽然就是),可以继续看代码。

注意this.f33179i所在类型BookRes只有一个构造函数public BookRes(BookResource bookResource),并且this.f33179i的值是直接从输入参数参数父类成员赋值过来。

ci-jadx-book-res.png

可以搜索构造函数的调用BookRes(,也可以直接搜索这个成员变量f33179i的赋值,得到这个值是怎么取的。此处搜索这个成员变量赋值的地方。

ci-jadx-f33179i.png

搜索结果主要注意左值表达式,前面几个是BookRes的构造函数赋值,只是简单的类型转换不用理会,而其中有一个比较this.version != a.f33179i,居然和version版本号比较,点进去。

ci-jadx-version.png

可以看到a.f33179i的值就是this.version,而当前类为BookResourceResult看其中的字段就是词书资源接口返回的结果,而这个f33179i显然就是version字段的内容了,这个函数名称checkVersion更加确认了这一点。

弄清楚参数的名称以后,我们就可以看那个解密工具类的方法了。

ci-jadx-encode.png

其实现逻辑很简单,就是把输入参数str转成字节码,然后每个字节取反,再作为参数进行Base64编码。

到此,终于确定了解压密码的生成方式了,接下来写一个python快速实现这个算法:

import base64

def generate_zip_file_password(version: int) -> str:
    version = str(version).encode('UTF-8')
    not_md5 = []
    # 按位取反,注意不能直接使用 ~ ,python中的byte不能为负,此处和 0xFF 取异或,最后得到的结果是一致的
    for byte in version:
        not_md5.append(byte ^ 0xFF)
    not_md5 = bytes(not_md5)
    password = base64.standard_b64encode(not_md5)
    return password.decode("UTF-8")

generate_zip_file_password(2110131156)

注意python中byte类型不能为负,所以不能直接取反,而是和0xFF取异或,为了验证最后的结果一致,可以输入相同的参数和Java版本比对。

// java版本的密码生成函数
public String generateZipPassword(String version) {
    byte[] bArr = version.getBytes(StandardCharsets.UTF_8);
    int length = bArr.length;
    byte[] bArr2 = new byte[length];
    for (int i = 0; i < length; i++) {
        bArr2[i] = (byte) (~bArr[i]);
    }
    byte[] password = Base64.getEncoder().encode(bArr2);
    return new String(password);
}

两边运行结果是一致的。而且将生成的密码填入,能够成功解压。

ci-zip-decode.png

词书内容解密

打开其中的word.txt显然就是词书的具体内容了,而且是xml格式,python中可以用xmltodict库把xml转成dict格式,然而事情并没有结束,仔细看里面的字段,很多都是加密过的。

好家伙,压缩包有密码,里面的内容还是加密的!双重加密!很安全。

不过并不用慌,回到jadx,看看能不能找到解密方式的线索。用刚才解压缩的word.txt里面的几个字段名称搜索可以看到这些字段怎么读取和操作的。

ci-field-decode.png

很容易发现其中调用了EncodeUtils.m39477a方法,就是之前分析的那个加密工具类,其实现也很简单,刚好和压缩密码生成反过来,先把加密字符串Base64.decode,然后对每个byte取反。

保险起见,搜索一下m39477a这个方法的调用。

ci-field-decode-use.png

这些调用,不就是解密字段吗,而且这些字段不就是刚才word.txt里面的字段名称吗,基本100%肯定这个方法就是解密方法了。

用python快速实现,然后解密试一下:

import base64

def decode_book_field(encode_content: str) -> str:
    encode_content = encode_content.encode('UTF-8')
    decode_content = base64.standard_b64decode(encode_content)
    result = []
    for byte in decode_content:
        result.append(byte ^ 0xFF)
    result = bytes(result)
    print(result.decode('UTF-8'))
    return result.decode('UTF-8')

decode_book_field('HHxTHHxiHHxDHHx3HH1tGWRHHH5wHH5gHH1+HH5UpBx8eBx8Qxx9QKIcfW0WZHkcfX4cfXQcf30=')

可以顺利得到解密文本。

至此,完成了对沪江开心词场词书的破解,并且用python实现了整个加密解密算法。

补充最后爬取和解密后的json数据样例子

ci-decode-final.png

下面是爬的真题数据。

ci-final-data.png

免费评分

参与人数 17威望 +1 吾爱币 +37 热心值 +14 收起 理由
s33d + 1 鼓励转贴优秀软件安全工具和文档!
zzzdozen + 1 + 1 热心回复!
jaffa + 1 谢谢@Thanks!
暧昧乱人心1314 + 1 我很赞同!
yjn12580 + 1 + 1 我很赞同!
fengbolee + 2 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
niuyufeng + 1 + 1 谢谢@Thanks!
skiss + 1 + 1 谢谢@Thanks!
allspark + 1 + 1 用心讨论,共获提升!
debug_cat + 2 + 1 热心回复!
pdcba + 1 + 1 谢谢@Thanks!
小朋友呢 + 2 + 1 我很赞同!
twl288 + 1 谢谢@Thanks!
正己 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
VTKme + 1 + 1 我很赞同!
testpapa + 1 我很赞同!
铺路的code泥水 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

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

 楼主| uusama 发表于 2024-11-8 23:12
看到有兄弟需要里面爬的日语资料,这儿补充一下云盘链接 https://pan.baidu.com/s/1NS5ApZykRn7WoBRLXTuAaA 提取码 uuuu。有需要自取。

免费评分

参与人数 2吾爱币 +2 热心值 +2 收起 理由
fengbolee + 1 + 1 热心回复!
VTKme + 1 + 1 热心回复!

查看全部评分

怜渠客 发表于 2024-11-6 22:55
小小面团 发表于 2024-11-6 11:53
ameiz 发表于 2024-11-6 12:23
非常厉害,学习了。
cherrytop 发表于 2024-11-6 12:32
谢谢楼主分享
comotemira 发表于 2024-11-6 14:23
看完了,楼主厉害~
铺路的code泥水 发表于 2024-11-6 14:38
学习了,思路很好
woeine 发表于 2024-11-6 14:49

学习了,谢谢楼主分享
XXCAINIAO110 发表于 2024-11-6 16:52
厉害666
Lityun 发表于 2024-11-6 18:30
感谢分享
binghe01 发表于 2024-11-6 22:04
感谢师傅的分享学到了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2024-11-24 09:16

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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