バッファーオーバーラン (バッファーオーバーフロー) の脆弱性をついた攻撃に入門する
0. 概要
脆弱性の基本的な存在、バッファーオーバーラン。簡単に言うと、メモリにロードされたプログラムを書き換えてしまうといったものである。
本来はバグであるものの、使い方によっては攻撃にも応用可能なため、脆弱性と呼ばれているわけである。今回はポインタの理解も含めて、C言語でバッファーオーバーランを説明する。
1. アドレス空間
アドレス空間について理解せずともバッファーオーバーランのプログラムを書くことはできるが、OSが提供するメモリ管理の基本なので概要だけでも掴んでおいた方が良い。
まず大きく分けて、アドレス空間にはカーネル空間とユーザ空間がある。
カーネル空間
カーネル空間は名前の通り、OS起動時にOSの重要な機能が読み込まれるメモリ領域のことである。
なお、Linux等の場合はカーネル空間→ユーザ空間(のプロセス)へ任意のシグナルを送ったりといった特権がある。
ユーザ空間
ユーザ空間は、プロセス(アプリケーション)起動時にそのプロセス情報が展開されるメモリ領域のことである。プロセス毎にページテーブルを持っており、メモリ領域はプロセス間で共有されない。一方、OSの情報は各プロセスで必要であったりするので、それらは共有的に使えるようカーネル空間の仮想アドレスがマッピングされている。
https://milestone-of-se.nesuke.com/sv-basic/architecture/user-space-kernel-space/
この説明からプロセスを超えてメモリを操作するのは難しいことや、ましてカーネル空間のメモリを操作することは難しいといったことが分かる。
基本的にメモリ空間に対する攻撃の初手は、攻撃するアドレスの予測を行うことであるが、アドレス空間配置のランダム化(ASLR)というデータ配置を無作為に行う技術も登場しておきり、攻撃は難化していっている。
2. バッファーオーバーラン
今回バッファーオーバーランを行う対象は、ユーザ空間に展開されたあるプロセスが入力を受け付けており、
それに対しバッファーオーバーランを起こさせるデータを入力して、データ入力を受け付けていない変数にこちらの意図した値を入力するといったことを試みたい。
2.1. 入力受付のプログラム
バッファーオーバーランを行うための素体プログラムを作る。入力を受け付けるプログラムを以下のように作る。
#include <stdio.h>
int main() {
int a = 0xFFffFFff;
char buf[4];
fgets(buf, 128, stdin);
return 0;
}
fgets
は入力データをbuf
にコピーする関数で、さらにデータサイズも指定できるセキュアな関数である。
しかし、バッファーオーバーランを起こすため今回はchar buf[4]
で確保したデータサイズより
大きな128
バイトのデータまでを受け入れられるようにしている。
今回はこのbuf
をオーバーフローさせてa
の変数を書き換えるということを行う。
仮にゲームであれば、名前入力でオーバーフローを起こさせて、何らかのステータスパラメータa
を好きな値にするというストーリーである。
2.2. アドレス配置の確認
では、buf
をどれくらいオーバーフローさせればa
を好きな値に書き換えられるのか、アドレスの配置を調べたいと思う。
そこで、以下のようにプログラムを書いて、各変数の先頭アドレスと中身を調べてみる。
#include <stdio.h>
void dispAddress(char *base) {
for (int i = 0; i < 20; i++) {
printf("[%p]: %02X \n", (base+i), *(base + i) & 0x000000FF);
}
}
int main() {
int a = 0xFFffFFff;
char buf[4];
printf("Buffer Address (hex): %p\n", &buf);
printf("A Address (hex): %p\n", &a);
printf("A value (hex): %p\n", a);
dispAddress(buf);
printf("\n");
fgets(buf, 128, stdin);
printf("%x %s\n", a, buf);
return 0;
}
アドレス番地と実体を1バイト単位で調べたいので、dispAddress
関数はchar
型を使っている。x86でint
型を用いると4byte
刻みになるので、分かりにくい。実行してみると以下のようになるはずである。
なお、相対アドレスは変わらないと思うが、絶対アドレスは実行環境によって変わると思うので、そこは適宜補完してほしい。
Buffer Address (hex): 007CFABC
A Address (hex): 007CFAC8
A value (hex): FFFFFFFF
?[007CFABC]: CC
?[007CFABD]: CC
?[007CFABE]: CC
?[007CFABF]: CC
?[007CFAC0]: CC
?[007CFAC1]: CC
?[007CFAC2]: CC
?[007CFAC3]: CC
?[007CFAC4]: CC
?[007CFAC5]: CC
?[007CFAC6]: CC
?[007CFAC7]: CC
?[007CFAC8]: FF
?[007CFAC9]: FF
?[007CFACA]: FF
?[007CFACB]: FF
?[007CFACC]: CC
?[007CFACD]: CC
?[007CFACE]: CC
?[007CFACF]: CC
このような結果が出力されたと思う。
buf
は007CFABC
アドレスから始まっており、本来なら4 bytes
分確保されている(buf[4]
と定義しているから)。
次にa
は007CFAC8
アドレスから4 bytes
分確保されていることが分かる。というのも、a
の先頭アドレス007CFAC8
から007CFACB
アドレスの4 bytes
までを合わせると初期化した数値と同じになるからである。
buf
の先頭アドレス007CFABC
及びa
の先頭アドレス007CFAC8
が12 bytes
離れており、なぜ5 bytes
以上、無駄にアドレス番地が離れているのかは、専門家ではないため詳しくは分からない。凡そ、プロセッサがデータを取り出しやすい大きさに分割するようなアライメント境界だとかの話だと思われる。5 bytes
以上なのはchar
には終了を示す00
が最後に自動的に入るためである。
ここで重要なのはbuf
からa
まで離れている量は12 bytes
だということである。
すなわち、それくらいの量を書き込めばa
の領域を侵犯することができる。
イメージでいうと、この様な感じ。
なお、実際は上下反転して考えるべきである。a
及びbuf
の変数宣言順序とメモリアドレスの順序が逆転していると思う。
これは変数がスタックされていっているからである。上に積みあがっていると考えればよい。
2.3. バッファーオーバーラン
これまでを踏まえてA
を14個ほど書き込んでみる。
なお、書き込んだ後、メモリ内の値がどのように変化しているか確認するため、再度dispAddress
関数で表示する。
#include <stdio.h>
void dispAddress(char *base) {
for (int i = 0; i < 20; i++) {
printf("[%p]: %02X \n", (base+i), *(base + i) & 0x000000FF);
}
}
int main() {
int a = 0xFFffFFff;
char buf[4];
printf("Buffer Address (hex): %p\n", &buf);
printf("A Address (hex): %p\n", &a);
printf("A value (hex): %p\n", a);
dispAddress(buf);
printf("Character code of a: %p \n", 'A');
printf("\n");
fgets(buf, 128, stdin);
printf("%x %s\n", a, buf);
dispAddress(buf);
return 0;
}
実行すると以下のような結果が得られたはずである。
Buffer Address (hex): 007CFABC
A Address (hex): 007CFAC8
A value (hex): FFFFFFFF
?[007CFABC]: CC
?[007CFABD]: CC
?[007CFABE]: CC
?[007CFABF]: CC
?[007CFAC0]: CC
?[007CFAC1]: CC
?[007CFAC2]: CC
?[007CFAC3]: CC
?[007CFAC4]: CC
?[007CFAC5]: CC
?[007CFAC6]: CC
?[007CFAC7]: CC
?[007CFAC8]: FF
?[007CFAC9]: FF
?[007CFACA]: FF
?[007CFACB]: FF
?[007CFACC]: CC
?[007CFACD]: CC
?[007CFACE]: CC
?[007CFACF]: CC
Character code of a: 00000041
AAAAAAAAAAAAAA
a4141 AAAAAAAAAAAAAA
?[007CFABC]: 41
?[007CFABD]: 41
?[007CFABE]: 41
?[007CFABF]: 41
?[007CFAC0]: 41
?[007CFAC1]: 41
?[007CFAC2]: 41
?[007CFAC3]: 41
?[007CFAC4]: 41
?[007CFAC5]: 41
?[007CFAC6]: 41
?[007CFAC7]: 41
?[007CFAC8]: 41
?[007CFAC9]: 41
?[007CFACA]: 0A
?[007CFACB]: 00
?[007CFACC]: CC
?[007CFACD]: CC
?[007CFACE]: CC
?[007CFACF]: CC
まずA
はASCIIコード(16進数)で41
である。
14文字打ったため、先頭アドレス007CFABC
から14 bytes
分埋まっていることが分かる。
変数a
のメモリ番地(007CFAC8
~007CFACB
)にも侵犯して41
が書き込まれている。
このため、FFFFFF
を示していたa
の整数はa4141
という値に変化している。
これで変数a
を直接操作せずとも値を変更できることが確認できた。
しかし1つ疑問が残る。以下の2つである。
?[007CFACA]: 0A
?[007CFACB]: 00
書き込んでいない余計なものまで書き込まれている。
これは文字の終了コードを示す0A00
、すなわちLF (0A) + NULL (00)
がメモリに書き込まれているだけである。
Author And Source
この問題について(バッファーオーバーラン (バッファーオーバーフロー) の脆弱性をついた攻撃に入門する), 我々は、より多くの情報をここで見つけました https://qiita.com/harmegiddo/items/dbb728f805da6630e420著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .