spacemeowx2 / DouyuHTML5Player

替换斗鱼的Flash弹幕播放器
MIT License
600 stars 98 forks source link

New API #28

Closed spacemeowx2 closed 7 years ago

spacemeowx2 commented 7 years ago

cc.dy.view.load.LoadMediator -> sample.xx.F_sub_13 -> F__Z8sub_C5E3P16DanmakuStructTagiPPcS2

F__Z8sub_C5E3P16DanmakuStructTagiPPcS2 主要调用了以下函数(按顺序)

F_func_2476e15e57ddb2b4 * 2

F_AES_decrypt

F_snprintf

F_strlen

F__Z21Func_86C8982CCCC5E7A0jPKciPc

F_strlen

rosynirvana commented 7 years ago

func_2476e15e57ddb2b4和 上一个版本的func_173e8124cdbdc90d应该是类似的? 在sub_C5E3(DanmakuStructTag*, int, char**, char**)里调用时把key固定成了 a1 1c f6 17 58 34 48 37 24 25

求问有没有动态分析的方法?

静态分析:

str2 = L__ZZ8sub_C5E3P16DanmakuStructTagiPPcS2_E3C_2E_2
str3 = L__ZZ8sub_C5E3P16DanmakuStructTagiPPcS2_E3C_2E_3
func_2476e15e57ddb2b4(key, str2, 32)
func_2476e15e57ddb2b4(key, str3, 32)
AES_decrypt(rid, ebp-33, ebp-66, ebp-166)
//ebp-33: modified str2?
//ebp-66: modified str3?
//ebp-166: size 100 buf, output ptr?
snprintf(v6, 499, "%d%s%lld%s%s", rid, did, tt, "1000", aes_output)
Func_86C8982CCCC5E7A0(rid, v6, len(v6), buf)

md5的K表还是像之前一样ff hh +1 gg ii -1 不知道正确率如何

新东西估计主要在这个EncodeFlashMain::EncodeDataC cpp的,有个全局的初始化,生成80+1字节的bss seg,然后再根据rid的值用一组4 bytes bss和一个_EncodeFlashMain::DecodeFlashMain_0[0-4][0-9](unsigned char*, int, IDataCache*)函数去算最后的encode,这种函数有50个,反向出来的代码量该有多少……

ptr2fun mapping:

modFunStart:int = CModule.allocFunPtrs(modPkgName,416,4);
_EncodeFlashMain::InitialCache() modFunStart + 0
__GLOBAL__I_EncodeFlashMain::InitialCache():int modFunStart + 4
_EncodeFlashMain::EncodeFlashMain_000(unsigned char*, int, IDataCache*):int modeFunStart + 8
_EncodeFlashMain::DecodeFlashMain_000(unsigned char*, int, IDataCache*)  modFunStart + 12
_EncodeFlashMain::EncodeFlashMain_001(unsigned char*, int, IDataCache*):int modFunStart + 16
_EncodeFlashMain::DecodeFlashMain_001(unsigned char*, int, IDataCache*)  modFunStart + 20
...
_EncodeFlashMain::EncodeFlashMain_049(unsigned char*, int, IDataCache*):int modFunStart + 400
_EncodeFlashMain::DecodeFlashMain_049(unsigned char*, int, IDataCache*)  modFunStart + 404
_EncodeFlashMain::DecodeDataC(unsigned char, unsigned char*, unsigned short):int modFunStart + 408
_EncodeFlashMain::EncodeDataC(unsigned char, unsigned char*, unsigned short):int modFunStart + 412
spacemeowx2 commented 7 years ago

@rosynirvana 可以写个 swf 加载斗鱼的 swf 并调用里面的方法

https://gist.github.com/spacemeowx2/f17dba507dd06bfd5529c5a065c0e246

spacemeowx2 commented 7 years ago

swf dumped from official player: https://drive.google.com/open?id=0B-f8qsx6cB7bSmxLNVpsSzcwdjA

spacemeowx2 commented 7 years ago

似乎逆向是挺难的, 我还是去把avm2补完吧

rosynirvana commented 7 years ago

能用shumway吗?

spacemeowx2 commented 7 years ago

