拾い物のコンパス

まともに書いたメモ

pycを読む(アセンブリ味)

pycファイルだけが手元にあり,なにが書いてあるか読みたいときがある.
pycのバイトコードは逆アセンブルして読める.
やり方と例文をまとめる.
ただし,これは低レベルな命令になるから可読性は良くない.
直接pythonコードにまで戻す方法があるから,そっちを使ったほうが良い.
厳密な仕様までは調べてないから,間違いはあるかも.指摘歓迎.

環境

各種バージョン

以下のとおり.

$ uname -a
 Linux poppycompass 4.18.6-arch1-1-ARCH #1 SMP PREEMPT Wed Sep 5 11:54:09 UTC 2018 x86_64 GNU/Linux

$ python2
Python 2.7.15 (default, Jun 27 2018, 13:05:28)
[GCC 8.1.1 20180531] on linux2

Arch Linuxの64ビット版.
つまりただのLinuxpythonは2系.

環境作成

環境はローカルに作成した.

$ virtualenv2 ENV
$ . ENV/bin/activate
$ pip install dis

pycを逆アセンブルするスクリプト

以下のスクリプトで変換できる.

# pyc_disasm.py
import dis, marshal
code = open("sample.pyc", "rb").read()[8:]
code = marshal.loads(code)
dis.dis(code)

ここでは,sample.pycというファイルを逆アセンブルする.
sample.pyに変換したいコードを書けばよい. 使い方は簡単.

$ python2 -m compileall sample.py # pyc生成
$ python2 ./pyc_disasm.py

変換一覧

アセンブルしたものはアセンブリ味になっている.
pythonによる記述と逆アセンブルしたものの比較をいくつか挙げておく. 各命令の左にある数値はアドレスのようだ.

ライブラリのインポート

普通にインポート

before

import random

after

    0 LOAD_CONST               0 (-1)
    3 LOAD_CONST               1 (None)
    6 IMPORT_NAME              0 (random)
    9 STORE_NAME               0 (random)

ちょっと書き方を変えたインポート

before

from random import *

after

    0 LOAD_CONST               0 (-1)
    3 LOAD_CONST               1 (('*',))
    6 IMPORT_NAME              0 (random)
    9 IMPORT_STAR

変数定義

before

a = ''

after

        0 LOAD_CONST               0 ('')
        3 STORE_NAME               0 (a)

関数呼び出し

before

def hoge(a,b): 
    return a+b

hoge(1,2)

after
最初の部分が関数定義,後半が呼び出したときの部分.
関数呼び出しは,関数名・引数・CALL・戻り値格納先の順になる

    0 LOAD_CONST               0 (<code object hoge at 0x7fc3dec66b30, file "./sample.py", line 1>)
    3 MAKE_FUNCTION            0
    6 STORE_NAME               0 (hoge)

    9 LOAD_NAME                0 (hoge)
   12 LOAD_CONST               1 (1)
   15 LOAD_CONST               2 (2)
   18 CALL_FUNCTION            2
   21 POP_TOP
   22 LOAD_CONST               3 (None)
   25 RETURN_VALUE

演算

before
2以上の変数による演算.

d = a + b + c

after
a, b, cには,文字列と数値のどちらをいれてもコード自体は変わらない.
直前のLOAD_NAMEで入れる値が変わるだけ.
BINARY_ADDは相手が文字列だと連結,数値だと数値演算を行う.
3つの変数が相手だと,2回BINARY_ADDする.
以下のコードは数値版.

        
        18 LOAD_NAME                0 (a)
        21 LOAD_NAME                1 (b)
        24 BINARY_ADD
        25 LOAD_NAME                2 (c)
        28 BINARY_ADD
        29 STORE_NAME               3 (d)
        32 LOAD_CONST               1 (None)
        35 RETURN_VALUE

文字列連結だと以下の通り
before

allchar = string.ascii_letters + string.punctuation + string.digits # import string後

after

        80 LOAD_NAME                0 (string)
        83 LOAD_ATTR                9 (ascii_letters)
        86 LOAD_NAME                0 (string)
        89 LOAD_ATTR               10 (punctuation)
        92 BINARY_ADD          
        93 LOAD_NAME                0 (string)
        96 LOAD_ATTR               11 (digits)
        99 BINARY_ADD          
       100 STORE_NAME              12 (allchar)

ループ

before

sum = 0
for i in range(100):
    sum += i

