window.onload=function(){ /*页面加载完成之后生成博客目录*/ BlogDirectory.createBlogDirectory("cnblogs_post_body","h2","h3",20); }

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

Description
I had something so important to say that I just had to tell three of my friends!
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

Description

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

Description:

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()
Solution:
当我们连接到服务器时,发现将有2个选项:创建帐户或登录。具有帐户创建功能。
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的解码,如下图。结果返回密钥中包含的帐户的ivnamebalance

成功登录后,我们能够以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

Description
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 a flag!
Hint: This is a math challenge
nc 34.132.166.199 11112
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}")
Solution
从本质上讲,这个问题的要点是,我们被允许输入40张票,其中9个数字在1到60之间,包括1到60。
连续1000次,我们需要至少3张与中奖彩票相同的号码。
我认为预期的解决方案是一些涉及实际数学和思维的归类原则逻辑,但是,我想为什么不尝试一下均匀分布数字呢?
有 40*9 = 360 个输入,所以我可以在 40 张票中放置每个数字的 6 个,确保没有一个数字在同一张票中出现两次。
这样以来有70%的失败率,不过完全够了。
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}

posted @ 2024-06-03 19:33  Kicky_Mu  阅读(171)  评论(0编辑  收藏  举报