shumway 不支持 li8,li32 之类的 undocumented 指令. 我写的avm2也有参考shumway, 但是没有实现 Flash 的大部分库, 主要是为了执行 flascc (CrossBridge) 生成的 swf

lki2019 commented 7 years ago

@spacemeowx2 @rosynirvana

链接: https://pan.baidu.com/s/1o8p1lLC 密码: 5y7e

有个录像软件的作者更新了,

1、解决斗鱼平台解析失效问题,暂时切换为标清线路下载

可以正常录制标清,,免费版限制多 只能录制一个直播间

大神们研究一下他用的什么接口吧

qiudaomao commented 7 years ago

@lki2019 只拿标清的话,用h5的接口就够了,目前是可以用的

curl 'https://m.douyu.com/html5/live?roomId=2532616' -H "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.38 (KHTML, like Gecko) Version/10.0 Mobile/14A300 Safari/602.1"

{"error":0,"data":{"hls_url":"http:\/\/hls3.douyucdn.cn\/live\/2532616r63nY0kyr_550\/playlist.m3u8?wsSecret=4d14ce4d3440607c3f1c6f2e4cf4aee0&wsTime=1500354950&did=&ver="}}

lki2019 commented 7 years ago

@fuzhuo 恩·先标清录制凑乎下吧,

fozzysec commented 7 years ago

@lki2019 我这里试的从m.douyu.com这个api拿的hls 480p的标清只能播放5分钟左右就不行了。

smovie commented 7 years ago

这个就是试看的,目的是要你下app

amonker0 commented 7 years ago

@rosynirvana 能不能提供一下反编译的代码,大家一起读,要快点

rosynirvana commented 7 years ago

检查了EncodeFlashMain_000和EncodeFlashMain_001 符合这样一种pattern

for s_index in range(len(input_str)):
    last = input_str[s_index]
    for index in range(magic_list):
        ret = ptr2fun[load32(cache+20)](cache, magic_list[index])
        last = f[index](last, ret)
        input_str[s_index] = last

用自然语言来解释

其中cache, input_str以及input_str的长度是整个EncodeFlashMain_0/[0-4][0-9]/的参数 magic_list是每个函数独有的,对于000来说是[29, 18, 20, 9, 8, 18],对于001来说是[10, 8, 9, 2]。 f是一组函数,必须两个参数,每次都是用上一轮迭代的值,和ptr2fun函数的返回值进行运算。运算有简单有复杂,对于000来说,函数是

lambda x, y: x ^ y
lambda x, y: x + y
lambda last, ret: ((last - ret) << 2) | 1
lambda last, ret: last - ret
lambda last, ret: (last - ret << 4) | (last - ret & 12)
lambda x, y: x ^ y

对于001来说f和magic_list当然一样长,是4,前两个是xor,后两个复杂一点 magic_list是可以用正则直接从拿到的as3里面抓的:

