Tiny BASIC と ASLR (2012/11/03, 2013/04/25, 2013/12/26)
2013-02-09-wheezy-raspbian 以降のカーネル(linux-3.6.11+)でフレームバッファが動作しない不具合を修正しました。linux-3.10.24+ でも動作確認しました(2013/12/26)
最近 Raspberry Pi で ARM版 rvtl を使うと Segmentation fault が発生する場合があることに気付きました。 最近の x86 や x86_64 のカーネルはバッファオーバーフロー攻撃への対策の一つとして、プログラムが使用するメモリの一部のアドレスをランダムに配置する機能(ALSR)が入っています。 少し前の ARM のカーネルはその機能を使っていなかったのですが、Raspberry Pi の (Debian wheezyの) カーネル(3.2.27+) には ASLR が採用されているようです。
本家でも「TinyBASIC for Raspberry Pi」で盛り上がっているので、同時代に日本で流行したTiny BASIC の一種の GAME III の子孫 である rvtl をちゃんと動作させることにしました。
ASLRの確認
ASLR とは Address Space Layout Randomization の略で、プロセスの実行時のメモリアドレスをランダムに配置することによってバッファオーバーフロー攻撃をやり難くする手段です。 下の図は Linux のプロセスが動作している状態のメモリの内容を示しています。カーネルや動的にリンクされたライブラリは省略しています。下の図の赤いラインがASLRのよってランダムに変化する部分です。
実際にアドレスが変化しているかどうかをアセンブリプログラムを作成して確認してみました。ヒープ領域のアドレスは C で書いたプログラムからも確認できますが、プログラムカウンタやスタックポインタは最低でもインラインアセンブラを使う必要があります。今回はすべてアセンブラで作成しました。
ASLRの効果を確認するために、スタックポインタのアドレス、実行中のプログラムのアドレス、BSSの領域の最終アドレス、ヒープ領域の先頭アドレスを表示するプログラムを作成しました。 プログラムの動作としては、単に r0 レジスタにアドレスを代入して PrintHex8 ルーチンで標準出力に16進数8桁で表示するだけです。最初にスタックポインタ、次にプログラムカウンタ、BSS領域の先頭アドレス、BRKシステムコールで取得したヒープ領域の先頭アドレスの4つを表示します。
@ ------------------------------------- @ 2012/10/27 check randomize_va_space @ as -o printbrk.o printbrk.s @ ld -o printbrk printbrk.o @ ------------------------------------- .equ sys_exit, 0x000001 .equ sys_write, 0x000004 .equ sys_brk, 0x00002D .text .global _start .align 2 _start: mov r0, sp bl PrintHex8 mov r0, pc bl PrintHex8 ldr r0, dummy0 bl PrintHex8 mov r0, #0 mov r7, #sys_brk swi 0 bl PrintHex8 Exit: mov r0, #0 mov r7, #sys_exit swi 0 PrintHex8: stmfd sp!, {r0-r3,lr} @ push mov r1, #8 mov r3, r1 @ column 1: and r2, r0, #0x0F @ mov r0, r0, LSR #4 @ orr r2, r2, #0x30 cmp r2, #0x39 addgt r2, r2, #0x41-0x3A @ if (r2>'9') r2+='A'-'9' stmfd sp!, {r2} @ push digit subs r3, r3, #1 @ column-- bne 1b mov r3, r1 @ column 2: ldmfd sp!, {r0} @ pop digit bl OutChar subs r3, r3, #1 @ column-- bne 2b mov r0, #10 @ LF bl OutChar ldmfd sp!, {r0-r3,pc} @ restore & return OutChar: stmfd sp!, {r0-r2, r7, lr} mov r1, sp @ r1 address mov r0, #1 @ r0 stdout mov r2, r0 @ r2 length mov r7, #sys_write swi 0 ldmfd sp!, {r0-r2, r7, pc} @ pop & return dummy0: .long dummy .bss .align 2 dummy: .long 0 @ -------------------------------------
以上のコードを printbrk.s というファイル名で保存して、次のようにアセンブル、リンクします。printbrk というファイルを実行します。
jun@raspberrypi ~/pi_asm $ as -o printbrk.o printbrk.s jun@raspberrypi ~/pi_asm $ ld -o printbrk printbrk.o
randomize_va_space の変更方法
スーパーユーザになって (sudo su)、 /proc/sys/kernel/randomize_va_space に対して 0、1、2 を書きこむことで変更できます。
# echo 1 > /proc/sys/kernel/randomize_va_space
randomize_va_space=2 の場合
実行するたびにスタック領域、ヒープ領域のアドレスが変化しています。
jun@raspberrypi ~/pi_asm $ cat /proc/sys/kernel/randomize_va_space 2 jun@raspberrypi ~/pi_asm $ ./printbrk BEF37810 スタックポインタ 00008084 プログラムカウンタ 00010114 BSS領域 00CD0000 ヒープ領域先頭 jun@raspberrypi ~/pi_asm $ ./printbrk BEF03810 00008084 00010114 01B28000 jun@raspberrypi ~/pi_asm $ ./printbrk BE9F0810 00008084 00010114 00734000
randomize_va_space=1 の場合
実行するたびにスタック領域が変化します。
jun@raspberrypi ~/pi_asm $ cat /proc/sys/kernel/randomize_va_space 1 jun@raspberrypi ~/pi_asm $ ./printbrk BE814810 00008084 00010114 00011000 jun@raspberrypi ~/pi_asm $ ./printbrk BEA54810 00008084 00010114 00011000 jun@raspberrypi ~/pi_asm $ ./printbrk BE9A2810 00008084 00010114 00011000
randomize_va_space=0 の場合
何度実行してもスタック、プログラム領域、ヒープ領域ともに変化していないことが確認できます。
jun@raspberrypi ~/pi_asm $ cat /proc/sys/kernel/randomize_va_space 0 jun@raspberrypi ~/pi_asm $ ./printbrk BEFFF810 00008084 00010114 00011000 jun@raspberrypi ~/pi_asm $ ./printbrk BEFFF810 00008084 00010114 00011000 jun@raspberrypi ~/pi_asm $ ./printbrk BEFFF810 00008084 00010114 00011000
rvtl の修正
rvtl は、BSS領域に最初に確保した256KBをプログラムの保存、配列データ領域に使用します。 プログラムサイズが大きい、またはデータ量が多い場合には、BSS領域の最終アドレスを持つシステム変数「*」の値に増やしたいメモリ量を加えることで好きなだけメモリを確保する事が出来ます。rvtl の実行開始時にヒープ領域の先頭アドレスがランダムに割り当てられる場合、BSS領域の最終位置とヒープ領域の先頭位置の間にギャップができて、その部分がつながっているものとしてアクセスした瞬間に Segmentation fault が発生することになっていました。
プログラムの保存、配列データ領域の初期領域としてBSS領域にメモリを確保するのではなく、最初に sys_brk システムコールを実行して取得したアドレス (ヒープ領域の先頭) をプログラムの保存、配列データ領域とすることで、その後のメモリ拡張領域との間にギャップを作らないように対応する事にします。
rvtl を実行中のプロセスのメモリイメージ +--------------------+ : : : : +--------------------+ | rvtlコマンド | | | +--------------------+ | 作業領域 | | | | 変数用領域 A-Za-z | | スタック領域 | | 変数スタック領域 | +--------------------+ : : ギャップ = +--------------------+ | (rvtlのプログラム) | | 100 A=& | | 110 B=A+100 | | 120 A(99)=123 | | 130 B[5]=456 | | 140 ?=B[5] | & +--------------------+ A=& プログラム末 | 配列Aの領域 | | | +--------------------+ B=A+100 | 配列Bの領域 | | | |- - - - - - - - - - | | | | 空き領域 | | | * +--------------------+ メモリ領域最終(可変) : : : : : : |- - - - - - - - - - | | プロセスのスタック | | 引数文字列 | +--------------------+
rvtlの初期化部分のコード
- 最初に sys_brk システムコールに引数 0 を渡して現在の BRK (確保済みメモリ最終) の値を取得する
- 取得した値をヒープ領域(vtlコードやデータ保存領域)の先頭アドレスとする
- 上のアドレスに256KBの初期ヒープ領域を加えた値を引数にしてsys_brk システムコールを実行する
- 確保した領域の先頭4バイトに0xFFFFFFFF(コード末マーク)を書き込む
- コード末マークの直後のアドレスを「& データ領域先頭」システム変数に設定
@ システム変数の初期値を設定 mov r0, #0 @ 0 を渡して現在値を得る mov r7, #sys_brk @ brk取得 swi 0 mov r2, r0 mov r1, #', @ プログラム先頭 (,) str r0, [fp, r1,LSL #2] mov r1, #'= @ プログラム先頭 (=) str r0, [fp, r1,LSL #2] add r3, r0, #4 @ ヒープ先頭 (&) mov r1, #'& str r3, [fp, r1,LSL #2] ldr r1, mem_init @ MEMINIT=256*1024 add r0, r0, r1 @ 初期ヒープ最終 mov r1, #'* @ RAM末設定 (*) str r0, [fp, r1,LSL #2] swi 0 @ brk設定 mvn r3, #0 @ -1 str r3, [r2] @ コード末マーク
Raspberry Pi 用 rvtl のダウンロード (2013/04/25)
上記の他、フレームバッファのオープン時の不具合と編集機能をUTF-8に対応する変更を行ったバージョン rvtl-3.04.1eabi.tar.gz (130KB) を作成 (2013/04/25 linux-3.6に対応) しました。
使用するには次のように展開して、「./rvtl」で実行できます。対話モードから終了するには「~ (チルダ)」を押して [Enter] で終了します。詳細は Readme.txt を参照してください。
jun@raspberrypi ~ $ tar zxf rvtl-3.04.1eabi.tar.gz jun@raspberrypi ~ $ cd rvtl-3.04.1eabi jun@raspberrypi ~/rvtl-3.04.1eabi $ ls -l total 156 -rw-r--r-- 1 jun jun 17996 May 18 1999 COPYING -rw-r--r-- 1 jun jun 28324 Oct 21 2012 gpl.text -rw-r--r-- 1 jun jun 64819 Apr 25 19:01 Readme.txt -rwxr-xr-x 1 jun jun 22572 Apr 25 19:06 rvtl -rw-r--r-- 1 jun jun 10683 Apr 25 19:00 rvtl_ref.txt drwxr-xr-x 2 jun jun 4096 Apr 25 19:06 source drwxr-xr-x 2 jun jun 4096 Apr 25 19:04 vtl
rvtl のプログラム
以下のコードを実行するとシステム変数を表示します。フレームバッファは sys_mmap2 システムコールでメモリにマッピングしているので、その先頭アドレスも表示して、mmapで取得したアドレスもランダムな値を返すかどうかを確認します。
jun@raspberrypi ~/rvtl-3.04eabi/vtl $ cat sysinf.vtl
100 : -------------------------------------------
110 : Information
120 : 2012/10/25
130 : -------------------------------------------
140 |fbo : open frame buffer
150 [=0 : Range check off
160 "# Start from Line Number : " ?=# / : # 実行中の行番号を保持
170 "= Program starts from : $" ??== / : = プログラム先頭アドレス
180 "& Program ends at : $" ??=& / : $ コードの最終使用アドレス+1
190 " Program size : " ?=&-= " bytes" /
200 ", Memory Top : $" ??=, / : , アクセス可能先頭アドレス
210 "* Memory End : $" ??=* / : * メモリ最終位置を保持
220 " Merory size : " ?=*-, " bytes" /
230 T=_ : _ 現在の秒
240 "_ Second : " ?=T /
250 U=% : % マイクロ秒
260 " Micro second : " ?[6]=U /
270 w=. : . ウィンドウサイズ
280 ". Screen Width : " ?=w>>16 / : 上位16ビットに幅
290 " Height : " ?=w&$ffff / : 下位16ビットに高さ
300 " Expand Memory *=*+$1000 $" *=*+$1000 ??=* / / : brkを設定
310 " Frame buffer mem start : $" ??=g[44] /
320 " Frame buffer mem length: " ?=g[45] /
330 :
340 |fbc : close frame buffer
350 "END" #=-1
実行例
sysinf.vtl を3回実行してみました。「Program starts from」が毎回変化していることが確認できます。フレームバッファの先頭アドレスは毎回同じ値を返しています。
jun@raspberrypi ~/rvtl-3.04.1eabi/vtl $ ../rvtl sysinf.vtl <0947> #=1 # Start from Line Number : 160 = Program starts from : $01618000 & Program ends at : $0161861C Program size : 1564 bytes , Memory Top : $01618000 * Memory End : $01658000 Merory size : 262144 bytes _ Second : 1366885126 Micro second : 870647 . Screen Width : 101 Height : 53 Expand Memory *=*+$1000 $01659000 Frame buffer mem start : $5C006000 Frame buffer mem length: 2621440 END <0947> ~ jun@raspberrypi ~/rvtl-3.04.1eabi/vtl $ ../rvtl sysinf.vtl <0948> #=1 # Start from Line Number : 160 = Program starts from : $01E98000 & Program ends at : $01E9861C Program size : 1564 bytes , Memory Top : $01E98000 * Memory End : $01ED8000 Merory size : 262144 bytes _ Second : 1366885136 Micro second : 109695 . Screen Width : 101 Height : 53 Expand Memory *=*+$1000 $01ED9000 Frame buffer mem start : $5C006000 Frame buffer mem length: 2621440 END <0948> ~ jun@raspberrypi ~/rvtl-3.04.1eabi/vtl $ ../rvtl sysinf.vtl <0949> #=1 # Start from Line Number : 160 = Program starts from : $010CA000 & Program ends at : $010CA61C Program size : 1564 bytes , Memory Top : $010CA000 * Memory End : $0110A000 Merory size : 262144 bytes _ Second : 1366885137 Micro second : 485063 . Screen Width : 101 Height : 53 Expand Memory *=*+$1000 $0110B000 Frame buffer mem start : $5C006000 Frame buffer mem length: 2621440 END <0949> ~
rvtl を起動すると対話モードになるため、rvtlを終了するには 「~ (チルダ)」を入力して [Enter]キー を押してください。
rvtl-3.04.1eabi は Raspberry Pi のフレームバッファにラインやパターンを表示する機能も持っています。詳細はこちらを参照してください。