1. セキュリティホール

  • システムの欠陥や仕様上の問題に起因する脆弱性(ぜいじゃくせい)
  • 主たる原因はソフトウェアの不具合(不具合のないソフトウェアは存在しない)
  • 悪意のあるユーザに不正操作目的で利用される
    • 情報改竄
    • 情報漏洩
    • 他のコンピュータへの攻撃の踏み台
    • サービス妨害など
  • インターネットの普及に伴い顕在化、放置することの危険性が高まる
  • オープンソースの功罪(良くも悪くもセキュリティホールの発見が容易に)
  • ほとんどが意図せず作られてしまったものであるが、中には意図的と疑われるものも
  • セキュリティホール探しやセキュリティホールの利用は、まるでゲームの攻略
  • 一般的な防御策
    • ファイアウォールの利用(ルータおよびホストベース)
    • 不要なサービスの停止
    • アカウント、パスワードの管理を厳重にする
    • 対策パッチをこまめにあてる
    • 不正アクセス試行のログを監査し、試行を繰り返すサイトからのアクセスを遮断
  • 古典的な代表例はバッファオーバフロー

Next































バッファオーバフロー

  • バッファ

    データの一時保持などを目的とするプログラム内部の有限なメモリ領域

  • バッファオーバフロー

    バッファのサイズを越えるデータを流し込むことで、対象のバッファに隣接する後ろのメモリ領域を破壊し、不正操作などに利用する

  • バッファオーバフロー自体はありふれた不具合
  • 引き起こされる動作は、そもそも開発者が想定していなかったものがほとんど
  • 不正操作利用へ結び付けるには知識および技術が必要
  • プログラム動作環境の権限の範囲で、任意の操作がなされる危険性
  • そのプログラムにsビット(suid, sgid)が設定されていた場合、プログラムの所有者の権限に近い状態で実行することが可能
  • # ls -l /usr/bin/passwd /etc/passwd /etc/shadow
    -rw-r--r-- 1 root root    1013 Aug 15 06:17 /etc/passwd
    -rw-r----- 1 root shadow   681 Aug 15 06:17 /etc/shadow
    -rwsr-xr-x 1 root root   31704 Nov 14  2009 /usr/bin/passwd
    
  • もしもroot権限でプロセスが起動されていたら…
  • WindowsでもSYSTEM権限の奪取は可能

Next































バッファオーバフローの例(C言語ソースレベル)

    以下のC言語ソースコードでは、スタック上に確保された4つのバッファが隣接

         1  int
         2  main()
         3  {
         4          char    buf4[4];
         5          char    buf3[4];
         6          char    buf2[4];
         7          char    buf1[4];
         8
         9          memset(buf1, '1', 8);
        10          memset(buf2, '2', 8);
        11          memset(buf3, '3', 8);
        12          memset(buf4, '\0', 4);
        13
        14          printf("%s\n", buf1);
        15          printf("%s\n", buf2);
        16          printf("%s\n", buf3);
        17
        18          return(0);
        19  }
    
  • buf1からbuf4が名前とは逆順に宣言されていることに注目
  • スタックは大きいアドレスから小さいアドレスの方向へ使用され(push命令)、返却時(pop命令)は逆方法に使用される
  • memset()で実際のバッファのサイズより大きい領域を更新している
  • 実行結果は?

Next































