拾い物のコンパス

まともに書いたメモ

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のはてなダイアリー

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レベルの操作を行えるライブラリを使うのが近道なようだ.
次の課題は,実行結果を受け取り,次の処理に活かす方法を調べていきたい.

参考

サイバーセキュリティプログラミング―Pythonで学ぶハッカーの思考

PEMUを動かしてみる

Dynamic Binary Instrumentation(DBI)を行うフレームワークの一つ,PEMUを使ってみた.githubの説明が簡潔すぎて動くまでに時間がかかったから,手順を書き残す.
pemu自体はqemuをベースに作成されているから,作業の所々にQEMU関連の操作が入る.

OSの準備

Linux上作業を行う.今回はVirtualboxUbuntu仮想マシンを作成し,その上で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_GetSyscallReturnPIN_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ならではな機能がいまいち理解できていない.
気が向いたら色々試してみたい.

参考

PinからPEMUへ | 一生あとで読んでろ

GitHub - utds3lab/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-4bytesの数値は32ビットとして,5-8bytesの数値は64ビットの値として取り扱われ,少ないバイトには00が書き込まれる(0xaabbだと\xbb\xaa\x00\x00となる).
9bytes以上の値を書き込もうとすると,失敗して全桁が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!"); }

volatilityにプロファイルを追加する方法

メモリダンプ解析用ツールvolatilityはデフォルトではWindowsのメモリダンプのみが解析できる.LinuxOSXで作成されたメモリダンプを解析するためにはプロファイルを追加してやれば良い.公式から用意されているものを使うこともできるし,自作することもできる.

概要

プロファイルの追加は定義ファイルを追加すればよい.いくつかは公式から用意されており,基本的にはGithubからcloneするだけで使える.

$ git clone https://github.com/volatilityfoundation/profiles
$ cd profiles
$ find -type d -maxdepth 2
... snip ...
./Mac
./Mac/10.10
./Mac/10.5
./Mac/10.8
./Mac/10.9
./Mac/10.6
./Mac/10.12
./Mac/10.7
./Mac/10.11
./Linux
./Linux/Fedora
./Linux/Ubuntu
./Linux/Debian
./Linux/CentOS
./Linux/RedHat
./Linux/OpenSUSE
... snip ...

以上のように複数のディストリのプロファイルが用意されている.各ファイルにはx86x64がある.

使い方

使い方はvol.pyまたはvolatiltyが参照しているpluginsというディレクトリにZIPのままコピーする.
以下はUbuntu14043x64版のプロファイルを使えるようにする場合.

$ pwd
profiles
# ローカルなvolatilityを使っている場合
$ cp Linux/Ubuntu/x64/Ubuntu14043.zip volatility/plugins/Linux
# apt/pacmanなどでインストールしたvolatilityを使っている場合
$ cp Linux/Ubuntu/x64/Ubuntu14043.zip /usr/lib/python2.7/site-packages/volatility/plugins/linux/

確認

以下は,/usr/lib/python2.7/site-packages/volatility/plugins/linux/にコピーした場合のコマンド.

$ volatility --info
Profiles                                                                                                         
--------                                                                                                         
LinuxUbuntu14043x64 - A Profile for Linux Ubuntu14043 x64
VistaSP0x64         - A Profile for Windows Vista SP0 x64                                    
VistaSP0x86         - A Profile for Windows Vista SP0 x86                                    
VistaSP1x64         - A Profile for Windows Vista SP1 x64                             
VistaSP1x86         - A Profile for Windows Vista SP1 x86                                                                         
... snip ...

後は通常通り
$ volatility -f <dump_file> --profile=LinuxUbuntu14043x64 linux_lsof
といった感じで解析できる.

プロファイルを自作する

IIJがいい感じでまとめていた.自作したい人はこちらを参照.ツールがvolatilityに用意されているから,作成したいディストリ上でmakeしてzipするだけのようだ.特定のバージョンを作りたいときはカーネルrpmで持ってくる必要あり.

Internet Infrastructure Review(IIR)Vol.32 | IIJの技術/セキュリティレポート | IIJ

おまけ

ZIPファイルはplugins以下に置けば認識してくれるから,

$ cp profile.zip /usr/lib/python2.7/site-packages/volatility/plugins/self_made

といった目的別に分けることもできる.便利.