2011/06/29

pydbgでNHK語学番組をダウンロード

NHK語学講座のweb配信されてるラジオがあっていつもお世話になってるんですが、
ダウンロードする人が増えてNHK怒ったのかどうか知りませんが、2011年度から仕様が変更になって、今までの方法だとダウンロード出来なくなりました。

もうみんなとっくに対応してるんで今更なんですけど、ふと思いついたので、自分流に改造して自動でダウンロード出来るようにしたNHK Stream Downloaderのpydbgバージョンを公開したいと思います。



以前は、NHK語学講座ダウンロード @ ウィキってとこにあるように、
講座ごとにあるXMLファイルに今週のストリーミングのURLの末尾が書いてあって、
例えば基礎英語1なら
にあるmusicのfile属性の前に
rtmp://flv9.nhk.or.jp/flv9/_definst_/flv:gogaku/streaming/flv/
と付けてそのURLからrtmpdumpflvstreamerでダウンロードできました。

そんな仕組みだったのが今年度からはファイル名を取得するXMLのURLが
http://www.nhk.or.jp/gogaku/english/basic1/<code>/listdataflv.xml
となり、取得したファイル名の前にはこんなのがつきます。
rtmp://flv9.nhk.or.jp/flv9/_definst_/flv:gogaku/streaming/flv/<code>/
<code>の部分は毎週変わる文字列です。
例えば今週はこの部分が0708VDUKWV57JAなので、XMLのURLは
となり、そこから取得した6月20日放送分のファイル名は11-ek1-4252-371.flvなので結局、
rtmp://flv9.nhk.or.jp/flv9/_definst_/flv:gogaku/streaming/flv/0708VDUKWV57JA/10-ek1-4252-022-re01.flv
がストリーミングのURLとなります。
毎週変わる上に、サイトのストリーミングプレイヤーがこのコード(名前がよくわからないんで適当にコードとでも呼んでおきます)を生成するアルゴリズムも毎週変えてるので、単にプログラムからプレイヤーのバイナリ読み込ませてコードを生成ってのも毎週変える必要が出でくるのであんま意味ないです。
だから、その週のアルゴリズムに対応したプログラムをいちいち配布するよりも、その週のコードを毎週公開して、それを読み込んでダウンロードする方がいいってことになります。
そんなわけで現在は、誰かが毎週そのコードをXMLで公開してくださっていて、それをプログラム側で読むってので安定してるのかな?たぶん
しっかしNHKもよくもまぁ毎週毎週コード生成のアルゴリズムを考えてますねw

余談ですが、仕様変更前は、ファイル名の中の数字が1ずつ増えるっていう簡単な命名法だったんで翌週分のストリーミングのURLも予測可能で、しかも実際にダウンロード出来ました。

結局、今ではもうダウンロードする方法も安定してるんで需要ないかもしれませんが一応配布します。
ダウンロード

方針としては、ストリーミングプレイヤーのアクセスするURLをスニッフィングして、そのまま利用させてもらおうという感じです。ただ、ブラウザのアドオンとか拡張機能として実装するのはめんどくさそうだったので、ブラウザなしでフラッシュを再生できるFlashPlayerDebuggerを使いました。

仕様が変更になった4月の頃、ちょうど読んでいたオライリー・ジャパンの「リバースエンジニアリング ―Pythonによるバイナリ解析技法 (Art Of Reversing)」という本にpydbgモジュールを使ったリバースエンジニアリングの例が紹介されており、Adobe Flex 4 SDKの中のFlashPlayerDebuggerを使えば自動でダウンロード出来るんじゃないかと思いつきまして、作ってみました。NHK Stream Downloaderを拡張する形で変更しました。以下のscripts/flashplayer_hook.pyがコードを取得してくるをモジュールで、同じディレクトリにあるmain.pyからget_code()を呼んでいます。
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import threading
import utils
import time
import re
from pydbg import *
from pydbg.defines import *

p = re.compile("swf/(?P.+)/listdataflv.xml")

def send_sniff(dbg, args):
    buffer = ""
    offset = 0

    while 1:
        byte = dbg.read_process_memory(args[1] + offset, 1)
        if byte != "\x00":
            buffer += byte
            offset += 1
            continue
        else:
            break

    if "listdataflv.xml" in buffer:
        global code
        code = p.search(buffer).group("code")
        dbg.terminate_process()
    return DBG_CONTINUE

def first_handler(dbg):
    hooks = utils.hook_container()
    hook_address = dbg.func_resolve_debuggee("ws2_32.dll", "send")
    #print "[*] setting hook on 0x%08x" % hook_address
    hooks.add(dbg, hook_address, 4, send_sniff, None)
    return DBG_CONTINUE

def get_code():
    exe_path = "bin\\FlashPlayerDebugger.exe"
    argument = "http://www.nhk.or.jp/gogaku/common/swf/streaming.swf"
    global code
    dbg = pydbg()

    pid = dbg.load(exe_path, argument, False, False)
    pid = dbg.pid
    dbg.set_callback(EXCEPTION_BREAKPOINT, first_handler)
    dbg.debug_event_loop()
    return code

if __name__ == "__main__":
    get_code()
見て分かるように、get_code関数内では、FlashPlayerDebuggerの引数にプレイヤーのURLを渡して起動して、エントリーポイントでブレークしたらws2_32.dllのsendにブレークポイントをセット、ブレークしたら引数からコードを取得してプロセスを終了させ、その得られたコードを返します。

pydbgモジュールをバンドルしてもう一度exe化するのが面倒だったのでそのまま入れちゃってます。

0 件のコメント:

コメントを投稿