第8回 FreeBSD勉強会 Jail機構と資源制御

変更履歴
2012/06/11 2:08 自分で試してみたところ、コマンド入力のミスに気づいたので参考しないように書いた。
2012-06-15 0:43発表資料URL追加
第8回 FreeBSD勉強会 Jail機構と資源制御 講師:佐藤広生さん
当日にFreeBSD勉強会に気づき、たまたま参加する機会がありました。内容としてはFreeBSD9で付け加えられたRACCTとRCTLを使ったjailの資源管理です。
発表内容の資料は以下で見ることができます。
http://people.allbsd.org/~hrs/FreeBSD/sato-FBSD20120608.pdf

基礎的な話

カーネルはずっと起動して、ハードウェアの適切にCPUの時間を割り当てるなどの資源の制御を行なっている。一方、ユーザランドは資源管理以外の事をやっておりユーザランドプログラムは必要に応じて増えたり減ったりしている。資源はルールと限界が許す限り早い者勝ちである。そのため、あるプロセスが専有できるようにはなっている。

最初の設計思想は一台のマシンを共有するようにしていた。だから他のユーザがみえていてよかった。また、管理単位としてユーザとグループを作り、お互いに独立して総合干渉しないようにした。

資源へのアクセス管理はカーネルが管理している。マシンの資源はファイルとして見えるようにしている。例えば、ディスクだと/dev/da*となっている。

ファイルのOner UIDとそのアクションを越したUIDを比較している。管理方法の基本はUIDで管理している。ファイルじゃない場合はrootかどうかで判断している。かなりおおざっぱである。そのため、MACを使うと強力なアクセス制御を実現できる。

資源管理の方法としては使用可能ディス容量の制限がある。これは4.2BSDでカーネルに追加された。その他にもsetrlimitというのがあり、これも4.2BSDから入ったものでプロセスが使用可能な共有資源の使用制限できる。カーネルのメモリ上にあるため、再起動したらなくなる。そのため、
/etc/login.confを使うとloginした時にその設定が読み込まれるため制御できるようになる。制限は/usr/bin/limitsで調べたり、変更できる。しかし、設計思想として全部ユーザ単位で管理するようにしていたため、複数のユーザやプロセスに対して設定できないという問題点があった。

環境の分割

2台の物理マシンを1台にしたいという場合がある。しかし、ユーザIDがぶつかっているかもしれないし、ユーザの設定が違うかもしれない。また、物理マシンの管理者を1台にした時に管理者にしたら、別のマシンの管理者権限も持つことになり、別のマシンが管理できたら問題である。

そうした問題に対して環境はユーザプロセスの集合とすればよいのではないかと考えだされたのがchroot jail。ただし、これは今のJailとは違う。
これは見えるファイルを管理すれば良いのではないかという考えからchrootさせるものである。
以下のようなコマンドで作成する。
/binなどすべてコピーするようにしている。名前空間を分離しており、他のディレクトリは存在しない。(見えないようにしている)

※以下のコマンド入力の行が間違っています。参考にしないで下さい。
mkdir -p /a/jail /a/jail/dev
cp -Rp /etc /usr /var/ /tmp /a/jail
cd /
mount -t devfs devfs /a/jail/dev
chroot /a/jail /bin/sh

chrootされているため他ユーザのファイルは見えないが、ファイルとして管理されてない資源は分離されてないので垣根をこえることができてしまう。例えば、プロセスなどは見えてしまうので、jailの中と外のUIDが同じ場合には見ているプロセスを殺せてしまう点やネットワークも見えてしまうという問題点があった。

そのため、他の資源も管理できるようにしたのがFreeBSD jail
名前空間が分離している。他のプロセスは見えない。

※以下のコマンド入力の行が間違っています。参考にしないで下さい。
mkdir -p /a/jail /a/jail/dev
cp -Rp /etc /usr /var/ /tmp /a/jail
cd /
mount -t devfs devfs /a/jail/dev
chroot /a/jail /bin/sh
jail -c name=j1 host.hostname=j1.example.com path=/a/jail comannd=/bin/sh