after FOR_ITERにジャンプしてループを回しているようだ.

       0 LOAD_CONST               0 (0)
       3 STORE_NAME               0 (sum)

       6 SETUP_LOOP              30 (to 39)
       9 LOAD_NAME                1 (range)
      12 LOAD_CONST               1 (100)
      15 CALL_FUNCTION            1
      18 GET_ITER
 >>   19 FOR_ITER                16 (to 38)
      22 STORE_NAME               2 (i)

      25 LOAD_NAME                0 (sum)
      28 LOAD_NAME                2 (i)
      31 INPLACE_ADD
      32 STORE_NAME               0 (sum)
      35 JUMP_ABSOLUTE           19
 >>   38 POP_BLOCK
 >>   39 LOAD_CONST               2 (None)
      42 RETURN_VALUE

ありがちなコード

before
とあるCTF問題にあった一文.

password = ('').join((choice(allchar) for a in range(randint(60, 60))))

after
繰り返し部はGET_ITERが入るようだ.

    103 LOAD_CONST               6 ('')
    106 LOAD_ATTR               13 (join)
    109 LOAD_CONST               7 (<code object <genexpr> at 0x7f2fb42d9330, file "ransomware.py", line 11>)
    112 MAKE_FUNCTION            0
    115 LOAD_NAME               14 (range)
    118 LOAD_NAME               15 (randint)
    121 LOAD_CONST               8 (60)
    124 LOAD_CONST               8 (60)
    127 CALL_FUNCTION            2
    130 CALL_FUNCTION            1
    133 GET_ITER            
    134 CALL_FUNCTION            1
    137 CALL_FUNCTION            1
    140 STORE_NAME              16 (password)

おわりに

正直,いざpyc読むときにはこんな低レベルなやつを読むのは労力が要る.正にドM.
pythonコードに直に変換する方法がある.
そっちを使ったほうが余程楽.

参考

pycファイルの逆アセンブル - one day in summer

.pycファイルを作成する方法メモ - かせきのうさぎさん

volatilityにSANSのプラグインを追加する方法

volatilityでmimikatzを使いたくなった.
セットアップ方法が見つからなかったから書き残す.

mimikatzとは

Windowsのパスワードなどの各種認証情報をメモリから抜き出すツール.
管理者権限の取得やなりすましにも使える.
CTFではメモリダンプからアカウントの平文パスワードを抜き出すのによく使う.
デフォルトではvolatilityと連携して使うことができない.
SANSがプラグインを書いて公開しているから,それを追加してやれば良い.

やり方

基本はプラグインをコピーするだけ.