バッファオーバフローの例(続き)

    実行結果は以下の通り。

    $ cc bof.c -o bof
    $ ./bof
    111122223333
    22223333
    3333
    

    デバッガ実行で何が起きているか確認。

    $ cc bof.c -g -o bof
    $ gdb bof
    GNU gdb 6.4.90-debian
    [snip]
    
    (gdb) list
    1        int
    2        main()
    3        {
    4                char        buf4[4];
    5                char        buf3[4];
    6                char        buf2[4];
    7                char        buf1[4];
    8        
    9                memset(buf1, '1', 8);
    10               memset(buf2, '2', 8);
    (gdb) b 10
    Breakpoint 1 at 0x8048375: file bof.c, line 10.
    (gdb) r
    Starting program: /home/iwasaki/course/day-1/securityHole/bof 
    Failed to read a valid object file image from memory.
    
    Breakpoint 1, main () at bof.c:10
    10               memset(buf2, '2', 8);
    (gdb) p &buf1
    $1 = (char (*)[4]) 0xbfaa8ce4
    (gdb) p &buf2
    $2 = (char (*)[4]) 0xbfaa8ce8
    (gdb) p &buf3
    $3 = (char (*)[4]) 0xbfaa8cec
    (gdb) p &buf4
    $4 = (char (*)[4]) 0xbfaa8cf0
    (gdb) p buf1
    $5 = "1111"
    (gdb) p buf2
    $6 = "1111"
    (gdb) n
    11               memset(buf3, '3', 8);
    (gdb) 
    12               memset(buf4, '\0', 4);
    (gdb) 
    14               printf("%s\n", buf1);
    (gdb) p buf1
    $7 = "1111"
    (gdb) p buf2
    $8 = "2222"
    (gdb) p buf3
    $9 = "3333"
    (gdb) p buf4
    $10 = "\000\000\000"
    (gdb) n
    111122223333
    15               printf("%s\n", buf2);
    (gdb) 
    22223333
    16               printf("%s\n", buf3);
    (gdb) 
    3333
    18               return(0);
    (gdb) 
    19       }
    (gdb) quit
    

    デバッガのp(print)コマンドで表示しても、隣接するバッファが一つの文字列となっていることに気づきにくい。

    これはデバッガは実行形式のシンボル情報から各バッファのサイズを把握しており、そのサイズ分の表示しかしないため。

Next































バッファオーバフローの例(続き2)

    各バッファにmemset()で値を格納した直後の状態をまとめる。

    buf1(0xbfaa8ce4):        1111                     9 memset(buf1, '1', 8);
    buf2(0xbfaa8ce8):        1111
    buf3(0xbfaa8cec):        
    buf4(0xbfaa8cf0):        
    
    buf1(0xbfaa8ce4):        1111                    10 memset(buf2, '2', 8);
    buf2(0xbfaa8ce8):        2222
    buf3(0xbfaa8cec):        2222
    buf4(0xbfaa8cf0):        
    
    buf1(0xbfaa8ce4):        1111                    11 memset(buf3, '3', 8);
    buf2(0xbfaa8ce8):        2222
    buf3(0xbfaa8cec):        3333
    buf4(0xbfaa8cf0):        3333
    
    buf1(0xbfaa8ce4):        1111                    12 memset(buf4, '\0', 4);
    buf2(0xbfaa8ce8):        2222
    buf3(0xbfaa8cec):        3333
    buf4(0xbfaa8cf0):        
    

    続くprintf()では各バッファの内容を表示しているが、printf()は指定されたアドレスからbuf4の先頭にある終端文字'\0'までの領域を文字列データとして認識する。

    そのため、buf1の内容表示ではbuf1の先頭からbuf4の先頭まで、buf2ではbuf2の先頭からbuf4の先頭までといった具合いに、指定されたバッファの領域を越えてデータにアクセスしている。

Next































