Squ1rrel CTF 2024 CRYPTO WP
Lazy RSA
Description
Generating primes is too hard, but I did find a couple posted online!
n: 23690620655271165329693230765997410033604713853187305472268813793031152348107488119317901392104240429826482611449247251262846508667797483465355228800439339041030982259847598574606272955688345490638311164838117491821117626835340577511562130640807587611523935604871183668968359720411023759980144229161581597397061850707647104033348795132205561234674677139395868595692235525931999596382758921793937149945229459379437008216713404350896206374483356969246476531491049930769999387038678280465689487577291475554699094024761030833540509263174840007922218340417888061099317752496279552046029470370474619439450870110783844218281
e: 65537
ct: 11420169733597912638453974310976296342840438772934899653944946284527921765463891354182152294616337665313108085636067061251485792996493148094827999964385583364992542843630846911864602981658349693548380259629884212903554470004231160866680745154066318419977485221228944716844036265911222656710479650139274719426252576406561307088938784324291655853920727176132853663822020880574204790442647169649094846806057218165102873847070323190392619997632103724159815363319643022552432448214770378596825200154298562513279104608157870845848578603703757405758227316242247843290673221718467366000253484278487854736033323783510299081405
Solution
Easy! We just have to factor n
from Crypto.Util.number import *
import gmpy2
# factordb.com n
n = 23690620655271165329693230765997410033604713853187305472268813793031152348107488119317901392104240429826482611449247251262846508667797483465355228800439339041030982259847598574606272955688345490638311164838117491821117626835340577511562130640807587611523935604871183668968359720411023759980144229161581597397061850707647104033348795132205561234674677139395868595692235525931999596382758921793937149945229459379437008216713404350896206374483356969246476531491049930769999387038678280465689487577291475554699094024761030833540509263174840007922218340417888061099317752496279552046029470370474619439450870110783844218281
p=136883787266364340043941875346794871076915042034415471498906549087728253259343034107810407965879553240797103876807324140752463772912574744029721362424045513479264912763274224483253555686223222977433620164528749150128078791978059487880374953312009335263406691102746179899587617728126307533778214066506682031517
q=173071049014527992115134608840044450224804187710129859708853805709176487316207010402251651554296674942983458628001825388092613984020357016543095854903752286499436288875811897772811421580394898931781960982007306544027009178109074133665714245347548210688178519450728052309689045110008994598784658702110905581693
e = 65537
ct = 11420169733597912638453974310976296342840438772934899653944946284527921765463891354182152294616337665313108085636067061251485792996493148094827999964385583364992542843630846911864602981658349693548380259629884212903554470004231160866680745154066318419977485221228944716844036265911222656710479650139274719426252576406561307088938784324291655853920727176132853663822020880574204790442647169649094846806057218165102873847070323190392619997632103724159815363319643022552432448214770378596825200154298562513279104608157870845848578603703757405758227316242247843290673221718467366000253484278487854736033323783510299081405
phi = (p-1)*(q-1)
d = gmpy2.invert(e,phi)
m = pow(ct,d,n)
print(long_to_bytes(m))
# squ1rrel{laziness_will_be_the_answer_eventually}
RSA RSA RSA
e: 3
n1: 96137714481560340073780038250015316564930752333880363375193088083653975552334517899735106334409092229494004991796910602440032630762575914714152238916128674595912438177270978040111855327624812652948702562503276973409716595778936978757384935820012322432156169815110042972411989274515686945691887468406312791931
ct1: 45640508926729498938915879450220374487095109122207451961200230820161694723491945276893630019713859109920025191680053056485030809079137883906737197875968862878423820820515399840094772412319820062860149582361429346029277273870654355752499436360499181221418835401103925420623212341317366954144592892392013649421
n2: 90990790933807553440094447797505116528289571569256574363585309090304380702927241663491819956599368816997683603352289726407304960362149545383683196526764288524742203975596414405902155486632888712453606841629050125783639571606440840246928825545860143096340538904060826483178577619093666337611264852255012241011
ct2: 58149644956871439128498229750735120049939213159976216414725780828349070974351356297226894029560865402164610877553706310307735037479690463594397903663323983980128060190648604447657636452565715178438939334318494616246072096228912870579093620604596752844583453865894005036516299903524382604570097012992290786402
n3: 86223965871064436340735834556059627182534224217231808576284808010466364412704836149817574186647031512768701943310184993378236691990480428328117673064942878770269493388776005967773324771885109757090215809598845563135795831857972778498394289917587876390109949975194987996902591291672194435711308385660176310561
ct3: 16168828246411344105159374934034075195568461748685081608380235707338908077276221477034184557590734407998991183114724523494790646697027318500705309235429037934125253625837179003478944984233647083364969403257234704649027075136139224424896295334075272153594459752240304700899700185954651799042218888117178057955
Solution
We need to use Hastad's Broadcast Attack
Refer to:https://drx.home.blog/2019/03/01/crypto-rsa/
from sage.all import *
from Crypto.Util.number import *
from gmpy2 import iroot
e = 3
n1 = 96137714481560340073780038250015316564930752333880363375193088083653975552334517899735106334409092229494004991796910602440032630762575914714152238916128674595912438177270978040111855327624812652948702562503276973409716595778936978757384935820012322432156169815110042972411989274515686945691887468406312791931
ct1 = 45640508926729498938915879450220374487095109122207451961200230820161694723491945276893630019713859109920025191680053056485030809079137883906737197875968862878423820820515399840094772412319820062860149582361429346029277273870654355752499436360499181221418835401103925420623212341317366954144592892392013649421
n2 = 90990790933807553440094447797505116528289571569256574363585309090304380702927241663491819956599368816997683603352289726407304960362149545383683196526764288524742203975596414405902155486632888712453606841629050125783639571606440840246928825545860143096340538904060826483178577619093666337611264852255012241011
ct2 = 58149644956871439128498229750735120049939213159976216414725780828349070974351356297226894029560865402164610877553706310307735037479690463594397903663323983980128060190648604447657636452565715178438939334318494616246072096228912870579093620604596752844583453865894005036516299903524382604570097012992290786402
n3 = 86223965871064436340735834556059627182534224217231808576284808010466364412704836149817574186647031512768701943310184993378236691990480428328117673064942878770269493388776005967773324771885109757090215809598845563135795831857972778498394289917587876390109949975194987996902591291672194435711308385660176310561
ct3 = 16168828246411344105159374934034075195568461748685081608380235707338908077276221477034184557590734407998991183114724523494790646697027318500705309235429037934125253625837179003478944984233647083364969403257234704649027075136139224424896295334075272153594459752240304700899700185954651799042218888117178057955
Cs = [ct1, ct2, ct3]
Ns = [n1, n2, n3]
m_e = crt(Cs, Ns)
m = m_e.nth_root(e)
print(long_to_bytes(int(m)))
# squ1rrel{math_is_too_powerful_1q3y41t1s98u23rf8}
Partial RSA
Hmm? What's wrong with using the same flag format again? Whisper it in my ear so they don't hear.
n: 103805634552377307340975059685101156977551733461056876355507089800229924640064014138267791875318149345634740763575673979991819014964446415505372251293888861031929442007781059010889724977253624216086442025183181157463661838779892334251775663309103173737456991687046799675461756638965663330282714035731741912263
e: 3
ct: 24734873977910637709237800614545622279880260333085506891667302143041484966318230317192234785987158021463825782079898979505470029030138730760671563038827274105816021371073990041986605112686349050253522070137824687322227491501626342218176173909258627357031402590581822729585520702978374712113860530427142416062
Solution
小明文攻击
from Crypto.Util.number import *
from gmpy2 import iroot
n = 103805634552377307340975059685101156977551733461056876355507089800229924640064014138267791875318149345634740763575673979991819014964446415505372251293888861031929442007781059010889724977253624216086442025183181157463661838779892334251775663309103173737456991687046799675461756638965663330282714035731741912263
e = 3
ct = 24734873977910637709237800614545622279880260333085506891667302143041484966318230317192234785987158021463825782079898979505470029030138730760671563038827274105816021371073990041986605112686349050253522070137824687322227491501626342218176173909258627357031402590581822729585520702978374712113860530427142416062
m = iroot(ct,3)[0]
print(long_to_bytes(m))
# b"\x14\xd0j\x13\x18\xfe\xfc\x8d\x81.\xcf\xda\xf2)\x81'\xf4G\x95\xf1\xa3Y\xbe\x07\x98B\x86W\x12\xc0\x08\xb0\x87\x82:\xbb\xca\x81h\x9e\xc0\x8a}"
然而并不像我们想象的那么顺利。。
但我们可以根据描述:用相同的标志?我们不难知道flag前缀“squ1rrel{”
这道题其实是另一种标准的RSA攻击--具体来说的哈,其实是 Coppersmith 的一种变体,称为刻板消息攻击。
从本质上讲,这个攻击是在我们知道明文的形式是 squ1rrel{XX...XX},其中 X 表示未知字符。
而 Coppersmith 的攻击使我们能够解密它。有关详细解释,请参阅此处。
由于这是一个非常标准的攻击,我们直接找一个脚本就行戳这里!
我们需要做的就是计算flag大括号之间的字节数。可以暴力:)
这里提供两种写法,脚本一(这个更好理解):
from Crypto.Util.number import *
n = 103805634552377307340975059685101156977551733461056876355507089800229924640064014138267791875318149345634740763575673979991819014964446415505372251293888861031929442007781059010889724977253624216086442025183181157463661838779892334251775663309103173737456991687046799675461756638965663330282714035731741912263
e = 3
ct = 24734873977910637709237800614545622279880260333085506891667302143041484966318230317192234785987158021463825782079898979505470029030138730760671563038827274105816021371073990041986605112686349050253522070137824687322227491501626342218176173909258627357031402590581822729585520702978374712113860530427142416062
prfx = b'squ1rrel{'
for i in range(100):
m = bytes_to_long(prfx + b'\x00'*i + b'}')
P.<x> = PolynomialRing(Zmod(n), implementation='NTL')
pol = (m + x)^e - ct
roots = pol.small_roots(epsilon=1/30)
print(i, "Potential solutions:")
for root in roots:
print(root, long_to_bytes((m+int(root))%n))
# squ1rrel{wow_i_was_betrayed_by_my_own_friend}
或者使用脚本二(没第一个好理解但速度很快哈哈哈):
from Crypto.Util.number import *
from sage.all import *
from tqdm import tqdm
n = 103805634552377307340975059685101156977551733461056876355507089800229924640064014138267791875318149345634740763575673979991819014964446415505372251293888861031929442007781059010889724977253624216086442025183181157463661838779892334251775663309103173737456991687046799675461756638965663330282714035731741912263
e = 3
c = 24734873977910637709237800614545622279880260333085506891667302143041484966318230317192234785987158021463825782079898979505470029030138730760671563038827274105816021371073990041986605112686349050253522070137824687322227491501626342218176173909258627357031402590581822729585520702978374712113860530427142416062
known = b"squ1rrel{"
known_int = bytes_to_long(known)
for i in tqdm(range(100)):
try:
x = PolynomialRing(Zmod(n), 'x').gen()
f = (known_int * 2**(i * 8) + x)**e - c
ans = f.small_roots(X = 2**(i * 8), beta = 0.5)[0]
print(known.decode() + long_to_bytes(int(ans)).decode())
break
except:
continue
# squ1rrel{wow_i_was_betrayed_by_my_own_friend}
Squ1rrel treasury
We recently opened a new bank, our exchange rate is pretty poor though
nc treasury.squ1rrel-ctf-codelab.kctf.cloud 1337
chal.py
from Crypto.Util.number import bytes_to_long, long_to_bytes
from Crypto.Util.strxor import strxor
from Crypto.Cipher import AES
import os
from secrets import KEY, FLAG
import random
ACCOUNT_NAME_CHARS = set([chr(i) for i in range(ord('a'), ord('z')+1)] + [chr(i) for i in range(ord('A'), ord('Z')+1)])
FLAG_COST = random.randint(10**13, 10**14-1)
def blockify(text: str, block_size: int):
return [text[i:i+block_size] for i in range(0, len(text), block_size)]
def pad(blocks: list, pad_char: chr, size: int):
padded = []
for block in blocks:
tmp = block
if len(block) < size:
tmp = tmp + pad_char*(size-len(tmp))
elif len(block) > size:
print("Inconsistent block size in pad")
exit(1)
padded.append(tmp)
return padded
class Account:
def __init__(self, iv: bytes, name: str, balance: int):
self.__iv = iv
self.__name = name
self.__balance = balance
def getIV(self):
return self.__iv
def getName(self):
return self.__name
def getBalance(self):
return self.__balance
def setBalance(self, new_balance):
self.__balance = new_balance
def getKey(self):
save = f"{self.__name}:{self.__balance}".encode()
blocks = blockify(save, AES.block_size)
pblocks = pad(blocks, b'\x00', AES.block_size)
cipher = AES.new(KEY, AES.MODE_ECB)
ct = []
for i, b in enumerate(pblocks):
if i == 0:
tmp = strxor(b, self.__iv)
ct.append(cipher.encrypt(tmp))
else:
tmp = strxor(strxor(ct[i-1], pblocks[i-1]), b)
ct.append(cipher.encrypt(tmp))
ct_str = f"{self.__iv.hex()}:{(b''.join(ct)).hex()}"
return ct_str
def load(key: str):
key_split = key.split(':')
iv = bytes.fromhex(key_split[0])
ct = bytes.fromhex(key_split[1])
cipher = AES.new(KEY, AES.MODE_ECB)
pt = blockify(cipher.decrypt(ct), AES.block_size)
ct = blockify(ct, AES.block_size)
for i, p in enumerate(pt):
if i == 0:
pt[i] = strxor(p, iv)
else:
pt[i] = strxor(strxor(ct[i-1], pt[i-1]), p)
pt = b''.join(pt)
pt_split = pt.split(b':')
try:
name = pt_split[0].decode()
except Exception:
name = "ERROR"
balance = int(pt_split[1].strip(b'\x00').decode())
return Account(iv, name, balance)
def accountLogin():
print("\nPlease provide your account details.")
account = input("> ").strip()
account = Account.load(account)
print(f"\nWelcome {account.getName()}!")
while True:
print("What would you like to do?")
print("0 -> View balance")
print(f"1 -> Buy flag ({FLAG_COST} acorns)")
print("2 -> Save")
opt = int(input("> ").strip())
if opt == 0:
print(f"Balance: {account.getBalance()} acorns\n")
elif opt == 1:
if account.getBalance() < FLAG_COST:
print("Insufficient balance.\n")
else:
print(f"Flag: {FLAG}\n")
account.setBalance(account.getBalance()-FLAG_COST)
elif opt == 2:
print(f"Save key: {account.getKey()}\n")
break
def accountNew():
print("\nWhat would you like the account to be named?")
account_name = input("> ").strip()
dif = set(account_name).difference(ACCOUNT_NAME_CHARS)
if len(dif) != 0:
print(f"Invalid character(s) {dif} in name, only letters allowed!")
print("Returning to main menu...\n")
return
account_iv = os.urandom(16)
account = Account(account_iv, account_name, 0)
print(f"Wecome to Squirrel Treasury {account.getName()}")
print(f"Here is your account key: {account.getKey()}\n")
if __name__ == "__main__":
while True:
print(r"""
⠀⠀⠀⠀⠀⠀⠀ ⢀⣀⣤⣄⣀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⢴⣶⠀⢶⣦⠀⢄⣀⠀⠠⢾⣿⠿⠿⠿⠿⢦⠀⠀ ___ __ _ _ _/ |_ __ _ __ ___| |
⠀⠀⠀⠀⠀⠀⠀⠀⠺⠿⠇⢸⣿⣇⠘⣿⣆⠘⣿⡆⠠⣄⡀⠀⠀⠀⠀⠀⠀⠀/ __|/ _` | | | | | '__| '__/ _ \ |
⠀⠀⠀⠀⠀⠀⢀⣴⣶⣶⣤⣄⡉⠛⠀⢹⣿⡄⢹⣿⡀⢻⣧⠀⡀⠀⠀⠀⠀⠀\__ \ (_| | |_| | | | | | | __/ |
⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⡈⠓⠀⣿⣧⠈⢿⡆⠸⡄⠀⠀⠀⠀|___/\__, |\__,_|_|_| |_| \___|_|
⠀⠀⠀⠀⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣈⠙⢆⠘⣿⡀⢻⠀⠀⠀⠀ |_|
⠀⠀⠀⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠹⣧⠈⠀⠀⠀⠀ _____
⠀⠀⠀⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠈⠃⠀⠀⠀⠀/__ \_ __ ___ __ _ ___ _ _ _ __ ___ _ _
⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀ / /\/ '__/ _ \/ _` / __| | | | '__/ _ \ | | |
⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀ / / | | | __/ (_| \__ \ |_| | | | __/ |_| |
⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⠀⠀⠀⠀⠀⠀ \/ |_| \___|\__,_|___/\__,_|_| \___|\__, |
⠀⠀⠀⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀ |___/
⠀⠀⠀⠈⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⢠⣿⣿⠿⠿⠿⠿⠿⠿⠟⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
""")
print("Welcome to squ1rrel Treasury! What would you like to do?")
print("0 -> Login")
print("1 -> Create new account")
opt = int(input("> ").strip())
if opt == 0:
accountLogin()
elif opt == 1:
accountNew()
def accountNew():
print("\nWhat would you like the account to be named?")
account_name = input("> ").strip()
dif = set(account_name).difference(ACCOUNT_NAME_CHARS)
if len(dif) != 0:
print(f"Invalid character(s) {dif} in name, only letters allowed!")
print("Returning to main menu...\n")
return
account_iv = os.urandom(16)
account = Account(account_iv, account_name, 0)
print(f"Wecome to Squirrel Treasury {account.getName()}")
print(f"Here is your account key: {account.getKey()}\n")
我们将被要求输入一个帐户名称。但是有一些限制,它必须只包含字母表的字母(允许大写)。
之后,它将继续为我们的帐户生成一个用于登录的密钥。此密钥的生成方式如下:
def getKey(self):
save = f"{self.__name}:{self.__balance}".encode()
blocks = blockify(save, AES.block_size)
pblocks = pad(blocks, b'\x00', AES.block_size)
cipher = AES.new(KEY, AES.MODE_ECB)
ct = []
for i, b in enumerate(pblocks):
if i == 0:
tmp = strxor(b, self.__iv)
ct.append(cipher.加密(tmp))
else:
tmp = strxor(strxor(ct[i-1], pblocks[i-1]), b)
ct.append(cipher.加密(tmp))
ct_str = f"{self.__iv.hex()}:{(b''.join(ct)).hex()}"
return ct_str
首先,它创建一个包含name:balance的字节字符串,其中传递的balance为0。将字符串拆分为16字节块。对于不超过16字节的块,我们将使用x00字节进行填充。编码过程如下:
第一个块将是XOR与之前的IV(生成的16字节随机)。然后使用AES_ECB加密。
对于其余块,它们将使用以前加密的块进行XOR,并使用AES_ECB进行加密。
这实际上是AES_CBC的一种形式,使用ECB加密。最后,键将作为字符串iv:ciphertext返回。
转到登录功能,我们有以下内容:
def accountLogin():
print("\nPlease provide your account details.")
account = input("> ").strip()
account = Account.load(account)
print(f"\nWelcome {account.getName()}!")
while True:
print("What would you like to do?")
print("0 -> View balance")
print(f"1 -> Buy flag ({FLAG_COST} acorns)")
print("2 -> Save")
opt = int(input("> ").strip())
if opt == 0:
print(f"Balance: {account.getBalance()} acorns\n")
elif opt == 1:
if account.getBalance() < FLAG_COST:
print("Insufficient balance.\n")
else:
print(f"Flag: {FLAG}\n")
account.setBalance(account.getBalance()-FLAG_COST)
elif opt == 2:
print(f"Save key: {account.getKey()}\n")
break
您将被要求输入帐户密钥以登录。此密钥将传递给load函数以检查合法性:
def load(key: str):
key_split = key.split(':')
iv = bytes.fromhex(key_split[0])
ct = bytes.fromhex(key_split[1])
cipher = AES.new(KEY, AES.MODE_ECB)
pt = blockify(cipher.decrypt(ct), AES.block_size)
ct = blockify(ct, AES.block_size)
for i, p in enumerate(pt):
if i == 0:
pt[i] = strxor(p, iv)
else:
pt[i] = strxor(strxor(ct[i-1], pt[i-1]), p)
pt = b''.join(pt)
pt_split = pt.split(b':')
try:
name = pt_split[0].decode()
except Exception:
name = "ERROR"
balance = int(pt_split[1].strip(b'\x00').decode())
return Account(iv, name, balance)
它将iv和ciphertext分开进行解码。这段代码类似于AES_CBC的解码,如下图。结果返回密钥中包含的帐户的iv、name和balance。
成功登录后,我们能够以10**13、10**14-1的随机价格购买flag。但从一开始就注入为零的余额(balance)完全不足以购买。因此我们必须找到一种方法来增加余额。注意,我们可以控制iv的输入。这里,我们可以在AES_CBC中使用位翻转技术(Bit flip)。
假设我们有一个名为t的帐户,在加密之前,plaintext只有一个块,并且
plaintext_block = b't:0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
在ECB解码后(在XOR与IV重新生成明文之前),将使用XOR与IV进行XOR,并在使用ECB解码后输出ciphertext。
在本文中,传递给AES_ECB的密钥是恒定的(可能会导致漏洞),但只要我们在会话中,就不需要注意这一点。
事实上,通过使用上面列出的纯文本块获取XOR以及IV,可以在ECB解码后计算ciphertext的值。假设我们能做到以下几点:
token_inf = bytes.fromhex(token[33:])
plaintext = b't:0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
pblock = xor(plaintext, token_iv)
# b'\xdf\xb9\x8f\xb98\xdb\xeaa\x1b\x9c\xdf\x7f\x0f@\xa8\x8b'
现在,我们将创建一个新的纯文本,如下所示:
payload_plaintext = b't:99999999999999'
新的IV将通过XOR两个字符串一起计算:
payload_iv = xor(payload_plaintext, pblock).hex()
# b'\xab\x83\xb6\x80\x01\xe2\xd3X"\xa5\xe6F6y\x91\xb2'
现在,如果我们使用payload_iv:ciphertext键登录,生成的纯文本将是我们的有效载荷。以13个9(==10**14-1)的价格,我们肯定可以购买到flag。
Code solution:
from pwn import *
conn = remote("treasury.squ1rrel-ctf-codelab.kctf.cloud", 1337)
def recvLine(n):
for _ in range(n):
conn.recvline()
print("[+] Getting Flag.....")
# Banner,menu
recvLine(20)
# Get Token
conn.sendline(b'1')
conn.sendline(b't')
recvLine(3)
token = conn.recvline().decode().strip()[26:]
# Banner,menu
recvLine(20)
# Make malicious token
token_iv = bytes.fromhex(token[:32])
token_inf = bytes.fromhex(token[33:])
plaintext = b't:0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
pblock = xor(plaintext, token_iv)
payload_plaintext = b't:99999999999999'
payload_iv = xor(payload_plaintext, pblock).hex()
payload_token = payload_iv + ":" + token_inf.hex()
# Send payload
conn.sendline(b'0')
conn.sendline(payload_token.encode())
recvLine(8)
conn.sendline(b'1')
# Flag
print("[+] Found flag!")
print("[+] " + conn.recvline().decode().strip()[2:])
# squ1rrel{7H3_4C0rN_3NCrYP710N_5CH3M3_15_14CK1N6}
Squ1rrel Lottery
import random
# user version
def input_lines():
lines = []
print("Welcome to the squ1rrel lottery! 9 winning numbers will be selected, and if any of your tickets share 3 numbers with the winning ticket you'll win! Win 1000 times in a row to win a flag")
for i in range(1, 41):
while True:
line = input(f"Ticket {i}: ").strip()
numbers = line.split()
if len(numbers) != 9:
print("Please enter 9 numbers")
continue
try:
numbers = [int(num) for num in numbers]
if not all(1 <= num <= 60 for num in numbers):
print("Numbers must be between 1 and 60")
continue
lines.append(set(numbers))
break
except ValueError:
print("Please enter only integers.")
return lines
user_tickets = input_lines()
wincount = 0
for j in range(1000):
winning_ticket = random.sample(range(1, 61), 9)
win = False
for i in user_tickets:
if len(i.intersection(set(winning_ticket))) >= 3:
print(f'Win {j}!')
win = True
wincount += 1
break
if not win:
print("99 percent of gamblers quit just before they hit it big")
break
if wincount == 1000:
print("squ1rrelctf{test_flag}")
from pwn import *
from random import *
sets = [[] for i in range(40)]
space = [9 for i in range(40)]
curr_indices = set()
for i in range(40):
curr_indices.add(i)
next_indices = []
for i in range(1, 61):
# print(space)
indices = sample(list(curr_indices), 6 - len(next_indices)) + next_indices
for j in indices:
sets[j].append(i)
space[j]-=1
curr_indices.remove(j)
for i in next_indices:
curr_indices.add(i)
next_indices = []
if len(curr_indices) <= 6:
for i in curr_indices:
next_indices.append(i)
for i in range(40):
if space[i] > 0 and i not in next_indices: curr_indices.add(i)
# print('\n'.join(map(str, sets)))
for i in sets:
assert len(set(i)) == len(i)
assert len(i) == 9
# exit()
p = remote('34.132.166.199', 11112)
for i in range(40):
# print(' '.join(map(str, sets[i])).split())
p.sendlineafter(b': ', ' '.join(map(str, sets[i])).encode())
p.interactive()
# squ1rrelctf{your_prize_is_519225_squ1rrel_bucks}