2018年4月7日土曜日

OSCP 受験記

Offensive Security Certified Profiessional (OSCP) を取得した.日本ではあまり認知されていない資格というイメージがあるし,実際 OSCP を紹介する日本語のリソースは 2018 年 4 月現在で 1 件くらいしか見つからない状況なので,自慢がてら資格の紹介や経緯,tips を書いておこうと思う.

1. OSCP について

OSCP はペネトレーションテストの資格.ペネトレーションテスト関係で業界で一番メジャーな資格は間違いなく EC-Council の CEH で,その他には GIAC の GPEN などが認知されている.OSCP は比較的新しい資格 (といっても 2006 年には始まっていたらしい) で特に日本での認知度は高くないように思うが,海外で,特に実務に携わる人間には CEH 以上に評価されている資格のようだ.

OSCP は Offensive Security によって運営されている.Offensive Security は Kali Linux (旧 BackTrack) や exploit-db.com などを運営している組織で,ペンテスターはほぼ間違いなく依存しているのではないだろうか.それで,OSCP の何が特徴的かというと,
  • 試験は 24 時間 (!) のオンラインラボ試験で,環境内のターゲットに実際に攻撃を行いシステムに侵入する
  • ラボ実施後にさらに 24 時間が与えられ,そこでレポートを書き上げて提出する
というもので,「ペンテスターとしての実務能力を証明する超実践的な資格」となる.これは,たくさんの選択式問題 + ちょっとしたシミュレーション問題といったその他の一般的な試験とは明らかに一線を画しており,その実践的性格によって相当な支持を集めている.これについてはいろいろな人がいろいろと書いているので興味のある方は “CEH or OSCP” などで検索してみると良い.

自分はこれらの評判などを調べた結果,OSCP を受験することを選んだ.結論から言ってとても有意義なものだった.OSCP は単なる試験対策の勉強で終わることはなく,勉強のなかで得た知識と経験は今後役立つだろうと感じている.もともと資格が好きな人間ではないのだけれども,これは良い資格だと本気で思っている.

選んだ理由のひとつに,価格の安さがある.最短だと 30 日のコース・ラボ環境へのアクセス + 試験 1 回で 800 USD. 1 週間のオンサイトのコースと試験を受けて 30 万円というような,会社の援助が前提な資格が多いなかで,これは違う.自分は 800 USD より多くかかってしまったが,自腹で受けた.

2. 難易度・前提知識

おそらくプロのペンテスターならば特に問題なく合格できるものだと思う.自分はセキュリティ関係の仕事をしているものの,ペネトレーションテストの経験はない状態で始めた.そんな自分にとっては,ものすごく難しかった.

コース自体は前提知識がないところからでも試験範囲をカバーできるように作られているが (それこそ Bash の for ループの書き方などから始まる),それはあくまでトピックが存在することの紹介であって,それらを自由に使いこなせることは実質的に必要な前提知識になる.その観点で自分の考える前提知識は以下となる.
  • 英語:
    この業界全てのリソースは英語 (ロシア語・中国語なんかもわかると有利なのかもしれないが).コースも試験も提出するレポートも全部英語.必須.
  • Windows/Linux の CLI 操作とシステム管理:
    両方をそれなりに知っているか,一方にかなり熟練していたほうが良い.わからないことは調べれば良い.
  • インターネットプロトコルの理解:
    HTTP や SMTP あたりのプロトコルはパケットキャプチャを眺めて意味がわかるくらいの必要はある.あと Wireshark. 細かいことは必要に応じて調べれば良い.
  • プログラミング・スクリプティング:
    経験があると有利なのは Bash と Python. それからたまに C と Ruby. 何かの言語をある程度やったことがあれば他の言語でもちょっとしたプログラムなら調べながらなんとかなる.
  • CPU (x86) の動作の理解:
    そこまで深く理解している必要はないけれども,CPU はメモリに配置された命令を実行していくものだとか,CPU のレジスタとかメモリのセクションのイメージくらいはあったほうが良い.自分は過去に『はじめて読む 486』を読み終わらなかった経験があるが,それがかなり役に立った.
そんな全部できるかいな,という範囲だけれども,最初からすべてできる必要はない.既に複数回書いているように,必要なときに調べて対応できることが全て.わからないものにぶつかったときに,調べるなり訊くなりしてとにかくなんとかするというのが,OSCP に限った話ではないが一番大事だと思う.

3. 自分の経緯

3.1. 最初の 30 日間

コースを始めたのは 2017 年 7 月後半.自分のスキルに根拠のない自信を持っていたために最短 30 日で登録.本当に最初のほうは Bash や Python のスクリプトの書き方から始まるので,1–2 日でコース資料の 1/3 程度は終了した.こりゃ余裕だと思って力を抜いて進めていたところ,ドラクエ 11 が発売され,ハマる.期限が近づいてきたためにあわててコース資料を全部終えた.そして気がついた.
  • 30 日の期間はコース資料を終えるための期間ではなく,それをふまえてラボ環境のターゲットを攻略する練習のための期間だった.
試しに 2–3 台のターゲットに攻撃をしかけてみるも,自分にはまったくターゲットを攻略できないことを知った.痛恨の極みである.ドラクエは楽しかった.そして 30 日の期間が終わり,試験は受けなかった (受けない場合,その分は後の受験のために取っておける).

3.2. 2 回目の 30 日間

2017 年 11 月に 30 日間のラボアクセスを追加購入 (250 USD).コース資料は終わっているのでラボでの練習に集中したが,まったく攻略できない.Nmap が脆弱だと言っている脆弱性があって,PoC コードがあって (自分用の) shellcode を組み合わせたエクスプロイトコードが全然動かない.試しに Metasploit で同じ脆弱性をつくと,あっさりと動く.仕事の合間を縫ってトラブルシューティングを続け,ようやく原因がわかって動作させることができたのは 3 週間後.しかも原因はよくよく見てみるとコース資料に書いてあることだった.がっくりした.

あっさりと 3 週間を無駄にしたように見えるかもしれないが,これは自分にとって重要な経験だった.その 3 週間いろいろな試行錯誤を繰り返していたからか,その後結構な確率でエクスプロイトを動作させることができるようになった.これを機に,何台かのターゲットは攻略することができるようになった.が,そのあたりで期間が終了.まだ試験を受ける状態になったとは思えなかった.

3.3. 3 回目の 15 日間

2018 年 1 月,年始から 15 日間のラボアクセスを購入 (150 USD).併せて,勉強に集中するためその期間は有給休暇を取得.このときになるとかなり面白いようにターゲットを攻略できるようになってきた.また,受験者用のフォーラムを見るようになった (それまではまったく見ていなかった).行き詰まったときには,そのフォーラムにヒントが書いてあるのでそれを足がかりに攻略することを繰り返した.

ラボには結構な数のターゲットがあるが,最初の 1 週間までで累計半分程度のターゲットを攻略した.そろそろ受験の時だと思い,まだラボ期間は終わっていなかったが試験を受け,合格した.

4. 所感,Tips

情報収集は大事

OSCP に取り組む際には,特に受験者の体験談とかをいろいろと読むようになる (“OSCP review” で検索).これは序盤,ないし取り組み始める前に行っておいたほうが良い.体験談には技術的に直接役立つ情報はほとんどないが (この記事のように),貴重なラボ期間を有効に活用するためのヒントが豊富にある.ある程度学習が進むと,この体験談の類は読まなくなる.

フォーラムを活用する

学習が進んできたときに技術的に役立つ情報源は,Offensive Security が運営する受験者のためのフォーラムになってくる.ここはスタッフが内容をモニタリングしていて,行き過ぎたヒントなどは <Spoiler> としてマスクされる.つまり,マスクされない程度のヒントまでを得たところからは自分で調べて解決できることが最低限受験者に求められるレベルだと考えて良いと思う.フォーラムには他の受験者がさまざまな書き込みをしており,自分で何かを尋ねる必要はない.自分も書き込みは 1 回もしていない.

自分はフォーラムを読み始めたのが極めて遅かったが,もっと早い時期に読んでいたほうが効率的だったと思う.ただし,簡単にフォーラムにヒントを求めに行くことには慎重になったほうが良いと思う.自分で思い浮かぶことは全部やった,けれどもわからない,というときにヒントを見ると大抵「なんだ,そんなことだったのか」とがっくりする.このあまり愉快ではない経験がとにかく大事.

メモを取る

これはコース資料でも言われていることだが,とにかくメモを取る,記録をつけることの練習を行っておくと良い.代表的なアプリケーションとして KeepNote が推奨されていて自分も KeepNote を使ったが,他のものを使えば良かったと思っている.今はメモのために CherryTree を使用していて,今のところ満足している.

Lab report を書く