バッファオーバフローの例をアセンブリ言語レベルで見る

    C言語ソースコードは以下のようにcc(C言語コンパイラドライバ)に-Sオプションを指定してアセンブリ言語ソースファイルを生成できる。

    $ cc -S bof.c
    

    生成されたアセンブリ言語ソースは以下の通り(抜粋)。

    main:
    	leal    4(%esp), %ecx
    	andl    $-16, %esp
    	pushl   -4(%ecx)
    	pushl   %ebp
    	movl    %esp, %ebp
    	pushl   %ecx
    	subl    $20, %esp
    	leal    -20(%ebp), %eax                 9    memset(buf1, '1', 8);
    	movl    $825307441, (%eax)
    	movl    $825307441, 4(%eax)
    	leal    -16(%ebp), %eax                10    memset(buf2, '2', 8);
    	movl    $842150450, (%eax)
    	movl    $842150450, 4(%eax)
    	leal    -12(%ebp), %eax                11    memset(buf3, '3', 8);
    	movl    $858993459, (%eax)
    	movl    $858993459, 4(%eax)
    	leal    -8(%ebp), %eax                 12    memset(buf4, '\0', 4);
    	movl    $0, (%eax)
    	leal    -20(%ebp), %eax                14    printf("%s\n", buf1);
    	movl    %eax, (%esp)
    	call    puts
    	leal    -16(%ebp), %eax                15    printf("%s\n", buf2);
    	movl    %eax, (%esp)
    	call    puts
    	leal    -12(%ebp), %eax                16    printf("%s\n", buf3);
    	movl    %eax, (%esp)
    	call    puts
    	movl    $0, %eax                       18    return(0);
    	addl    $20, %esp
    	popl    %ecx
    	popl    %ebp
    	leal    -4(%ecx), %esp
    	ret
    
  • ※上記はGCC version 4.1.2でのコンパイル結果。演習環境のversion 4.3.2では更に最適化された結果となる。
  • 小さいサイズのmemset()はmovl命令(4バイト単位のデータ格納)に、printf()はputs()にコンパイラによって置換されている。
  • memset()の部分に注目すると、leal命令でスタック上に確保されたバッファのアドレスを計算し、2回のmovl命令で合計8バイトの連続領域を書き換えていることが分かる。
  • 蛇足ながら、$825307441といった即値は10進数表記であり、16進数表記では0x31313131、つまりASCIIコードの'1'が4つ並んだバイト列となる。

Next































継続は力なり

  • セキュリティ対策は継続しなければ意味がない
  • セキュリティの定番サイト
  • バッファオーバフローを例にした演習の目的
    • セキュリティホールの利用を経験することで、現実の脅威であることを実感
    • 新たなセキュリティホールを作らないためのプログラミング上のポイントを理解
    • セキュリティホールを利用するクラッカーの手口を考察し、その予防策を検討

Next































バッファオーバフローを悪用してみる?

はい! ぜひ!






























 STOP: c0000218 Unknown Hard Error
 Hard Unknown Error
 Beginning dump of physical memory. 
 Physical memory dump complete. Contact your system administrator or 
 technical support group for further assistance.
 Stop 0xc0000218 (0xe11a30e8, 0x00000000, 0x000000000, 0x00000000) 
 UNKNOWN_HARD_ERROR 






































こんなことになっても当方は一切責任を負いません
Next





























































[実例1] rootのみ参照可能なファイルを一般ユーザ権限で参照する

  • ミッション
  • /etc/passwdは一般ユーザも参照可能であるが、各ユーザのパスワードは暗号化された状態で/etc/shadowに格納されている。

    しかしながら/etc/shadowは参照するにはroot(または特定グループshadow)の権限が必要となる。

    $ cat /etc/shadow
    cat: /etc/shadow: Permission denied
    

    バッファオーバフローのセキュリティホールが確認されているプログラムを利用して、rootの暗号化パスワード文字列を入手せよ。

    なお入手した暗号化パスワード文字列は、手元の同一環境のコンピュータでじっくりとbrute-force的に解読する予定。

  • ターゲット
  • なぜか所有者がrootでsビット(suid: set user id, sgid: set group id)が設定されているプログラム。

    $ make
    [snip]
    $ ls -l sec_hole1
    -rwsr-sr-x 1 root    root     8326 Aug 15 20:28 sec_hole1
    
    

    実行して質問にYesと入力すると特定のファイルの内容が表示される。

    $ ./sec_hole1
    Do you want to read userfile.txt? (Yes/No) ->Yes
    OK, this is the content of userfile.txt
    --------
    USER FILE.
    

Next