プロセスを見るとSTATのところにJがつく。ファイルシステムについては通常通り操作できるが、systcl jailなどのシステムコールはできない。
jaiilを作る場合にはsysutils/ezjailを使うと簡単に作れる。jailではJのプロセスを立ち上げて、同じJの集合としてみる。
jaiilを作る場合にはsysutils/ezjailを使うと簡単に作れる。

デモとしてFreeBSD jailでCentOS4を動かしているのを見た。umameをするとLinuxという表記とFreeBSDという表記があった。その理由としてはunameFreeBSDだからだそうだ。

完全な独立ネットーワークスタックを備えたjailとしてVIMAGE Jial VNETがある。FreeBSD9から利用可能になっており、options VIMAGEで利用可能。ネットワーク上に複数のマシンがあるように見える。

KVMとの違い
カーネルは分けていない。ハイパーバイザは存在していない。OSの管理をするOSは存在していない。資源の見え方を変えただけなのでオーバヘッドは非常に小さい。カーネルが複数あるということはカーネルのメモリが台数分に必要なのでオーバヘッドが大きい。

RACCT RCTLを用いた資源管理

さあ資源管理しようぜ(こっからが本番)
資源は早い者勝ちなので資源管理されて無いので、Jail単位で資源がどれだけつかわれてないのか知らないのが問題だった。
setrlimitの欠点
ユーザ単位での制限が難し。
限界を超えたときにカーネルが起こすアクションがかえれない。もうだというのがわからない、ダメになってからわかるのであんまり意味が無い。
一度設定したあとに制限値を自由に変更できない。

そのために、RACCT RCTLができた。
RACCT 使用資源量をカーネルが把握するためのフレームワーク
RCTL RACCTの結果を利用して資源量制限を設定するフレームワーク

options RACCTとoptions RCTLを使うことによって利用可能になる。
コマンドはrctl(読み方:アールコントロール)を使い、/etc/rctl.confで設定する。

subject:subejct-id:resorce:action = amount/per

現在の制限値を表示するには以下のコマンドを使う。

rctl -hu user:hrs
-uは現在の制限値を表示する。
-hは人間が見やすいような形にする。
subject=user,process,loginclass,jail
action=deny,log,devctl,sig*

有る値になった起こす行動を決める事ができる。

1g/user ユーザ単位で1G
使用例:ユーザが10プロセスをたちあげたら拒否する。

rctl -a user:hrs:maxproc:deny=10

10のプロセスを立ち上げるとno more processes拒否される。

使用例:ユーザ単位あたり10個で、超えた場合にはログに取る
rctl -a jail:j1:maxproc:log=10/user
-a 追加
-r 削除

設定は/etc/devd.confで行なう。

感想としては今までFreeBSD jailを使ったことがなかったので、使ってみようかなという気持ちにはなりました。今動かしているのがFreeBSD8なのでFreeBSD9をインスールして試してみようかなと思った。現在はOSを仮想化する方法としてESXiを利用してFreeBSDCentOSUbuntuを動かしています。しかし、デモでjail内でCentOSが動いているのを見てそいういう方法もあるんだなと思ってjailに興味を持ちました。

間違えている箇所が有りましたら、コメントかはてダにて知らせて貰えると助かります。

多重起動防止
参考URL
http://sp-.up.seesaa.net/image/taju-1.html
(同じAPI使っているのでほとんどまんま一緒だったりする)

バイナリ
http://dl.dropbox.com/u/13673436/FindWindow.exe
ソース
http://dl.dropbox.com/u/13673436/FindWindow.cpp
多重起動防止のためのAPIはFindWindowを使った。今回作成されるウインドウ名は”多重起動Crackme”なので以下のようにした。

FindWindow(NULL,_T("多重起動Crackme"))

