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
                 }