$ git clone https://github.com/volatilityfoundation/volati
$ git clone https://github.com/sans-dfir/sift-files       
$ cp sift-files/volatility/* volatility/volatility/plugins

これでmimikatzコマンド自体は使えるよう.しかし・・・

$ python2 volatility/vol.py -f ./memdump.mem mimikatz
Volatility Foundation Volatility Framework 2.6
*** Failed to import volatility.plugins.openioc_scan (ImportError: No module named ioc_writer)
ERROR   : volatility.debug    : You must specify something to do (try -h)

pythonモジュール不足でまだ動かない.
ERROR : volatility.debug : You must specify something to do (try -h)はコマンドが無いときにも出る.わかりにくい.
モジュールについてはvirtualenv2でローカル環境を作成して,整えていく.

$ virtualenv2 ENV && . ENV/bin/activate
$ pip install distorm3 pycrypto lxml ioc_writer colorama construct==2.5.5-reupload

Crypt.HashモジュールのためにpycryptoAttributeError: 'module' object has no attribute 'ULInt32'construct==2.5.5-reuploadで対処.

$ python2 volatility/vol.py -f <mem file> --profile=WinXPSP2x86 mimikatz

これで動く.

参考

error in attribute with construct module · Issue #15 · volatilityfoundation/community · GitHub

python - ImportError: No module named Crypto.Cipher - Stack Overflow

lsass.exe のダンプからユーザーパスワードを抽出 :mimikatz

https://www.iij.ad.jp/dev/tech/techweek/pdf/171108_02.pdf

GitHub - gentilkiwi/mimikatz: A little tool to play with Windows security

Linux系のバックアップ方法メモ

Linuxが動いているサーバのバックアップを取ろうとした時に考えたことのメモ.
差分とか定期的バックアップとかいった頭の良いことは他に書いてあるから,取り扱わない. サクッとできるやつだけ. あくまでメモだから,詳しい方法とかは割愛する.

状況

そのうちバックアップを取ろうと思っていたLinuxがある日突然壊れた.
幸いハードの故障だったからデータは無事だったが,今後を考えて一度バックアップを作ったほうが良いなと思った.

方法

cronで定期的に取ったり,差分だけ取ったりとか色々あるみたいだが,難しいことを考えなければ以下の3つくらいか.

フルバックアップ

物理的な記憶装置に書いてあるデータを全部丸々コピー.
一番確実にデータを取得できる上に,複数パーティション・未使用領域といったデータも一緒に保存できるから,この時点で消してしまっているファイルとかも復旧できるのが強み.
おまけにそのままOSも起動できる(デュアルブートも).
欠点は1TBのバックアップには1TBの記憶装置が必要になること.例え9GBしか使っていなくても,問答無用で1TB必要.
安全にコストはつきものである.
具体的な方法としてはddコマンドかな.
$ dd if=/dev/sda of=backup bs=32M

ファイルシステムごとバックアップ

使っている領域だけをコピーする.
9GBのLinuxなら9GBだけの容量で済む.フルバックアップで悩ましかった問題を解決できる.
当然,未使用領域は保存されないから注意は必要.
フルバックアップの次に検討すると良さそう.

今回調べるまで知らなかったが,Ubuntuextシリーズで,CentOSXFSファイルシステムが違う.
だから,使うツールも前者は$ e2image -ra -p /dev/sda1 /dev/sdb1で後者は$ xfsdump -l0 - /dev/sda1 ./backup.fsみたいになる.
CentOSは古いやつだとxfsdumpではなく,xfs_copyになるようだ.
自分のディストリに合ったやつを選ぶこと.

必要なやつだけまとめる

重要なファイルやディレクトリのみを保存する.
最も簡単かつ玄人向け.
一番容量が小さくなりやすい.
しかし,必要なファイルがわかる人でないと後で困る.
やり方はzip/tar/cpなりで頑張る感じ.
厄介なことに,$ cp -r / /tmp/backup とかやると特殊ファイル関係でエラーが出て時間を溶かすこともある.
シンプルな手法だからといって,作業量も少なくはいかないようだ(経験談).
/etc, /usr, /var, /home, /rootを中心に見ると良さそう.

おわり

とりあえずファイルシステムのバックアップを取ろうかな.

参考

ディスクのクローン - ArchWiki

Obtain file system information | Linux

6.9. ext4 から XFS への移行 - Red Hat Customer Portal

いつの間にかLinuxの/bin関係が/usrへのシンボリックリンクへ変わっていた話

タイトルの通り.気になったから調べた.
調査結果を書き残すが,誰にとって役に立つのかはわからない.

何が起こった

普段はArch Linuxを使っている.
何気なくls / をすると,いつの間にか/bin/usr/binへのシンボリックリンクになっていた.
な・・・ なにを言っているのかわからねーと思うが,おれも何でこうなっているのかわからなかった・・・.
複数のディレクトリがシンボリックリンクになっていて,変わっていたのは以下の通り.

/bin       -> /usr/bin/
/sbin      -> /usr/bin/
/usr/sbin  -> /usr/bin/
/lib       -> /usr/lib/
/lib64     -> /usr/lib/
/usr/lib64 -> /usr/lib/

新手のマルウェアに感染したのかとも思ったが,それにしてはどうにも様子がおかしい.
調べたら,割りと普通のことみたいだった.

調査結果

概要

答えはここにあった.
昔は/usrなどを分けることに意味があったが,最近のLinuxのファイル構造的に必要がなくなってきたらしい.
統合してしまえば,スクリプトに書くパスやらが簡略化されて利点が多いから,統合していこうぜ,ということらしい.

もうちょい詳細

英語の解釈が怪しいから,間違っていたら指摘お願いします.
通常,/bin/sbinディレクトリには,single user mode用のユーティリティが入るようにFilesystem Hierarchy Standardで定義されている.
昔は/usrは必要に応じて後からマウントするやつだから,ディレクトリを分けていた.
しかし,今では起動時にマウントされるディレクトリに含まれている上に,最近の/bin & /sbinのツールの多くは/usrを予めマウントしないと使えないやつが多くなったから,不可分になってしまっているらしい(未確認,本当か?).
だから,現行のOSでは,/bin/usr/binを分けることなく,/にある全てのディレクトリは対応する/usrディレクトリとマージすることにしたらしい.
というわけで,/bin/usr/bin/lib/usr/lib/sbin/usr/binへと言うように統合された.
/sbin/binを統合しちゃったことに対しては,良いのか?,という気はする.1つにするに越したことはないが.

利点

大きな利点は,システムの複雑さを軽減できること.
例えば,ネットワーク共有の観点から言えば,今までは共有時に/usr以下にあるコアコンポーネントを共有できず,マウントする側で用意する必要があった.これがなくなる.
更に,開発時にパスの違いからツール場所をハードコーディングしていたやつを1つにできる.
他には,/usrを複数のゲストOSで読み取り専用で共有できるようになり,ゲストのファイルシステムが数MBまで縮小できる.
以上のような利点があるらしい.

トリビア

初めて今回のマージをしたのは,15年前のOracle Solarisからで,完了したのはSolaris 11らしい.
思ったより歴史が古い.

さいごに

まだArch Linuxでしか確認していないが,Ubuntu 17.10とかでも同じことになっているのだろうか.
気が向いたら確認して見る予定.

参考URL

同じことを考えた人がいたようだ: linux - Why is /bin a symbolic link to /usr/bin? - Unix & Linux Stack Exchange

詳細: TheCaseForTheUsrMerge

Arch Linuxでカーネルパニックが起こった時にやったこと

普段使っている相棒はArch Linuxをインストールしている。
Ubuntuと違い、提供されるパッケージが最新のものだから使いやすい。
しかし今回pacman -Syuを実行したところ、多分kernel panic(freeze?、hang up?、違いがわからない)と呼ばれる現象が起こるようになった。
何が起こったかとどう対処したかを書き残す。
ついでにパッケージ全体を一つ前に戻すスクリプトも書いてみた。

最初に

これを読んでいるということは、それっぽい惨劇が起きたのかもしれない。
そんな人はまず落ち着こう。
テンパってパッケージをやたらと消したり、変更を加えすぎると復旧したときに後が怖い。

問題

普段通りに

$ sudo pacman -Syu

した後、頻繁に画面が凍結して、入力を一切受け付けなくなった。
凍結時の特徴はCapsLockキーのランプやWifiのランプが点滅する。
Ctrl+Alt+F1とかでコンソールに切り替えようとしてもダメ。
1時間に10回(落とす・点ける)以上電源ボタンを触ったのは初めてで壊れやしないかとヒヤヒヤした。
毎回起動時にジャーナル復元メッセージが出たぜ・・・。

パニックトリガー

パニックする引き金になった動作は以下の通り。

  • ネットに繋ぐ(短時間でパニック、特にWifi)
  • 普段通りに使う(プログラムのコンパイル・ファイル操作)
  • ファイルマネージャ(pantheon-files)でUSBをマウント(一発アウト、nautilusはセーフ)

その他起こった事象

$ dmesg
...(snip)...
[drm:i915_gem_idle_work_handler [i915]] *ERROR* Timeout waiting for engines to idle

perf: interrupt took too long (3982 > 3935), lowering kernel.perf_event_max_sample_rate to 50100

Direct firmware load for iwlwifi-6000-6.ucode failed with error -2
...(snip)...

などのエラーが出るようになった。
xmobarも定期的にセグフォした。
かれこれ2年ほどArch Linuxを使っているが、こんな問題だらけなのは初めてだ。

以下を試してみたが、効果はなし。

  • 発生したカーネルメッセージに対してそれぞれ対処
  • ファイルマネージャをアンインストール
  • ウィンドウマネージャ(Xmonad)のダウングレード

最初は入っているソフトがまずいかと思ったが、そうでは無さそうだ。

PC全体が止まること、アップデート以降様々なエラーが出始めたことから、カーネル周りが怪しい感じがしてくる。
案の定,アップデート時に4.13.4-1へと変更があった。

対処

Arch Linuxはこれまでインストールしたパッケージを/var/cache/pacman/pkg以下に全部持っている。
これを利用するといつでも戻すことができる。
問題はカーネル関連だと考えられるため、カーネルを戻した。

$ ls -l /var/cache/pacman/pkg/linux*
-rw-r--r-- 1 root root  62M May 22 14:58 /var/cache/pacman/pkg/linux-4.11.2-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  62M May 29 13:58 /var/cache/pacman/pkg/linux-4.11.3-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  62M Jul  6 02:07 /var/cache/pacman/pkg/linux-4.11.9-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  65M Jul 29 17:55 /var/cache/pacman/pkg/linux-4.12.4-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  65M Aug 11 21:28 /var/cache/pacman/pkg/linux-4.12.5-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  66M Sep 22 04:11 /var/cache/pacman/pkg/linux-4.13.3-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  66M Sep 28 17:15 /var/cache/pacman/pkg/linux-4.13.4-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  57M Oct 27  2015 /var/cache/pacman/pkg/linux-4.2.5-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  58M Mar 15  2016 /var/cache/pacman/pkg/linux-4.5-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  59M Jun  8  2016 /var/cache/pacman/pkg/linux-4.6.2-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  59M Jun 25  2016 /var/cache/pacman/pkg/linux-4.6.3-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  60M Oct  1  2016 /var/cache/pacman/pkg/linux-4.7.6-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  61M Dec  9  2016 /var/cache/pacman/pkg/linux-4.8.12-3-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  61M Dec  9  2016 /var/cache/pacman/pkg/linux-4.8.13-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  61M Feb  7  2017 /var/cache/pacman/pkg/linux-4.9.8-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root 853K Mar  7  2017 /var/cache/pacman/pkg/linux-api-headers-4.10.1-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root 921K Aug 17 19:47 /var/cache/pacman/pkg/linux-api-headers-4.12.7-1-any.pkg.tar.xz
-rw-r--r-- 1 root root 767K Aug  6  2015 /var/cache/pacman/pkg/linux-api-headers-4.1.4-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root 786K Feb 20  2016 /var/cache/pacman/pkg/linux-api-headers-4.4.1-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root 789K May 20  2016 /var/cache/pacman/pkg/linux-api-headers-4.5.5-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root 811K Aug  2  2016 /var/cache/pacman/pkg/linux-api-headers-4.7-1-x86_64.pkg.tar.xz
-rw-r--r-- 1 root root  30M Sep  4  2015 /var/cache/pacman/pkg/linux-firmware-20150904.6ebf5d5-1-any.pkg.tar.xz
-rw-r--r-- 1 root root  35M Jan 14  2016 /var/cache/pacman/pkg/linux-firmware-20160113.40e9ae8-1-any.pkg.tar.xz
-rw-r--r-- 1 root root  36M May 20  2016 /var/cache/pacman/pkg/linux-firmware-20160516.80d463b-1-any.pkg.tar.xz
-rw-r--r-- 1 root root  40M Oct  5  2016 /var/cache/pacman/pkg/linux-firmware-20161005.9c71af9-1-any.pkg.tar.xz
-rw-r--r-- 1 root root  40M Dec 23  2016 /var/cache/pacman/pkg/linux-firmware-20161222.4b9559f-2-any.pkg.tar.xz
-rw-r--r-- 1 root root  42M May  5 15:37 /var/cache/pacman/pkg/linux-firmware-20170422.ade8332-1-any.pkg.tar.xz
-rw-r--r-- 1 root root  45M Jul 14 06:13 /var/cache/pacman/pkg/linux-firmware-20170622.7d2c913-1-any.pkg.tar.xz
-rw-r--r-- 1 root root  46M Sep  8 20:52 /var/cache/pacman/pkg/linux-firmware-20170907.a61ac5c-1-any.pkg.tar.xz

ここからそれっぽいパッケージとカーネル依存のものをダウングレードする。
以下はあくまでこちらの環境の場合だから、自分で調整して欲しい。ファイルの作成日時が鍵となる。
linux-?.??.?-?-x86_64.pkg.tar.xzは間違いなく必要。

$ sudo pacman -U /var/cache/pacman/pkg/util-linux-2.30.1-2-x86_64.pkg.tar.xz /var/cache/pacman/pkg/linux-firmware-20170622.7d2c913-1-any.pkg.tar.xz /var/cache/pacman/pkg/linux-4.12.4-1-x86_64.pkg.tar.xz /var/cache/pacman/pkg/virtualbox-host-modules-arch-5.1.26-3-x86_64.pkg.tar.xz

今のところdmesgに出ていたエラーも消えて、パニックも起こらなくなった。
とりあえずしばらくはこれで使っていこうと思う。

安定した運用への備え

安定して動くバージョンリストを作成する

本当はシステム全体のパッケージをダウングレードしたかったが、その方法はぱっと調べた限り見つからなかった。
downgradeコマンドは,単一パッケージのダウングレードするバージョンを選べるだけ。

というわけで自作してみた。
動作に関しての検証は済んでいないから、利用は自己責任でお願いします。 使った感想とか大歓迎です。

downgrade_all

システムが安定しているときに以下を実行。

$ git clone https://github.com/poppycompass/downgrade_all
$ downgrade_all/save_pkglist.sh

これでpkg(実行した日付).listにパッケージ名のリストが作成される。
一部のパッケージは/var/cache/pacman/pkg以下に無いこともあるから注意。

システムがぶっ壊れたときの復旧は

downgrade_all/recover_pkg.sh <作成したファイル>.list

単調な実装だから、時間がかかる。

同じシステムを一から作成する

インストールしているパッケージのリストをUSBや外部HDDに格納しておくと、OSを一新するときに役立つ。

$ sudo pacman -Qqne > pkg.list
$ sudo pacman -S --needed $(< pkg.list)

所感

リリースされているカーネルの不具合は都市伝説ではなかったようだ。
公式のWikiを見てもあまり情報が無いことに驚いた。
4.13.4-1というバージョンはしばらく忘れられそうにない。

同じ症状に見舞われた人の相棒が元気な姿に戻ってくれればいいなと思う。
数日掛けてのんびり復旧するくらいがちょうど良さそうだ.

参考

Arch Linux の安定化 - ArchWiki

Ubuntuでインストールできるradare2が古い時の対処法

Ubuntuapt/apt-getでインストールできるradare2が古いため、一部のコマンドが実行できないという話をブログや知り合いから聞くようになった。
初めて使うツールは動かないだけで使うのをやめてしまうことが多いだけに、割と深刻である。
というわけで、調査と対策をまとめてみた。

問題

Ubuntuは広く使われているディストリであるため、radare2を始める際にはaptで導入する人が多い(データ数は周りの4人・・・)。
しかし、現在(2017/10/6)提供されているパッケージは0.9.6と非常に古い(最新は1.6.0)。
そのため、コマンドが動かなかったり、わけわからん挙動を起こすことがある。

古いバージョンを使うことによる問題例

セキュリティコンテストのためのCTF問題集の初めにあるSelfReferenceのところでいえば、axtコマンドが使えない。

[0x0040526]> ax?
XXX: This command conflicats with 'ar'
Usage: ax[-cCd?] [src] [dst]
 axc sym.main+0x38 sym.printf ; add code ref
 axC sym.main sym.puts ; add call ref
 axd sym.main str.helloworld ; add data ref
 ax- sym.main str.helloworld ; remove reference

[0x0040526]> ar?
XXX: This command conflicts with 'ax'
Usage: ar[?d-l*]
 ar addr [at] Add code ref pointing to addr (at is curseek)
 ard addr [at] Add data ref
 arj List refs in json format
 ar- [at] Clean all refs (or refs from addr)
 ar List refs
 ar* Output radare commands

コマンドが競合していると出る。
最新バージョン(1.6.0)では

[0x00201040]> ar?
|Usage: ar # Analysis Registers
| ar              Show 'gpr' registers
| ar0             Reset register arenas to 0
| ara[?]          Manage register arenas
| ar 16           Show 16 bit registers
| ar 32           Show 32 bit registers
| ar all          Show all bit registers
| ar <type>       Show all registers of given type
| arC             Display register profile comments
| arr             Show register references (telescoping)
| ar=             Show register values in columns
| ar? <reg>       Show register value
| arb <type>      Display hexdump of the given arena
| arc <name>      Conditional flag registers
| ard <name>      Show only different registers
| arn <regalias>  Get regname for pc,sp,bp,a0-3,zf,cf,of,sg
| aro             Show old (previous) register values
| arp[?] <file>   Load register profile from file
| ars             Stack register state
| art             List all register types
| arw <hexnum>    Set contents of the register arena
| .ar*            Import register values as flags
| .ar-            Unflag all registers

古いやつだとVisual Modeを使うとWebサーバが立ち上がることもあり、厄介だ。
この時はlocalhost:9090/enyo/からconsoleボタンを押せばWebページからコマンドライン操作できる。

対処策

2つ(+おまけ)考えた。

その1:ソースからビルド

機能的に最新。
どのディストリでも安定して実行できる。

$ git clone https://github.com/radare/radare2
$ cd radare2
$ sys/install.sh

その2:Ubuntuのtestingパッケージから新しいものを引っ張ってくる

unstable/testingパッケージであれば、新しいバージョンが提供されている。
Ubuntuaptで管理したいときはこれを使うとよい。
以降、作業。

まずはファイル作成。99targetはなかったら新規作成。一行書くだけ。

$ sudo vim /etc/apt/apt.conf.d/99target
[+] APT::Default-Release "stable"; 

testingをレポジトリに追加。

$ sudo vim /etc/apt/sources.list
[+] deb http://ftp.jp.debian.org/debian testing main contrib non-free
[+] deb-src http://ftp.jp.debian.org/debian testing main contrib non-free 

仕上げ。

$ sudo apt-get update

$ sudo apt-get install radare2/testing

(おまけ)ゼロバージョンって格好良いって思うんだ・・・

古いバージョン(0.9.6)のままで頑張らないといけない時もたまにある(時間やネット環境がない)。
とりあえずaxtコマンドの代替を探した話。

コマンドの違い

新旧(0.9.6/1.6.0)でコマンド体系が結構変わっているから、メモ。

version 0.9.6 ar: manage refs/xrefs
version 1.6.0 ar: like 'dr' but for the esil vm. (registers)

version 0.9.6 ax: manage code/call/data xrefs
version 1.6.0 ax: manage refs/xrefs (see also afx?)

0.9.6のarと1.6.0のaxが同じコマンドになっている(最新のarレジスタの表示)。
しかし、探してみてもrefを追加するコマンドはあるが、探すコマンドがない。
現状諦めるしかないんだろうか・・・。
古いやつでar-コマンドを使うとセグフォで落ちる。これはプログラム自体のバグでどうしようもなさそうだ。

調査結果

以降は各ディストリにおけるradare2のバージョンとかを調べた情報。
読まなくてもよい(まとめサイトへのリンクを載せているだけ)。

各ディストリにおけるバージョン情報

各ディストリが提供しているパッケージのバージョン情報はここにまとめられている(CentOSなど一部のディストリは載っていない)。
Ubuntuだけを見るのなら、16.04まではtrusty0.9.6となっている。最早江戸時代。
17.??では1.6.0と新しくなっている。
それ以前では、unstable/testingとして新しいバージョンを提供している。

Ubuntuの古いバージョン(16.xx以前)における状況

バグレポートでパッケージを新しくするよう要望とかは出ているみたいだが、返事が返ってきていないようだ。
一瞬自分でパッケージを更新しようと頑張ってみたが、やり方がわからなくて断念。知っている人がいたら教えてください。

最後に

radare2を始めた当時はIDAで十分だとかぼろくそに言われていたが、ちょっとずつ認知が進んでいるようだ。
楽しいradare2ライフを!

参考文献

Repology: versions for radare2

Debianでtestingやunstableからパッケージを借りる時の手順 - kotakのひまじめ日記

Bug #1529528 “Update the package to version 0.9.9” : Bugs : radare2 package : Ubuntu

main関数の無いプログラムを動かすために奔走した話

ディレクトリを眺めていたら,セキュリティを始める直前あたりに頑張ったプログラムが見つかった.
初めの一歩みたいなものだから,改めて見直しがてらいじったことを書き残す.
内容はmain関数が実装されていないけど動くプログラム.

確か元ネタはこれ
そのままじゃ動かないから動くよう修正した.
失敗したやり方も書いておく.

環境

$ uname -a
Linux poppycompass 4.12.5-1-ARCH #1 SMP PREEMPT Fri Aug 11 12:40:21 CEST 2017 x86_64 GNU/Linux

$ gcc -v
gcc (GCC) 7.1.1 20170630                                                                                                         
Copyright (C) 2017 Free Software Foundation, Inc.                                                                                
This is free software; see the source for copying conditions.  There is NO                                          
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 

何のプログラムか

C言語でプログラムを書くときには必ずと行っていいほどmain関数を書く.
ライブラリでない限り,これがないとエラーを吐くはず.
頑張ればint main(void)を書かなくても動くプログラムを書ける,という話. 通常では以下のようなコード.

/* normal.c */
#include <stdio.h>
void put(char c)
{
    putchar(c);
}