re.findall(r'si32\((\d+),[^\)+]\), as3_text)

顺带,这一组50个函数的选择取决于room_id,room_id % 50应该就是选择的函数,cache的选择有个变量loc7来决定,loc7是(room_id % 50) << 24 >> 24。具体的规则可参考F__ZN15EncodeFlashMain11EncodeDataCEhPht,ffdec处理50个以上的branch生成的代码乱嵌套,所以cache的选择不容易看出来需要点功夫

但是f没有简单的方法可以自动处理 剩下的工作,只要搞清楚crossbridge vm,明白这个ptr2fun是怎么映射的,然后把80字节的cache提出出来,然后就是手动整理每个函数的f了 :P 注意ffdec生成稍复杂一点的as3可能有错,每个函数记得写unit test :PP

对于后继的挑战者,个人建议从libdanmu.so入手,安卓上的动态分析手段发展了这几年,还是很强大的,静态还有ida pro可以用,大概会比分析flash简单一些

rosynirvana commented 7 years ago

@amonker0

装一个ffdec,从上面google drive的地址下载swf,就能看到源码了 一共10k个as3文件和205个defineBinary tag

Justsoos commented 7 years ago

斗鱼今年狠抓流量成本,大主播被要求不允许开高码率(55开直播亲自承认),即便牺牲用户体验。 看来斗鱼一个月3亿元带宽费,快要烧死了。 不得已开始从站方角度认为的“盗播”开始下手了。 因此估计,未来除了swf调用一劳永逸,恐怕还真对付不了斗鱼的疯狂折腾。

spacemeowx2 commented 7 years ago

ffdec 反编译时推荐在设置里打开以下几个选项: 显示初始化脚本, 显示方法ID, 显示$$dup说明

amonker0 commented 7 years ago

@spacemeowx2 能不能把swf上传国内网盘

lki2019 commented 7 years ago

@amonker0

链接: https://pan.baidu.com/s/1eSnF9JW 密码: jgwr

我下载下来传上去了

rosynirvana commented 7 years ago

windows版的ffdec可以从进程里面扫到那个swf 大小是4MiB+的就对了

amonker0 commented 7 years ago

@lki2019 谢谢

amonker0 commented 7 years ago

@rosynirvana 谢谢了 但我用linux,我总是找不到

rosynirvana commented 7 years ago

斗鱼给大门上全套安保,后门居然是虚掩着的……

访问一次hls playlist可以重建segments url 比如拿到了一个playlist url

 http://hls3.douyucdn.cn/live/3484r6QnwW1QEoxu_550/playlist.m3u8?wsSecret=94e031aab878337be7eb14232e5158fa&wsTime=1500466909&did=&ver=
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:NO
#EXT-X-TARGETDURATION:3
#EXT-X-MEDIA-SEQUENCE:2379

#EXTINF:2.960
508933.ts?wsApp=HLS&wsMonitor=-1
#EXTINF:2.968
508934.ts?wsApp=HLS&wsMonitor=-1
#EXTINF:2.976
508935.ts?wsApp=HLS&wsMonitor=-1

根据HLS协议重建第一个seg的url

http://hls3.douyucdn.cn/live/3484r6QnwW1QEoxu_550/508933.ts?wsApp=HLS&wsMonitor=-1

_550可以去掉,拿到原画;改成_900是中间画质 508933这种seg_id每次+1 但seg_id+1可能不是严格的 如果seg_id无效,返回现在最新的segment

只要处理好不让seg_id超前就是一种解决方案了

rosynirvana commented 7 years ago

初始化cache实际做的是构建了20个obj,其中8个是CDataList,12个是CDataMap F__GLOBAL__I__ZN15EncodeFlashMain12InitialCacheEv demangle后是F__GLOBAL__I_EncodeFlashMain::InitialCache

分配第一块cache的相关代码

si32(16,_loc2_);
ESP = _loc2_;
F__Znwj(); //demangled: new
_loc2_ = int(_loc2_ + 16);
_loc3_ = int(eax); // v3 = new(16)
 si32(_loc3_,_loc1_ - 136);
_loc4_ = li32(_loc1_ - 136);
//……
si32(_loc4_,_loc2_);
ESP = _loc2_;
F__ZN9CListDataC1Ev(); // F_CListData::CListData(v4)
_loc3_ = li32(_loc1_ - 136);
si32(_loc3_,__ZN15EncodeFlashMainL9g_pCache0E);

然后代码里面搜Map,可以知道cache 2, 4, 5, 9, 10, 12, 13, 14, 15, 16, 17, 18是CDataMap,其余是CDataList

剩下的是需要明白ptr2fun[load32(cache+20)](cache, magic_list[index])是在干什么,这要参考CDataList的ctor:

var _loc2_:* = int(ESP);
_loc1_ = _loc2_;
_loc3_ = __ZTV9CListData + 8; //vtable + 8
_loc4_ = li32(_loc1_);
si32(_loc3_,_loc4_);
_loc3_ = _loc4_ + 4;
si32(_loc3_,_loc4_ + 4);
si32(_loc3_,_loc4_ + 8);
si32(305833592,_loc4_ + 12);

obj的第一个slot里是vtable+8, 再加20就是它的第8个成员函数,把ffdec的desplay script inits打开能看到注册的第8个函数是__ZNK9CListData7GetDataEi ,也就是_CListData::GetData(int) const

说以说那个函数调用其实是 AClistDataObj.GetData(magic_num) 仔细看CListData还有两个成员是两个方向的指针,初始化的时候指向自身。向里面添加数据的时候会计算RSHash,其中305833592是初始化的magic

到这里静态分析的工作就真的完成了,CDataMap只比CDataList稍微复杂一点 可能有很多错误,比如ptr2fun是虚函表里面的哪个方法,如果是这个GetData(int),因为里面好像没数据会一直返回0(也可能是哪里填充过数据但是我没看到)

spacemeowx2 commented 7 years ago

可以通过编辑 P-code 插入如下代码执行 Test.as 中的 callback, 从而可以获取当前函数的参数和结果(函数头尾各插一个

getlex Qname(PackageNamespace(""),"test")
getlex Qname(PackageNamespace("sample.xx"),"ESP")
convert_i
callpropvoid Qname(PackageNamespace(""),"callback") 1

参数为当前 ESP

amonker0 commented 7 years ago

@spacemeowx2 有没有办法直接调用这样swf算出地址

rosynirvana commented 7 years ago

作者上面发的那个gist改一改就可以的

Justsoos commented 7 years ago

https://github.com/rosynirvana/douyu_cli/issues/4 利用本地支持 flash 的浏览器可以取出 api 和加密数据,参考此贴。目前都可用。相当于调用斗鱼 swf 算出地址

ERioK commented 7 years ago

如果swf不太好逆,可以试试android https://gist.github.com/ERioK/d73f76dbb0334618ff905f1bf3363401

rosynirvana commented 7 years ago

居然还有用最早签名的api的

ERioK commented 7 years ago

目测我发了这个后,这个api活不了多久了,这是hd版的api,普通版已经换了新版api了,且用且珍惜

rosynirvana commented 7 years ago

https://gist.github.com/rosynirvana/120d619cd3f0ca86fedc01f54999a03d

斗鱼的播放器命名为movie.swf,一个rid.txt里面写room id,都放在同一级路径下面

Justsoos commented 7 years ago

@ERioK 不至于,要安卓HD客户端强制升级,斗鱼不敢作。这个 api 看来能用个起码一年。

lki2019 commented 7 years ago

@Justsoos 是啊,我也试了,标清画质了,看来斗鱼程序员也在关注大神们讨论啊 封的真快!

Justsoos commented 7 years ago

@lki2019 我还在测试,情况不一定。不过现在有安卓 api 了,也没什么动力做这个了。

fozzysec commented 7 years ago

但是安卓的app都挺乱的阿一般都有hotfix热补丁,就怕过两天安卓这个也挂了

rosynirvana commented 7 years ago

发现这个安卓HD的api在房间没东西的时候依然会返回url 应该检查data -> online 一项 或者用html5的api检查是不是在线

fozzysec commented 7 years ago

@rosynirvana JSON最后有show_status 1是在线,2是离线

"rtmp_url": "http://hdl3.douyucdn.cn/live",
        "show_details": "\u76f4\u64ad\u901a\u77e5\u7fa4\uff08\u5168\u5458\u7981\u8a00\uff09\uff1a549194877\n\u8c46\u9c7c\u8bad\u7ec3\u8425\uff08\u804a\u5929\u7fa4\uff09\uff1a651127067\n\u7f51\u6613\u4e91\uff1a\u8c46\u513fcoral\n\u7f51\u6613\u4e91\u7535\u53f0\uff1a\u5927\u5200\u7d20\u8c46\u306e\u5408\u96c6\nyy\u5e38\u5e74\uff1a12960326\n\u7136\u540e\u4e0d\u77e5\u9053\u8981\u5199\u5565\u4e86",
        "show_status": "1",
        "show_time": "1500638778",
        "specific_catalog": "nado",
        "specific_status": "1",
        "url": "/nado",
        "use_p2p": "0",
        "vertical_src": "https://rpic.douyucdn.cn/a1707/21/23/2020877_170721235923.jpg",
        "vod_quality": "0"
    },
    "error": 0
}
TaoziDB commented 7 years ago

