大数跨境
0
0

Y老师的密码学 · 第一篇

Y老师的密码学 · 第一篇 WgpSec狼组安全团队
2025-08-27
0
导读:Y老师的密码学课程,对一些有趣的题目做讲解复现工作

我们新点击蓝字

关注我们



声明

本文作者:Generate

本文字数:3745字

阅读时长:约20分钟

附件/链接:无

本文属于【狼组安全社区】原创奖励计划,未经许可禁止转载


由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,狼组安全团队以及文章作者不为此承担任何责任。

狼组安全团队有对此文章的修改和解释权。如欲转载或传播此文章,必须保证此文章的完整性,包括版权声明等全部内容。未经狼组安全团队允许,不得任意修改或者增减此文章内容,不得以任何方式将其用于商业目的。


团队每周会报名参加各类CTF比赛,writeup在公众号更新。

我们建立了一个关于CTF的公开交流群,大家赛后可以交流技巧思路。


如果做不出交流群的flag,base64解码它到群里问问吧 !

ZmxhZ3t0aDFzX3dncF9mbDBnfQ==


整理了几个以前存的题目附件,依次对这些题目做了一个复现工作

记录一下这几个题目的做题思路

Y老师的密码学 · 第一篇


fizzbuzz100

题目

#!/usr/local/bin/python
from Crypto.Util.number import *
from os import urandom

flag = open("flag.txt""rb").read()
flag = bytes_to_long(urandom(16) + flag + urandom(16))

p = getPrime(512)
q = getPrime(512)
n = p * q
e = 0x10001
d = pow(e, -1, (p-1)*(q-1))
assert flag < n
ct = pow(flag, e, n)

print(f"{n = }")
print(f"{e = }")
print(f"{ct = }")

whileTrue:
    ct = int(input("> "))
    pt = pow(ct, d, n)
    out = ""
    if pt == flag:
        exit(-1)
    if pt % 3 == 0:
        out += "Fizz"
    if pt % 5 == 0:
        out += "Buzz"
    ifnot out:
        out = pt
    print(out)

从脚本可以看出来这道题属于典型的"选择密文攻击"。想要实现"选择密文攻击"肯定是需要和远程环境实现交互的,然后我们可以构造任意的密文并且获得对应的明文,这也是选择密文攻击的主要思想:

通过选择密文攻击获得特定的明文

下面使用了lazzzaro博客中写到的构造思路:

通过这样的方式我们就可以通过我们构造的密文以及返回回来的数据来求解出我们想要的明文。

由于对解密过后的pt有限制不能被3整除也不能被5整除,我们可以直接写脚本一直传输各种我们所构造的ct,直到回显数字过来,然后手动解密一下即可

from Crypto.Util.number import *
from pwn import *
# a=remote("be.ax",31100)
# l=a.recv().split(b'=')
# print(l)
# while True:
#     X = getPrime(10)
#     c = int(l[3][:-2])
#     n = int(l[1][:-2])
#     Y = (c * (X ** 65537)) % n
#     a.sendline(str(Y).encode())
#     print(X)
#     print(Y)
#     print(a.recvline())
n=109235908866342288271346584662343329621605469702598068759472999551170947232996590557964241534101853321789427925631360625471407190264507981047481840604577029503316535875248549400561470650694604159439358769594819721166974367763712018514398742922521556119264931745691626361879268890616276343169833999231861189833
c=54154300348903111005066683187322110240325256042602814495907789220256344603932965149221705493685749178531643137134769583336652306519327753298004337131622195496840709892914894693840688844013339917136532406319553405215428628337555863105880457193242611315359206147315741465280238895273742546724024586290588394695
w=907
y=26641182241400186140764833732531788410188640530460941989584702007887972922089308897736657230632495490138397006968382235153358170724154868714588801750507397028728784901215019435825808542306682388514239090900283743849756631160550184102438364341973285800800919002105937122573558829974207210810107339187277577768
ct=1617872921486161438959529626097492037660629295223293116750255222467849722125817349217082369174984216697864465452996697846569528891027592080706141175012700498085383500035413467709314885420625836615662777908956038879208002465859460678671983566645413919704836898065353721740490145081034
flag=ct*inverse(w,n)%n
print(long_to_bytes(flag))
#corctf{h4ng_0n_th15_1s_3v3n_34s13r_th4n_4n_LSB_0r4cl3...4nyw4y_1snt_f1zzbuzz_s0_fun}