[実例1] rootのみ参照可能なファイルを一般ユーザ権限で参照する(続き)

  • ソースコード
  • 入手したソースコードはC言語で記述されていた。以下は抜粋である。

         5  #define USERFILE        "userfile.txt"
         6
         7  int
         8  main(int argc, char *argv[])
         9  {
        10          int             fd, n;
        11          static char     *file;
        12          static char     buf[16];
        13
        14          file = USERFILE;
        15          printf("Do you want to read %s? (Yes/No) ->", file);
        16
        17          gets(buf);
        18          if (buf[0] == 'N' || buf[0] == 'n') {
        19                  exit(0);
        20          }
        21
        22          printf("OK, this is the content of %s\n--------\n", file);
        23
        24          fd = open(file, O_RDONLY);
        25          if (fd < 0) {
        26                  perror("file open error");
        27                  exit(0);
        28          }
        29          while (n = read(fd, buf, sizeof(buf))) {
        30                  write(1, buf, n);
        31          }
        32
        33          close(fd);
        34          exit(0);
        35  }
    

    入力データの長さのチェックをおこなわない悪名高いgets()を使用している。

    gets()で使用するbufに隣接する変数fileの内容を/etc/shadowに書き換えて、root権限で/etc/shadowの内容を表示できそうだ。

Next































[実例1] rootのみ参照可能なファイルを一般ユーザ権限で参照する(続き2)

  • アプローチ
  • gets()の入力に長い文字列を与えてバッファ(buf[16])を溢れさせて、隣接する変数fileに"/etc/shadow"という文字列が格納されたアドレスを書き込む。

    "/etc/shadow"という文字列が格納されたアドレスを0x12345678とすると、与える文字列は以下のような感じになる。

    	YYYYYYYYYYYYYYYYY[  0x12345678 ]
    	←─buf[16]へ─→←─ fileへ─→
    

    ただし今回のターゲットのコンピュータは、データの下位バイトから並べるリトルエンディアン(little endian)のシステムなので、格納するアドレスを指定する際には注意が必要だ。こんな感じになる。

    	YYYYYYYYYYYYYYYYY[ 78 56 34 12 ]
    	←─buf[16]へ─→←─ fileへ─→
    

    少し問題なのが、 どうやってターゲットの実行環境に"/etc/shadow"という文字列を渡して、その正確なアドレスを知るか なのだが・・・

Next































[実例1] rootのみ参照可能なファイルを一般ユーザ権限で参照する(続き3)

  • アプローチ(続き)
  • 今回はターゲットの起動時にコマンドライン引数として"/etc/shadow"を与え、ターゲットのargv[1]のアドレス値を予想して変数fileに書き込むことにする。

    ターゲットを関連する変数アドレスを表示するように修正して、これまでのアイディアの実現可能性を確認してみよう。

        14          printf("argv[0] = %p: %s\n", argv[0], argv[0]);
        15          printf("argv[1] = %p: %s\n", argv[1], argv[1]);
        16          printf("buf     = %p\n", &buf);
        17          printf("file    = %p\n", &file);
    
    $ ./sec_hole1 /etc/shadow
    argv[0] = 0xbffffa38: ./sec_hole1
    argv[1] = 0xbffffa44: /etc/shadow
    buf     = 0x804990c
    file    = 0x804991c
    

    想定どおりbuf[16]の直後に変数fileが配置されている。

    この場合、argv[1]の0xbffffa44というアドレスをlittle endian形式でfileへ格納できれば成功だ。

    実行するたびに表示されるargv[1]のアドレスが変わるが、何度か繰り返していると必ず同じアドレスが使われることも確認できた。

    まだ未完成だが、sec_hole1のセキュリティホールを利用するプログラムを、sec_hole1_crack.cとして作成している。

    さて、そろそろ残りのコードでも書こうか。

Next































