概要

最も広く知られているCPUアーキテクチャ

CPUのすべてのコンポーネント、ALU、制御ユニット、レジスタ、およびCPUの外部にあるメインメモリとI/Oデバイスを含むCPUを示す画像

CPUの構成

レジスタ

レジスタの分類

命令ポインタ

汎用レジスタ

EAX・RAX

色々あるけど、要は、1 本のレジスタを “拡大/縮小” してアクセスしているだけ?

64-bit モード
┌────────────────────────────────────┐
│                RAX (64 bit)        │
├──────────────┬──────────────┤
│    EAX (32)  │   使われない   │ ←32 bit 書込みはここを 0 クリア
├──────┬──────┤
│ AX16 │      │                   ←16 bit (=下半分)
├──┬──┤
│AL│AH│                               ←8 bit×2
└──┴──┘

EBX・RBX

ECX・RCX

EDX・ RDX

ESP・RSP

ESPとRSPは、スタックの一番上(最新)に積まれたデータの次のアドレスを指している

  高アドレス          ─────────────────
                     ← スタックは下へ伸びる
        以前に積んだデータ
        ︙
        最新のデータ
        [ RSP ]  ← “今ここ”(次の PUSH が書かれる場所)
  低アドレス          ─────────────────

EBP・RBP

役割

ESI・RSI

EDI・RDI

R8~R15

昔(32bit)の汎用レジスタは8個(EAX〜EDI)しかなかった

ステータスフラグレジスタ

ゼロフラグ ・ZF

キャリーフラグ ・CF

サインフラグ・SF

トラップフラグ・TF

セグメントレジスタ

コードセグメントレジスタ・CS

データセグメントレジスタ・DS

スタックセグメント・SS

追加セグメント・ES・FS・GS

メモリ

主メモリ

メモリアドレス空間(仮想アドレス)

0xFFFFFFFFFFFFFFFF  ← 高アドレス
│
│   ┌───────────────┐
│   │     スタック     │  ← 関数のローカル変数、戻り番地、引数など
│   │     (下向き成長) │
│   └───────────────┘
│
│   ┌───────────────┐
│   │  マップ領域    │  ← mmap()、共有ライブラリ、メモリマップファイルなど
│   └───────────────┘
│
│   ┌───────────────┐
│   │     ヒープ     │  ← malloc/new の領域(上向きに成長)
│   └───────────────┘
│
│   ┌───────────────┐
│   │   BSS領域      │  ← 初期化されていないグローバル変数
│   ├───────────────┤
│   │   データ領域    │  ← 初期化済みのグローバル/静的変数
│   ├───────────────┤
│   │   コード領域    │  ← 実行ファイルの機械語(実行専用)
│   └───────────────┘
│
0x0000000000000000  ← 低アドレス

コード領域

データ領域

ヒープ領域

バッファーオーバーフローが起こる例

char *buffer = malloc(32);
strcpy(buffer, user_input);  // ← 入力サイズ不明なまま書き込み

user_input が 32 バイトを超えるとバッファーオーバーフロー起きる

スタック領域

スタックレイアウト

おさらい

スタックバッファオーバーフロー

バッファーオーバーフローが起こる例

void vulnerable() {
    char buffer[16];         // ← スタックに確保された小さいバッファ
    gets(buffer);            // ← 入力の長さを制限せず読み込む ← ここが原因!
}

ここで16以上書き込むとバッファーオーバーフローが起きて、バッファを超えて入力が入り、関数の戻り先(RET)を書き換えてしまうので、任意コード実行できる

[stack]
--------------------------
| 戻り番地(RETアドレス) | ← ここが壊れる!
|------------------------|
| saved RBP              |
|------------------------|
| buffer[16]             | ← ここに書きすぎると上を上書き!
--------------------------

引数

関数のプロローグ

  1. 関数が呼び出されると、関数の実行に備えてスタックが準備される
    • つまり、関数の実行開始前に引数がスタックにプッシュされる
  2. リターンアドレスと旧ベースポインタがスタックにプッシュされる
  3. ベースポインタのアドレスはスタックの先頭(その時点での呼び出し元関数のスタックポインタ)に変更される
  4. 関数の実行中、スタックポインタは関数の要件に従って移動する
    1. 引数、リターンアドレス、およびベースポインタをスタックにプッシュし、スタックとベースポインタを再配置する

関数のエピローグ

  1. 関数終了時に、旧ベースポインタはスタックからポップされ、ベースポインタにセットされる
  2. リターンアドレスは命令ポインタにポップされ、スタックポインタはスタックの先頭を指すように再配置される

参考・引用

https://tryhackme.com/room/x8664arch