eyes

题目:

from Crypto.Util.number import bytes_to_long, getPrime

# my NEW and IMPROVED secret sharing scheme!! (now with multivariate quadratics)

with open('flag.txt''rb'as f:
    flag = f.read()

s = bytes_to_long(flag)
p = getPrime(len(bin(s)))
print(p)
F = GF(p)
N = 1024

conv = lambda n: matrix(F, N, 1, [int(i) for i in list(bin(n)[2:][::-1].ljust(N, '0'))])

A = random_matrix(F, N, N)

for i in range(0, N):
    for j in range(0, i):
        A[i, j] = 0
B = random_matrix(F, N, 1)
C = matrix(F, [F(s)])

fn = lambda x: (x.T * A * x + B.T * x + C)[0][0]

L = []
for i in range(7):
    L.append(fn(conv(i + 1)))

print(L)

这个题目看起来很复杂,其实实质很简单,通过观察conv矩阵可以发现可以将每一次计算出的L用A,B,C里面的元素表达出来,具体表达如下:

通过等式变形可以得到:c=(L[6]-(L[2]+L[4]+L[5]-2*(L[0]+L[1]+L[3]))-(L[0]+L[1]+L[3]))%p

我们便可以轻松求出flag了,当然这道题目也还有别的求解方法,大家可以自行去选择突破口。

from Crypto.Util.number import *
p=1873089703968291141600166892623234932796169766648225659075834963115683566265697596115468506218441065194050127470898727249982614285036691594726454694776985338487833409983284911305295748861807972501521427415609
L=[676465814304447223312460173335785175339355609820794166139539526721603814168727462048669021831468838980965201045011875121145342768742089543742283566458551844396184709048082643767027680757582782665648386615861147234980195796010023968927237093810288627596298482272524808199825446760838482015673480726012056470171582669494545528289994839922442187845050221935339239032527541370194185260348374631275840081957078673514813220289943305632464689424329639457849754980604744816396063838013586887133600033469295579924724384724060519999694295963795815708697705165422570042759919300253615784801552746206003385215022321779008184718189601810659828067998906159909958244122530766074880632408551005805132219622985980024683388232255861711075391046358081083564921231673151751105150861929322309984265129475811153587386512062731788679119440346901388255831676559204037482856674710667663849447914859348633288513196735253002541076530170853584406282605482862202276451646974549657672382936948091649764874334064431407644457518190694888175499630744741620199798070517691132967026171038683053276060654188012838658596012974137325946741633081768367198889735293183462559551070093062391071734904297184386583824024631221346904384253510006543350783210562704280730719581555368007556261049859675181292817835885218912868452922769382959555558223657616187915018968273717037070599055754118224873924325840103339766227919051395742409319557746066672267640510787473574362058147262440814677327567134194]
F = GF(p)
N = 1024
conv = lambda n: matrix(F, N, 1, [int(i) for i in list(bin(n)[2:][::-1].ljust(N, '0'))])
c=(L[6]-(L[2]+L[4]+L[5]-2*(L[0]+L[1]+L[3]))-(L[0]+L[1]+L[3]))%p
print(long_to_bytes(c))
#corctf{mind your ones and zeroes because zero squared is zero and one squared is one}

cbc

题目:

import random

def random_alphastring(size):
    return"".join(random.choices(alphabet, k=size))

def add_key(key, block):
    ct_idxs = [(k_a + pt_a) % len(alphabet) for k_a, pt_a in zip([alphabet.index(k) for k in key], [alphabet.index(pt) for pt in block])]
    return"".join([alphabet[idx] for idx in ct_idxs])

def cbc(key, plaintext):
    klen = len(key)
    plaintext = pad(klen, plaintext)
    iv = random_alphastring(klen)
    blocks = [plaintext[i:i+klen] for i in range(0, len(plaintext), klen)]
    prev_block = iv
    ciphertext = ""
    for block in blocks:
        block = add_key(prev_block, block)
        prev_block = add_key(key, block)
        ciphertext += prev_block
    return iv, ciphertext
    
def pad(block_size, plaintext):
    plaintext += "X" * (-len(plaintext) % block_size)
    return plaintext

alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
bs = 16

message = open("message.txt").read().upper()
message = "".join([char for char in message if char in alphabet])
flag = open("flag.txt").read()
flag = flag.lstrip("corctf{").rstrip("}")
message += flag
assert all([char in alphabet for char in message])

key = random_alphastring(bs)
iv, ct = cbc(key, pad(bs, message))
print(f"{iv = }")
print(f"{ct = }")

很有趣的一个题目,我们可以先用ct-iv这样可以获得pt+key,这一部分我主要是通过编写python代码进行求解的:

iv = 'RLNZXWHLULXRLTNP'
ct = 'ZQTJIHLVWMPBYIFRQBUBUESOOVCJHXXLXDKPBQCUXWGJDHJPQTHXFQIQMBXNVOIPJBRHJQOMBMNJSYCRAHQBPBSMMJWJKTPRAUYZVZTHKTPUAPGAIJPMZZZDZYGDTKFLWAQTSKASXNDRRQQDJVBREUXFULWGNSIINOYULFXLDNMGWWVSCEIORQESVPFNMWZKPIYMYVFHTSRDJWQBTWHCURSBPUKKPWIGXERMPXCHSZKYMFLPIAHKTXOROOJHUCSGINWYEILFIZUSNRVRBHVCJPVPSEGUSYOAMXKSUKSWSOJTYYCMEHEUNPJAYXXJWESEWNSCXBPCCIZNGOVFRTGKYHVSZYFNRDOVPNWEDDJYITHJUBVMWDNNNZCLIPOSFLNDDWYXMYVCEOHZSNDUXPIBKUJIJEYOETXWOJNFQAHQOVTRRXDCGHSYNDYMYWVGKCCYOBDTZZEQQEFGSPJJIAAWVDXFGPJKQJCZMTPMFZDVRMEGMPUEMOUVGJXXBRFCCCRVTUXYTTORMSQBLZUEHLYRNJAAIVCRFSHLLPOANFKGRWBYVSOBLCTDAUDVMMHYSYCDZTBXTDARWRTAFTCVSDRVEENLHOHWBOPYLMSDVOZRLENWEKGAWWCNLOKMKFWWAZJJPFDSVUJFCODFYIMZNZTMAFJHNLNMRMLQRTJJXJCLMQZMOFOGFPXBUTOBXUCWMORVUIIXELTVIYBLPEKKOXYUBNQONZLPMGWMGRZXNNJBUWBEFNVXUIAEGYKQSLYSDTGWODRMDBHKCJVWBNJFTNHEWGOZFEZMTRBLHCMHIFLDLORMVMOOHGXJQIIYHZFMROGUUOMXBTFMKERCTYXFIHVNFWWIUFTGLCKPJRFDRWDXIKLJJLNTWNQIOFWSIUQXMFFVIIUCDEDFEJNLKLQBALRKEYWSHESUJJXSHYWNRNPXCFUEFRJKSIGXHFTKNJXSYVITDOGYIKGJIOOHUFILWYRBTCQPRPNOKFKROTFZNOCZXZEYUNWJZDPJDGIZLWBBDGZJNRQRPFFGOTGFBACCRKLAPFLOGVYFXVIIJMBBMXWJGLPOQQHMNBCINRGZRBVSMLKOAFGYRUDOPCCULRBE'

alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
bs = 16

import random


def random_alphastring(size):
    return"".join(random.choices(alphabet, k=size))


def add_key(key, block):
    ct_idxs = [(k_a + pt_a) % len(alphabet) for k_a, pt_a in
               zip([alphabet.index(k) for k in key], [alphabet.index(pt) for pt in block])]
    return"".join([alphabet[idx] for idx in ct_idxs])


def sub_key(key, block):
    ct_idxs = [(pt_a - k_a) % len(alphabet) for k_a, pt_a in
               zip([alphabet.index(k) for k in key], [alphabet.index(pt) for pt in block])]
    return"".join([alphabet[idx] for idx in ct_idxs])


def cbc(key, plaintext):
    klen = len(key)
    plaintext = pad(klen, plaintext)
    iv = random_alphastring(klen)
    blocks = [plaintext[i:i + klen] for i in range(0, len(plaintext), klen)]
    prev_block = iv
    ciphertext = ""
    for block in blocks:
        block = add_key(prev_block, block)
        prev_block = add_key(key, block)
        ciphertext += prev_block
    return iv, ciphertext


def decode(key, ciphertext):
    klen = len(key)
    blocks = [ciphertext[i:i + klen] for i in range(0, len(ciphertext), klen)]
    plaintext = ""
    for block in blocks:
        block = sub_key(key, block)
        plaintext += block
    return plaintext


def pad(block_size, plaintext):
    plaintext += "X" * (-len(plaintext) % block_size)
    return plaintext

blocks = [ct[i:i + bs] for i in range(0, len(ct), bs)]
ct_noiv = ""
prev_block = iv
for block in blocks:
    sblock = sub_key(prev_block, block)
    ct_noiv += sblock
    prev_block = block

print(ct_noiv)
#IDJUSTLIKETOINTERJECTFORAMOMENTWHATYOUREREFERINGTOASLINUXISINFACTGNULINUXORASIVERECENTLYTAKENTOCALLINGITGNUPLUSLINUXLINUXISNOTANOPERATINGSYSTEMUNTOITSELFBUTRATHERANOTHERFREECOMPONENTOFAFULLYFUNCTIONINGGNUSYSTEMMADEUSEFULBYTHEGNUCORELIBSSHELLUTILITIESANDVITALSYSTEMCOMPONENTSCOMPRISINGAFULLOSASDEFINEDBYPOSIXMANYCOMPUTERUSERSRUNAMODIFIEDVERSIONOFTHEGNUSYSTEMEVERYDAYWITHOUTREALIZINGITTHROUGHAPECULIARTURNOFEVENTSTHEVERSIONOFGNUWHICHISWIDELYUSEDTODAYISOFTENCALLEDLINUXANDMANYOFITSUSERSARENOTAWARETHATITISBASICALLYTHEGNUSYSTEMDEVELOPEDBYTHEGNUPROJECTTHEREREALLYISALINUXANDTHESEPEOPLEAREUSINGITBUTITISJUSTAPARTOFTHESYSTEMTHEYUSELINUXISTHEKERNELTHEPROGRAMINTHESYSTEMTHATALLOCATESTHEMACHINESRESOURCESTOTHEOTHERPROGRAMSTHATYOURUNTHEKERNELISANESSENTIALPARTOFANOPERATINGSYSTEMBUTUSELESSBYITSELFITCANONLYFUNCTIONINTHECONTEXTOFACOMPLETEOPERATINGSYSTEMLINUXISNORMALLYUSEDINCOMBINATIONWITHTHEGNUOPERATINGSYSTEMTHEWHOLESYSTEMISBASICALLYGNUWITHLINUXADDEDORGNULINUXALLTHESOCALLEDLINUXDISTRIBUTIONSAREREALLYDISTRIBUTIONSOFGNULINUXANYWAYHERECOMESTHEFLAGITSEVERYTHINGAFTERTHISATLEASTITSNOTAGENERICROTTHIRTEENCHALLENGEIGUESS

由于key不知道,但是我们可以把pt+key当作维吉尼亚加密过后的密文,由于文本长度很长,直接使用在线工具对其分析就可以还原出flag:https://www.guballa.de/vigenere-solver。

从这道题目也可以看出来我们在做题的时候要灵活一点,一道题的求解不一定只用到一种思路,可能会用到很多东西,不要被限制住了思想。

#corctf{ATLEASTITSNOTAGENERICROTTHIRTEENCHALLENGEIGUESS}

fizzbuzz101

题目:

#!/usr/local/bin/python
from Crypto.Util.number import *
from os import urandom

flag = open("flag.txt""rb").read()
flag = bytes_to_long(urandom(16) + flag + urandom(16))

p = getPrime(512)
q = getPrime(512)
n = p * q
e = 0x10001
d = pow(e, -1, (p-1)*(q-1))
assert flag < n
ct = pow(flag, e, n)

print(f"{n = }")
print(f"{e = }")
print(f"{ct = }")

whileTrue:
    ct = int(input("> "))
    pt = pow(ct, d, n)
    out = ""
    if pt == flag:
        exit(-1)
    if pt % 3 == 0:
        out += "Fizz"
    if pt % 5 == 0:
        out += "Buzz"
    ifnot out:
        out = "101"
    print(out)

相对于100做了改进,不再返回解密后的数字过来,但是我们可以通过回显判断明文的低位,这时候我们可以想到LSB Oracle Attack。这道题我们只能确定明文是否是3,5或者是15的倍数,因为padding是随机的,我们先找到一个2*flag是5的倍数的padding,接下来我们需要找到一个k满足(k-1)*pt<n,k*pt>n。当<n时一定会返回Buzz或者是FizzBuzz,一旦大于n我们可以得到下面这个式子:

由于k*pt是5的倍数,而n不是5的倍数,所以返回的结果一定不是5的倍数,所以不会返回Buzz。由此我们可以获得这样一个信息:

得到了这样一个大概范围后我们使用二分法去不断逼近,可以获得一个相差等于1的范围。此时其实还是在一个很大的范围内,我们还需要想办法去缩小范围,于是可以选择将等式两边全都乘5

将5*k0作为新的k我们又可以获得以下信息:

其中k*pt是5的倍数,5n也是5的倍数,所以解密得到的明文也会是5的倍数,所以我们根据是否出现Buzz来判断二分法的缩减方式,这里注意此时和前一种情况的二分判断条件刚好相反。此时pt在下面这一范围内:

我们可以发现pt的范围在缩小,所以我们循环这一过程,不断在前一个基础上乘5,我们就能逐渐的逼近pt的值,因为pt经过了padding,而flag处于中间部分,所以我们不需要找到完全正确的pt就能获得flag

from Crypto.Util.number import *
from pwn import *
def oracle(x):
    io.sendlineafter(b'> ', str(pow(x, e, n) * ct % n).encode())
    returnb'Buzz'notin io.recvline()

def binary_search(left, right, reverse=False):
    while left < right - 1:
        mid = (left + right) // 2
        if oracle(mid) ^ reverse:
            right = mid
        else:
            left = mid
    return left, right

whileTrue:
    io = remote("be.ax",31101)
    io.recvuntil(b'n = ')
    n = int(io.recvline().decode())
    io.recvuntil(b'e = ')
    e = int(io.recvline().decode())
    io.recvuntil(b'ct = ')
    ct = int(io.recvline().decode())
    ifnot oracle(2):
        k = 1
        whileTrue:
            k *= 2
            if oracle(k):
                break
        left, right = binary_search(k // 2, k)
        prog = io.progress('Flag')

        for i in range(11000):
            left, right = binary_search(5 * left, 5 * right, reverse=True)
            print(left,right)
            prog.status(str(long_to_bytes(5 ** i * n // right)[16:-16]))
        break

    io.close()
#corctf{\'\'.join(fizz_buzz(x) for x in range(99, 102)) == "FizzBuzz101" == cool_username}

以后会经常对一些有趣的题目做讲解复现工作,感兴趣的师傅可以多关注我们团队公众号的更新,相关问题也可以留言,期待和大家共同探讨更多的密码学题目。




作者



Generate

follow your heart



扫描关注公众号回复加群

和师傅们一起讨论研究~


WgpSec狼组安全团队

微信号:wgpsec

Twitter:@wgpsec



【声明】内容源于网络
0
0
WgpSec狼组安全团队
WgpSec 狼组安全团队由几位热爱网络安全的年轻人一同组成过去的几年内没来得及让团队发生有效且质的变化这一次,为了我们的slogan:打造信息安全乌托邦。前进!
内容 136
粉丝 0
WgpSec狼组安全团队 WgpSec 狼组安全团队由几位热爱网络安全的年轻人一同组成过去的几年内没来得及让团队发生有效且质的变化这一次,为了我们的slogan:打造信息安全乌托邦。前进!
总阅读71
粉丝0
内容136