いつの間にか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
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
コマンドは,単一パッケージのダウングレードするバージョンを選べるだけ。
というわけで自作してみた。
動作に関しての検証は済んでいないから、利用は自己責任でお願いします。
使った感想とか大歓迎です。
システムが安定しているときに以下を実行。
$ 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
というバージョンはしばらく忘れられそうにない。
同じ症状に見舞われた人の相棒が元気な姿に戻ってくれればいいなと思う。
数日掛けてのんびり復旧するくらいがちょうど良さそうだ.
参考
Ubuntuでインストールできるradare2が古い時の対処法
Ubuntuのapt/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
パッケージであれば、新しいバージョンが提供されている。
Ubuntuのapt
で管理したいときはこれを使うとよい。
以降、作業。
まずはファイル作成。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まではtrusty
が0.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 main
.
C言語において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 ff
のb7
が呼び出すアドレスになっている.ここがずれると上手く動かない場合がある.
よって,配列の要素として埋め込むときは一回適当な値でコンパイルして,どれくらいずれているのかを確認して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
に代入されている-61
は0xffffffc3
で,命令としては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, CODE
かDATA
フラグが立っていて,書き込めそうになかった.
唯一書き込めそうな.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
pythonでシェルコードを実行する方法
オライリー出版のサイバーセキュリティプログラミング(英題: Black Hat Python)にPythonでシェルコードを実行するスクリプトが載っていた.前から実行する方法を探していたから,喜んで試してみたが上手く行かない.
原因は簡単でPythonのバイナリ自体にNXビットが立っていることだった.という訳でNXを無効にしたPythonバイナリを作成して,実行する手順を書き残す.
Pythonって誰だ
LinuxなどにインストールされているPythonバイナリはcpythonをコンパイルしたものらしい.
これがPythonのデフォルトの実装になる.
環境
$ uname -a Linux poppycompass 4.11.9-1-ARCH #1 SMP PREEMPT Wed Jul 5 18:23:08 CEST 2017 x86_64 GNU/Linux
Arch Linux 64bitで作業した.
cpythonのコンパイル
Pythonのバージョンは2.7
を作成する.
$ git clone https://github.com/python/cpython/ $ cd cpython $ git checkout 3dbe11a # 2.7にバージョン変更 $ ./configure # コンパイルオプションに -z execstack を加えるだけ.加えた箇所はとてもいい加減. $ vim Makefile [-] CC= gcc -pthread [+] CC= gcc -fno-stack-protector -z execstack -pthread [-] CXX= g++ -pthread [+] CXX= g++ -fno-stack-protector -z execstack -pthread $ make
これでNX無効のPythonは作成完了.
以下は確認作業.やらなくてもよい.
trapkit.de - checksec.shからchecksec.sh
をダウンロード.
$ ~/Downloads/checksec.sh --file <path>/cpython/python RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX disabled No PIE No RPATH No RUNPATH cpython/python
というわけで,ちゃんと無効になっていることが確認できた.
シェルコード実行用スクリプト
上記のサイバーセキュリティプログラミングの8章に載っているshell_exe.py
から不要なところを消した.
#!/usr/bin/env python # -*- coding: utf-8 -*- import ctypes # お馴染みの/bin/sh起動するだけ.64ビット向けのコード code = "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05\xc3" buf = ctypes.create_string_buffer(code, len(code)) func = ctypes.cast(buf, ctypes.CFUNCTYPE(ctypes.c_void_p)) func()
Pythonで関数ポインタの作り方がわからなかったが,ctypes
のキャストを使えばよかったらしい.勉強になった.
$ ./cpython/python ./shell_exe.py sh-4.4$
といった感じでシェルが起動する.
所感
アセンブラ関連を扱うには,Cレベルの操作を行えるライブラリを使うのが近道なようだ.
次の課題は,実行結果を受け取り,次の処理に活かす方法を調べていきたい.
参考
PEMUを動かしてみる
Dynamic Binary Instrumentation(DBI)を行うフレームワークの一つ,PEMUを使ってみた.githubの説明が簡潔すぎて動くまでに時間がかかったから,手順を書き残す.
pemu自体はqemuをベースに作成されているから,作業の所々にQEMU関連の操作が入る.
OSの準備
Linux上作業を行う.今回はVirtualboxでUbuntuの仮想マシンを作成し,その上でPEMUを動かした.
仮想マシン(VirtualBox)上で仮想マシン(QEMU)を動かすことになるから,動作が重かった.
以降,ホストOSは仮想マシン上のUbuntu 12.04 32bit.ゲストにも同じバージョンのUbuntuを使った.下手に新しいバージョンのUbuntuを使うとPEMUのコンパイルエラーが起きて厄介.
ホストOSのインストール
http://releases.ubuntu.com/12.04/
辺りからISOファイルをダウンロードして,仮想マシンを作成する.Desktop版がおすすめ.
$ uname -a Linux pemu-VirtualBox 3.13.0-32-generic #57~precise1-Ubuntu SMP Tue Jul 15 03:50:54 UTC 2014 i686 i686 i386 GNU/Linux
ホスト側での作業
$ sudo apt-get update && sudo apt-get upgrade $ sudo apt-get install p7zip-full qemu-utils git qemu
QEMUで動かすゲストをダウンロード.
一から作成するもの手間だったから,出来合いのものを使った.
https://virtualboxes.org/images/ubuntu-server/
から適当なイメージを取ってくる.バージョン違いによる諸問題を避けるため,12.04(32bit)を使った.他のバージョンは試していない.Desktop版だと重すぎるからServer版を使用.
$ wget http://sourceforge.net/projects/virtualboximage/files/UbuntuServer/12.04/ubuntu-12.04-server-i386.7z $ 7z x ubuntu-12.04-server-i386.7z VBox向けのイメージをQEMU用に変換. $ qemu-img convert ubuntu-12.04-server-i386.vdi -O qcow2 ubuntu32_1204.qcow2 $ qemu-system-i386 -m 512 ubuntu32_1204.qcow2 -monitor stdio # ゲストOSの起動
ゲストのログインパスワードは
Username: ubuntu Password: reverse
PEMUの準備
PEMUのREADME.mdの一番最初に書いてあることをやっていく.task-infoのカーネルモジュールを実行して,結果をlinux.cへと移植する.
移植を忘れるとプロセスの解析が上手く行かない.
ゲストOS上での作業
<guest>$ sudo loadkeys us # キー配列がおかしくなっていたときのみ <guest>$ sudo apt-get update <guest>$ sudo apt-get -y install git gcc make linux-generic この部分は時間がかかるから,CPU数を増やすかのんびり待つ. <guest>$ sudo reboot <guest>$ git clone https://github.com/utds3lab/pemu <guest>$ cd pemu/task-info/task-info.c && make <guest>$ sudo /sbin/insmod task-info.ko
メッセージとして
insmod: error inserting 'task-info.ko': -1 Operation not permitted
と出れば成功のようだ.
<guest>$ dmesg | tail -n10
出力の最後にカーネルモジュールからのメッセージが入っている.今回は
0xC1820FE0, /* task struct root */ 444, /* offset of task_struct list */ 472, /* offset of mm */ 44, /* offset of pgd in mm */ 748, /* offset of comm */ }
これをメモして,一旦ゲストを終了.メッセージの値はゲストによって違うようだ.
以降,ホストに戻る.
ホスト上でPEMUのコンパイル
$ git clone https://github.com/utds3lab/pemu $ cd pemu $ vim ./target-i386/PEMU/linux.c
出力に合わせて./target-i386/PEMU/linux.c
の25行目以降を書き換える.
次にPEMUをコンパイルしていく
$ sudo apt-get -y install autoconf dh-autoreconf # READMEに書いてはないが,いれないとエラーが出る $ sudo apt-get -y build-dep qemu $ mkdir build && cd build $ ../myconfig && make install $ cd ../plugins && make $ cd ../build/bin && ./qemu-system-i386 -m 512 ubuntu32_1204.qcow2 -monitor stdio (qemu)> pemu ls strace.so # qemuのプロンプトで実行 <guest>$ ls
qemuを動かしているターミナル上で,PEMU_start
から始まる何らかの出力は得られる.これで一応動いたことになる.
ソース解析
出力のPEMU_start
はmonitor.cに書かれている.その後の数字の出力はplugins/strace.cの
VOID SysBefore(ADDRINT ip, ADDRINT num) { fprintf(stdout,"0x%lx: %ld\n", (unsigned long)ip, (long)num); }
のようだ.strace.c の main は
int main(int argc, char*argv[]) { PIN_AddSyscallEntryFunction(SyscallEntry, 0); PIN_AddFiniFunction(Fini, 0); PIN_StartProgram(); return 0; }
のように,いつ関数を呼び出すかを指定している.
出力のip
, num
について詳しく見ると,値PIN_GetSyscallReturn
とPIN_GetSyscallNumber
の戻り値が入っている.
VOID SyscallEntry(THREADID threadIndex, CONTEXT*ctxt, SYSCALL_STANDARD std, VOID*v) { SysBefore(PIN_GetSyscallReturn(ctxt, std), PIN_GetSyscallNumber(ctxt, std)); }
実装はsystemcall_api.c
にある.出力しているのはシステムコールの戻り値(PIN_GetSyscallReturn: systemcall_api.cでreturn EAXしてる)と,システムコール番号(正確にはLinux/Win/Macのどれかを調べた後,Linuxなら呼び出し時のEAXを返している.まあ,システムコール番号)のようだ.
Linuxで実行する限り,二つの関数で得られる値は同じになるんじゃ・・・.
pemu ls <plugin>
によって,plugins/
のプログラムを呼び出していて,自作も簡単そうだ.
実装としては,monitor.cからplugins以下のプログラムが呼び出される仕組み.
反省
論文に書かれた検証環境のOSバージョンはしっかり守る
所感
とりあえず動くようにはなったが,PEMUならではな機能がいまいち理解できていない.
気が向いたら色々試してみたい.
参考
[SOLVED] insmod error "operation not permitted" as root
第278回 Ubuntuカーネルとの付き合い方:Ubuntu Weekly Recipe|gihyo.jp … 技術評論社
radare2によるバイナリ編集
radare2
はデバッガであり,バイナリエディタでもある.バイナリエディタとして使えると何かと便利だと思い,調べてみた.
基本的なことを書き残す.
環境
$ uname -a Linux poppycompass 4.11.3-1-ARCH #1 SMP PREEMPT Sun May 28 10:40:17 CEST 2017 x86_64 GNU/Linux $ r2 -v radare2 1.6.0-git 15021 @ linux-x86-64 git.1.4.0-472-g7512396ab commit: 7512396ab5ae5f7292f41a80dc0b4dca09c4d6f2 build: 2017-06-11__02:50:45
準備
radare2がインストールされていればOK.
# Ubuntu $ sudo apt install radare2 # Arch Linux $ sudo pacman -S radare2 # 最新好きの人 $ git clone https://github.com/radare/radare2 && cd radare2 && sys/install.sh
$ r2 -v
でそれっぽいのが出れば良い. 次に,適当なバイナリファイルを用意する.
$ echo -en "\xaa\xbb\xcc\xdd" > test.bin
基本
write mode
でファイルを開いて編集していく
$ r2 -w ./test.bin -- It's working! Look at the door! シェルを既に起動した場合は [0x00000000]> o+ <file> [0x00000000]> px 4 # ファイルの内容を確認 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x00000000 aabb ccdd .... [0x00000000]> w? # ヘルプ |Usage: w[x] [str] [<file] [<<EOF] [@addr] | w[1248][+-][n] increment/decrement byte,word.. | w foobar write string 'foobar' | w0 [len] write 'len' bytes with value 0x00 | w6[de] base64/hex write base64 [d]ecoded or [e]ncoded string | wa[?] push ebp write opcode, separated by ';' (use '"' around the command) | waf file assemble file and write bytes | wao[?] op modify opcode (change conditional of jump. nop, etc) | wA[?] r 0 alter/modify opcode at current seek (see wA?) | wb 010203 fill current block with cyclic hexpairs | wB[-]0xVALUE set or unset bits with given value | wc list all write changes | wc[?][ir*?] write cache undo/commit/reset/list (io.cache) | wd [off] [n] duplicate N bytes from offset at current seek (memcpy) (see y?) | we[?] [nNsxX] [arg] extend write operations (insert instead of replace) | wf -|file write contents of file at current offset | wh r2 whereis/which shell command | wm f0ff set binary mask hexpair to be used as cyclic write mask | wo[?] hex write in block with operation. 'wo?' fmi | wp[?] -|file apply radare patch file. See wp? fmi | wr 10 write 10 random bytes | ws pstring write 1 byte for length and then the string | wt[f][?] file [sz] write to file (from current seek, blocksize or sz bytes) | wts host:port [sz] send data to remote host:port via tcp:// | ww foobar write wide string 'f\x00o\x00o\x00b\x00a\x00r\x00' | wx[?][fs] 9090 write two intel nops (from wxfile or wxseek) | wv[?] eip+34 write 32-64 bit value | wz string write zero terminated string (like w + \x00)
以降,編集していく.
16進数の書き込み
[0x00000000]> wx eeff # 最初の2バイトを"eeff"に書き換え [0x00000000]> px 4 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x00000000 eeff ccdd .... [0x00000000]> wx 1122 @ 0x2 # オフセットを指定し,0x2(3バイト目)から0x1122を書き込み [0x00000000]> px 4 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x00000000 eeff 1122 ...
書き込みはオフセットを指定しない限り,現在のアドレスからの書き込みになる.
アドレスを変更したいときは> s <addr>
で移動できる.
文字列の書き込み
[0x00000000]> w poppycompass # "poppycompass"という文字列を書き込み [0x00000000]> px 20 - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF 0x00000000 706f 7070 7963 6f6d 7061 7373 ffff ffff poppycompass.... 0x00000010 ffff ffff ....
どうやら,ファイルサイズより大きく出力するとff
が入るようだ.
Visual modeで常に結果を見ながら編集する
実際に編集するときは,変更を加えてから毎回px 20
みたいなコマンドを打つのは手間だ.この問題は簡単に解決できる,そうVisual mode
ならね!
[0x00000000]> V # Visual modeに移行 [0x00000000 0% 504 ./bin]> xc - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF comment 0x00000000 aabb ccdd ffff ffff ffff ffff ffff ffff ................
この状態で:
を押して,通常通りコマンドを入力するだけ.
Press <enter> to return to Visual mode. :px 11223344<enter><enter> [0x00000000 0% 392 ./bin]> xc - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF comment 0x00000000 1122 3344 ffff ffff ffff ffff ffff ffff ."3D............
リトルエンディアンで書き込みたい!
> wv 0xaabbccdd # \xdd\xcc\xbb\xaaとして格納 > wv 0xaabbccddeeff0102 # \x02\x01\xff...として格納
1-4
bytesの数値は32ビットとして,5-8
bytesの数値は64ビットの値として取り扱われ,少ないバイトには00
が書き込まれる(0xaabb
だと\xbb\xaa\x00\x00
となる).
9
bytes以上の値を書き込もうとすると,失敗して全桁がff
になる.
アセンブルして書き込みたい
以下のファイルを用意する. $ echo "push eax\npop eax" > asm.txt $ r2 -w ./test.bin [0x00000000]> waf ./asm.txt もしくは [0x00000000]> "wa pop eax;push eax"
注意点は,> wa "pop eax;push eax"
じゃないこと.
Visualmodeで直接編集
Vimっぽく編集する方法がある.Stirling
などに慣れた人はこっちの方が親しみやすいかも.
[0x00000000]> V
c
を押すと,16進ダンプが囲まれるはず.ここからはvim.
移動はhklj
,編集場所にカーソルが来たらi
でインサートモード.
数値をいれたら上書きされる.終わったらEsc
を押す.
おまけ
Visual mode
の編集機能を使うと,通常のエディタっぽく使えなくもない.
以下はCでHello, Worldプログラムを書いた例.使いみちは無さそうだ.
$ r2 -- # ファイルを指定しないでシェルを立ち上げる > o+ hello.c # 書き込みモードで hello.c を開く > V # ヴィジュアルモードに移行して,書き込みを逐次見られるようにする
c
を押すと,16進ダンプが囲まれる.ここでタブを押すと,ASCII
の方が囲まれる.この状態でi
を押して,書くだけ.
改行(\x0a)とダブルクウォート(\x22)は16進からしか書けないのが難点.
[0x00000000 + 30> * INSERT MODE * - offset - 0 1 2 3 4 5 6 7 8 9 A B C D E F |0123456789ABCDEF| comment 0x00000000 2369 6e63 6c75 6465 203c 7374 6469 6f2e |#include <stdio.| 0x00000010 683e 0a69 6e74 206d 6169 6e28 766f 6964 |h>.int main(void| 0x00000020 2920 7b20 7072 696e 7466 2822 4865 6c6c |) { printf("Hell| 0x00000030 6f2c 2057 6f72 6c64 2122 293b 207d ffff |o, World!"); }..| $ cat hello.c #include <stdio.h> int main(void) { printf("Hello, World!"); }