暇つぶし文@謎

無料アクセスカウンターofuda.cc「全世界カウント計画」

2018-04-15

[その他] Anitubeの広告配信

怪しい記事を見かけたので、こちらの環境で解析したAnitubeの広告配信経路をここに書いておきます。なおこれは一例であって、環境や機会により広告関連スクリプトの流れも変わるはずです。


とりあえず、以下のページのソースコードを開きます。

http://www.anitube.se/video/#####/XYZ

すると、ページのヘッダ、サイド、フッタ、動画の下の広告表示部分に以下のようなHTMLコードが埋めこまれています。これが、広告表示スクリプトを読み込むための起点となります。ここでは蘭Adglare社のスクリプトを読み込んでいます。

		<!-- Advert Start -->
			<div id="pageAdvert" align="center">
			<script async src='//anigrupo.engine.adglare.net/?#########'></script><span id=#########></span>
			</div>
		<!-- Advert End -->

その後、以下のURLJavascriptが読み込まれ実行されます。(なお、URLの後ろの#########はzIDとのこと。)

//anigrupo.engine.adglare.net/?#########

スクリプトの内容は大幅に省略しますが、以下のような感じで、別のJavascriptを読み込むためのものです。

(前略)
AdGlare.loadJS('//anigrupo.engine.adglare.net/?#########&t=1&tt=##########-########');

loadJS関数URLが色々加工された後、最終的に以下のURLにあるJavascriptが読み込まれ実行されます。

http://anigrupo.engine.adglare.net/?#########&t=1&tt=##########-########&winID=#####&screen=####x####&framed=0&vb=1&crIDsLoaded=&referer=http%3A%2F%2Fwww.anitube.se%2Fvideo%2F#####%2FXYZ

上記URLJavascriptの内容はこんな感じ。

(前略)
var AdGlareSettings_###### = { zID: #########, width: ###, height: ###, impURL: "//anigrupo.engine.adglare.net/imp?data=(略)", isThirdParty: true, autoClickTracking: true, isSync: false, alwaysOnScreen: false, alwaysOnScreenData: '', animationType: 'none', allowClose: true, closeButtonURL: '//anigrupo.cdn.adglare.net/inventory/close_button.png', isBase64Encoded: true, zoneHTML: "(BASE64っぽい文字列)", (略)}

(略)document.write('<span id=zone'+this.settings.zID+'>'+this.settings.zoneHTML+'</span>'(略)

BASE64っぽい文字列をデコードすると、HTMLの断片が現れます。

<!--  ad tags Size: 728x90 ZoneId:#######-->
<script type="text/javascript" src="http://js.sprout-ad.com/t/###/###/x#######.js"></script>

そして、そこに書かれたスクリプトが読み込まれます。このURL (*.sprout-ad.com) は日本のトラストリッジ社のものですね。

内容は下のような感じ。

(略)
var di={(略),vd:"sprout-ad.genieesspv.jp",(略)};
(略)
u+="//"+di.vd+"/yie/ld/jsk";
u+=(略)

そして、ここで以下のURLが生成されます。このURL (*.genieesspv.jp) は日本のジーニー社のものですね。

http://sprout-ad.genieesspv.jp/yie/ld/jsk?zoneid=#######&cb=###########&charset=UTF-8&loc=http%3A%2F%2Fwww.anitube.se%2Fvideo%2F#####%2FXYZ&referer=http%3A%2F%2Fwww.anitube.se%2Fvideo%2F#####%2FXYZ&sw=####&sh=####&topframe=0

生成されたURLからスクリプトが更に読み込まれます。内容は下のような感じ。

gen_tag = "%3cscript%20type%3d%27text%2fjavascript%27(略)%2fscript%3e";document.write(decodeURIComponent(gen_tag));

gen_tagのエスケープを解いてやると、ついに広告本体のHTMLが出てきます (謎なiframeも付属してましたが解説では省略)。今回は最終的な広告のURL (*.gsspat.jp) がジーニー社のものでした。

<script type='text/javascript'>()<a href=\"http:\/\/rt.gsspat.jp\/c?c=(略)&amp;y=1&amp;p=XXXXXXXXXXX&amp;do=(略)&amp;vs=(略)\" style=\"border:none;margin:0;padding:0;\" target=\"_top\" >\\n');(略)document.write(\"<img src=\\\"\" + scheme + \"\/\/rt.gsspat.jp\/b?p=XXXXXXXXXXX&amp;y=1&amp;v=(略)\\\" height='1' width='1' style='display: none;'>\");(略)</script>

ということで、今回調べた時は蘭Adglare社→日トラストリッジ社→日ジーニー社という流れでしたが、別の会社の広告が出てくることも確認していますし、経路はまだまだあるはずです。

それと、これだけでは契約の流れがどうなってるのかまでは分かりませんね。更に中間業者が挟まっている気がします。

# …にしても凄く長いタライ回しだなぁ…。

2017-12-30

[カラーサイエンス][Python] 正しく変換したアニメカラーデータ

ねこまたやさんのアニメカラー測定データがどうにも怪しい (実測データと目視データが異なる) ので調べてみた。

同サイトのD65 XYZ値は100倍になっているようで、まずここでハマる。測定機器の凸版 CS CM1000は調べても詳細が出てこないが、下記を見る限りD50光源らしい?

このツールでの測色はD50にしないと正常なプロファイルが取れません。

光源の切替えはCS-CM1000付属のソフト上で行います。

『信頼性の高いプリンタプロファイル作成ツール』 のクチコミ掲示板

ピンと来たので、測定データのD65 XYZ値をXYZ Scaling変換でD50 XYZ値に一旦戻してから、より高精度なBradford変換でD65 XYZ値に変換してみたら、どうにも其れっぽい色が出てきた。値のズレはXYZ Scaling変換にも一因があるようだ。

import numpy as np
def fix_color(xyz):
	xyz_mat = np.matrix(xyz).T

	# http://technorgb.blogspot.jp/2015/08/blog-post_22.html
	# http://www.brucelindbloom.com/index.html?Eqn_ChromAdapt.html
	xyz_to_lms_mat = np.matrix([
		[ 0.8951,  0.2664, -0.1614],
		[-0.7502,  1.7135,  0.0367],
		[ 0.0389, -0.0685,  1.0296]
	])

	d65_mat = np.matrix([0.95047, 1.0, 1.08883]).T
	d50_mat = np.matrix([0.96422, 1.0, 0.82521]).T

	d65_xyz_to_d50_xyz_mat = np.diagflat(d50_mat.A / d65_mat.A) # XYZ Scaling method
	d50_xyz_to_d65_xyz_mat = xyz_to_lms_mat.I * np.diagflat((xyz_to_lms_mat * d65_mat) / (xyz_to_lms_mat * d50_mat)) * xyz_to_lms_mat # Bradford method
	xyz_mat = d50_xyz_to_d65_xyz_mat * (d65_xyz_to_d50_xyz_mat * xyz_mat)	

	return xyz_mat.A1

sRGBへの変換 (Numpy使いきれてないのがバレる)

import numpy as np

# https://www.w3.org/Graphics/Color/srgb
def linearsrgb_to_srgb(lin):
#	return [0 if c < 0 else 12.92 * c if c <= 0.0031308 else 1.055 * np.power(c, (1/2.4)) - 0.055 for c in lin]
	return [12.92 * c if abs(c) <= 0.0031308 else math.copysign(1.055 * np.power(abs(c), (1/2.4)) - 0.055, c) for c in lin]

def d65xyz_to_srgb(xyz):
	d65xyz_to_linearsrgb_mat = np.matrix([
		[ 3.2406255, -1.537208 , -0.4986286],
		[-0.9689307,  1.8757561,  0.0415175],
		[ 0.0557101, -0.2040211,  1.0569959]
	])

	xyz_mat = np.matrix(xyz).T

	lin_srgb = d65xyz_to_linearsrgb_mat * xyz_mat

	srgb = linearsrgb_to_srgb(lin_srgb)
	return [i*255 for i in srgb]

書き出し

import numpy as np
import csv

with open("test.html", "w") as htmlfile:
	htmlfile.write("<html><head></head><body>")
	# http://www.nekomataya.info/teck_info/taiyo_color/TaiyoChart.txt
	with open("TaiyoChart.txt", "rb") as cvsfile:
		cl = 0
		chartreader = csv.reader(cvsfile, delimiter='\t')
		for row in chartreader:
			if cl < 2:
				cl = cl + 1
				continue
			if len(row) < 9:
				continue
			cname = row[2]
			xyz   = map(float,[row[7], row[8], row[9]])
			xyz_str = ",".join(map(str,xyz))
			srgb  = d65xyz_to_srgb(fix_color(np.array(xyz)/100))
			srgb_str = ",".join([str(int(round(c))) for c in srgb])
			o_srgb = [row[12], row[13], row[14]]
			o_srgb_str = ",".join(o_srgb)
			m_srgb = [row[4], row[5], row[6]]
			m_srgb_str = ",".join(m_srgb)
			print("%s = XYZ(%s), sRGB(%s), o_sRGB(%s)"%(cname, xyz_str, srgb_str, o_srgb_str))
			htmlfile.write("<div style='display: inline-block; height 5em; width: 10em;'>\
							<p style='margin: 0'>%s</p>\
							<p style='background-color: rgb(%s); margin: 0'>XYZ=%s<br>sRGB=%s</p>\
							<p style='background-color: rgb(%s); margin: 0'>o_sRGB=%s</p>\
							<p style='background-color: rgb(%s); margin: 0'>m_sRPG=%s</p></div>"%\
							(cname, srgb_str, xyz_str, srgb_str, o_srgb_str, o_srgb_str, m_srgb_str, m_srgb_str))
	htmlfile.write("</body></html>")

若干表現変更。

暗い色はsRGB変換よりもgamma 2.2変換の方が、アニメカラー測定データにある目視カラーデータに近くなるように見える (X7より後はsRGB変換の方が近い)。うーむ、目視カラーデータの方も怪しい気がするような。

2017-12-19

[][] AbemaTVの仕様とHLSの暗号化の弱さ

AbemaTVの仕様について気になったので調べてみた (研究目的です念の為)。

AbemaTVはPCへの動画配信において、配信プロトコルにHLSを使用しているようだ。HLSはMPEG-DASHと異なりDRMが使えず (厳密にはMac環境のFairplayなどの例外もあるが) 、AbemaTVでは鍵の生成に若干工夫を行ってるのみのようだ。


まず、APIを使ってチャンネル一覧をダウンロード

$ curl https://api.abema.io/v1/channels
{"channels":[{"id":"abema-news","name":"AbemaNewsチャンネル","playback":{"hls":"https://linear-abematv.akamaized.net/channel/abema-news/playlist.m3u8"}},{"id":"abema-special","name":"AbemaSPECIALチャンネル","playback":{"hls":"https://linear-abematv.akamaized.net/channel/abema-special/playlist.m3u8"}},(後略)

次に画質一覧をダウンロード

$ curl https://linear-abematv.akamaized.net/channel/abema-news/playlist.m3u8
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=184000
180/playlist.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=300000
240/playlist.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=900000
360/playlist.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=1400000
480/playlist.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=2200000
720/playlist.m3u8
#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=4200000
1080/playlist.m3u8

映像のフラグメント一覧をダウンロード

$ curl https://linear-abematv.akamaized.net/channel/abema-news/1080/playlist.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:951004
#EXT-X-DISCONTINUITY-SEQUENCE:16994
#EXT-X-KEY:METHOD=AES-128,URI="abematv-license://XXXXXXXXXXXXXXXXXXXXXX",IV=0x000000000000000000000000000000
#EXTINF:5.005000,
https://linear-abematv.akamaized.net/tsnews/abema-news/h264/1080/xxxxxxxxxxxxxxxxxxxxxx.ts
#EXTINF:5.005000,
https://linear-abematv.akamaized.net/tsnews/abema-news/h264/1080/xxxxxxxxxxxxxxxxxxxxxx.ts
#EXTINF:5.005000,
https://linear-abematv.akamaized.net/tsnews/abema-news/h264/1080/xxxxxxxxxxxxxxxxxxxxxx.ts
#EXTINF:5.005000,
https://linear-abematv.akamaized.net/tsnews/abema-news/h264/1080/xxxxxxxxxxxxxxxxxxxxxx.ts

さて、映像はAES-128方式で暗号化されているようだ。暗号の鍵には初期化ベクトル(IV)とURIが指定されているが、URIに使われているabematv-licenseスキーマとは何だろう。仕組みは良く分からないが、Chrome通信ログを見ると、スキーマの後ろの部分 (XXXXXXXXXXXXXXXXXXXXXX) と何処かにあるトークンを使って、とあるURLにアクセスしているようだ。

トークンはローカルストレージにあるものと同じようなので、Chromeのコンソールからwindow.localStorage["abm_mediaToken"]と打つと手に入る。このトークンスキーマの後ろの部分を使って、ライセンスキーの種を手に入れる。

$ curl https://license.abema.io/abematv-hls?t=トークン --data '{"lt":"スキーマの後ろの部分","kv":"wd","kg":166}'
{"cid":"abema-news","k":"XXXXXXXXXXXXXXXXXXXXXXX"}

さて、どうやってライセンスキーの種 (k) をキーに変換するのだろう? 調べた所、遅延ロードされた以下のJavascriptがこの変換を処理しているようだ。

https://abema.tv/xhrp.js

若干難読化されているけれども、肝心の部分はそのままだし、コードインジェクションもし放題なので割と何とかなる。

キー計算の表面部分のロジックはこんな感じ。

function _0x569113(cid, uid, k){
  var _k = k.substring(0,k.length-1);
  var c = k.charAt(k.length-1);
  return c=='5'?_0x1e2ccc(cid, uid, _k):
         c=='4'?_0xa25b8f(cid, uid, _k):
                _0x2782e2(cid, uid, _k);
}
var _0x5ee3af=_0x569113(cid, window.localStorage["abm_userId"], k)

キー計算の中心部分は解読していないけれど、alert(_0x5ee3af);をインジェクションしてコードを実行するだけでキーが手に入る。

手に入ったキーは、バイナリ化してkey.binとして保存しておく。あとはそのキーを使って再生するだけ。

$ wget -N https://linear-abematv.akamaized.net/channel/abema-news/1080/playlist.m3u8 \
&& sed -i 's/URI=.*\,/URI=\"key.bin\",/g' playlist.m3u8 \
&& ffplay playlist.m3u8 -protocol_whitelist file,http,https,tcp,tls,crypto

フラグメント毎にしか再生できないので実用性には欠けるけれども、何にせよHLSが弱いことは証明できたので良いかな。

今後、AbemaTVでも強固なDRM付きのMPEG-DASHが導入されていくらしいので期待。


追記。何となく「簡単さ」が伝わってないようで残念なので、Python + Selenium WebDriverで自動化した方法を書いておきます。といっても大したものでもないですが。

from selenium import webdriver
from time import sleep
import requests
import re
import os

if __name__ == '__main__':
    browser = webdriver.Chrome(executable_path = "/usr/lib/chromium-browser/chromedriver")
    browser.get("https://abema.tv/now-on-air/abema-news")
    sleep(1)
    js = requests.get("https://abema.tv/xhrp.js").text
    mod_js = re.sub("(_0x31a687=.*?);", "\\1;window.key=_0x31a687;", js)
    browser.execute_script(mod_js)
    sleep(1)
    key = browser.execute_script("return window.key;")
    print(key)
    browser.close()

    f = open("key.bin", "wb")
    f.write(bytearray(key))
    f.close()

    pl = requests.get("https://linear-abematv.akamaized.net/channel/abema-news/1080/playlist.m3u8").text
    mod_pl = re.sub('URI=.*?\,', 'URI=\"key.bin\",', pl)

    f = open("playlist.m3u8", "w")
    f.write(mod_pl)
    f.close()

    os.system("ffplay playlist.m3u8 -protocol_whitelist file,http,https,tcp,tls,crypto")

なお、これはHLSの弱さを伝えるための単なる技術デモであり、フラグメント毎にしか再生できないため実用的ではなく、研究目的以外での利用は想定していません。

再追記。普通に独自スキーマへXMLHttpRequestするだけで良いと聞いたのでテストコード。

from selenium import webdriver
from time import sleep
import requests
import re
import os

if __name__ == '__main__':
    browser = webdriver.Chrome(executable_path = "/usr/lib/chromium-browser/chromedriver")
    browser.get("https://abema.tv/now-on-air/abema-news")
    sleep(2)

    pl = requests.get("https://linear-abematv.akamaized.net/channel/abema-news/1080/playlist.m3u8").text

    key_url = re.search(u'URI=\"(.*?)\"\,',pl).group(1)

    browser.execute_script('''
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && xhr.status == 200)
        window.key = new Uint8Array(xhr.response)
}
xhr.open("GET", "%s");
xhr.send();
'''%key_url)

    sleep(1)
    key = browser.execute_script("return window.key;")
    browser.close()

    f = open("key.bin", "wb")
    f.write(bytearray(key))
    f.close()

    mod_pl = re.sub('URI=.*?\,', 'URI=\"key.bin\",', pl)

    f = open("playlist.m3u8", "w")
    f.write(mod_pl)
    f.close()

    os.system("ffplay playlist.m3u8 -protocol_whitelist file,http,https,tcp,tls,crypto")

ありゃ、本当だ。色々難しく考えすぎてたようです。Javascriptへのcode injectionは不要だし、他のサイトにも使えそう。

ついでに複数フラグメントについても調べてみたら、単にプレーヤー側がリロードを繰り返すだけとのこと。ちょっと信じられないので、とりあえずPython3でプロキシを書いてみた。

from http.server import HTTPServer, SimpleHTTPRequestHandler
import requests
import re

class MyHandler(SimpleHTTPRequestHandler):

    def do_GET(self):
        if self.path == "/key.bin":
            f = open("key.bin", "rb")
            body = f.read()
            f.close()
        else:
            pl = requests.get("https://linear-abematv.akamaized.net/channel/abema-news/1080/playlist.m3u8").content
            body = re.sub(b'URI=.*?\,', b'URI=\"key.bin\",', pl)
        self.send_response(200)
        self.send_header('Content-type', 'application/x-mpegURL')
        self.send_header('Content-length', len(body))
        self.end_headers()
        self.wfile.write(body)

httpd = HTTPServer(('localhost', 8000), MyHandler)
httpd.serve_forever()

…本当ですね。思った以上にザルだった。MPEG-DASHへの一本化が待たれます。

なお、これらコードはエラーチェックが適当ですし、何故かたまに途切れりします。研究目的以外での利用は想定していません。また、暗号化においてHLSを使用することも推奨しません。

2017-01-25

[] NVIDIAIntelでマルチディスプレイ

ハマったので書いておく。

BusIDを調べる

$ lspci | grep NVIDIA | grep -v Audio | sed -e "s/^0*\(.\+\):0*\(.\+\)\.\(.\)\ .*$/PCI:\1:\2:\3/"

xorg.confを設定

Section "ServerLayout"
    Identifier "layout"
    Screen 0 "nvidia"
    Inactive "intel"
EndSection

Section "Device"
    Identifier "nvidia"
    Driver "nvidia"
    BusID "<BusID>"
EndSection

Section "Screen"
    Identifier "nvidia"
    Device "nvidia"
    # Uncomment this line if your computer has no display devices connected to
    # the NVIDIA GPU.  Leave it commented if you have display devices
    # connected to the NVIDIA GPU that you would like to use.
    #Option "UseDisplayDevice" "none"
EndSection

Section "Device"
    Identifier "intel"
    Driver "modesetting"
EndSection

Section "Screen"
    Identifier "intel"
    Device "intel"
EndSection

ディスプレイIDを調べる

$ xrandr --setprovideroutputsource 1 0
$ xrandr | grep \ connected | sed -e "s/\ .*$//"

.xsessionrcを設定

xrandr --setprovideroutputsource 1 0
xrandr --auto
xrandr --output <ディスプレイID 1> --right-of <ディスプレイID 2>

補足。nvidia-settingsではIntel側のモニタをPRIME Displayとして表示しているものの、そのレンダリングはNVIDIA GPU側で行っているようだ。それは良いとしても、二つのモニタの周波数が異なる場合に、セカンダリ側のアプリケーションのfpsもNVIDIA側モニタの周波数に制限されてしまっているように見える (アプリケーションの問題かも?)。

GPU毎にディスプレイを立ち上げて、DISPLAY=:0.1 xfwm4&のようにすればどうかなと思ったけど、何故か上手くいかないので保留。

ディスプレイオーバークロック

ついでだから、片方のディスプレイオーバークロックしてみた。

$ cvt 1280 1024 76
# 1280x1024 75.98 Hz (CVT) hsync: 81.45 kHz; pclk: 140.75 MHz
Modeline "1280x1024_76.00"  140.75  1280 1368 1504 1728  1024 1027 1034 1072 -hsync +vsync
$ xrandr --newmode "1280x1024_76.00"  140.75  1280 1368 1504 1728  1024 1027 1034 1072 -hsync +vsync
$ xrandr --addmode HDMI-1-2 1280x1024_76.00
$ xrandr --output HDMI-1-2 --mode 1280x1024_76.00

削除は以下で。

$ xrandr --delmode HDMI-1-2 1280x1024_76.00
$ xrandr --rmmode 1280x1024_76.00

75Hz→76Hzにしかならなかったけど、しないよりはマシか。

2016-11-11

[] PR-S300SEからPR-400KIに

PR-S300SEのSPIによるパケット破棄問題に困っていたので、光回線終端装置を交換してもらった。もっと早くやっとくべきだったなぁ。

デバッグコマンド叩けなくなってしまったけれども、そもそもデバッグコマンド叩ける方がセキュリティ的に問題だから仕方ないか。

Connection: close