[実例2] リモートからroot権限で任意のコードを実行する

  • ミッション
  • 先日のrootのパスワード暗号化文字列のミッションの際に、侵入経路の痕跡がログに残ってしまったらしい。

    ログの改竄はroot権限でしか出来ないため、以下のスクリプトをroot権限で実行して欲しい。

    	cd /var/log
    	sed 's/133\.87\.136\.1/202.229.63.242/g' auth.log > auth.log.tmp
    	mv auth.log.tmp auth.log
    

    なお、今後このホストへのログインは極力避け、リモートから操作する仕組みを構築すること。

  • ターゲット
  • inetd経由でroot権限で動作しているサーバプログラム。ポート番号10000で通信する文字列変換サービスをおこなうらしい。

    入手したこのプログラムのプロトコル(通信規約)は以下の通り。単純な構造だ。

    クライアント側
    (リクエスト)
    サーバ側
    (レスポンス)
    備考
    0,1,2のいづれかの番号 最後にリターンキーを入力する
    "OK"または"NG" 入力値が妥当なら"OK"、不正なら"NG"を返して通信切断
    任意の文字列 最後にリターンキーを入力する
    変換後の文字列 文字列送信後、サーバ側から通信切断

    クライアントプログラムは入手できなかったので、telnetで接続して挙動を確認してみる。

    $ telnet localhost 10000
    1
    OK
    Hi, I won't do any wrong things,  so please trust me.
    HI, I WON'T DO ANY WRONG THINGS,  SO PLEASE TRUST ME.
    Connection colosed by foreign host.
    $
    
    

    規約通りの動作だ。最初の番号1は大文字変換の意味らしい。

Next































[実例2] リモートからroot権限で任意のコードを実行する(続き)

  • ソースコード
  • ターゲットのソースコードを入手した。3種類の文字列変換をおこなう処理は関数として実装されており、 main()で呼び出す関数を変数funcptrへ設定して呼び出す構造だ。

    以下はmain()とそれに関連するコードの抜粋。

    
        55  typedef struct {
        56          int     id;
        57          int      (*funcptr) (const char *str);
        58  } funcdef_t;
        59
        60  funcdef_t funcdef[] = {
        61          {0, (int (*) (const char *str))func0},
        62          {1, (int (*) (const char *str))func1},
        63          {2, (int (*) (const char *str))func2},
        64          {3, 0}
        65  };
        66
        67  int
        68  main(void)
        69  {
        70          int     i, funcid;
        71          char    *ep;
        72          static char     tmp[256];
        73          static int      (*funcptr) (const char *str);
        74          static char     buf[64];
        75
        76          memset(tmp, 0, sizeof(tmp));
        77          read(0, tmp, sizeof(tmp));
        78          funcid = strtoq(tmp, &ep, 10);
        79
        80          funcptr = 0;
        81          for (i = 0; funcdef[i].funcptr != 0; i++)
        82          {
        83                  if (funcid == i)
        84                  {
        85                          funcptr = funcdef[i].funcptr;
        86                          break;
        87                  }
        88          }
        89
        90          if (funcptr == 0)
        91          {
        92                  write(1, "NG\n", 3);
        93                  exit(0);
        94          }
        95          write(1, "OK\n", 3);
        96
        97          memset(tmp, 0, sizeof(tmp));
        98          read(0, tmp, sizeof(tmp));
        99
       100          strncpy(buf, tmp, strlen(tmp));
       101
       102          (void)(*funcptr) (buf);
       103
       104          exit(0);
       105  }
    

    どこにバッファオーバフローのセキュリティホールがあるのか、もう一目瞭然だ。

Next































[実例2] リモートからroot権限で任意のコードを実行する(続き2)

  • アプローチ
  • この変数funcptrは関数へのポインタ(アドレス)として宣言されているため、隣接するバッファを溢れさせてここを書き換えてやればよい。

    書き換える内容はシェルコマンドを実行するsystem()へのポインタにしよう。 このライブラリ関数は基本的なものなので、どんなプログラムにもリンクされるlibcに含まれている。

    とりあえずsystem()の仕様を確認しておこう。

    $ man 3 system
    

    マニュアルページには 「system() は command で指定したコマンドを /bin/sh -c command の形で実行する。指定したコマンドが終了すればこの関数も終了する。コマンド実行中は、 SIGCHLD はブロックされ、 SIGINT と SIGQUIT は無視される。」 と書いてある。

    つまり、入力文字列のバッファの内容を目的のshellコマンド文字列を設定して、 変数funcptrをsystem()へのポインタへ書き換えると、root権限で任意のコマンドを実行できそうだ。

    少し問題なのが サーバプログラムのプロセス空間でのsystem()の配置アドレス なのだが…

