扇贝单词前端 JavaScript 逆向
在写扇贝单词的 Chrome 插件中,需要使用一些非公开的 API,在此记录一下遇到的波折。
插件 Github 链接:https://github.com/zerolfx/shanbay-helper
由于需要完成任意网页查词的功能(扇贝只支持自己网页内的查词),所以要先观察一下,在扇贝网页内查词时调用了什么 API。借助 Chrome 的开发者工具中的 Network,很容易定位到这个请求。这是一个 GET
请求,请求的 URL 为 https://apiv3.shanbay.com/wordsapp/words/vocab?word=reverse
(以单词 reverse 为例),返回的 JSON 信息如下。
1 | {"data": "T5SXR44XQRUOCNXHCXNQYAP22AOCMSOCCFTGYAPMNBYAP256CO4XOCNXHCXNQ4XOCCFOCORIH2AGEORXYATSTGX2AGEOPP2TT7IEZXHZ2PTGR44XQRUIHK363P2CFURYA2EYABYAIHORCPVZURJX56G32LP256CFWUVTSURHZVMHZCFWUVTSUREWZOTSIH2AURYAPMNBYAGJCFA7QCFTWVMG2XHZ2PTGX2AQIHP2SCB4XGEEWL2AP2ORCPVZURJXVZG3VZCPVZURJQEZURWURYAPMNBYAG3CFVMEZGJTTCO63P2CFGE63PEEW3OKOC2LXHCXNQEWURKZO252EJL25URHZIHTS2EKZORMOCEWXTWURRMAEWUR56VMYA2ESCG3MSOCHZZO4XURSCGEEWGENXSNXNQ7QP2TT7TRUPEVZCOVZGJVZCOU3G3TTVZORXHZ2PTGXHZOCHZGEHZZORMGEEWXRMGETT3EWOCHZXTWURJQOR2EJXTSURRMG3CPGEHZQYAGEKUVRM2EJ3ORXYATSTGXEW3VZOCKOROPCFTWVMRMCFTWVM4XPEWURYA2EYAAPEXEW7Q25P2WARM2EYA4VOPPETTVMEZGJTTLOKG3CFVMEZGJTT4VCPPMEWQ7QR4TT7TU3PMEWGEOPPE2LCO7QG3EWGE63IHWVMYACF563VZOCKOROPPMRMLMSGERMBTSURHZTGTWOCEWVMHZUR56QHZ2ESCZOTSURKUV7IGETTUR56URHZ7QEWG3RMBTSURRMZOTSPMHZ7TYAOCSCL56OCKXTWG3RMQHZGEHZXVZGERMGEEWGERM37QGEJZORMGESC7TORGEJUV56URRMUVCPPMEWVZTSUR4XXSNXNQ2PP2W7TTSIHRM2POPPM56VZOROCKOR7QPMTT3VZOCKOROPUR2L7IYAG3CFOREZPEYA7IHZPE5625OPG3TTQHZCFTWAVZG3ORCO7QP2TT7TRUPE4X4VCP2EJIHTSURJXMSGE56OCORG3RMGEYAG3RMEZ25URJAOR2ESCVMHZGEHZXMSOCEWURTSURJUR25URNMS25G3EWUVCPGEEW7TYAGE56UR4XG3RMG34XGESCIHRMOCHZIHCPG3SCZO25URRMUV25OCSCZOVZGEHZURVZUR2L7IOKIHJURYACF2LTSTGXEW3VZOCKOROPCFTWVMG2CFTWVM4XPEWURYA2EYAAPEXEW7Q25P2WARM2EYA4VOPPETTVMEZGJTTLOKG3CFVMEZGJTT4VCPPMEWQ7QR4TT7TU3PMEWGEOPPE2LCO7QG3EWGE63IHWVMYACF563VZOCKOROPPMRMUVMSURRMQYAG3RMX56URHZVMEWURJZOCPOCHZL25GEHZ37QGEKUR56URTTX4XOCSCZOCPG3EWVMEWPMEW7TORUREWVMEWOCEWG356G3SC7TYAGETT3YAGEHZXMSOCTTGEYAG3RMG3VZURJL4XURHZIHRMG3SCTGMSPMEWVZTSUR4XXSNXNQ2PP2W7TTSIHRM2POPPM56VZOROCKOR7QPMTT3VZOCKOROPUR2L7IYAG3CFOREZPEYA7IHZPE5625OPG3TTQHZCFTWAVZG3ORCO7QP2TT7TRUPE4X4VVZ2EJUR4XG3EWUR4XGEHZXVZOCHZB25URTTG3CPGEJG3CPG3SC7THZGEHZ3YAUREWUV25URTTQOROCYA7IEZOCSCQOROCEWOCEWGEEWL25G3HZVM7QG3HZG34X2EKVMHZG3EWUR56GESCBCPURHZXTWUR56LMS2EN7IOKIHJURYACFCF25SNXNQHZIHEWVM7QP2KVMEZCF56325XHZ2PTGXHZXTSURSCTGOKURJLOKURSCAUVURSCZOHCURJZOHCGESCURU32EJLMSURRMIH56IZRMBTS2EHZBTSXYATSTGX2AVMTSOCK325OCTT7T63G3CFZOYA2EYABYAURHZBCP2E2L25TSUR4X25CPGE3ZOTS2EJ2PRM2ESC2PRM2ENMSRMGEJEZ56URRMEZG2URJBHCURJBYAPMNBYAIHEWVMEWCF56OREZXHZ2PTGXEW37QG3TT37QXYATSTGX2AOCOPG3563YACFTW7T7IIHKUVYA2EYABTSPMNBYAG356COOKPETTVMU3P2NXHCXNXYA63ZO=="} |
看到大小为 32 的字符集以及最后的两个等号,就知道这是 Base32 编码了。然后用这个在线工具 https://cryptii.com/pipes/base32 (这个网站支持许多能自定义的编码方式的加解密)尝试解码,然而事情并没有那么简单,转换出来的二进制并不能显示为文本。于是随手试了试把 Base32 中的 A-Z
和 2-7
交换前后顺序以及内部颠倒,但还是不行。所以这肯定是定制的 Base32 的,是否单单修改了编码表也不能确认。
由于解码是在前端完成的,所以我决定不如定位到对应的 JS 代码来做解密。在开发者工具的 Source 页面,可以看到扇贝的大量前端源码(React + Typescript),而且是没有经过打包前的结果。在 assets.baydn.com/web_static/words_wordsweb/static/js/services/words.ts
中找到了所有需要的 API(这个文件的源码我会贴在最后),也在其中找到了一个叫做 bayDecode
的解码函数。然后在开发者工具的 Source 页面选择在所有文件中搜索这个函数名,定位到了这个函数的定义,代码如下。
1 | export const bayDecode = (str: string) => { |
可以看到有一个名叫 bays4
的全局函数做了解码的主要工作。(插一句题外话,这个函数名取得真是不错,取了 shanbay
的一部分,和 base
这个单词读音近似。)如果继续在源码中搜索 bays4
,会发现除了之前那个函数的调用,只出现在了 HTML 中的 script 标签内。打开 Console,输入 bays.d
,可以看到这个函数的定义,单击后能跳转到源码,果然就是嵌在 HTML 中的那个函数。
可惜的是,这个函数经过了混淆,可读性极差。但是我只需要能完成解码工作的函数,并不需要理解解码的具体过程,于是把那一大段 JS 复制到本地,用之前 API 请求来的加密信息测试了一下,果然可以使用了。最后我把那段代码嵌入到了插件的 JS 文件中,供请求 API 的时候调用。
补充:在 Source 中查看 JS 源码还是一件很痛苦的事情,但是想保存到本地的话,一次只能保存单个文件。但是在这个插件的帮助下,可以批量保存到本地,用 IDE 打开,无论是搜索还是跳转都方便了许多。
解码后结果
1 | { |
代码 assets.baydn.com/web_static/words_wordsweb/static/js/services/words.ts
1 | import { v3 } from 'helpers/withPrefix'; |