int main(void)
{
    put('A');
    return 0;
}
/* E.O.F. */

結論コード

結論としてのコードは以下の通り.x86/x86-64の両方のバージョンを作成した.

32ビット版.retの部分はなくても動く.

/* no_main32.c */
#include <stdio.h>

void put(char c)
{
    putchar(c);
}
extern int ret[1];

__attribute__((section(".text")))
unsigned int main[] = {
  0x83e58955, 0xec83f0e4, 0x2404c710, 0x00000041,
  0xffffcfe8, 0x90c3c9ff,
};
int ret[] = {-61};
/* E.O.F. */

下は64ビット版.

/* no_main64.c */
#include <stdio.h>

void put(char c)
{
  putchar(c);
}

__attribute__((section(".text")))
unsigned long int main[] = {0x000041bfe5894855, 0x00b8ffffffd2e800, 0x1f0f66c35d000000, 0x909090900000441f};
/* E.O.F. */

どちらも普通にコンパイルすれば動く. 64ビット版は最初unsigned intにしていたらエラーが出た.
64ビットでもintは4バイトなのか・・・.

$ gcc no_main64.c
/tmp/cchS2iOj.s: Assembler messages:                                                                                   
/tmp/cchS2iOj.s:27: Warning: ignoring changed section attributes for .text
$ ./a.out
A%

