[原创]某商城的电子书加密分析
当初某商城的电子书不提供下载,只能在线阅读。而阅读器的界面实在反人类,因此有了分析的想法。
这方面已经有一篇前人的经验总结。虽然因为时间久远已经失效,但其中的原理是相通的。
首先用chrome查看网络通信,发现每次翻页都会产生一个请求,显然这是每一页的内容。根据前文所述,这是一个带文件头的加密swf,但这次的文件头与文中说的不一样,应该是改了算法。
继续按文中的思路,反编译reader.swf,不出所料在application.view.DupLoader发现了感兴趣的东西。画成示意图如下。

其中唯一的难点是最开始的密文怎么得到。脚本中是一个名为hexKey的变量,逐步追踪,线索指向application.view.reader,最后才弄明白这是名为AMF的flash通信协议,可调用服务器端的函数。于是用pyamf模拟,发现返回值是固定的,后面就偷懒直接用了。
整体而言,这套加密方法还是比较清晰的。主要原因是正文的内容实在太多,没法实时加密,只能事先加密之后存储,这样就只能在文件头上想办法,于是就有了上面迭床架屋式的加密。受限于flash的处理能力又没办法做得很复杂,于是就变成现在这样了。
分析完毕用python写个脚本测试。发现前20页(商城中可以免费试读的部分)正常,后面的取不到,怀疑是没加cookie认证失败造成的。由于python的cookiejar与chrome的格式不太兼容,又重新换回wget,就是最终的程序了。
取到swf之后用swf2video批量转换成png,再用freepic2pdf合并成pdf,最后用briss切边,就可以放进eink里看了。不足之处是图片格式的pdf体积较大,可以再用其他软件优化。
最后放出python脚本的代码,供研究讨论
#!/usr/bin/python
#USE AT YOUR OWN RISK
from pyamf.remoting.client import RemotingService
import logging
from Crypto.Cipher import AES
import base64
from Crypto.Cipher import ARC4
import sys
import subprocess
import os
BS=16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]
if(len(sys.argv) < 5):
print "Usage: dangdown.py bookID startPages endPages bookCookieFile"
sys.exit()
bookID = sys.argv[1]
startPages = int(sys.argv[2])
endPages = int(sys.argv[3])
bookCookieFile = sys.argv[4]
def gethexkey():
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(levelname)-5.5s [%(name)s] %(message)s')
agent="Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.32 Safari/537.36"
client = RemotingService('http://e.dangdang.com/datasvr/messagebroker/amf', logger=logging, user_agent=agent)
client.addHTTPHeader("Origin", "http://e.dangdang.com")
client.addHTTPHeader("Accept-Encoding","gzip,deflate,sdch")
client.addHTTPHeader("Host","e.dangdang.com")
client.addHTTPHeader("Accept-Language","zh-CN,zh;q=0.8")
client.addHTTPHeader("Content-Type","application/x-amf;charset=UTF-8")
client.addHTTPHeader("Accept","*/*")
client.addHTTPHeader("Referer","http://e.dangdang.com/html/reader.swf?v=121130/[[DYNAMIC]]/12")
client.addHTTPHeader("Cookie","JSESSIONID=4B6CEC0EF9531C926A2BFFFC2716D714")
service = client.getService('ebookManager')
return service.getHexKey()
def decryptSwf(pageno):
filehandle = open('%d.swf'%(pageno), "rb")
status=filehandle.read(1)
status=int(ord(status))
if(status != 3):
print "error in %d, status %d"%(pageno, status)
sys.exit()
hexKeyHex="4723e225d1e3924f88ec9429600e4e9ad4b93608b8c78fcd9928dfb9ec7279dfd503f590a12e969c4a868daf36b838d8"
readerKeyHex="843c3748ad8722b387100a629f4a0f61"
readerKey=readerKeyHex.decode("hex")
hexKey=hexKeyHex.decode("hex")
aescipher = AES.new(readerKey,AES.MODE_ECB)
serverKeyHex = aescipher.decrypt(hexKey)
serverKeyHex = unpad(serverKeyHex)
serverKey=serverKeyHex.decode("hex")
swfHeaderHex=filehandle.read(64)
swfHeader=swfHeaderHex.decode("hex")
aescipher = AES.new(serverKey, AES.MODE_ECB)
swfKeyHex=aescipher.decrypt(swfHeader)
finalKey=base64.b64decode(swfKeyHex)
rccipher = ARC4.new(finalKey)
buf = filehandle.read()
buf = rccipher.decrypt(buf)
filehandle.close()
os.remove('%d.swf'%(pageno))
fswf = open("page%04d.swf"%(i) , "wb")
fswf.write(buf)
fswf.close()
return
for i in range(startPages,endPages+1):
strURL="wget -q -O %d.swf --load-cookies %s --user-agent='Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.32 Safari/537.36' --header='Host: e.dangdang.com' --header='Referer: http://e.dangdang.com/html/reader.swf?v=121130/[[DYNAMIC]]/12' http://e.dangdang.com/datasvr/permissionAuth.do?bookUid=%s\&pageNo=%d"%(i, bookCookieFile, bookID, i)
subprocess.check_call(strURL, shell=True)
decryptSwf(i)
print i