試験後には exam report の提出が必要だが,オプションの (些細な) 加点要素として lab report を書いて提出することができる.試験の結果が合格・不合格のボーダーにあったときには lab report が考慮されて合格になる場合があるということ.実際に合格する時にはボーダーは軽々越えたという実感を持つと思うのだが,それでも lab report を書くとは expam report を書くための非常に良い練習になるのでおすすめ.レポートのテンプレートは .doc と .odt のものが用意されていて自分は Kali 上の LibreOffice で両レポートを書いたが,主に画像配置の処理がひどいのと,苦労して整形したレポートが PDF 出力時に崩れて目も当てられない状況だったので,MS Word を使ったほうが良い.厳密に言うと「従わなければいけないテンプレート」はないので TeX でも HTML でも良いはずだけれど,仕事ではないからそこはこだわらなくても良いように思う.

Metasploit は (あまり) 使わない

試験では Metasploit の利用は 1 件の例外を除き許可されておらず,自分で作成したエクスプロイトコードを使用することが求められる.これは単にツールが利用できることではなくそのバックグラウンドの技術を理解し利用できることのために非常に有効なことだと思う.試験で Metasploit は使えないので,ラボ練習中も Metasploit の利用は基本的に避けたほうが良い.ただし,試行錯誤のなかで「この脆弱性は本当に利用可能なのか Metasploit で試してみよう」とか「Metasploit で攻撃したときのトラフィックとキャプチャを見比べてみよう」といった用途には有用だと思う.

権限昇格にはツールの活用を

エクスプロイトは多くの場合,リモートからシステムへのアクセスを得るための (pre-)exploitation とその後の権限昇格 (post-exploitation) に分けられる.前者はスキャナや使用されているソフトウェアから使用可能な脆弱性を見付けやすいが,後者は簡単ではない.対象 Windows システムに適用されているパッチや Linux パッケージのバージョンをひとつずつ調べて脆弱性を探すというのは少々非人間的なので,そこは適切なツールを使って脆弱性のあたりを付けることが重要.

パスワードアタックも重要

こうやって OS や ソフトウェアの脆弱性を探して突く,ということを繰り返していると,どうしてもパスワードアタックや,設定不備の類はどうしても面白みのないものになるが,これは避けて通るべきものではない.弱い・推測可能なパスワードも脆弱性であり,そのようなシステムも用意されている.現実のセキュリティインシデントもかなりの確率で弱いパスワードや流出したパスワードが関係していることが報告されていることを考えると,本当に実践的なコースなのだと思う.

5. おわりに

以上.繰り返しになるが本当に良いプログラムだと思う.ただプログラムを作るだけでなく,日々新しく出てくる脆弱性に対してラボ・試験環境を対応させたりさせなかったりといったメンテナンス,フォーラムの運営など,Offensive Security は素晴しい仕事をしている.自分は結局合計すると 1,000 USD 以上と 100–200 時間くらいをつぎこんで資格を取得したが,それに見合う対価を得たと感じている.こんな資格,多分なかなかない.

2016年12月20日火曜日

Raspberry Pi から A/D コンバータ ADC0832 を使う

最近 Raspberry Pi (RPi) で電子工作を勉強し始めた.『カラー図解 最新 Raspberry Pi で学ぶ電子工作—作って動かしてしくみがわかる—』を参考に勉強をしているのだけれど,対応するキットを買わなかったことによりパーツ特有の接続・制御の部分で少し苦労したので,その時のメモ.

重要: 電子工作について確かな知識も十分な経験もないので,間違ったことを書いている可能性が他の記事と比べても高いので,ご注意ください.

1. 環境と経緯