警告が出ているが,ちゃんと__attribute__が動いているみたい.
よくわからないツンデレメッセージ.

解説

手元のOSが64ビットだから,64ビットベースの説明する.
注目すべきは当然unsigned long int mainC言語においてmain関数が最初に呼ばれるのは,libcがmainというシンボルをバイナリの中から見つけて,それをエントリポイントとして処理を開始するようになっているかららしい.
よって,mainというシンボルを含んでさえ入れば,これがエントリポイントとして登録される.
配列の中身は通常のコードをコンパイルした際に生成される機械語をそのまま抜き出して,リトルエンディアンで格納しただけ.内容としては以下の通り.

$ gcc normal.c
$ objdump -M intel -d ./a.out | grep "<main>:" -A10
...(main部のみ抜粋)...
00000000000006a5 <main>:
 6a5:   55                      push   rbp
 6a6:   48 89 e5                mov    rbp,rsp
 6a9:   bf 41 00 00 00          mov    edi,0x41
 6ae:   e8 d7 ff ff ff          call   68a <put>
 6b3:   b8 00 00 00 00          mov    eax,0x0
 6b8:   5d                      pop    rbp
 6b9:   c3                      ret
 6ba:   66 0f 1f 44 00 00       nop    WORD PTR [rax+rax*1+0x0]