请问大神们有可用的登录和关注的API吗?

Justsoos commented 7 years ago

斗鱼前端今天把大多数房间页面内 re.search(r'"room_id":(\d+)', page_content) 改成了: re.search(r'data-onlineid=(\d+)', page_content) 看来又要开始新的折腾了。

steven7851 commented 7 years ago

斗鱼的公开API还是能用的,都支持返回json http://open.douyucdn.cn/api/RoomApi/room/nado http://open.douyucdn.cn/api/RoomApi/room/lpl http://open.douyucdn.cn/api/RoomApi/room/67373 json里都有"room_id""room_status",很够用了 检查"room_status":"1"代表在线,2为离线 在线则把room_id再用android-hd的API返回stream url

Justsoos commented 7 years ago

@steven7851 但斗鱼open api 应付不了活动页面,或者说,有bug,比如 https://www.douyu.com/lpl 这个页面里暗藏了两个房间,用 http://open.douyucdn.cn/api/RoomApi/room/lpl 只能查出第一个房间号 "288016" 还有一个 "424559" 就丢了。

也就是说这个 open api 可以用唯一数字 room id来查 room status 是否在线,但不能用房间的 url 来查 room id。目前 room id 还是只能从房间web page的源码里取,这个最稳。

spacemeowx2 commented 7 years ago