環境というか,買ったものは以下.
  • Raspberry Pi 1 Model B
    買ったの自体はずいぶん昔だったので,Raspberry Pi 1. (今は) Raspbian をインストールして使用している.
  • cocopar®最新Ultimateスターターキット 初心者専用実験キット 基本部品セットNew Ultimate Starter Learning Kit for Raspberry Pi 3 Model B/2B/B+ Python (https://www.amazon.co.jp/dp/B01EFJ70HC/)
    こちらは最近電子工作を勉強するために買った.
よく見ると,完全に失敗した買い物だった.RPi 1 Model B は GPIO ピンが 26 ピンなのに対し,RPi 2, 3 は GPIO 40 ピン,さらに上記スターターキットは  RPi 2, 3 用ということで 40 ピン用のパーツなどが用意されている.また,参照している『Raspberry Pi で学ぶ電子工作』とも使用パーツが完全に一致はしないので,電子工作初心者としてのチョイスとしては完全に間違いである.でも悔しいのでパーツの買い足しなどはせず,できるところまではそのまま突き進むことにした.

その結果最初に困ったのが,書籍 6 章の A/D 変換である.書籍では MCP3208 という 8 入力,12 ビット A/D コンバータが使用されているが,上記キットに含まれていた A/D コンバータは ADC0832 という 2 入力,8 ビットのもの.なので,配線や A/D コンバータを操作する処理のプログラムは MCP3208 用から ADC0832 用に変更する必要があるのだけれども,不運なことに書籍ではまさに当該部分の処理の説明が「当面は,特に初学者の間はこの実装を理解する必要はない」ということでばっさり省略されていた.これは意地でもなんとか ADC0832 で動かさねばならない,ということで頑張った.きっとこういうのが勉強になるんだと思う.

2.接続

書籍 6 章と同じように,半固定抵抗と A/D コンバータを接続することにより A/D コンバータの動作を確認することにする.書籍には MCP3208 用の接続が書かれているので,AD0832 での接続はデータシート (http://www.ti.com/lit/ds/symlink/adc0832-n.pdf) を参照して,書籍と似たような感じで接続する.また,データシートは検索して一番上位に出てくる Texas Instruments のサイトを参照しているが,おそらく OEM されているからか,いろいろな会社名から同じ製品名で販売されており,どこが本当の一次情報なのかわからない.

結果として,以下のような接続を行った.

写真で見るとこんな感じ.

3. プログラム

これは正直手がかりが全くつかめなかったのだが,Gist に ADC0832 用のコードを上げている方がいて助かった (https://gist.github.com/HeinrichHartmann/27f33798d12317575c6c).

とりあえず最終的なコードを示すと,以下のようになった.
# 6-1_adc0832.py
import RPi.GPIO as GPIO
from time import sleep

def readadc(adcnum, clockpin, mosipin, misopin, cspin):
    if adcnum > 2 or adcnum < 0:
        return -1

    GPIO.output(cspin, GPIO.HIGH)
    GPIO.output(cspin, GPIO.LOW)

    GPIO.output(clockpin, GPIO.LOW)

    for i in [1, 1, adcnum]:
        if i == 1:
            GPIO.output(mosipin, GPIO.HIGH)
        else:
            GPIO.output(mosipin, GPIO.LOW)
        GPIO.output(clockpin, GPIO.HIGH)
        GPIO.output(clockpin, GPIO.LOW)

    adcout = 0
    for i in range(8):
        GPIO.output(clockpin, GPIO.HIGH)
        GPIO.output(clockpin, GPIO.LOW)
        adcout <<= 1
        if GPIO.input(misopin) == GPIO.HIGH:
            adcout |= 0x1

    GPIO.output(cspin, GPIO.HIGH)
    return adcout


GPIO.setmode(GPIO.BCM)
SPICLK = 11
SPIMOSI = 10
SPIMISO = 9
SPICS = 8
GPIO.setup(SPICLK, GPIO.OUT)
GPIO.setup(SPIMOSI, GPIO.OUT)
GPIO.setup(SPIMISO, GPIO.IN)
GPIO.setup(SPICS, GPIO.OUT)

try:
    while True:
        inputVal0 = readadc(0, SPICLK, SPIMOSI, SPIMISO, SPICS)
        inputVal1 = readadc(1, SPICLK, SPIMOSI, SPIMISO, SPICS)
        print("ADC[0]: {}\t ADC[1]: {}".format(inputVal0, inputVal1))
        sleep(0.2)

except KeyboardInterrupt:
    pass

GPIO.cleanup()
肝の部分は readadc 関数のところで,この中身が書籍の MCP3208 用から ADC0832 用に書き換えてある (ほぼ Gist の方のコードのまま) .また,main 部分では入力 2 チャンネルの両方の値を毎回出力するようにした.

このコードとデータシートを睨んでいると,ようやくコードの意味がわかってきた.まず見るべきは,Timing Diagram の図だと思う.
(データシートより引用)

この図を見ると,各ピンがどのタイミングでどの状態になるのかを理解できる.そのとおりに RPi 側のコードを書いてやれば良い.具体的には以下の流れ.

1. CHIP SELECT (CS) ピンを HIGH -> LOW にセット
    GPIO.output(cspin, GPIO.HIGH)
    GPIO.output(cspin, GPIO.LOW)

    GPIO.output(clockpin, GPIO.LOW)
CS ピンの制御により 1 回の値読み取りプロセスが初期化される (のだと理解した).また,各ピンの値の入力・出力は CLOCK (CLK) ピンの状態変化のタイミング (図中に矢印が書かれている場所) で行われる (と推測している).初回の入出力は DATA IN (DI) の START BIT の入力で,CLK ピンが LOW -> HIGH に変化した時に発生するはずなので初期化プロセスとして CLK ピンも LOW にセットしておく.

2. DI に各パラメータをセット
ADDRESS MUX と書かれている部分で, ここでは 3 つのパラメータ START BIT, SGL/DIF, ODD/SIGN を DI ピンにセットし,CLK ピンを操作することによりシーケンスを進めていく.これらのパラメータにより,どの入力チャネルの値を取得するかといったことを指定できる.
    for i in [1, 1, adcnum]:
        if i == 1:
            GPIO.output(mosipin, GPIO.HIGH)
        else:
            GPIO.output(mosipin, GPIO.LOW)
        GPIO.output(clockpin, GPIO.HIGH)
        GPIO.output(clockpin, GPIO.LOW)
ここで,1, 1, adcnum (0 or 1) という値を送っている.この値の選択は,Multiplexer Addressing の表を見ると良いのだと理解した.ここでは純粋に adcnum に指定された入力チャネルの値を取得するように設定したが,2 番目の SGL/DIF に 0 を設定することで 2 チャネルの差分を取得するようにもできるようだ (試してはいない; 理由は後述).
(データシートより引用)

3. DATA OUT (DO) から値の読み取り
DI を使用した値の設定と逆のパターンで,DO から 1 ビットずつ値を読み込んでいく.
    adcout = 0
    for i in range(8):
        GPIO.output(clockpin, GPIO.HIGH)
        GPIO.output(clockpin, GPIO.LOW)
        adcout <<= 1
        if GPIO.input(misopin) == GPIO.HIGH:
            adcout |= 0x1
ここからは,CLK ピンが HIGH -> LOW に変化したタイミングで DO の値がセットされていく.最初の 8 ビットは MSB FIRST DATA, その後の 8 ビットが LSB FIRST DATA ということで同じ値を 1 ビットずつ,CLK ピンの電圧変化のタイミングで読み出していくことができるようだ (LSB の読み取りは試していない).

4. 読み取りプロセスの終了
最後に CS ピンを LOW -> HIGH に戻すことにより,1 回の読み取りプロセスを終了させる.
    GPIO.output(cspin, GPIO.HIGH)

4. 実行

実際に実行してみたところ,完璧とはいかなかった.
  • チャネル 0 の入力が読み取れず,常に値が 0 になってしまう.同じ接続をチャネル 1 に変更すると値を読み取ることができるので,おそらくコンバータが壊れているのではないかと推測する.
  • チャネル 1 の入力は読み取れるが,安定しない.半固定抵抗に触らない状態でも読み取られる値が結構変動する.ジャンパ線をちょっと触っても値が変動するくらいなので,おそらく流れる電流が不安定だからではないかと推測する.
以下に,半固定抵抗の出力をチャネル 0, 1 の双方に入力した時 (最初に示した接続図からは接続を追加している) のコード実行結果を示す.
pi@raspberrypi:~ $ python 6-1_adc0832.py
ADC[0]: 0        ADC[1]: 192
ADC[0]: 0        ADC[1]: 156
ADC[0]: 0        ADC[1]: 128
ADC[0]: 0        ADC[1]: 158
ADC[0]: 0        ADC[1]: 144
ADC[0]: 0        ADC[1]: 144
ADC[0]: 0        ADC[1]: 144
ADC[0]: 0        ADC[1]: 156
ADC[0]: 0        ADC[1]: 144
ADC[0]: 0        ADC[1]: 152
ADC[0]: 0        ADC[1]: 128
ADC[0]: 0        ADC[1]: 144
ADC[0]: 0        ADC[1]: 128
## snip ##
こんな感じ.

参考

2016年7月26日火曜日

Linux/DDoS-Xor.A リバージング

先日の Cuckoo によるマルウェア解析の続きで,少しだけれどもリバージングにチャレンジしてみる.

1. 対象マルウェア

前回と同じく sha256: da8140274788214c9451d950e0f323c0c51188c5bf847bafef9f48c023e91a55 のファイルを対象とする.前回やったときは VirusTotal の結果はなかったけれど,今検索してみると Linux/DDoS-Xor.A などと結果が出てくる (https://www.virustotal.com/en/file/da8140274788214c9451d950e0f323c0c51188c5bf847bafef9f48c023e91a55/analysis/).

2. Cuckoo による解析の続き

前回の投稿では Linux マルウェアでは strace や子プロセスの追跡ができないと書いたが,実行のタイミングを調整することでうまく動作させることができた.(そして Pull Request デビューした: https://github.com/cuckoosandbox/cuckoo/pull/1007)

トレースありの Cuckoo レポートを https://drmn.jp/blog/20160726-reversing/2.zip にアップロードした.一応 PCAP 等含むのでパスワード “infected” をかけている.

トレースが見れると非常にはかどる.このマルウェアは何回もプロセスを作ったり消したりする動作をしていたのだけれど,たとえば外部ホストに接続しに行こうとしたプロセスのトレースは以下.

わかりづらく表記された .org のドメインの名前解決をして当該 IP アドレスに接続しているように見受けられる.ではこの接続先を strings などで探すことができないかと考えたが,できなかった.
cuckoo@cactus /data/cuckoo/storage/analyses/2 $ strings binary | grep org
C/o Keld Simonsen, Skt. Jorgens Alle 8, DK-1615 Kobenhavn V
TLS generation counter wrapped!  Please report as described in <http://www.gnu.org/software/libc/bugs.html>.
DNS に使用されるアドレスのほうは出てくる.
cuckoo@cactus /data/cuckoo/storage/analyses/2 $ strings binary | grep -F 114.114.114.114
114.114.114.114
ということで,strings だけでは得られない情報を求めてリバージングしていく.できれば動作全体の解明をしていきたいのだけれど,まだまだ始めたばかりなので…….

3. リバージング

リバージングには Radare2 を使用している.

main の逆アセンブル結果を見ていくと,最初のほうにこんなパートが出てくる.
PATH 環境変数をセットしているのは良いとして,0x0804b67f 以降,謎の文字列をセットして dec_conf が呼び出されている.これは怪しい.次に dec_conf を見てみる.
いろいろと値のコピーなどを行っているようだが,結局のところ encrypt_code が呼び出されていて,これが肝のようだ.
encrypt_code 自体は小さな関数で,さらにそこからどこかにも飛んでいない純粋な処理を行う関数のようなので,ここを丹念に見ることにする.しかし,decrypt_conf の中で encrypt_code という名前の関数が呼び出されるということは……2 回処理にかけたら元に戻るアルゴリズム? XOR 的な? という想像が働いてくる.コードの中にもちらっと見えるし.読んだ結果を以下に書き込んだ.
どうやら,この関数は文字列とその長さを引数に受けとり,その文字列と “BB2FA36AAA9541F0” (の繰り返し) の ASCII コードで 1 文字ずつ XOR をかける処理を行うもののようだ.

ということで,同等の関数を Python で書いてみた.main とのあいだには memmove している dec_conf 関数があるが,たぶん特に何も考えず,dec_conf への引数を渡してみたら良いのではないかとやってみる.
# decrypt.py
import sys

def decrypt(code):
    div = 0x10 # len(key)
    key = "BB2FA36AAA9541F0"

    result = ""
    for i in range(len(code)):
        result = result + chr(ord(code[i]) ^ ord(key[i % div]))

    return result

for code in sys.stdin:
    print(decrypt(code.rstrip()))
main で渡されていた文字列をいくつか試してみる.
cuckoo@cactus ~/work $ python decrypt.py
m ])5
/boot
m4S4nAC/n2_AD
/var/run/sftp
m.[$nFR$7nLQQGF
/lib/udev/udev
良さそう!

もともとのドメインを探して strings を全部喰わせてみる:
cuckoo@cactus ~/work $ strings /data/cuckoo/storage/analyses/2/binary | python decrypt.py | grep -a org
2.org
2.org
まだもう少しちゃんとコードを追う必要がありそうだけれども,まあまあいい線行っているのではないか.

参考

2016年7月13日水曜日

Cuckoo で Linux マルウェアの解析

マルウェアの動的解析を行うための Cuckoo Sandbox をセットアップしたときのメモ.

1. 動機と結論

最近運用しはじめたハニーポットにマルウェアがひっかかってきている.いずれは静的解析も含めて解析を行っていきたいと考えているが,まずはお手軽なサンドボックス解析をできる環境を整えようと思った.使用しているハニーポットの種類の問題かもしれないが意外と Linux 向けのマルウェアがかかっていること,また個人的な趣向により Linux 向けマルウェアの解析を目標とする (ゲストの VM に Linux を使用する).

実際にセットアップした感想としては,Cuckoo は Linux ゲストの対応がまだ発展途上にあり,Windows ゲストでの解析に比べまだレベルが落ちると感じた.自分が正しく理解しているならば,以下のような事項はデフォルトでは実現できない.
  • 検体が fork した子プロセスの追跡
  • システムコールのトレース (strace; SystemTap を使用すれば検体の親プロセスのトレースはできていそう)
  • ファイル作成等アクティビティの検出
  • Volatility によるメモリフォレンジック
とはいえ,実際にサンドボックス内で検体を一定時間実行させることにより発生するネットワークトラフィックの PCAP や,そのメモリダンプを取得してある程度手動ででもその解析を行うことができるのは非常に魅力的であった.

2. 環境,方針

自宅に VMware ESXi サーバがあるのでそれを利用することとする.
  • ホスト OS
    (Hardened) Gentoo. 通常であれば Ubuntu あたりを使用するのが楽なのだろうけど,リソースに限りがある自宅サーバで使用するという状況においては,不要なコンポーネントをできる限り排除できるという理由で Gentoo を選んだ.Gentoo であれば Qt をインストールしなくても VirtualBox が使用できる! Gentoo とは選択です (ステマ).Hardened の意味は特になくて,SELinux や grsecurity による制御は使用していない.なんとなくの選択.ホスト OS と言っても ESXi の上で動いている VM であるため,ここで Cuckoo を使用するとなると Nested VM を使用することとなる.
  • 仮想化ソフトウェア
    VirtualBox. 最近の Cuckoo のバージョンは仮想化ソフトウェアに依存しておらず VMware や KVM でも動作するそうだが,特にこだわりはなかったので一番実績がありデフォルトである VirtualBox を使用する.
  • ゲスト OS
    Ubuntu Desktop 16.04 LTS. 一番メジャーなディストリビューションを.もちろんひとつに限定する必要はないが,今回の記事ではこれだけを対象とする.
  • virtualenv
    Cuckoo をはじめとして,関連ツールは大きく Python に依存している.しかも Python 2.7 (Gentoo は現在デフォルトで 3.4 を使用している.もちろん 2.7 も選択できるが.Gentoo ですから). 必要なモジュールを普通にシステムにインストールするとなると Gentoo のパッケージ管理システムである Portage とだいぶ相性が悪いのと,メンテナンスがしづらくなることが考えられるので,Cuckoo 関連用に virtualenv を用意し,その中で作業を行うこととする.
Cuckoo の Web インターフェイス利用までは今回の記事では行かない.あくまでコアと考えられる部分を対象とする.

3. ホスト OS の準備

ホスト OS の上でさらに VM を走らせる必要があるために,それなりのリソースを確保する必要がある.ホスト OS はこのような構成にした.
  • CPU: 2 コア
  • メモリ: 4GB
  • HDD: 64GB (OS, ソフトウェア用)  + 1TB (データ用; マウントポイント /data)
また,ESXi で Nested VM を使用する場合,(ホスト OS の) VM の設定として Virtual Hardware-Assisted Virtualzation (VHV) を有効にする必要がある.vSphere Web Client (要 vCenter) からであれば普通に設定できるようだが,自宅ユーザが普通に使える vSphere Client にはその設定がなく,SSH コンソールから .vmx ファイルを直接編集する必要がある.ちなみに,今回ホスト OS の名前は “cactus.”

- cactus.vmx
vhv.enable = "TRUE"

この設定は vSphere Client から設定を変更するたびに消えてしまうことに注意.そのつど手動で追加してやる必要がある.

Gentoo のインストールは普通に.profile は hardened/linux/amd64 で USE フラグもそんなにいじってないが,CPU 機能の有効化 (cpuinfo2cpuflags の結果) と,acl, caps, icu は有効にした (caps は後に必要なになってくる.他はいつもの癖).

Cuckoo に必要なソフトウェアをドキュメント (http://docs.cuckoosandbox.org/en/latest/installation/host/requirements/) を参考にインストールする.Portage から (システムとして) 以下をインストールした.バージョンは現時点のもので参考まで.
  • app-emulation/virtualbox-5.0.20 -qt4 +headless
  • app-emulation/virtualbox-extpack-oracle-5.0.20.106931
  • net-analyzer/tcpdump-4.7.4
  • dev-vcs/git-2.7.3-r1
  • dev-python/virtualenv-13.1.2
  • dev-lang/python-2.7.10-r1 +sqlite
  • net-dns/dnsmasq-2.75 (オプション; 後述)
少し USE フラグの調整をする.VirtualBox はこのあと全てコマンドラインで扱うことになるので,GUI は不要.“-qt4 +headless” すると依存コンポーネントがかなり減る.また Python は 2.7 系も最初からインストールされていたが,sqlite フラグを立ててアップデート.Volatility が Python の sqlite3 モジュールに依存するため.また,VirtualBox 関連のパッケージは package.accept_keywords に記載して新しいバージョン (5.x 系) を入れることにした.

ドキュメントでは Volatility のインストールも必要とされていて,標準で ebuild も提供されているが,virtualenv を使用するとシステムにインストールされたモジュール (/usr/lib64/python2.7/site-packages 以下) が参照されないこともあり,virtualenv のほうで自前でインストールすることとする.

このあたりからドキュメントの流れには沿わずにやっていく.

ガイドにしたがって Cuckoo 用ユーザが tcpdump を使用できるように cabality を設定.caps フラグがここで効いてきている.
dr@cactus ~ $ sudo setcap cap_net_raw,cap_net_admin=eip /usr/sbin/tcpdump
dr@cactus ~ $ sudo getcap /usr/sbin/tcpdump
/usr/sbin/tcpdump = cap_net_admin,cap_net_raw+eip

Cuckoo 用ユーザを作成.通常のデーモン用ユーザのようにログインシェルを無効化して作成したいところだが,当該ユーザのシェルからやることがたくさんあるので,ほぼ普通のユーザとして作る.
dr@cactus ~ $ sudo useradd -r -m cuckoo
dr@cactus ~ $ sudo gpasswd -a cuckoo vboxusers

今後複数のユーザを行き来して作業を行う.ますは Cuckoo ユーザで virtualenv の作成.Python 2.7 を指定すること.
dr@cactus ~ $ sudo su - cuckoo
cuckoo@cactus ~ $ virtualenv --python=python2.7 ~/cuckoo
cuckoo@cactus ~ $ source ~/cuckoo/bin/activate
(cuckoo)cuckoo@cactus ~ $

4. Volatility

まずは Volatility のインストールから.pip には提供されていない (と思う) ので,GitHub レポジトリから取得し,(virtualenv に) インストール.あと関連パッケージも pip でインストール.参考: https://github.com/volatilityfoundation/volatility/wiki/Installation
(cuckoo)cuckoo@cactus ~ $ cd volatility/
(cuckoo)cuckoo@cactus ~/volatility $ python setup.py install
(cuckoo)cuckoo@cactus ~/volatility $ pip install distorm3 pycrypto OpenPyxl ujson

PIL (Python Imaging Library) は現在は Pillow としてメンテナンスされていると理解している.libjpeg に依存していたので,それもインストール.
dr@cactus ~ $ sudo emerge -av media-libs/libjpeg-turbo
(cuckoo)cuckoo@cactus ~/volatility $ pip install Pillow

yara も書かれているのだけど,実際インストールすると Cuckoo 起動時にエラーが出るようになるのでアンインストールした.

Volatility のインストールはここでしておくが,実際には Cuckoo から利用できる解析内容は Windows 向けの内容に限られており Linux ゲストのための解析を (自動で) かけることは現時点ではできない.でも,Cuckoo 実行時にメモリダンプを取得すればあとからの解析はできてとても興味深いので…….

5. Cuckoo インストール

GitHub から取得.解析結果のレポート等はこの cuckoo ディレクトリの下に保存されることになるので,データ用のパーティションに配置する.
dr@cactus /data $ sudo git clone https://github.com/cuckoosandbox/cuckoo.git
dr@cactus /data $ sudo chown -Rv cuckoo:cuckoo cuckoo

(cuckoo)cuckoo@cactus /data/cuckoo $ pip install -r requirements.txt
ここで設定なしに実行してみようとすると,Windows モニタ用のバイナリ (使わないけど),およびシグニチャをダウンロードしてくるように言われるので従ってダウンロード.

(cuckoo)cuckoo@cactus /data/cuckoo $./utils/community.py -waf

ここまでで Cuckoo が起動するようになる.ただし,これからゲスト VM の作成や設定などが待っている.

6. ゲスト OS の準備

前述のとおり,Ubuntu Desktop 16.04 を VirtualBox で動作させることとする.少しネットワーク周りの設定がややこしい.
  • “hostonly” ネットワークを使用する.必要に応じてホスト OS で NAT し,インターネットに出られるようにする.
  • ホスト OS の IP アドレスアサインに DHCP は使用せず,固定アドレスを設定する.これは Cuckoo が固定の IP アドレスを対象にパケットキャプチャを行うため.
  • ホスト OS で dnsmasq を動作させて,ゲスト OS が名前解決できるようにする.
NAT や DNS は,少なくともセットアップ時にはインターネット接続が使用できた便利だが,解析を行う時に有効にしたままだと,検体 (マルウェア) のトラフィックがインターネットに出ていくことになるので注意が必要.

ここから先はとにかく慣れない VirtualBox CLI で頑張る.

“hostonly” ネットワークを使用するためのホスト OS 側インターフェイスの作成:
(cuckoo)cuckoo@cactus ~ $ VBoxManage hostonlyif create
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Interface 'vboxnet0' was successfully created
cuckoo@cactus ~ $ VBoxManage hostonlyif ipconfig vboxnet0 --ip 192.168.56.1
cuckoo@cactus ~ $ ip addr show dev vboxnet0
5: vboxnet0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
    link/ether 0a:00:27:00:00:00 brd ff:ff:ff:ff:ff:ff
    inet 192.168.56.1/24 brd 192.168.56.255 scope global vboxnet0
       valid_lft forever preferred_lft forever

NAT 設定:
dr@cactus ~ $ sudo sysctl -w net.ipv4.ip_forward=1
dr@cactus ~ $ sudo iptables -S
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-A FORWARD -s 192.168.56.0/24 -i vboxnet0 -o tun0 -m conntrack --ctstate NEW -j ACCEPT
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
dr@cactus ~ $ sudo iptables -S -t nat
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-A POSTROUTING -j MASQUERADE
あと必要に応じて /etc/sysctl.conf にも記述.

VirtualBox の DHCP サーバ機能停止:
(cuckoo)cuckoo@cactus ~ $ VBoxManage list dhcpservers
NetworkName:    HostInterfaceNetworking-vboxnet0
IP:             192.168.56.100
NetworkMask:    255.255.255.0
lowerIPAddress: 192.168.56.101
upperIPAddress: 192.168.56.254
Enabled:        Yes

(cuckoo)cuckoo@cactus ~ $ VBoxManage dhcpserver remove -netname HostInterfaceNetworking-vboxnet0
dnsmasq の設定と起動.DHCP 機能を切ることを忘れずに:
# /etc/dnsmasq.conf
interface=vboxnet0
no-dhcp-interface=vboxnet0
bind-interfaces

dr@cactus ~ $ rc-service dnsmasq start

続いてゲスト VM の作成.1 点ポイントとしては,VRAM の容量を増やしてやらないと VRDE で RDP 接続したときの表示がうまくいかなかった.

(cuckoo)cuckoo@cactus ~ $ VBoxManage createvm -name "ubuntu-16.04_x64" --ostype Ubuntu_64 --basefolder /data/vm --register
Virtual machine 'ubuntu-16.04_x64' is created and registered.
UUID: eb33474a-6f34-4165-8ee8-9828bf9672a8
Settings file: '/data/vm/ubuntu-16.04_x64/ubuntu-16.04_x64.vbox'
(cuckoo)cuckoo@cactus ~ $ VBoxManage modifyvm "ubuntu-16.04_x64" --memory 2048 --vram 64 --nic1 hostonly --hostonlyadapter1 vboxnet0
(cuckoo)cuckoo@cactus ~ $ VBoxManage createhd --filename /data/vm/ubuntu-16.04_x64/ubuntu-16.04_x64.vdi --size 25600
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Medium created. UUID: 3ade2e5c-6277-4a00-b5a5-09921a84a8ac
(cuckoo)cuckoo@cactus ~ $ VBoxManage storagectl "ubuntu-16.04_x64" --name sata --add sata --portcount 2 --bootable on
(cuckoo)cuckoo@cactus ~ $ VBoxManage storageattach "ubuntu-16.04_x64" --storagectl sata --port 1 --type hdd --medium /data/vm/ubuntu-16.04_x64/ubuntu-16.04_x64.vdi
(cuckoo)cuckoo@cactus ~ $ VBoxManage storageattach "ubuntu-16.04_x64" --storagectl sata --port 2 --type dvddrive --medium /home/cuckoo/ubuntu-16.04-desktop-amd64.iso
(cuckoo)cuckoo@cactus ~ $ VBoxHeadless --startvm "ubuntu-16.04_x64" --vrde on

Oracle VM VirtualBox Headless Interface 5.0.20_Gentoo
(C) 2008-2016 Oracle Corporation
All rights reserved.

VRDE server is listening on port 3389.
RDP で接続して OS インストール作業.終わったらインストールメディアの detatch.
(cuckoo)cuckoo@cactus ~ $ VBoxManage storageattach "ubuntu-16.04_x64" --storagectl sata --port 2 --medium emptydrive

ゲスト OS の中ではいくつかの設定を.
  • 自動アップデート,diag 情報送信の無効化
  • ネットワーク設定 (今回自分は 192.168.56.101/24, ゲートウェイと DNS は 192.168.56.1 (ホスト OS の vboxnet0) を設定)
  • Cuckoo エージェント (agent/agent.{py,sh}) のダウンロード・実行.起動時に agent.sh が実行されるように /etc/rc.local に登録しておくと良い
  • タイムゾーン (今回ホスト,ゲスト OS ともに UTC を設定した)
一通り終わったら,スナップショットを取って終了.この状態から検体が実行されることになる.
cuckoo@cactus ~ $ VBoxManage snapshot "ubuntu-16.04_x64" take "initial" --pause
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Snapshot taken. UUID: 27d7efba-187d-47fd-a3ab-caaa0a0052c5
cuckoo@cactus ~ $ VBoxManage controlvm "ubuntu-16.04_x64" poweroff
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
cuckoo@cactus ~ $ VBoxManage snapshot "ubuntu-16.04_x64" restorecurrent
Restoring snapshot 27d7efba-187d-47fd-a3ab-caaa0a0052c5
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%

7. Cuckoo の設定

基本的にはドキュメントとファイルを眺めて,ということろだが必要最小限な設定について.
# conf/cuckoo.conf
[cuckoo]
version_check = off
machinery = virtualbox
memory_dump = off
max_machines_count = 1

[routing]
route = none
internet = none

[resultserver]
ip = 192.168.99.60
routing の設定に注意.コメントを読むと route = none を設定しておくとトラフィックは外部に出ていかないように読めるのだが,実際には,「Cuckoo 側では特にネットワーク関連の制御を行わない」という意味である.そのため,上で行ったホスト OS での NAT の設定が残っている状態だと,検体解析時のゲスト OS (つまりマルウェアの) トラフィックはインターネットに出ていくことになる.また,memory_dump はデフォルトの off のままにしているが,これを on にすれば実行時のメモリダンプが生成される.ただし,フルメモリダンプになるため容量を食うので注意 (今回作成した VM だと 2GB).でも,それを Volatility で見てみるといろいろなことがわかる.resultserver の IP アドレスはホスト OS の IP アドレス.デフォルトでは vboxnet0 のデフォルトアドレスが設定されているが,VM が立ち上がっていないときにリンクダウンしたりするので,その他のアドレスの指定が推奨されている.

# conf/virtualbox.conf
[virtualbox]
machines = ubuntu-16.04_x64

[ubuntu-16.04_x64]
label = ubuntu-16.04_x64
platform = linux
ip = 192.168.56.101
ここまで設定すると,ようやく解析ができるようになる.

8. 解析

cuckoo.py を実行すると,
(cuckoo)cuckoo@cactus /data/cuckoo $ python cuckoo.py

  .-----------------.
  | Cuckoo Sandbox? |
  |     OH NOES!    |\  '-.__.-'
  '-----------------' \  /oo |--.--,--,--.
                         \_.-'._i__i__i_.'
                               """""""""

 Cuckoo Sandbox 2.0-dev
 www.cuckoosandbox.org
 Copyright (c) 2010-2015

2016-07-12 10:46:08,338 [lib.cuckoo.core.scheduler] INFO: Using "virtualbox" as machine manager
2016-07-12 10:46:11,059 [lib.cuckoo.core.scheduler] INFO: Loaded 1 machine/s
2016-07-12 10:46:11,086 [lib.cuckoo.core.scheduler] INFO: Waiting for analysis tasks.
と待ち状態になるので,別コンソールから util/submit.py で検体を提出:
(cuckoo)cuckoo@cactus /data/cuckoo $ ./utils/submit.py /data/malware/da8140274788214c9451d950e0f323c0c51188c5bf847bafef9f48c023e91a55
Success: File "/data/malware/da8140274788214c9451d950e0f323c0c51188c5bf847bafef9f48c023e91a55" added as task with ID 1


すると Cuckoo コンソール側ではこんな出力が出てくる:
2016-07-12 10:48:39,473 [lib.cuckoo.core.scheduler] INFO: Starting analysis of FILE "da8140274788214c9451d950e0f323c0c51188c5bf847bafef9f48c023e91a55" (task #1, options "")
2016-07-12 10:48:39,580 [lib.cuckoo.core.scheduler] INFO: Task #1: acquired machine ubuntu-16.04_x64 (label=ubuntu-16.04_x64)
2016-07-12 10:48:39,721 [modules.auxiliary.sniffer] INFO: Started sniffer with PID 22438 (interface=vboxnet0, host=192.168.56.101, pcap=/data/cuckoo/storage/analyses/1/dump.pcap)
tcpdump: listening on vboxnet0, link-type EN10MB (Ethernet), capture size 262144 bytes
2016-07-12 10:48:48,464 [lib.cuckoo.core.guest] INFO: Starting analysis on guest (id=ubuntu-16.04_x64, ip=192.168.56.101)
2016-07-12 10:49:00,126 [lib.cuckoo.core.guest] INFO: ubuntu-16.04_x64: analysis completed successfully
63 packets captured
67 packets received by filter
0 packets dropped by kernel
2016-07-12 10:49:02,226 [lib.cuckoo.common.objects] WARNING: Unable to import yara (please compile from sources)
2016-07-12 10:49:08,274 [lib.cuckoo.core.scheduler] INFO: Task #1: reports generation completed (path=/data/cuckoo/storage/analyses/1)
2016-07-12 10:49:08,305 [lib.cuckoo.core.scheduler] INFO: Task #1: analysis procedure completed

そしてレポートやログが示されたディレクトリ (/data/cuckoo/storage/analyses/1) に保存されている.

9. 感想

まず,上記のタイムスタンプを見てみると 12 秒間しか実行されていない.短い.これは多分,検体自身の親プロセスしか追えていなくて,親プロセスの終了とともに解析が終了しているのだと思う.実際の挙動をもっと見るために解析タイムアウト (デフォルト 120 秒) まで続ける場合には,util/submit.py--enforce-timeout オプションが使える.また,レポート (reports/report.json) の内容は少し期待外れで,ネットワークアクティビティと strings, VirusTotal でのスキャン結果くらいしか得られていない.これは Windows 向けマルウェアを malwr.com に投げたときとはずいぶん状況が違うので,Linux ゲスト対応がまだ発展途上なのであるのだと思う.
参考までに,当該ディレクトリの ls -lRreport.jsonhttps://drmn.jp/blog/20160713-cuckoo/ にアップロードした.

参考

2016年3月7日月曜日

Gephi ことはじめ

グラフとかビジュアライゼーションとかやってみたい,ということで Gephi を触ってみた.試してみるネタとして,手元の Gentoo にインストールされているパッケージの依存関係をグラフにするというもの.

1. データの準備

現在インストールされているパッケージは 1,100 ほど.eix と equery を使用して,それぞれのパッケージの依存関係をすべて CSV に落とす.
# eix-installed -q all | while read pkg; do equery -q d $pkg | sed -e "s|$|,$pkg|"; done | tee data.csv
# wc -l data.csv
9783 data.csv
# head data.csv
x11-libs/gtk+-3.18.7,app-accessibility/at-spi2-atk-2.18.1
app-accessibility/at-spi2-atk-2.18.1,app-accessibility/at-spi2-core-2.18.3
app-accessibility/speech-dispatcher-0.8.1,app-accessibility/espeak-1.47.11-r1
www-client/chromium-49.0.2623.75,app-accessibility/speech-dispatcher-0.8.1
app-eselect/eselect-blas-0.1,app-admin/eselect-1.4.4
app-eselect/eselect-cblas-0.1,app-admin/eselect-1.4.4
app-eselect/eselect-ctags-1.18,app-admin/eselect-1.4.4
app-eselect/eselect-fontconfig-1.1,app-admin/eselect-1.4.4
app-eselect/eselect-java-0.1.0,app-admin/eselect-1.4.4
app-eselect/eselect-lib-bin-symlink-0.1.1,app-admin/eselect-1.4.4
それなりの規模のデータになった.

2. グラフの作成

Gephi のチュートリアルを見ながら試行錯誤してみた結果が以下.
SVG 版: https://drmn.jp/blog/20160307-portage.svg

何かの目的を持ってグラフを作成したわけではないのだけれど,簡単なことは見えてくる.
  • pkgconfig が依存され度ナンバーワン
  • Python, Perl, Java, Haskell 関連のパッケージは当該言語内の依存関係が強い (当然).そのなかでも Haskell は孤高の存在
  • 依存によるコミュニティ分類が,パッケージの種類に対応していそうなところと,混ざっていそうなところがある.メディア関連ライブラリはかなり独立しているのに対し,X 関連と開発関連が混じっているという点が見られる

参考

2016年2月14日日曜日

xmonad いじり

3 年ぶりくらいに xmonad をいじって,なかなか快適になったので忘れないようにメモしておく.3 年前の記事が dr's tech memo: Haskell に興味がある人向け xmonad 設定ガイド にあるため,その後どういった変更を行ったかについてまとめる.現在の動作環境は以下.
  • もちろん Gentoo
  • ghc-7.8.4
  • xmonad-0.11-r1
  • xmonad-contrib-0.11.2

1. startupHook

startupHook を利用して,従来 .xinitrc に書いていたものを少し xmonad のほうに持ってきた.とはいえ .xinitrc との棲み分けをちゃんとできているわけではなく,適当.
import XMonad.Util.Cursor               -- setDefaultCursor
import XMonad.Util.Run                  -- unsafeSpawn, spawnPipe, hPutStrLn

main = do
    myStatusBar <- spawnPipe "xmobar"
    xmonad $ ewmh
           $ defaultConfig {
                   layoutHook      = myLayoutHook
                 , manageHook      = myManageHook
                 , handleEventHook = myHandleEventHook
                 , modMask         = myModMask
                 , logHook         = myLogHook myStatusBar
                 , startupHook     = myStartupHook
                 }
                 `additionalKeysP` myAdditionalKeysP

myStartupHook = do
    setDefaultCursor xC_left_ptr
    unsafeSpawn "thunderbird"
    unsafeSpawn "pidgin"
startupHookX () の型として定義されている.
ghci> :m +XMonad
ghci> :i startupHook
data XConfig (l :: * -> *)
  = XConfig {..., startupHook :: !X (), ...}
        -- Defined in ‘XMonad.Core’
xmonad における 「アクション」 はこれを満たす関数 (モナド) になっているので,スタートアップ時に実行したいアクションを (do でつなげて) 列挙してやれば良い.上記の setDefaultCursor はその名のとおりポインタを設定するもの.従来 xsetroot コマンドで行っていたけれど,たまたま見つけたので使ってみたというレベルのもの.なお参考までに,.xinitrc は以下のようになっている.
# ~/.xinitrc
xrdb -merge $HOME/.Xresources

xmodmap $HOME/.Xmodmap

autocutsel &
autocutsel -selection PRIMARY &
dispad

export GTK_IM_MODULE="uim"
export QT_IM_MODULE="uim"
uim-xim &
export XMODIFIERS="@im=uim"

exec xmonad
他のものも xmonad のほうに移せるだろうけれど,とりあえず残している.特に明確な基準はない.
それから,startupHook を使用せずとも,xmonad 関数を呼び出す前の xmobar を spawn しているところにアクションを列挙していけば同様のことができると想像している (試していないけれど).ただ,せっかく startupHook という機構を用意してくれているので,戻り値が必要な特殊なケースを除けば,別のところにまとめておいたほうが見通しが良くなるのだと思う.

2. additionalKeysP

前回はキーバインドの追加に additionalKeys を使用していたが,同様に XMonad.Util.EZConfig に用意されている additionalKeysP を使用することにした.従来は xbindkeys で割り当てていたものを xmonad に持ってこようとしたときに,additionalKeysP のほうが手軽に利用できるキーが多いようだったため.
import XMonad.Util.Run                  -- unsafeSpawn
import XMonad.Util.EZConfig             -- additionalKeysP

myAdditionalKeysP = [
    --snip --
    
    -- volume control
    , ("<XF86AudioRaiseVolume>", unsafeSpawn "amixer set Master playback 10+")
    , ("<XF86AudioLowerVolume>", unsafeSpawn "amixer set Master playback 10-")
    , ("<XF86AudioMute>",        unsafeSpawn "amixer set Master toggle")
    ]
利用できるキーは XMonad.Util.EZConfig で参照可能.

3. xmonad と相性の悪いアプリケーションへの対応

3.1. mpv (MPlayer)

MPlayer の時からそうだったけれど,mpv でフルスクリーン表示にならないという問題があった.これは manageHook で mpv のウィンドウを float させてやることで解決する.
import XMonad.Hooks.ManageHelpers       -- isFullscreen, doFullFloat

myManageHook =   manageDocks
             <+> composeAll [
                        isFullscreen                      --> doFullFloat
                      , className =? "mpv"                --> doFloat
                      -- snip --
                      ]

3.2. Java Swing アプリケーション

JabRef など,Swing を利用した Java アプリケーションはうまく表示が行えないという問題があった.まさかウィンドウマネージャとの相性の問題だとは…….と思ったら FAQ にあった.

4. その他

4.1. XMonad.Prompt.Shell

dmenu の代わりに XMonad.Prompt.Shell. 外部プログラムへの依存が減る,カスタマイズ性が高いといったところがメリット.
import XMonad.Prompt                    -- XPConfig
import XMonad.Prompt.Shell              -- shellPrompt

myAdditionalKeysP = [
      ("M-p",   shellPrompt myXPConfig)
      -- snip --
    ]

myXPConfig = defaultXPConfig {
                   font              = "xft:sans-serif:size=6"
                 , bgColor           = "black"
                 , fgColor           = "grey"
                 , promptBorderWidth = 0
                 , position          = Top
                 , alwaysHighlight   = True
                 , height            = 30
                 }

4.2. XMonad.Layout.HintedTile/XMonad.Layout.Renamed

Tall の代わりに XMonad.Layout.HintedTile. ターミナルのようなアプリケーションで 1 行以下の 「余り」 が出ないように,ウィンドウサイズのほうを調整 (縮小) する.場合によっては並んだウィンドウの高さや幅ががたがたになるので,Tall とどっちが良いかは好みによりそう.XMonad.Layout.Spacing も併用すると綺麗になることを期待したのだけれど,どうやら HintedTile とは相性が悪そう (ヒンティングしたあとにスペースを入れることになり,結果として全てのウィンドウでヒンティングが合わなくなる).
それから,レイアウトに機能を足していくと名前が長くなってくるので,それをリネームするための XMonad.Layout.Remaned.
import XMonad hiding (Tall)
import XMonad.Layout.HintedTile
import XMonad.Layout.Renamed

myLayoutHook =   renamed [CutWordsLeft 2]
                 $ smartBorders $ avoidStruts $ maximize $ minimize
                 $ (hintedTile Tall ||| hintedTile Wide)
             ||| (avoidStruts $ noBorders Full)
    where
        hintedTile = HintedTile nmaster delta ratio TopLeft
        nmaster    = 1
        ratio      = 1/2
        delta      = 3/100

4.3. XMonad.Layout.LayoutScreens

スクリーンのなかで workspace を切るという機能.最近 31.5 インチのディスプレイを買ったので,便利に使えるかどうかを試してみている.
import XMonad.Layout.LayoutScreens      -- layoutSplitScreen
import XMonad.Layout.TwoPane

myAdditionalKeysP = [
    -- snip --
    -- split screen
    , ("M-M1-<Space>",   layoutSplitScreen 2 (TwoPane (3/100) 0.7))
    , ("M-M1-S-<Space>", rescreen)
    ]

4.4. XMonad.Hooks.InsertPosition

insertPosition を使用して,新しいウィンドウがアクティブなウィンドウの次に来る,すなわち新しいウィンドウにマスタを取られないようにした.
import XMonad.Hooks.InsertPosition

myManageHook =   insertPosition Below Newer
             <+> manageDocks
             <+> composeAll [ -- snip -- ]

5. スクリーンショット

高解像度っぷりのアピールを含め.

6. 現在の xmonad.hs

 -- ~/.xmonad/xmonad.hs
import XMonad hiding (Tall)
import XMonad.Hooks.DynamicLog          -- logHook-related
import XMonad.Hooks.EwmhDesktops        -- ewmh, fullscreenEventHook
import XMonad.Hooks.InsertPosition
import XMonad.Hooks.ManageDocks         -- manageDocks, avoidStruts, docksEventHook
import XMonad.Hooks.ManageHelpers       -- isFullscreen, doFullFloat
import XMonad.Layout.HintedTile
import XMonad.Layout.LayoutScreens      -- layoutSplitScreen
import XMonad.Layout.Maximize
import XMonad.Layout.Minimize
import XMonad.Layout.NoBorders          -- smartBorders, noBorders
import XMonad.Layout.Renamed
import XMonad.Layout.TwoPane
import XMonad.Prompt                    -- XPConfig
import XMonad.Prompt.Shell              -- shellPrompt
import XMonad.Util.Cursor               -- setDefaultCursor
import XMonad.Util.EZConfig             -- additionalKeysP
import XMonad.Util.Run                  -- unsafeSpawn, spawnPipe, hPutStrLn
import XMonad.Util.WorkspaceCompare     -- getSortByXineramaRule

main = do
    myStatusBar <- spawnPipe "xmobar"
    xmonad $ ewmh
           $ defaultConfig {
                   layoutHook      = myLayoutHook
                 , manageHook      = myManageHook
                 , handleEventHook = myHandleEventHook
                 , modMask         = myModMask
                 , logHook         = myLogHook myStatusBar
                 , startupHook     = myStartupHook
                 }
                 `additionalKeysP` myAdditionalKeysP

myModMask = mod4Mask

myLayoutHook =   renamed [CutWordsLeft 2]
                 $ smartBorders $ avoidStruts $ maximize $ minimize
                 $ (hintedTile Tall ||| hintedTile Wide)
             ||| (avoidStruts $ noBorders Full)
    where
        hintedTile = HintedTile nmaster delta ratio TopLeft
        nmaster    = 1
        ratio      = 1/2
        delta      = 3/100

myManageHook =   insertPosition Below Newer
             <+> manageDocks
             <+> composeAll [
                        isFullscreen                      --> doFullFloat
                      , className =? "mpv"                --> doFloat
                      , className =? "Gimp"               --> doFloat
                      , className =? "Pidgin"             --> doShift "9"
                      , className =? "Thunderbird"        --> doShift "9"
                      ]

myHandleEventHook =   docksEventHook
                  <+> fullscreenEventHook

myLogHook h = dynamicLogWithPP xmobarPP {
                    ppSep    = " | "
                  , ppTitle  = xmobarColor "green" "" . shorten 80
                  , ppOutput = hPutStrLn h
                  , ppSort   = getSortByXineramaRule
                  }

myStartupHook = do
    setDefaultCursor xC_left_ptr
    unsafeSpawn "thunderbird"
    unsafeSpawn "pidgin"

myAdditionalKeysP = [
      ("M-p",   shellPrompt myXPConfig)
    , ("M-m",   withFocused (sendMessage . maximizeRestore))
    , ("M-n",   withFocused minimizeWindow)
    , ("M-S-n", sendMessage RestoreNextMinimizedWin)

    -- toggle dock visibility
    , ("M-b", sendMessage ToggleStruts)

    -- split screen
    , ("M-M1-<Space>",   layoutSplitScreen 2 (TwoPane (3/100) 0.7))
    , ("M-M1-S-<Space>", rescreen)

    -- volume control
    , ("<XF86AudioRaiseVolume>", unsafeSpawn "amixer set Master playback 10+")
    , ("<XF86AudioLowerVolume>", unsafeSpawn "amixer set Master playback 10-")
    , ("<XF86AudioMute>",        unsafeSpawn "amixer set Master toggle")

    -- other commands
    , ("M-S-l", unsafeSpawn "alock -auth pam -bg blank")
    , ("M-c",   unsafeSpawn "chromium --incognito --force-device-scale-factor=1.6")
    ]

myXPConfig = defaultXPConfig {
                   font              = "xft:sans-serif:size=6"
                 , bgColor           = "black"
                 , fgColor           = "grey"
                 , promptBorderWidth = 0
                 , position          = Top
                 , alwaysHighlight   = True
                 , height            = 30
                 }

2015年8月23日日曜日

著作権法と DVD と Linux

先日,以下のニュースを知った.DVD リッピングを行うソフトウェアを配布していた人物が著作権法違反により逮捕されたというものである.
  • JVA ニュースリリース リッピングソフトをアップロードしていた者を著作権法違反で初めて検挙 及び、出版社従業員等を著作権法違反の幇助でも検挙
    http://jva-net.or.jp/news/news_150819/news.pdf
DVD リッピングのソフトウェアなんて,特に Linux を使っていればごく普通に入手できるものである.なぜこれが違法になるのだと疑問に思い調べ始めたところ,2012 年の著作権法改正によってこのあたりが大きく変わり,ソフトウェア配布のみならず私的使用における複製も違法であることがわかった.そんなことはこれまでまったく知らなかったので,3 年遅れで整理しようというエントリである.

なお,できるかぎり正確な記述をしようと努力はしているが,ちゃんと法律を勉強したわけでもないので,エントリの内容に誤りがないことの保証はできないのでご了承ください.

1. 前提: DVD のスクランブルと Linux における扱い

市場に流通している大多数の DVD には Content Scramble System (CSS) というスクランブルがかかっており,視聴時にはプレイヤーがスクランブルを解除する動作となっている.スクランブル解除のために必要な CSS の仕様は DVD Copy Control Association (DVD CCA) にライセンスを受けることで入手できるが,ライセンスにより当該仕様の情報公開が禁止される.PC において DVD 再生を行うにあたり,スクランブル解除を担っているのは DVD ドライブではなくソフトウェアであり,正規のソフトウェアは DVD CCA にライセンスを受けスクランブル解除のコードを利用するということとなる.

Linux 等ではオープンソースソフトウェアが標準的に利用されている.オープンソースソフトウェアは,上記の情報公開禁止条項を満たすことができないため正規に DVD CCA のライセンスを受けることができずスクランブルを解除できないということになる.だが実際にはオープンソースソフトウェアでも DVD の再生を行うことができる.これはライセンスを受けずに CSS を解析した技術を利用しているからである.当該技術は最初は DeCSS というプログラムによって公開され,現在は CSS 解除ライブラリ libdvdcss として標準的に利用されている.Linux 等で DVD 再生を行うことができるメディアプレイヤーは現在ほぼすべてこの技術を利用している.

CSS 仕様が DVD CCA のライセンス範囲に閉じていればコピープロテクションとしての機能を果たすことができるが,DeCSS により CSS 解除機能を持つソフトウェアを自由に作ることができるようになった結果,DVD の複製が可能となった.

2. 著作権法の理解

著作権法における,上記 DVD スクランブルと Linux における扱いに関連する箇所に関してまとめる.なお,引用は現行の著作権法 (2015 年改正) から.

まず,2012 年の著作権法改正によって,CSS が 「技術的保護手段」 に含まれるようになった.
第二条  この法律において、次の各号に掲げる用語の意義は、当該各号に定めるところによる。
二十  技術的保護手段 電子的方法、磁気的方法その他の人の知覚によつて認識することができない方法(次号において「電磁的方法」という。)により、第十七条第一項に規定する著作者人格権若しくは著作権又は第八十九条第一項に規定する実演家人格権若しくは同条第六項に規定する著作隣接権(以下この号、第三十条第一項第二号及び第百二十条の二第一号において「著作権等」という。)を侵害する行為の防止又は抑止(著作権等を侵害する行為の結果に著しい障害を生じさせることによる当該行為の抑止をいう。第三十条第一項第二号において同じ。)をする手段(著作権等を有する者の意思に基づくことなく用いられているものを除く。)であつて、著作物、実演、レコード、放送又は有線放送(次号において「著作物等」という。)の利用(著作者又は実演家の同意を得ないで行つたとしたならば著作者人格権又は実演家人格権の侵害となるべき行為を含む。)に際し、これに用いられる機器が特定の反応をする信号を著作物、実演、レコード若しくは放送若しくは有線放送に係る音若しくは影像とともに記録媒体に記録し、若しくは送信する方式又は当該機器が特定の変換を必要とするよう著作物、実演、レコード若しくは放送若しくは有線放送に係る音若しくは影像を変換して記録媒体に記録し、若しくは送信する方式によるものをいう。
「当該機器が特定の変換を必要とするよう著作物、実演、レコード若しくは放送若しくは有線放送に係る音若しくは影像を変換して記録媒体に記録」 の部分は 2012 年の改正により追加された部分となる.CSS 解除は 「特定の変換」 に該当するため CSS は 「技術的保護手段」 に含まれることになった.

CSS が 「技術的保護手段」 に含まれることによるユーザへの影響として,以下条文により CSS を回避した複製が私的使用においても違法となる (罰則はなし).
第三十条  著作権の目的となつている著作物(以下この款において単に「著作物」という。)は、個人的に又は家庭内その他これに準ずる限られた範囲内において使用すること(以下「私的使用」という。)を目的とするときは、次に掲げる場合を除き、その使用する者が複製することができる。
二  技術的保護手段の回避(第二条第一項第二十号に規定する信号の除去若しくは改変(記録又は送信の方式の変換に伴う技術的な制約による除去又は改変を除く。)を行うこと又は同号に規定する特定の変換を必要とするよう変換された著作物、実演、レコード若しくは放送若しくは有線放送に係る音若しくは影像の復元(著作権等を有する者の意思に基づいて行われるものを除く。)を行うことにより、当該技術的保護手段によつて防止される行為を可能とし、又は当該技術的保護手段によつて抑止される行為の結果に障害を生じないようにすることをいう。第百二十条の二第一号及び第二号において同じ。)により可能となり、又はその結果に障害が生じないようになつた複製を、その事実を知りながら行う場合
なお,CSS 回避を行った視聴については問題ないと理解している.DVD は視聴するものであることを否定する人はいないだろうから,視聴行為自体は 「当該技術的保護手段によつて防止される行為」 には当然含まれない.

であるから,Linux ユーザとしては,以下のようになる.
  • libdvdcss を使用するソフトウェアで DVD を再生することは合法
  • libdvdcss を使用するソフトウェアで DVD をリッピングすることは違法

また,ソフトウェア開発者などへの影響として,以下条文により CSS を回避して複製を行うプログラムの作成・配布を行うことができなくなる (罰則あり,非親告罪).
第百二十条の二  次の各号のいずれかに該当する者は、三年以下の懲役若しくは三百万円以下の罰金に処し、又はこれを併科する。
一  技術的保護手段の回避を行うことをその機能とする装置(当該装置の部品一式であつて容易に組み立てることができるものを含む。)若しくは技術的保護手段の回避を行うことをその機能とするプログラムの複製物を公衆に譲渡し、若しくは貸与し、公衆への譲渡若しくは貸与の目的をもつて製造し、輸入し、若しくは所持し、若しくは公衆の使用に供し、又は当該プログラムを公衆送信し、若しくは送信可能化する行為(当該装置又は当該プログラムが当該機能以外の機能を併せて有する場合にあつては、著作権等を侵害する行為を技術的保護手段の回避により可能とする用途に供するために行うものに限る。)をした者
冒頭に挙げた事例は,当該ソフトウェアをアップロードしていた人物が著作権法違反で,当該サイトにリンクを張っていた人物が著作権法違反の幇助で逮捕された事例であり,上記条文の行為に該当していることになる.

そうすると Linux ディストリビューション / パッケージのミラーを行っているサイトなどが問題になるのではないかと思ったのだが,国内ミラーサーバの Gentoo / Arch Linux のパッケージを確認したところ,libdvdcss とソフトウェアが分離された (ごく一般的な) かたちでソース,バイナリの双方での配布が行われているようだ.一方,Linux Mint は “No codecs” バージョンのみの配布となっている.状況から考えると,以下のような整理になるのだろうかと推測する
  • libdvdcss は CSS 回避の機能のみを持ち,単体での配布は 「著作権等を侵害する行為」 である複製の用途に供するとの判断はできない
  • libdvdcss に依存する DVD 複製ソフトウェアはそれ自身では CSS 回避の機能を持たず,「技術的保護手段の回避」 に該当しない
  • libdvdcss に依存するメディアプレイヤーは,「著作権等を侵害する行為」「技術的保護手段の回避」 の双方に該当しない
  • libdvdcss に依存しない,もしくはバンドル済みの DVD 複製ソフトウェアは,「著作権等を侵害する行為を技術的保護手段の回避により可能とする用途に供する」 に該当する
  • libdvdcss に依存しない,もしくはバンドル済みのメディアプレイヤーは,「著作権等を侵害する行為」 に該当しない
なお,これらの配布に関しては 2012 年以前から不正競争防止法も関連するもよう.ここでは触れない.

これら条文の根底にある考えはこうなのだと思う: 「DVD を複製できないようにスクランブルをかけたのだから,複製してくれるな.複製の手段を配布することも認めない.スクランブルを回避しても目的が視聴のみであれば,いたしかたあるまい」

3. 個人の感想

恥ずかしながら,私的使用における複製も違法であるということを今までまったく知らず,そのことを知ったときにはかなりのショックだった.それまでは音楽 CD と同じようなものと思っていた.音楽 CD は私的使用の範囲内での複製が認められているため吸い出したコンテンツを携帯プレイヤーで楽しむといったことに問題がないが,CSS が利用された DVD にはそのような選択肢がなくなったということになる.
DRM 一般について言えることだが,利用者の選択を制限する方向性の技術は嫌いだ.

以下に著作権法に関して持った疑問を 2 点挙げておく.
  • 私的使用まで踏み込んで違法化することの有効性
    2012 年改正で CSS が 「技術的保護手段」 に含まれるようになったことにより,私的使用においても CSS を回避して複製することが違法となった.ただし,2012 年改正以前においても,複製コンテンツを不特定多数がダウンロードできるようにするといった行為は私的使用の範疇を超えた違法行為であった.実際問題として,CSS を回避した DVD の複製は海外では特に制限対象となっておらず,海外のリソースを参照すればソフトウェアも情報もたやすく入手できる.私的使用まで踏み込んだ制限を行うことで,著作権者の利益をどれだけ守れることになるのだろうか.
  • スクランブルは複製を防止するための手段なのか
    まあこれは,2012 年改正から認められるようになったということなのだけれど.地デジ放送は? などと考え始めると,あまり納得感はない.

参考