...(snip)...

スタック処理の後は引数('A')をediに入れることで引数を渡し(x64では第1引数はrdiに格納),呼び出すだけ.
ここでのポイントは,call 68a <put>のところ.
e8 b7 ff ff ffb7が呼び出すアドレスになっている.ここがずれると上手く動かない場合がある.
よって,配列の要素として埋め込むときは一回適当な値でコンパイルして,どれくらいずれているのかを確認してput関数の先頭になるよう調整する必要がある.

これで準備は整ったと思いきや,まだ動かない.今の状態では,mainはオブジェクトとして認識されて実行できない領域に格納される.
実際に確認してみると,

$ readelf -s ./a.out | grep "\<main\>"
0000000000201040    32 OBJECT  GLOBAL DEFAULT   24 main
0000000000201040に配列の`main`が格納されていることがわかる.
$ objdump -h ./a.out
...(snip)...
 23 .data         00000044  0000000000201020  0000000000201020  00001020  2**5
                  CONTENTS, ALLOC, LOAD, DATA
 24 .bss          00000004  0000000000201064  0000000000201064  00001064  2**0
                  ALLOC
...(snip)...

上記のように初期値付きのデータであるため,.dataセクションに配置されている.
.dataのフラグはCONTENTS, ALLOC, LOAD, DATA
詳しく調べきれていないが,この中でDATAは実行不可だが書き込み可能. よって実行できない.
実行するためには実行可能なコードが置かれる領域である.textセクションへ置いてもらう必要がある.
これを指定するために__attribute__を使っている.
実行可能なセクションに置きさえすれば実行できておしまい.