暂时Close, 其他讨论还可以继续.

fozzysec commented 7 years ago

android这个API目前遇到了问题,但是问题不大,就是当房间人数低于2000时这个API不管用,当人数高于2000时才能正常使用,如果原本房间人数高于2000,下降到2000以后还正常,如果主播关了一下直播之后再开直播就不行了 https://github.com/streamlink/streamlink/issues/1151 但是我测的几个房间还没发现这个问题,再等等看有具体的url的话我再测一下

update 搞定了,只是小于2000的没有middle和middle2在multi_streams里面

xhanglizhu commented 7 years ago

看不懂的讨论,能说下整个解决方案吗? Linux上用网页非常卡。 使用streamlink,douyu直接403. 使用mpv 有卡顿,fozzy.co/douyu/playlist.m3u8?room= 有卡顿。 本机部署了一个playlist.m3u8 ,只能看几秒,--autosync=30 --cache=no ,就只固定看几秒,无法继续播放。 playlist.m3u8 code: `#EXTM3U

EXT-X-VERSION:3

EXT-X-ALLOW-CACHE:NO

EXT-X-TARGETDURATION:3

EXT-X-MEDIA-SEQUENCE:1239

EXTINF:2.980

http://hlsa.douyucdn.cn/live/300401r797eBtkc5/1428483.ts?wsApp=HLS&wsMonitor=-1

EXTINF:2.999

http://hlsa.douyucdn.cn/live/300401r797eBtkc5/1428484.ts?wsApp=HLS&wsMonitor=-1

EXTINF:2.994

http://hlsa.douyucdn.cn/live/300401r797eBtkc5/1428485.ts?wsApp=HLS&wsMonitor=-1 `

rosynirvana commented 7 years ago

讨论关注点是绕开斗鱼的播放器直接用它的api层 卡顿应该首先切CDN linux distro还要关注网卡驱动之类的

fozzysec commented 7 years ago

@xhanglizhu 现在的安卓api可以用,上面那个m3u8的只是之前临时的方案,而且已经被修复了 只有标清画质 streamlink可以直接看了,但是要更新到最新

xhanglizhu commented 7 years ago

@fozzysec 最新版本 0.7.0 直接提示403。 命令:streamlink www.douyu.com/453751 结果: [cli][info] Found matching plugin douyutv for URL www.douyu.com/453751 error: Unable to open URL: https://www.douyu.com/lapi/live/getPlay/453751 (403 Client Error: Forbidden for url: https://www.douyu.com/lapi/live/getPlay/453751)

steven7851 commented 7 years ago

@xhanglizhu streamlink 的最新版本还没 release,可以先自己下载来替换 https://raw.githubusercontent.com/streamlink/streamlink/master/src/streamlink/plugins/douyutv.py

xhanglizhu commented 7 years ago

刚在Windows上替换douyutv.py 测试了下了,可以使用了 。 感谢@steven7851 感谢@fozzysec

spacemeowx2 commented 7 years ago

FlashEmu现在可以运行斗鱼的签名算法了, 然而速度还比较慢(一开始完成的时候是60s从加载到算出, 现在优化到6-7s), 还需要优化一阵子...