成功すると0が返ってくるということなので,その通りに書いた。(あいかわらず説明いい加減だななぁ…)

解析
まず、最初に起動させて”ぶひひひ成功”と出るのを確かめる。2つ目を起動しようとしていると”既に起動しています”とでる。OKボタンを押して終了する。次にollydbgから開く。そうすると以下のような感じなると思う。この画面がでないのであれば、Ctrl+NでFindWindowWを探してたどり着くこと。

000F1007   . FF15 AC200F00  CALL DWORD PTR DS:[<&USER32.FindWindowW>>; \FindWindowW
000F100D   . C74424 10 4000>MOV DWORD PTR SS:[ESP+10],40
000F1015   . C74424 0C 0421>MOV DWORD PTR SS:[ESP+C],FindWind.000F21>
000F101D   . C74424 04 0000>MOV DWORD PTR SS:[ESP+4],0
000F1025   . 85C0           TEST EAX,EAX
000F1027   . 75 0E          JNZ SHORT FindWind.000F1037

これをぱっと見た感じでTESTしてJNZしている01371025の当たりが判定処理をしていることがなんとなくわかるだろう。とりえず、000F1025にF2でブレークポイント仕掛けてF8でステップ実行してJNZの命令まで行く。JNZの処ではジャンプしようとしている。しかし、ジャンプすると失敗のメッセージがでるのでここをNOPにするか、TESTをXORにすればよい。

000F1025     33C0           XOR EAX,EAX
000F1027   . 75 0E          JNZ SHORT FindWind.000F1037

XOR版
00000425: 85 33

ドライブチェック練習問題3

バイナリ
http://dl.dropbox.com/u/13673436/GetDriveTypeFuncC.exe
ソース
http://dl.dropbox.com/u/13673436/GetDriveTypeFuncC.cpp

この問題はGetDriveTypeB.exeのちょっと改良型
前回のように以下のCALLをNOPで潰しても認証成功にはならない.

004011D3     E8 F8FEFFFF    CALL GetDrive.DriveCheck

なんでかというと認証が成功した場合にフラグを立てている.そして,Windowに文字列を描画する時にもう一度そのフラグが立っているかを判定し,立ってない場合には認証失敗の文字列を描画して,立っている場合には成功の文字列を出している.
実際にフラグを立てている以下の場所である.

004011BC  |> C605 70334000 >MOV BYTE PTR DS:[FinalCheckFlag],1

MOVは移動命令.FinalCheckFlagのメモリ空間に1を代入している.実際のソースではFinalCheckFlag=true;
となっている.解析する側としてはこのフラグを立ててやればいいんだから.CALL命令を潰した場所にフラグを立てる処理に置き換えれば良い

004011D3     E8 F8FEFFFF    CALL GetDrive.DriveCheck
004011D8     84C0           TEST AL,AL
004011DA     75 1D          JNZ SHORT GetDrive.004011F9
↓
004011D3     C605 70334000 >MOV BYTE PTR DS:[FinalCheckFlag],1
004011DA     EB 1D          JMP SHORT GetDrive.004011F9

*== TARGET_FILE ================
FILENAME GetDriveTypeC.exe
* FileSize: 8704 bytes
* LastMod.: 2011/02/21 14:36:27
*===============================
000005D3: E8 C6
000005D4: F8 05
000005D5: FE 70
000005D6: FF 33
000005D7: FF 40
000005D8: 84 00
000005D9: C0 01
000005DA: 75 EB

GetDriveType系練習問題

認証用ISO 認証している場合が見たい時に
http://dl.dropbox.com/u/13673436/SAMPLE.iso
説明用
http://dl.dropbox.com/u/13673436/GetDriveType.AllDrive.exe
練習問題1
http://dl.dropbox.com/u/13673436/GetDriveTypeFuncA.exe
http://dl.dropbox.com/u/13673436/GetDriveTypeFuncA.cpp
練習問題2
http://dl.dropbox.com/u/13673436/GetDriveTypeFucnB.exe
http://dl.dropbox.com/u/13673436/GetDriveTypeFucnB.cpp

練習問題の解答
説明用
ドライブ名チェック部分

00EF10BD     85C0           TEST EAX,EAX
00EF10BF  |. 74 3D          |JE SHORT GetDrive.00EF10FE

解析

00EF10BD     85C0           TEST EAX,EAX

TEST->XORにする。XORにすると判定条件がひっくり返るのでSAMPLEというディスクがあった場合には認証失敗になる。もし、それが困るならTESTの下のJNZをNOPで潰した方がいいだろう。

ドライブの種類チェック部分

00EF1069  |. 83F8 05        |CMP EAX,5
00EF106C  |. 75 53          |JNZ SHORT GetDrive.00EF10C1

JNZ SHORT GetDrive.00EF10C1->NOP

0000046C: 75 90
0000046D: 53 90
000004BD: 85 33

HDDしか存在しないドライブもあるのでドライブタイプが違っている場合でもジャンプしないようにした。

GetDriveTypeFuncA.exe

013510F5     84C0           TEST AL,AL
013510F7    ^74 90          JE SHORT GetDrive.01351089
013510F9   . C74424 0C 2021>MOV DWORD PTR SS:[ESP+C],GetDrive.013521>
01351101   . C74424 08 2021>MOV DWORD PTR SS:[ESP+8],GetDrive.013521>;成功
01351109   .-FF25 B8203501  JMP DWORD PTR DS:[<&USER32.MessageBoxW>] ;  USER32.MessageBoxW
0135110F   > C74424 0C 2C21>MOV DWORD PTR SS:[ESP+C],GetDrive.013521>;  ASCII ""8D,"",8A,"<",8A,"1YWe"
01351117   . C74424 08 3821>MOV DWORD PTR SS:[ESP+8],GetDrive.013521>;失敗
0135111F   .-FF25 B8203501  JMP DWORD PTR DS:[<&USER32.MessageBoxW>] ;  USER32.MessageBoxW

解析

013510F7    ^74 90          JE SHORT GetDrive.01351089

JE->NOP
000004F7: 74 90
000004F8: 16 90

GetDriveTypeFucnB.exe

012F11B0  /$ 83EC 44        SUB ESP,44
012F11B3     E8 90909090    CALL 91BFA248
012F11B8     84C0           TEST AL,AL ;
012F11BA  |. 75 1D          JNZ SHORT GetDrive.012F11D9

CALL 91BFA248でドライブのチェック関数を呼んでる。失敗した場合は関数内でMesageBoxを呼び出す。
JNZ で飛ぶとウインドウを消して、終了する。CALL命令をNOPで潰して終了

前回作った全ドライブ調べてドライブの文字列がSAMPLEというのがあったら成功というプログラムの解析が思った以上に難しかったので、文字列のみ比較するプログラムを作成し、そしてそのプログラムの解析を行った。
バイナリ
http://dl.dropbox.com/u/13673436/CompreString.exe

ソース

#include <stdio.h>
#include <windows.h>
#include <tchar.h>
#include <locale.h>
int WINAPI WinMain(
		HINSTANCE hInstance ,
		HINSTANCE hPrevInstance ,
		LPSTR lpCmdLine ,
		int nCmdShow ) {
    setlocale( LC_ALL, "Japanese");  //ロケール(地域言語)を日本語でセット
	   WCHAR volumeName[10] = L"ユニコード文字列";
	if(wcscmp(volumeName,_T("SAMPLE"))==0)
	{
		MessageBox(NULL , TEXT("認証成功") ,
		TEXT("認証成功") ,
		MB_OK | MB_ICONINFORMATION);
	} else {
		MessageBox(NULL , TEXT("認証失敗") ,
		TEXT("認証失敗") ,
		MB_OK | MB_ICONINFORMATION);
	}
	return 0;
}

解析

00BA108D  |. 85C0           TEST EAX,EAX			   ;EAXは1
00BA108F    ^75 90          JNZ SHORT CompreSt.00BA1021
00BA1091  |. 68 3421BA00    PUSH CompreSt.00BA2134
00BA1096  |. 68 3421BA00    PUSH CompreSt.00BA2134
00BA109B  |. EB 0A          JMP SHORT CompreSt.00BA10A7
00BA109D  |> 68 4021BA00    PUSH CompreSt.00BA2140                   ;  ASCII ""8D,"",8A,"<",8A,"1YWe"
00BA10A2  |. 68 4021BA00    PUSH CompreSt.00BA2140                   ;  ASCII ""8D,"",8A,"<",8A,"1YWe"
00BA10A7  |> 6A 00          PUSH 0                                   ; |hOwner = NULL
00BA10A9  |. FF15 B020BA00  CALL DWORD PTR DS:[<&USER32.MessageBoxW>>; \MessageBoxW

TEST EAX,EAXはAND演算を行ったつもりになる命令。”したつもりなので”ゼロフラグが変化するだけ。
JNZ SHORT CompreSt.00BA1021 JNZ(Jump Not Zero)はゼロ以外であればジャンプする命令。今回はEAXが1なのでジャンプする。ANDは両方1のときのみ1ORは両方1のときに1に。XORは「0と1」か「1と0」のときのみ1

ここでジャンプしないようするためにはJNZをNOPにして命令を潰すかTESTをXORにする。
XORに変えた場合
0000048D: 85 33

有効なドライブを調べてドライブ名がSAMPLEがあれば成功例

前回のだと一つのドライブにしかGetDriveTypeをやってなかったけど、今回は有効なドライブを調べてドライブ名がSAMPLEがあれば成功するという風にした。
参考URL
http://hp.vector.co.jp/authors/VA007799/tips/tips1.htm
バイナリ
http://dl.dropbox.com/u/13673436/GetDriveType.AllDrive.exe
MD5:9BA4EA1C8B90AD7F37954E799AFED017
ソース

#include <stdio.h>
#include <windows.h>
#include <tchar.h>
#include <locale.h>
int WINAPI WinMain(
		HINSTANCE hInstance ,
		HINSTANCE hPrevInstance ,
		LPSTR lpCmdLine ,
		int nCmdShow ) {
    int flag=1;
    DWORD drive=GetLogicalDrives();//利用可能なドライブを調べる
    const int INFOBUF_SIZE=256;
    TCHAR volumeName[INFOBUF_SIZE];
    TCHAR moji[20];
	int drivearry;

    for(drivearry=0;drivearry<25;drivearry++,flag<<=1)
    {
        if(drive&flag)
        {
            wsprintf(moji,_T("%c:\\"),'A'+drivearry);
            if(GetDriveType(moji) == DRIVE_CDROM)//CDROMなら判定する
			{
				 GetVolumeInformation(moji, volumeName, INFOBUF_SIZE, 0, 0, 0, NULL,NULL);  //  ボリューム名を取得
			     if(_tcscmp(volumeName,_T("SAMPLE"))==0)
					 { 
						MessageBox(NULL , TEXT("認証成功") ,
						TEXT("認証成功") ,
						MB_OK | MB_ICONINFORMATION);
						return 0;
					 }
            }
		}
    }
	MessageBox(NULL , TEXT("ボリュームネームが「SAMPLE」のCDを入れてください") ,
	TEXT("認証失敗") ,
	MB_OK | MB_ICONINFORMATION);
	return 0;
}

バレンタインチョコ欲しい!

バレンタインチョコ欲しい!
日記を書こうとしたらキャンペーンってのが出てきたのでついでに書いてみる。
義理でもいいからチョコが欲しいと思ったが、そんなことをされると今までモテたことが無い俺は間違いなく誤解するのでやめておいた方がいい。

かといって他に欲しい物無い。新鮮な空気が欲しい。(眠いので投げやり)