ctfshow月饼杯II

crypto

ctfshow上的题目真不错,能学到不少新东西

1.ctfshow_login

from random import randint
from Crypto.Util.number import *

MASK = (1 << 1024) - 1

CTFSHOW_MEMBERS = [
    'Joker',
    'h1xa',
    'Daniu',
    'FW_Mumuzi',
    'cheyenne',
    'striving',
    'ThTsOd',
    'bit',
    'Lazzaro',
    'Y4tacker',
    'ISHAO',
    'aliga',
    'whwhwzwz',
    'XunYing',
    'cs',
    'Ricky',
    'FW_Suica',
    'duck0123',
    'yuchouxuan',
    'MiGooli',
    'owod',
    'yu22x',
    'ZM.J',
    'Yasso',
    'daliangba',
    '7herightp4th',
    'zealot',
    '0x7e',
    'i_kei',
    'V3geD4g',
    'paidx0',
    'lorlike',
    'saulgoodman',
    'Asofia',
    'ENJOEY',
    'g4_simon',
    'werewolfcjj',
    'lewiserii',
]

CTFSHOW_MEMBERS = [f'{mem}@CTFshow'.encode() for mem in CTFSHOW_MEMBERS]

def get_login_keys():
    x = randint(1, p-2)
    y = pow(g, x, p)
    return x, y

def generate_token(username, x):
    while True:
        u = bytes_to_long(username) & MASK
        k = randint(2, p-2)
        if GCD(k, p - 1) != 1:
            continue 
        r = pow(g, k, p)
        s = (u - x*r) * inverse(k, p-1) % (p-1)
        if s == 0:
            continue
        return (r,s)

def verify_token(username, r, s, y):
    u = bytes_to_long(username) & MASK
    # No cheating
    if any([x <= 0 or x >= p-1 for x in (u,r,s)]):
        return False
    return pow(g, u, p) == (pow(y, r, p) * pow(r, s, p)) % p

def check_login(username):
    return any([mem in username for mem in CTFSHOW_MEMBERS])

def login(username, r, s, y):
    if verify_token(username, r, s, y):
        if check_login(username):
            return flag
        else:
            return '[Error] You are NOT a CTFshow member!'
    else:
        return '[Error]: Token verification failure!'

if __name__ == "__main__":
    with open("flag.txt", 'rb') as f:
        flag = f.read()

    p = getPrime(1024)
    g = 7
    
    _, y = get_login_keys()
    print('Welcome to CTFshow login system!')
    print('Login as a CTFshow member to capture the flag!')
    print(f"Here is the public key: {(p, y)}")
    print('However, the private key is lost, so you must have saved your login token properly :)')
    print(f'Please send your username and the corresponding token (r,s)')
    username = input('username (in hex): ')
    username = bytes.fromhex(username)
    r = int(input('r (in hex): '), 16)
    s = int(input('s (in hex): '), 16)
    msg = login(username, r, s, y)
    print(msg)

题目意思大概就是让我们在不知道私钥x的情况下绕过dsa的签名验证

正常情况下是没法进行的,但有意思的是这里把原算法的散列函数改成了一个掩码,抓住这个点控制一下u的值

我的想法:

理论推导:

我们需要找到满足条件的(u,r,s)使得 gu ≡ yrrs mod(p)

最好想的肯定是构造相应的幂次使得模n为1,但是题目限定了u,r,s的大小都<p-1

所以我们只能寄希望于底数不是原根(这样就能构造小于p-1的幂次模p后余1了)

我想的是构造p-1//2,因为y(p-1)/2 = gx(p-1)/2 mod(n) 这样的话只要2|x,

我们就能使y(p-1)/2 ≡1 mod(n) 故我们取r=(p-1)/2 由于r幂次仍然是不可控的故我们取s也为(p-1)/2 ,这样就保证了同余式右边只能为1或-1(这个看概率了取决于7和p-1/2是否为p的二次剩余)

所以当同余式右边为1or-1时我们只要取到u=p-1/2即可(出于掩码造成的漏洞,u值可以随意选取只需<p-1即可)

后面就是看你运气的时候了

代码如下:

from Crypto.Util.number import *
a=b'ZM.J@CTFshow'
print(bytes_to_long(a).bit_length())
p=98272802883261850328914697242839600278597750271885535446398004907272987852494202350389483910091751598506806140180251028499169956941249387682363211873632259149274112232054758934406573221742675420102540336781136558389514884208553484945321975728070953352075692255222237016356963013725671606933341892745710491251
print((p-1)//2)
print(bytes_to_long(b'ZM.J@CTFshow'+long_to_bytes((p-1)//2))&(pow(2,1024)-1))
print(hex(bytes_to_long(b'ZM.J@CTFshow'+long_to_bytes((p-1)//2))))

import gmpy2
a=[98272802883261850328914697242839600278597750271885535446398004907272987852494202350389483910091751598506806140180251028499169956941249387682363211873632259149274112232054758934406573221742675420102540336781136558389514884208553484945321975728070953352075692255222237016356963013725671606933341892745710491251, 74790176312015261221937580326733561284786059301365403095401770628606355609005934764397716460906602944580228390662911998933425117322452922082496793030690447193011313964419558403329582227848426138796890622004870795163737386600447295879076803166572532320487863122784727924510538562913388545001532452616963571024]
p=a[0]
y=a[1]
ni=gmpy2.invert(2,p)
r=(p-1)//2
s=r
u=p-1
print(hex(r))
print((pow(r,s,p)+1)%p)
print((pow(y,r,p)+1)%p)
print((pow(7,(p-1)//2,p)-pow(r,s,p)*pow(y,r,p))%p)
wp的不同想法:
from Crypto.Util.number import *
from random import randint

y = 
p = 
g = 7

while (True):
    i, j = randint(2, p-2), randint(2, p-2)
    if GCD(j, p-1) != 1:
        continue
    r = pow(g, i, p) * pow(y, j, p) % p
    s = -r * inverse(j, p-1) % (p-1)
    m = s * i % (p-1)
    m = b'Daniu@CTFshow'.hex() + f'{m:0260x}' 

    print(m)
    print(f'{r:x}')
    print(f'{s:x}')
    break

也算是一种比较自然的想法了,奈何没想到菜哭

2.切记务必一定要简单

from Crypto.Util.number import *
import random, sys

def get_prime(base, offset):
    l = base - (1 << offset)
    r = base + (1 << offset)
    p = random.randrange(l, r) | 1
    while (not isPrime(p)):
        p += 2
    return p

if __name__ == "__main__":
    p = getPrime(1127)
    q0 = p * 13 // 17
    q = get_prime(q0, 573) # Konami
    with open('flag.txt', 'rb') as f:
        flag = bytes_to_long(f.read())
    N = p * q
    assert isPrime(p) and isPrime(q) and flag < N
    e = 0x10001
    c = pow(flag, e, N)
    print(f"N = {N}")
    print(f"e = {e}")
    print(f"c = {c}")

费马分解的魔改(原来是p和q差的不多)模数相关攻击 - CTF Wiki (ctf-wiki.org)

做适当变形满足和费马分解相同的条件即可

代码如下:

import gmpy2
N = 1711204307569282091899498480099641624286102399848239899677049846159687308851015554615626304582185416706466345739594474863019142823512870220995393783983640970218248314447517263597626228711488360171047572959556042822841939247568507708133871240195992970125827492930489585715737518959133344446661509036935914075206126501212730103442015007393591330464103435781470570149580793122736949847723919885105792805214273455685691599099716616120965127696853757256844385253349885477110847148134521541475491009683189204030139613046297747416442082016693095781148499630823282707489051375886353698855567734300499477130801500945454964680327575052146423038097525270949258342779549417371917272611784187
e = 65537
c = 1322358117764442021913215703056423056747877151467704643846090687658097026572195643938336087168546286538267367888855364352827715639812403844383495515049002678326046156658652997357861431643476810210020215148437329443517282761161363286358866718896822450137280412864515713155515877307680679960576209641297687325064254736684163570014888733802262925571229517607279786919547808977872881381420505887375180442175610434498527476719430046944410639621761933584735074113762280944593714188847448504706708933089994057976546534390017790683237111239527065608550445100818242635781966592165392531459859899790416330920989768242026423724684105711983800457734277450249735413668515808895277047258339148
n=int(isqrt(4*13*17*N))+1
for i in range(2^22):
    a=n+i
    b_=gmpy2.iroot(a^2-4*13*17*N,2)
    if(b_[1]):
        b=b_[0]
        p1=(a+b)//26
        q1=(a-b)//34
        if(p1*q1==N):
            phi1=(p1-1)*(q1-1)
            d1=gmpy2.invert(e,phi1)
            m1=pow(c,d1,N)
            print(m1)
        else:
            p2=(a+b)//34
            q2=(a-b)//26
            phi2=(p2-1)*(q2-1)
            d2=gmpy2.invert(e,phi2)
            m2=pow(c,d2,N)
            print(m2)

3.张八炫CTFshow三结义

from Crypto.Util.number import *
from secret import zang, ba88, xuan, p, q, e

# F91 is my wife forever
assert isPrime(e) and (e < 91)

N = p*q
ps = list(map(bytes_to_long, (zang, ba88, xuan)))
cs = [pow(i,e,N) for i in ps]
s = sum(ps) # UNITE
print(f"N = {N}")
print(f"cs = {cs}")
print(f"s = {s}")

不会做看的wp(刚开始以为是二元copper但是bits不太好确定而且e比较大的话copper完全行不通)

wp上介绍了一种求多元多项式的公约数的一种方法(Gröbner基),实际上就是解多元方程,Gröbner基的规约就是将你原来的式子做辗转相除化成最简式,而如果原来的几个式子都有共同根,那么规约的结果就是这几个根的值(暂时只能这么理解,寒假再补补)

代码如下:

N = 26460796909924755283406719656570178457813340954352679980513127014282118295078571429553112331500207669606105291832884678268564852045755678763071621455141057393260678588770260242312814596764612819146243562088535355768503952372094794447168065134522254491803912808529865346008266444473374837203515196268682366110658434174993094824271324552649145093543907743028456380152416784274353250053116627489780447285190174560168396903690087687011651706646655882389270339741362686717094482423964204551414332070648472972696771948426037782591652705433473850232900468797265194795918574995145536173209214156982337300262509664231236441383
cs = [16516846324091775542009355311775618153437816964134548882145133160805144573047408680723429225955134987835821900603988409823864906027142827131894655015253329265952020245354682553058989483110891591076369055373208062986130849422852567612482827712105486929127624117213552466077304703413560768487646188049866969108835955991611753717065635893650668710019079549293348731030052726074649855142169871726258442756901804195060212665913516499563355737925365082918275493896649125683158195341162423628931454503886821729064729255654647806384142404677741992237271197698721538311814960316279724790304291340969470572225027550746987603991, 1898801287061664028141673075446556445726220592856016544372981195580697008706290999658937596339713824167254801056944421845092996056173113830529181385304952596775160448865655242927383969587404567301114196376600655567841687124195304293056700613557525555457810087129416215555243225305055564813865640547526051432504299843109919031999401831723837017530082596407034372530098557220872560960546311975506662300716114713566629527217845007986310019092282411322025305763577046936997783715435220166481407602838303644821933477794541793837505379868304478620293578139812537365052755162840585457512662806374241191970998530573860702346, 15942488000354399674775329887679507570026619080787435129329794829020070770089684027676565260577325003970083744963375254291525187185589110433910465490832633249559889215916318334779671927166693895757333076605372463718027562862847895242170813124575042164821412324673293301432304800501171787725985870279956813800811342643368141049042327181958944577281505870641003055909943795033858880181186408947943407561881394772845816349216796358414634043368737512841151060218988970994000886231695848221375744890001192241771873136755744979289774200752499075640200681420520922288541238044309242131309284245450463300387215161702205452583]
s = 66154121590146726433387125105800637092
PR.<x,y,z>=PolynomialRing(Zmod(N))
for e in range(1,91):
    if(sympy.isprime(e)):
        f1=x^e-cs[0]
        f2=y^e-cs[1]
        f3=z^e-cs[2]
        f4=x+y+z-s
        G=Ideal([f1,f2,f3,f4]).groebner_basis()
        if(len(G)!=1)
            print(G)

4.沐沐子倒拔垂杨柳

import os
import socketserver
import string
import threading
from time import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import random
import time
import binascii

flag = open("flag.txt", "rb").read()
BLOCK_SIZE = 16
level_flag = [pad(flag[i:i+BLOCK_SIZE], BLOCK_SIZE) for i in range(0, len(flag), BLOCK_SIZE)]

banner = """
+---------------+
|       *       |
|       *       |
| ************* |
|      * *      |
|     *   *     |
|    ********   |
|  ** *     **  |
|**   ******  **|
|     *         |
|     ******    |
|     *         |
|***************|
|     *         |
|    *      *   |
|   **********  |
|            *  |
+---------------+

===== MUMUZI PULLS OUT THE WILLOW =====

Hello my friend! As you know, Mumuzi is trying to pull out the willow, which has several parts.

Try to help him!

You can try shaking or kicking the willow.
"""


class Service(socketserver.BaseRequestHandler):
    def handle(self):

        self.send(banner)
        self.willow = self.select_level()

        while True:
            self.send("1: Shake the willow")
            self.send("2: Kick the willow")
            self.send("3: Change a willow")
            self.send("4: Give up")
            choice = self.receive().decode()
            if choice == "1":
                self.shake_willow()
            elif choice == "2":
                self.kick_willow()
            elif choice == "3":
                self.willow = self.select_level()
            elif choice == "4":
                self.send("886!")
                return

    def send(self, string, newline=True):
        if type(string) is str:
            string = string.encode()

        if newline:
            string = string + b"\n"
        self.request.sendall(string)

    def receive(self, prompt="> "):
        self.send(prompt, newline=False)
        return self.request.recv(4096).strip()

    def shake_willow(self):
        try:
            self.send("You shake the willow with the HEXADECIMAL force:")
            force_shake = self.receive().decode().strip()
            force_shake = pad(bytes.fromhex(force_shake), BLOCK_SIZE)
            cipher = AES.new(self.willow, AES.MODE_CBC, iv=self.willow)
            force_kick = cipher.encrypt(force_shake)

            self.send("The willow sways with the force ")
            self.send(force_kick.hex())
            self.send("You could kick the willow with this force next time.")
        except:
            self.send("Your force is smaller than Mumuzi's.")
            self.send("You are so weak! How can you help him?")

    def kick_willow(self):
        try:
            self.send("You kick the willow with the HEXADECIMAL force:")
            force_kick = self.receive().decode().strip()
            force_kick = bytes.fromhex(force_kick)
            cipher = AES.new(self.willow, AES.MODE_CBC, iv=self.willow)

            force_shake = cipher.decrypt(force_kick)
            self.send("The willow sways with the force ")
            self.send(force_shake.hex())
            self.send("You could shake the willow with this force next time.")
        except:
            self.send("Your force is smaller than Mumuzi's.")
            self.send("You are so weak! How can you help him?")

    def select_level(self):
        while True:
            self.send("Which level of willow do you want to choose?")
            level = self.receive().decode().strip()
            try:
                level = int(level)
                assert 0 <= level < len(level_flag)
                return level_flag[level]
            except:
                continue


class ThreadedService(
    socketserver.ThreadingMixIn,
    socketserver.TCPServer,
    socketserver.DatagramRequestHandler,
):
    pass


def main():

    port = 13337
    host = "0.0.0.0"

    service = Service
    server = ThreadedService((host, port), service)
    server.allow_reuse_address = True

    server_thread = threading.Thread(target=server.serve_forever)

    server_thread.daemon = True
    server_thread.start()

    print("Planted willow on " + str(server.server_address) + "!")
    while True:
        sleep(17)


if __name__ == "__main__":
    main()

采用aes的CBC模式key和iv都是flag,拿异或关系随便推推就行不想打式子了

最终脚本还是有点问题,得到的flag需要稍作修改(问题应该是出在交互过程中的一些\n,没去纠结,原理懂就行)

代码如下:

from pwn import *
from Crypto.Util.number import *
p=remote('pwn.challenge.ctf.show',28091)
m=hex(bytes_to_long(b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'))
mm=hex(bytes_to_long(b'aaaaaaaaaaaaaaa\n'))
p.recvuntil('>')
p.sendline('0')
p.recvuntil('>')
p.sendline('1')
p.recvuntil('>')
p.sendline(str(m)[2:])
p.recvuntil('with the force \n')
m0=hex(int(p.recvline()[:-1],16))
p.recvuntil('>')
p.sendline('2')
p.recvuntil('>')
p.sendline(str(m0)[-32:]*2)
p.recvuntil('with the force \n')
m1=hex(int(p.recvline()[:-1],16))
mmm=int(str(m0)[2:34],16)^int(str(m1)[2:34],16)^int(mm,16)
print(long_to_bytes(mmm))
p.interactive()

5.我的木头啊!!!

c6_I_@t216MG_0q_Uf673JTYYzBXs{31QJmTTg=hw63XZFZiHho5GzE}

W栅栏加base家族

栅栏密码加密/解密【W型】 - 一个工具箱 - 好用的在线工具都在这里! (atoolbox.net)

解密得到flag ctfshow{base16_base32_base64_base58_base}

posted @ 2022-01-02 23:46  hash_hash  阅读(343)  评论(0)    收藏  举报