Next































[実例2] リモートからroot権限で任意のコードを実行する(続き3)

  • アプローチ(続き)
  • サーバプログラムのプロセス空間でのsystem()の配置アドレスを表示するようソースコードを修正してみる。

        76          printf("%p\n", system);
    

    この修正を加えたプログラムを同一の実行環境で動作させ、得られたアドレスを基にターゲットの環境で試行錯誤しよう。 大きく変わらないはずだから、少しづつアドレスをずらして試していけば、いずれ成功するだろう。

    $ telnet localhost 10000
    1
    OK
    Hi, I won't do any wrong things,  so please trust me.
    HI, I WON'T DO ANY WRONG THINGS,  SO PLEASE TRUST ME.
    0x804843c
    Connection colosed by foreign host.
    $
    

    system()の引数として指定する文字列は、shellコードとして正しくないとsystem()が異常終了して目的が達成できなくなるので注意が必要だ。

    system()へのポインタは文字列ではなく32ビットのアドレスを表現するバイナリのデータなので、そのままshellには解釈させられない。 シングルクォーテンションで括って、ダミーでechoコマンドにでも喰わせておこう。

Next































[実例2] リモートからroot権限で任意のコードを実行する(続き4)

  • アプローチ(続き)
  • 一般ユーザでターゲットのコンピュータにログインし、ログの改竄をおこなう一連のコマンドを"/tmp/manipulateLog.sh"に記述しておけばよいだろう。

    このファイルの最後に"rm /tmp/manipulateLog.sh"とでも書いておけば、証拠隠滅工作も完璧だ。

    バッファに流し込む内容はこんな感じだ。

        /tmp/manipulateLog.sh; exit; echo '(中略)[ 3c 84 04 08 ]'
        ←─────── buf[64]へ ──────→← funcptrへ →←─ tmp[256]へ─→
    

    C言語で表現すると、以下の処理をroot権限でおこなうのと等価だ。

        system("/tmp/manipulateLog.sh; exit; echo ' (中略) '");
    

    "/tmp/manipulateLog.sh"の部分は、今後の遠隔操作の継続を考慮して、任意のコマンドを指定できるよう可変にしておこう。

    まだ未完成だが、sec_hole2のセキュリティホールを利用するプログラムを、sec_hole2_crack.cとして作成している。

    さて、そろそろ残りのコードでも書こうか。

    注意: 演習ではmanipulateLog.shで実際にログを改竄する必要は無く、rootのみ実行可能なコマンド(cat /etc/shadowなど)が実行できることを確認すればOK。

Next































2. アセンブリ言語の基礎

  • CPUが直接解釈するマシン語の次に低位レベルのプログラミング言語
  • すべてのOSのカーネルの記述ではCPUを直接制御する部分が必ず存在し、この部分のコードは大抵の場合アセンブリ言語が使用される
  • 一般のアプリケーションを実行する場合でも、最終的にはCPUがその実行形式のマシン語を解釈
  • アセンブリ言語は可読性の低いマシン語を、人間が解釈する場合の強力なツール
  • C言語などの、より高級言語で記述されたプログラムは、コンパイラによってアセンブリ言語へ変換
  • アセンブリ言語はアセンブラによってオブジェクトモジュールへ変換され、リンカによって最終的な実行形式となる
  •         hoge.c ──→ hoge.s ──→ hoge.o ──→ hoge
                     ↑            ↑            ↑
                     │            │            │
                     │            │            │
                Cコンパイラ    アセンブラ      リンカ
                    cc            as            ld
    
  • C言語ソースの段階では、CPUがプログラムをどのように解釈して実行するかは想像する他は無いが、アセンブリ言語の段階では明白となる
  • コンパイラの開発やデバッグにもアセンブリ言語の知識は必須
  • CPUのアーキテクチャによって命令セットはもちろんアセンブリ言語の仕様は異なるものの、大局的に見て基本は同じ