32ビット版ではretという配列を作っているが,これは元ネタの関数ではretが含まれていないから,その代わりにしたんじゃないだろうか.
retに代入されている-610xffffffc3で,命令としてはretとなる.

恐らく,__attribute__を使わずにコンパイルしているから,配列mainとretは続けて配置されて,main配列が実行された後はret配列内の要素を実行するようになっていたんじゃなかろうか(適当な憶測).
つまり,今回のコードでは無用の不要ということだ!
元ネタコードは,extern int retは初期値を後で入れているから,多分.dataセクションへ配置される..dataセクションに配置されるのにどうやって実行したのか気になるところ.もしかして古い環境だから.dataも実行できたのかな.

課題

main配列を関数として実行するよう頑張ったが,問題がある.

$ objdump -M intel -d ./a.out | grep "<main>:" -A3
00000000000006c0 <main>:                                        
 6c0:   55 48 89 e5 bf 41 00 00 00 e8 d2 ff ff ff b8 00     UH..
 6d0:   00 00 00 5d c3 66 0f 1f 1f 44 00 00 90 90 90 90     ...]

ご覧の通り関数としてディスアセンブルされない.
objdumpのオプションを-dから全セクションアセンブル-Dに変えれば見える.
原因をちゃんと突き止められていないが,怪しそうなのはこれ.

