本文转载自:http://bbs.byr.cn/#!article/SoftDesign/42090?p=1
当迅雷刚出现的时候,那个神奇的P2SP让很多人惊叹(当然让站长们头疼)。无论是热门的下载链接、第一次遇到的冷门地址,还是死链,它都能匹配到资源完成下载。据传它是根据文件名+文件大小来判断,佐证就是有可能会下到错误的文件。。那迅雷究竟做了些什么,其实看看hash算法就知道了。。
=================================================================
从迅雷离线获得的地址中,存在着大量的Hash值,这些hash看似都是base64,sha1,md5但却有所不同。
比如这是一个典型的离线地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
http:
/
/
gdl.lixian.vip.xunlei.com
/
download?
fid
=
3KUoDlBy9IotAFn5vQAHc5VUIgKiwSgmAAAAAHYA3duVUUeWgaJI4JGu5syK9PLK
&
/
/
base64: 与cid,size,gcid相关(size为小字节序)
mid
=
666
&
/
/
maybe: always
666
threshold
=
150
&
/
/
maybe: always
150
tid
=
A950BAE38A2E7398186D4127315DB76F
/
/
unknow:
256bit
relate with size
srcid
=
4
/
/
maybe: always
4
verno
=
1
/
/
maybe: always
1
g
=
7600DDDB9551479681A248E091AEE6CC8AF4F2CA
&
/
/
gcid:
for
normal download gcid
=
=
cid
scn
=
c7&
/
/
section
i
=
3547930B96AFA7B0A1CFCC80D516ADE97A34DAE0
&
/
/
cid
=
=
infoid
=
=
btih
=
=
ed2k
hash
, files share a same cid
in
a bt task, cid
is
the btih of the torrent
t
=
6
&
/
/
type
:
1
=
normal
4
=
ed2k
6
=
bt
ui
=
18
×××
9640
&
/
/
userid
ti
=
33742
×××
247
&
/
/
tid
from
get free url
s
=
640205218
&
/
/
totalByte
m
=
0
&
/
/
mayby: always
0
n
=
01324486025B4775690D459D7F43726F770F6CBF6F345D5B46347DA817445D5B422876D1025B783236556EA51C335D2E6D0A47E45F00000000
/
/
filename
|
根据已有的数据分析/比对,大致各个字段的含义已经标识出来了。其中除了ui,ti是与用户相关的变元,i是来源相关的变元,其他的字段对于某一个文件来说一般是相同的。
cid
算法源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def
cid_hash_file(path):
h
=
hashlib.sha1()
size
=
os.path.getsize(path)
with
open
(path,
'rb'
) as stream:
if
size <
0xF000
:
h.update(stream.read())
else
:
h.update(stream.read(
0x5000
))
stream.seek(size
/
3
)
h.update(stream.read(
0x5000
))
stream.seek(size
-
0x5000
)
h.update(stream.read(
0x5000
))
return
h.hexdigest().upper()
|
算法来自于 https://github.com/iambus/xunlei-lixian 。
在api中,cid主要用于文件的索引。观察代码可知,cid并没有hash整个文件,而是根据文件的头/中/尾部的0×5000字节的内容计算Hash。这样就可以在不下载完整个文件,就能够查询到其他服务器上可能的相同文件。于是 在下载支持range的文件的时候,即使该地址没有被索引到,但是通过cid,依旧可以被p2sp加速。
当然了,由于没有hash整个文件,文件在事实上有可能是不同的,那么根据下面这个gcid就可以唯一确定一个文件了。
gcid
1
2
3
4
5
6
7
8
9
10
11
12
|
def
gcid_hash_file(path):
h
=
hashlib.sha1()
size
=
os.path.getsize(path)
psize
=
0x40000
while
size
/
psize >
0x200
:
psize
=
psize <<
1
with
open
(path,
'rb'
) as stream:
data
=
stream.read(psize)
while
data:
h.update(hashlib.sha1(data).digest())
data
=
stream.read(psize)
return
h.hexdigest().upper()
|
这个算法借助了18万个已有文件的数据,以及迅雷咖啡吧上的一句话:“如果文件很大,则计算gcid非常耗时,因此可以在大文件传输过程中计算gcid,文件传输完毕,则gcid也计算好了“。。
gcid的作用是文件的唯一键,在迅雷服务器上唯一确定一个文件。可以说,只要有了gcid,实际上是可以任意下载到指定文件的。算法采用了分片hash再二次sha1的算法。。猜测原因是因为分片被限制在512个一下,当hash较大文件的时候,可以边下载边hash,再在最后hash那个不到512*20字节的串即可,当文件下载完成的时候就能立即得出gcid。还有一个原因是bt文件也是用sha1分片Hash的,那么获得种子文件也就同时有可能获得gcid了。同时,如果迅雷服务器保存了每个分片的sha1 hash的话,那么在下载通过cid匹配的文件同时,就能同时比较各个分片是否正确,以此保证最终结果正确。
fid
1
2
3
4
5
6
|
def
parse_fid(fid):
cid, size, gcid
=
struct.unpack(
"<20sq20s"
, fid.decode(
"base64"
))
return
cid.encode(
"hex"
).upper(), size, gcid.encode(
"hex"
).upper()
def
gen_fid(cid, size, gcid):
return
struct.pack(
"<20sq20s"
, cid.decode(
"hex"
), size, gcid.decode(
"hex"
)).encode(
"base64"
).strip()
|
首先这很明显是一个base64,但是一开始我并没有发现她们和cid,size,gcid的关系,[del]直到我膝盖中了一箭[/del]。。
fid就是cid,size,gcid的二进制然后再base64而已。但是有了fid,神马cid,size,gcid这三大要素都不是问题了。应该是用于api分析url的便利,所做的一个接口性参数。
tid
未知算法。
根据18万的文件数据,唯一能够知道的是,这个tid和文件大小一一对应。。size相同的文件tid一定相同,但是又不是size的直接hash,目前来说完全不知道这个参数的意义何在。。难道这个hash印证了迅雷根据文件大小来确定文件?
如果有兴趣,您可以在 https://github.com/binux/lixian.xunlei/blob/master/tid.dict 文件里面找到目前已知的映射。。如果分析出算法了请务必告诉我。
====
由上面的分析可见,一个文件的离线地址完全就是根据文件的信息生成的,于是这意味着什么?知道了算法,我们完全不需要通过迅雷服务器就可以生成自己的离线地址!如果这个文件在迅雷服务器上存在,我们可以直接下载回来!(等等,你说参数n。。那不就是文件名嘛。。我只关心内容。。文件名这种小问题。。)
说到做到,您可以现在就可以通过 https://github.com/binux/lixian.xunlei/blob/master/check_file.py 这个脚本直接计算出文件的cid,gcid,fid,并且检测文件在迅雷服务器上是否存在。
然后,把fake_url添加到迅雷软件里面。然后。。就可以直接下载了!可以开启高速通道,可以在快盘秒传,运气好可以开启离线秒传。。。