Next































CPUの基本構成

  • CPUの基本構造の概念図
  •       ┌────────────────┐                  ┌───┐
          │┌────┐    ┌──────┐│                  │      │
          ││        │    │            ││────────→│メモリ│
          ││レジスタ│←→│            ││                  │      │
          ││        │    │            ││←────────│      │
          │└────┘    │            ││                  └───┘
          │                │演算ユニット││                          
          │                │            ││                  ┌───┐
          │                │            ││                  │      │
          │                │            ││────────→│入出力│
          │     CPU        │            ││                  │ 機器 │
          │                │            ││←────────│      │
          │                └──────┘│                  └───┘
          └────────────────┘
    
  • CPUの基本動作
    • メモリバスから命令を読み込む
    • メモリバスからデータを読み込む
    • レジスタへデータを格納する
    • 演算を実行する
    • レジスタからデータを取り出す
    • メモリバスへデータを書き込む
    • I/Oバスからデータを読み込む
    • I/Oバスへデータを読み込む

Next































レジスタ

  • レジスタはCPU内部に複数個用意されている高速アクセス可能な小さなメモリ
  • レジスタの種類 説明
    汎用レジスタ 算術、論理演算命令で利用可能な広範囲用途のレジスタ
    インデックスレジスタ 連続メモリ領域の転送、配列操作などで使用される
    ポインタレジスタ 一時的な領域割り当て、サブルーチンでの引数パラメータへのアクセス、スタック操作などで使用される
    セグメントレジスタ メモリ操作の際に使用される
    フラグレジスタ 演算ユニットの状態を参照する際に使用される
    制御レジスタ プロセッサの状態を参照または変更する際に使用される

Next































レジスタ(続き)

  • Intelアーキテクチャ 32ビットプロセッサの代表的なレジスタ
  • レジスタの種類 レジスタ名 32ビット
    表記
    16ビット
    表記
    8ビット
    表記
    汎用レジスタ アキュームレータ EAX AX AH, AL
    ベース EBX BX BH, BL
    カウント ECX CX CH, CL
    データ EDX DX DH, DL
    インデックスレジスタ ソースインデックス ESI SI
    デスティネーション EDI DI
    ポインタレジスタ ベースポインタ EBP BP
    スタックポインタ ESP SP
    命令ポインタ EIP
    セグメントレジスタ コード CS
    データ DS
    エクストラ ES
    データ FS
    データ GS
    スタック SS

Next































Intelシンタックス、AT&Tシンタックス

  • アセンブリ言語のシンタックスの代表的なものは、IntelシンタックスとAT&Tシンタックスの二つ
  • IntelシンタックスはIntelなどのCPUベンダのマニュアルで使用されている他、商用OSのアセンブラでも多く使われている
  • AT&Tシンタックスは、Unix系のOSのアセンブラで使用されてきた経緯があり、FreeBSDやLinuxで使われているGNUアセンブラもこちらのシンタックスを採用
  • アセンブリ言語の最小要素は、オペコード(命令)とオペランド(命令のパラメータ)
  • 両者のシンタックスには、即値オペランド、レジスタオペランド、オペランドのソースとディスティネーションの順序、 メモリオペランドのサイズ、メモリオペランドなどの違いがある。
  • 差異 Intel
    シンタックス
    AT&T
    シンタックス
    備考
    即値オペランド PUSH 4 push $4 値4をスタックへ積む
    レジスタオペランド MOV DS, AX movw %ax, %ds AT&Tシンタックスではレジスタの前に%をつけない場合は、メモリオペランドとして解釈される
    ソースとディスティネーションの順序 MOV EBX, 4 movl $4, %ebx EBXに4を代入しているが、Intelはプログラミング言語的、AT&Tは自然言語的
    メモリオペランドのサイズ MOV AL,
    BYTE PTR SYMBOL

    MOV AX,
    WORD PTR SYMBOL

    MOV EAX,
    DWORD PTR SYMBOL
    movb symbol, %al
    movw symbol, %ax
    movl symbol, %eax
    8, 16, 32ビットそれぞれの場合での修飾の方法が違う