$ readelf -s ./a.out | grep "\<main\>"
00000000000006c0    32 OBJECT  GLOBAL DEFAULT   14 main

3つ目にOBJECTとある.mainはオブジェクトとして見られてしまっている.
ここをFUNCとしたいところだけど,Cでのやり方が見つからなかった.
GNU asなどではtypeディレクティブを使って指定できる.

失敗談

.text以外のセクションに配置して頑張る

他のセクションで,書き込める場所を探したが全部READONLY, CODEDATAフラグが立っていて,書き込めそうになかった.
唯一書き込めそうな.bssセクションは初期値なしの変数しか格納できない.つまり無理. 流石現代OS,隙がない.
mainの内容を動的に確保してコピーすれば実行はできるが,アドレスが変わるから,それでは意味がない.

.dataセクションに実行権限を付与

OS機構に逆らおうとする話.
論文とかでDynamic generated codeとか呼ばれる手法の一つ.マルウェアがよく使うやつ.
mmap((void *)main, 0x20, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);でmainに書き込み権限をつけようとしたが,mmapは違うアドレスを返して頓挫.
mmapの第1引数addrは与えたアドレスを参考にはするが,ダメなときはきっぱり違うところを返す.

まとめ

C言語がmain関数から実行するのは,libcがmainというシンボルをエントリポイントに設定するようになっているから.
関数でないmainを作成するときの条件は,

  • mainという名前であること
  • 実行可能領域(主に.text)に置かれること

参考

GNU Compiler Collection (GCC) Internals: Machine Modes

Using the GNU Compiler Collection (GCC): Label Attributes

linux - Flags in objdump output of object file - Stack Overflow

"main関数の無いプログラム"の解析 - みずぴー日記

Linux カーネルのコンテキストスイッチ処理を読み解く - naoyaのはてなダイアリー