我对优酷和土豆播放器的研究仍然流于表面, 希望有能之士多加指导和补充.
注意: 优酷api 现在加入了跨域限制, 我自行搭建了一个反向代理来绕过, 在此不多赘述.
〇、 写在前面
之前写过一篇破解优酷播放器的文章, 但那时对优酷源代码的阅读尚不成深入, 不成体系. 这两周我把优酷播放器钻研了一遍, 在这里把流程记录一下.
按照这个方法修改播放器能做到功能与原版播放器完全一致, 而且去掉超时等待之后播放器的响应速度比原版播放器更快 (优酷播放器的超时等待为8秒, 土豆播放器的超时等待为4秒) . 修改后的播放器已经整合到OpenGG.Clean.Player 里.
附表的数据可能随播放器的升级而失效, 但本文所述的反编译播放器方法是始终有效的.
可能用到的软件:
wget http://gnuwin32.sourceforge.net/packages/wget.htm , 用于下载播放器
swftools http://www.swftools.org/download.html , 用于压缩和解压flash
Sothink SWF Decompiler 商业软件, 用于反编译flash
Adobe Flash Pro 商业软件, 用于avm2 bytecode 实验
yogda https://code.google.com/p/opengg-clean-player/downloads/detail?name=Yogda.1.0.564.zip , 用于反汇编bytecode
HxD http://mh-nexus.de/en/hxd/, 用于修改flash 文件
wamp http://www.wampserver.com/en/ , 用于搭建测试环境
Firefox https://www.mozilla.org/en-US/firefox/new/ , 用于测试
一、 下载原版播放器.
进入优酷, 随便打开一个视频, 右键点击Adblock Plus for Firefox 的图标, 选择Blockable items (可过滤项目 ), 这里会列出该页引用的所有元素. 我们选择优酷播放器(type 为Objcet ), 复制其地址 "http://static.youku.com/v1.0.0223/v/swf/player.swf" . 土豆同理.
用wget 等工具下载这个播放器到硬盘里:
wget "http://static.youku.com/v1.0.0273/v/swf/player.swf"
wget "http://js.tudouui.com/bin/player_online/TudouVideoPlayer_Homer_NewSkin_30.swf"
复制代码
二、 解压swf文件.
似乎是从flash 6 开始, swf 文件都可以用zlib 压缩了, 我们用swftools 自带的 swfcombine 解压出来, 以方便修改. 详见swftools faq .
swfcombine -d player.swf -o player.fws.swf
swfcombine -d TudouVideoPlayer_Homer_234.swf -o TudouVideoPlayer_Homer_234.fws.swf
复制代码
三、 反编译优酷播放器, 得到源代码.
这里需要用到商业版的Sothink SWF Decompiler , 下载地址就不提供了.
用Sothink SWF Decompiler 打开player.fws.swf, 在右侧export 栏中就能看到播放器内的图片等元素, 还能看到反编译出来的源代码. 我们只关心源代码, 于是去掉Shape 和 Image 等其他选项, 只选择Action, 导出到一个文件夹里.
四、 阅读源代码.
导出的源代码非常完整, 可以看到播放器的用户数据收集和广告获取等模块如何工作, 进而可以得出对策.
分析与修改的主要策略有二:
自顶向下, 直接在逻辑里去掉对模块或函数的调用;
自下而上, 针对具体的网络行为, 以url 的参数为关键字查找, 去掉函数内对应的语句.
自顶向下的修改, 能把功能删得比较干净, 但一个函数往往会被调用很多次, 可能需要改很多地方. 自下而上的修改则比较直接而方便. 具体采用哪种策略, 可以根据工作量来权衡, 尽量选择修改最少的方法. 对优酷播放器主要用前一种策略, 对土豆播放器主要用后一种策略.
在此我就不详细展开了, 以下直接给出结论.
优酷播放器:
ADFacade 不能直接去除, 否则会使播放器工作不正常, 只能修改com.youku.plugins.ads.model 下各类的行为, 使之不加载广告.
FrontMediator.showADHolder() 函数与黑屏有关.
com.youku.plugins.ads.model.ADProxy 是播放器广告最核心的部分, FrontProxy 等子类通过ADProxy.loadData() 加载广告和事件绑定;
修改 ADProxy.loadData() 函数后, 播放前会等待8秒, 需要修改 FrontProxy.init(), 去除超时等待;
清空ReportFacade.init() 函数, 可以直接使用户数据收集模块不工作.
PlayerData.getJumpURL()函数的返回值即外链播放器的弹出地址, 可以返回一个无统计的地址, 避免用户数据被优酷服务器收集.
土豆播放器:
AdLoader.load() 函数调用AdLoader.loadAdInfo() 加载广告;
AdTracker.adExpect() 函数中的sendToURL(_loc_5) 发送用户数据;
PlayerEventTracker.sendStartEventTrack() 函数发送用户数据;
GlobalTracker.sendLog() 函数中的sendToURL(_loc_4) 发送用户数据;
ErrorTracker.send() 函数中的sendToURL(_loc_7) 发送用户数据;
ErrorReportTracker.send() 函数中的sendToURL(_loc_6) 发送用户数据;
AdCommand.execute() 函数中的 _loc_3.delayTime = PlayerConsts.AD_TIMEOUT_TIME * 1000; 会在播放前等待4 秒.
PageInfo.collect() 函数中的 _expandStage = pageInfo["scale"] == "1" 控制播放器的宽屏, 把它改为_expandStage = true 即可默认宽屏.
五、 修改播放器.
但是我们不能直接用Sothink SWF Decompiler 反编译出来的代码进行编译, 只能通过hex 编辑器修改player.fws.swf 文件来修改播放器的行为. 修改的原理是, 先分析出源代码编译出来的指令, 在hex 编辑器里查找这些指令, 修改它们, 从而修改播放器的行为.
在这里我们用到两个免费的软件: avm2动态分析工具yogda 和hex编辑器HxD.
然后为了进行在线调试, 需要把播放器放到web 服务器的www 目录里, 我使用的是wamp, 安装在D盘 .
先把player.fws.swf 复制到D:\wamp\www\ 命名为player.new.swf ;
运行wamp, 在Firefox 里打开http://localhost/player.new.swf?VideoIDS=XMjA0Mjg4Nzk2 ,打开Firebug 的net tab和 Adblock Plus 的Blockable items , 方便我们观察播放器的网络行为;
用yogda 打开player.new.swf 查找需要修改的函数名, 记录下需要修改的语句对应的hex 指令;
用HxD 修改player.new.swf ,把需要修改的指令改掉, 或者直接用02 抹掉(02 对应的行为是 Do nothing, 详见http://learn.adobe.com/wiki/display/AVM2/nop ), 注意修改前后要保持指令等长, 不要影响文件的大小. 改好后 ctrl+s 保存修改;
有的指令在整个swf 中出现很多次, 为了找准修改点, 可以在同一个函数里选择一些比较罕见的指令作为锚点 (如对某些罕见字符串的调用), 在锚点附近查找目标指令.
每修改一次都在firefox 里刷新看有无效果, 是否影响功能, 如果无效就到D:\wamp\www\ 把player.new.swf 删掉, 把player.new.swf.bak 重命名为player.new.swf , 回滚到修改前的状态, 另寻思路进行修改.
修改语句的过程中, 可能需要拼凑指令, 此时可以用Adobe Flash Pro 写一个小的Hello World, 看编译的结果, 也可以直接在yogda 里修改汇编代码, 记录修改后的对应的指令, 但不要直接使用yogda的保存功能. 我们只用yogda 看汇编, 只用HxD 改文件.
六、 压缩swf 文件.
现在我们可以用swfcombine 把改好的swf 压缩, 节省体积.
swfcombine -dz player.new.swf -o player.pack.swf
swfcombine -dz TudouVideoPlayer_Homer_234.new.swf -o TudouVideoPlayer_Homer_234.pack.swf
复制代码
附、 修改优酷和土豆播放器的指令对照表
优酷 player.swf :
修 改 FrontMediator.showADHolder() 函数, 把if (this._playerProxy.playerData.videoTotalTime < 60) 改成 if (this._playerProxy.playerData.videoTotalTime >= 0):
用HxD 查找 D0 66 D3 0C 66 A5 02 66 E1 04 24 3C 0c 42 00 00 , 替换为 D0 66 D3 0C 66 A5 02 66 E1 04 24 00 0F 42 00 00.
修改ADProxy.loadData() 函数, 去除 _loc_5.load(new URLRequest(param2 + _loc_8)) 语句:
用HxD 查找 62 05 5D 3F D2 62 08 A0 4A 3F 01 4F AD 0B 01 , 右键选择"填充选择内容", 用02填充.
修改FrontProxy.init() 函数, 把var _loc_11:* = setTimeout(this.loadDataTimeOut, 8000) 里的8000 改成0:
用HxD 查找 25 C0 3E (即short int 8000 ), 改成 25 00 02 (25 00 即为short int 0, 02 为do nothing ).
清空ReportFacade.init() 函数:
用HxD 查找 5D 9C 10 D1 D2 4A 9C 10 02 80 9C 10 D7 5D F1 06 D3 4F F1 06 01 5D 77 D2 4A 77 01 80 77 63 04 5D DF 06 62 04 4F DF 06 01 60 B8 02 60 C8 02 66 D3 25 62 04 4F B3 25 02 , 右键选择"填充选择内容", 用02填充.
用HxD 查找 5D 9F 10 D1 D2 4A 9F 10 02 80 9F 10 D7 5D F1 06 D3 4F F1 06 01 5D 77 D2 4A 77 01 80 77 63 04 5D DF 06 62 04 4F DF 06 01 60 B8 02 60 C8 02 66 D8 25 62 04 4F B8 25 02 , 右键选择"填充选择内容", 用02填充.
修改PlayerData.getJumpURL()的返回值:
用HxD 查找 2C F6 2A D2 A0 A0 85 D5 D1 48, 替换为 2C F6 2A D2 A0 A0 85 D5 D2 48 .
用HxD 查找 2C 81 2B D2 A0 A0 85 D5 D1 48, 替换为 2C 81 2B D2 A0 A0 85 D5 D2 48 .
土豆 TudouVideoPlayer_Homer_234.swf :
修改AdLoader.load() 函数, 删除 loadAdInfo():
用HxD 查找 5D F5 01 4F F5 01 00 , 右键选择"填充选择内容", 用02填充.
修改AdTracker.adExpect() 函数, 删除 sendToURL(_loc_5):
用HxD 查找 61 BD 20 5D CC 21 62 05 4F CC 21 01 , 替换为 61 BD 20 02 02 02 02 02 02 02 02 02 .
清空PlayerEventTracker.sendStartEventTrack() 函数:
用HxD 查找 5D 8F 06 D1 4F 8F 06 01 60 D8 08 60 92 06 4F 83 1A 01 60 D7 08 60 92 06 60 92 06 66 DF 03 4F 83 1A 02 5D 8C 06 4F 8C 06 00 60 D2 08 60 92 06 4F 83 1A 01 5D 8D 06 4F 8D 06 00 , 右键选择"填充选择内容", 用02填充.
修改 GlobalTracker.sendLog() 函数 , 删除sendToURL(_loc_4):
用HxD 查找 61 BD 20 5D CC 21 62 04 4F CC 21 01 , 替换为 61 BD 20 02 02 02 02 02 02 02 02 02 .
修改ErrorTracker.send() 函数, 删除sendToURL(_loc_7) :
用HxD 查找 61 F2 05 5D CC 21 62 07 4F CC 21 01 , 替换为 61 F2 05 02 02 02 02 02 02 02 02 02 .
修改ErrorReportTracker.send() 函数, 删除sendToURL(_loc_6) :
用HxD 查找 62 05 4A 54 01 80 54 63 06 5D CC 21 62 06 4F CC 21 01 , 替换为 62 05 4A 54 01 80 54 63 06 02 02 02 02 02 02 02 02 02 .
修改 AdCommand.execute() 函数 , 把 _loc_3.delayTime = PlayerConsts.AD_TIMEOUT_TIME * 1000; 里的1000 改成0 :
用HxD 查找 D3 60 89 06 66 DA 19 25 E8 07 , 替换为 D3 60 89 06 66 DA 19 25 00 02 .
修改 PageInfo.collect() 函数 , 把 _expandStage = pageInfo["scale"] == "1"; 改成_expandStage = true :
用HxD 查找 60 FB 03 2C 91 0F 66 F0 20 2C 9B 19 AB , 替换为 26 02 02 02 02 02 02 02 02 02 02 02 02 (26 即为 true) .
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)