Next































主要な命令セット

  • よく使われる代表的な命令セットは以下のとおり(AT&Tシンタックス)
  • 形式 基本形
    (メモリオペランド修飾無し)
    説明
    movl op1, op2 MOV op1からop2へデータを転送する
    (Move)
    xorl op1, op2 XOR op1とop2のビットごとの排他的論理和(XOR)演算をおこない、結果をop2へ格納してフラグをセットする
    (Logical Exclusive OR)
    subl op1, op2 SUB op2からop1を減算し、結果をop2へ格納してフラグをセットする
    (Subtract)
    addl op1, op2 ADD op2とop1を加算し、結果をop2へ格納してフラグをセットする
    (Add)
    cmpl op1, op2 CMP op2とop1を比較し、フラグをセットする
    (Compare)
    jc op JC CF(キャリーフラグ) = 1の場合に、opで指定されるアドレスへ制御を移行
    (Jump if carry)
    jz op JZ ZF(ゼロフラグ) = 1の場合に、opで指定されるアドレスへ制御を移行(JEも同じ)
    (Jump if zero) (Jump if equal)
    jmp op JMP 無条件でopで指定されるアドレスへ制御を移行
    (Jump)
    leal op1, op2 LEA op1で指定されるメモリオペランドの実効アドレスを計算し、結果をop2で指定されるレジスタへ格納する
    (Load Effective Address)
    pushl op PUSH スタックポインタレジスタをデクリメントし、新しいスタックエントリを作成してopの値をコピーする
    (Push Word or Doubleword onto the Stack)
    popl op POP スタックトップにある値をopで指定されたオペランドへコピーし、スタックポインタレジスタをインクリメントする
    (Pop a Value from the Stack)
    leave LEAVE スタックフレームを破壊し、ローカルなスタック空間を解放
    (High Level Procedure Exit)
    ret RET スタックトップにあるアドレスに制御を移行
    (Return from Procedure)
  • 完全な命令セットのリファレンスについて、以下のURL からダウンロードできる「IA-32 インテル(R)アーキテクチャ ソフトウェア・デベロッパーズ・マニュアル」 中巻AおよびBの「命令セット・リファレンス」を参照
  • http://www.intel.co.jp/jp/download/

Next































[演習]アセンブリ言語の基礎

  • C言語ライブラリstrlen()のアセンブリ言語版を作成する。C言語プログラムからの利用の際のプロトタイプ宣言は以下のとおりとする。
  • size_t asm_strlen(const char *s);
    
  • 以下のソースのTODOの部分を埋める形で完成させる。
  •      1          .text
         2  .globl asm_strlen
         3
         4  asm_strlen:
         5          pushl   %ebp
         6          movl    %esp, %ebp
         7          movl    8(%ebp), %esi
         8          xorl    %eax, %eax
         9  loop:
        10          /* TODO */
        11  finish:
        12          leave
        13          ret
    
    • この関数への制御移行時点でスタックにはアドレスの小さい方から、呼び出し元の次に実行すべき命令が格納されているアドレス(32ビット)、関数への引数が格納されているアドレス(32ビット)の順に格納されている。
    • 同じレジスタへのXOR演算は、0を代入するのと等価。
    • 関数の戻り値は%eaxに格納してリターンする。

  • テストドライバをmain.cとして用意しているので、makeした後に以下のように動作確認をおこなう。
  • $ make
    $ ./main abc
    3
    $
    
  • ヒント1: strlen() 関数は文字列 s の長さを計算する。このとき、終端文字 '\0' は計算に含まれない。
  • ヒント2: CMP命令を使う

Next