のらりくらり

物理化学分野のポスドクです。プログラミング、読書、自転車などが好きです。

Macで固定アドレスにプログラムをロードする

最近のトレンド?でもあるみたいですが、Macもそれに漏れず、OS X 10.5以降はプログラムのロード時に、「アドレス空間のランダム化(Address Space Layout Randomizationといいます。頭文字をとって以下はASLRと表記)」処理が行われています。
バイナリがASLRをEnableな状態にしてビルドされたものかどうかは、Macが標準で採用しているMach-oフォーマットのヘッダを見た時に、フラグでMH_PIEというのが立っているかどうかで確認できます(Mach-oフォーマットの規格については/usr/include/mach-o/loader.hに書かれています。これについては他に詳しいサイトがいくらでもあります)。
MH_PIEのMHは、Mach-Headerの略、その後ろのPIEがミソで、もともとはPIEというのはPosition Independent Executableの略で別にASLRに直接に対応する概念ではないのですが(PIE自体の元々の意味はPICとほとんど同じ)、Mach-oのフラグとしてはこれがASLR enableのフラグとして用いられています。

まずはASLRがONになっていることを確かめるために、以下のような簡単なコードを書いて3,4回実行させてみて実行させてみます。

#include <stdio.h>

int main(void) {
LABEL:
	printf("%p\n", &&LABEL);
	return 0;
}

ちなみに、この&&LABELっていうのは確かGNU独自拡張の一つで、gotoラベルの名前に&&をつけることで、その場所のポインタを取得することができます。
これを実行してみると、

$> gcc aslr.c
$> ./a.out
$> 0x107b3def8
$> ./a.out
$> 0x1002a0ef8
$> ./a.out
$> 0x101a10ef8

こうして見ると確かにアドレス自体は毎回変わっています。とは言っても、プロセスに割り当てられる仮想メモリ空間の中で完全にシャッフルしているわけではなくて、16進数の下3桁は毎回同じであるように、ページ単位でシャッフルが行われるようにはなっています(Macのページ協会は4096bytesだったと思います)

これは絶対アドレスの指定によるジャンプを用いた攻撃を避けやすくなる、という意味ではセキュリティ的には素晴らしいのですが、デバッグの時とかは実にめんどくさい機能です。実際、GDBとかのデバッガはデバッグ情報から取得できる関数のアドレスから、実際のメモリ上でのアドレスを取得する必要があるわけなので、そういう場合にやはりRandomizationは少々厄介な機能となっています(そうしないとstartとかmainを呼び出す前にブレークポイントすら貼れない)。GDBでは(実行中のプロセスにアタッチするときは別ですが)基本は、自プロセスからフォークして子プロセスをexecする前に、アドレス空間のランダム化をdesableな状態にすることでこの問題を解決しているようです。それについてのメモ。

とりあえず、まずは参考になりそうなGDBのソースを引用してみます。以下は、gdb-7.5のソース中の、gdb-7.5/gdb/darwin-nat.cの1501行目からです。

static void
darwin_execvp (const char *file, char * const argv[], char * const env[])
{
  posix_spawnattr_t attr;
  short ps_flags = 0; 
  int res; 

  res = posix_spawnattr_init (&attr);
  if (res != 0)
    {    
      fprintf_unfiltered
        (gdb_stderr, "Cannot initialize attribute for posix_spawn\n");
      return;
    }    

  /* Do like execve: replace the image.  */
  ps_flags = POSIX_SPAWN_SETEXEC;

  /* Disable ASLR.  The constant doesn't look to be available outside the
     kernel include files.  */
#ifndef _POSIX_SPAWN_DISABLE_ASLR
#define _POSIX_SPAWN_DISABLE_ASLR 0x0100
#endif
  ps_flags |= _POSIX_SPAWN_DISABLE_ASLR;
  res = posix_spawnattr_setflags (&attr, ps_flags);
  if (res != 0)
    {    
      fprintf_unfiltered (gdb_stderr, "Cannot set posix_spawn flags\n");
      return;
    }    

  posix_spawnp (NULL, argv[0], NULL, &attr, argv, env);
}

このposix_spawnとかのAPIはもともとfork+execみたいなもので、子プロセスをフォーク→実行してくれるもの(どちらかと言えばWindowsでいうCreateProcessに近い)みたいなのですが、まずはps_flagsとして、POSIX_SPAWN_SETEXECをすることで、forkされた子プロセス内でexec的な挙動のみをするように設定されています。
次に、

#ifndef _POSIX_SPAWN_DISABLE_ASLR
#define _POSIX_SPAWN_DISABLE_ASLR 0x0100
#endif
  ps_flags |= _POSIX_SPAWN_DISABLE_ASLR;
  res = posix_spawnattr_setflags (&attr, ps_flags);

もともと、Macのヘッダ中にも_POSIX_SPAWN_DISABLE_ASLRというプリプロセッサは定義されていないのですが、ここでそれを有効にしてやることで、ASLRをdesableにしているようです。
最後に、それをposix_spawnattr_setflagsをコールして設定して実際のバイナリを実行します。
以上をまとめると、とりあえずforkしてASLRをOFFにして実行する流れとしては以下のようになります。

これを

$> gcc -o spawn spawn.c
$> gcc -o random random.c

とすると、毎回、同じアドレスが出力されるようになります。ちなみに、なぜこのようにforkしてexecみたいな使い方をspawnでしているかというと、こうすることで、forkした後、spawnの直前に子プロセス側から親プロセスにむけてptraceシステムコールでATTACH_MEをすることで、デバッガみたいなものができるためです。