NewStarCTF公开赛week2密码学题目wp

文章目录

  • 前言
  • 一、unusual_base
    • 1.分析
    • 2.解题脚本
  • 二、Affine
    • 1.分析
    • 2.解题脚本
  • 三、robot
    • 1.分析
    • 2.解题脚本
  • 四、ezPRNG
    • 1.分析
    • 2.解密脚本
  • 五、ezRabin
    • 1.分析
    • 2.解密脚本
  • 总结


前言

第二周的题做起来感觉已经很吃力了,第三周已经做不动了,于是开始摆烂,整理一下第二周的题目。
按照结束后的解题数量排序(基本就是从易到难


一、unusual_base

原题如下:

from secret import flag
from Crypto.Util.number import *
from random import shuffle
from string import ascii_lowercase, ascii_uppercase, digits

alphabet = ascii_uppercase + ascii_lowercase + digits +'$&'
alphabet = list(alphabet)
bits = ''
pad_len = len(flag) % 3

for f in flag:
            bits += bin(f)[2:].rjust(8,'0')
bits += '0000'*pad_len
encoded = ''
shuffle(alphabet)
alphabet = "".join(alphabet)
for i in range(0, len(bits), 6):
    encoded += alphabet[int(bits[i:i+6], 2)]
encoded += '%'*pad_len
print(f'encoded = "{encoded}"')
print(f'alphabet = "{alphabet}"')

并已知密文输出和alphabet表的样子:

encoded = "GjN3G$B3de58ym&7wQh9dgVNGQhfG2hndsGjlOyEdaxRFY%"
alphabet = "c5PKAQmgI&qSdyDZYCbOV2seXGloLwtFW3f9n7j481UMHBp6vNETRJa$rxuz0hik"

1.分析

说白了就是本题考察的换表,把原base64表中的+/换成了$&,并将顺序打乱,形成了一个叫alphabet的表,按这个表编码了明文,在base64中用于填充的“=”这里也被换成了“%”。
base64编码的原理是不变的一个字符占8位转换成6位,不过在填充上有一点小坑,但问题不大,后面说),只是最后根据6位二进制数对应的十进制数去对应表中的字符时,由于表变了所以对应的字符变了。所以想要恢复出明文,需要先找到密文的每个字符在alphabet表中对应的位置(就是6位二进制数对应的十进制数),再找到该位置在原base64表对应的字符(“原本”该被编码成什么字符),把密文换成这些字符,就得到按base64编码的密文了,再调用base64解码函数即可得到明文。
此外,题目给出的脚本在填充上“偷懒”了。由base64的编码原理可以知道,base64编码时需要将二进制位数凑成24位的整数倍(也就是三个字符的整数倍),而一个字符占8位,所以base64只会出现需要填充16位(只有一个字符或“多出来”一个字符)和需要填充8位(只有两个字符或“多出来”两个字符)的情况。题目的脚本中,用flag的长度对3取余,得到的数无非就是0,1,2,表面看似合理而实际有坑。一是:bits += '0000'*pad_len导致需要填充16位的情况只填充了4位,少填充了12位,需要填充8位的情况填充了8位没问题,但还有二:encoded += '%'*pad_len导致需要填充16位的情况只补了一个%,需要填充8位的情况却有两个%,这显然是不合理的,这里可以在解题时根据遇到的问题来解决问题:看一下密文encoded的长度,是47位,肯定是不合理对不对,怎么也得48位啊,所以也能想到最后只有一个%填充是不对的,转换成base64编码的话最后应该是有==。

2.解题脚本

想解释的都写在注释里啦~

from Crypto.Util.number import *
from string import ascii_lowercase, ascii_uppercase, digits
import base64
encoded = "GjN3G$B3de58ym&7wQh9dgVNGQhfG2hndsGjlOyEdaxRFY%"
alphabet = "c5PKAQmgI&qSdyDZYCbOV2seXGloLwtFW3f9n7j481UMHBp6vNETRJa$rxuz0hik"
b64table = ascii_uppercase + ascii_lowercase + digits +'+/='#base64标准表
l=[]
#因为%在alphabet里找不到对应下标,会报错中断程序所以写try了
for e in encoded:
    try:
        l.append(alphabet.index(e))#l存了密文每个字符在alphabet对应的下标
    except:
        pass
s=''
for ll in l:
    s+=b64table[ll]#s是base64编码下对应的密文
#print(s) #ZmxhZ3thMXBoNGJldF9jMHUxZF9iZV9kMWZmaTNyM250fQ 要补俩=才能用base64解码
#可以这样试一试填充几个=,也可以发现上面s的长度是46位,直接补俩=解码即可
try:
    print(base64.b64decode((s).encode()))
except:
    try:
        print(base64.b64decode((s+'=').encode()))
    except:
        print(base64.b64decode((s+'==').encode()))
        

flag: flag{a1ph4bet_c0u1d_be_d1ffi3r3nt}

二、Affine

原题如下:

from secret import flag
from Crypto.Util.number import *

a = getPrime(8)
b = getPrime(8)

ciphertext = []

for f in flag:
    ciphertext.append((a*f + b) % 0x100)

print(bytes(ciphertext))

并已知了密文是:

b"\xb1\x83\x82T\x10\x80\xc9O\x84\xc9<\x0f\xf2\x82\x9a\xc9\x9b8'\x9b<\xdb\x9b\x9b\x82\xc8\xe0V"

1.分析

看题目名称,affine,那就是仿射密码咯。看题目脚本的加密过程,就是对flag中的每一个字母(的ascii值)乘a加b再模256(再转字节格式)得到密文。
这里我个人只能想到尝试借助“隐含”的已知条件去求出a、b的值了,“隐含”的已知条件就是:既然加密的就是想要求得的flag本身,那么明文的前五位就是“flag{”。理论上说a、b两个未知数,列两个方程就能求出来a和b,不过实际尝试过程中发现用f和l求不出来……但是用f和a可以啊,保险起见在实际解题过程中我还用字母g验证了所求的a、b是否正确。
求出a和b后,根据模运算的性质,如果明文中每个字符的ascii码用m表示,密文中每个字符的ascii码用c表示,那么有

m=(c-b)*invert(a,256)%256

2.解题脚本

直接上脚本吧:

import gmpy2
f1=ord('\xb1')
#f2=ord('\x83') #因为'l'-'f'的值是6,6找不到模256的乘法逆元所以不能用f和l两个字母求解未知因数a
f3=ord('\x82')
f4=ord('T')  #ord('T')=84,是g对应的密文
m1=ord('f')
#m2=ord('l')
m3=ord('a')
m4=ord('g') #ord('g')=103,可以验证a=163,b=191时满足(103*a+b)%256=84=ord('T')
a=(f3-f1)*gmpy2.invert(m3-m1,256)%256 #可以用'f'和'a'求解出a
print(a)#a=163
b=(f1-a*m1)%256
print(b)#b=191

c="\xb1\x83\x82T\x10\x80\xc9O\x84\xc9<\x0f\xf2\x82\x9a\xc9\x9b8'\x9b<\xdb\x9b\x9b\x82\xc8\xe0V"

flag=''
for cc in c:
    m=(ord(cc)-b)*gmpy2.invert(a,256)%256
    flag+=chr(m)
print(flag)

flag: flag{Kn0wn_p1aint3xt_4ttack}

三、robot

原题如下:

from hashlib import sha256
from secret import flag
from base64 import *
import random

cipher = []

def fun1(x):
    return sha256(x.encode()).hexdigest()

def fun2(x):
    return pow(114514,ord(x),1919810)

def fun3(x):
    key = random.randint(0,1145141919810)
    ans = x.encode()
    if key & 1:
        ans = b32encode(ans)
    key >>= 1
    if key & 1:
        ans = b64encode(ans)
    key >>= 1
    if key & 1:
        ans = b16encode(ans)
    key >>= 1
    return ans

def encrypt(msg):
    res = []
    for i in msg:
        tmp = list(map(random.choice([fun1,fun2,fun3]),[i]))[0]
        res.append(tmp)
    return res

print(encrypt(flag))

'''
['252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111', 1495846, 1452754, b'M4======', '021fb596db81e6d02bf3d2586ee3981fe519f275c0ac9ca76bbcf2ebb4097d96', '2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6', '4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a', b'Tg==', '1b16b1df538ba12dc3f97edbb85caa7050d46c148134290feba80f8236c83db9', b'52304539505430395054303D', 'e3b98a4da31a127d4bde6e43033f66ba274cab0eb7eb1c70ec41402bf6273dd8', b'58773D3D', '3f39d5c348e5b79d06e842c114e6cc571583bbf44e4b0ebfda1a01ec05745d43', '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce', '2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6', b'T0k9PT09PT0=', '18f5384d58bcb1bba0bcd9e6a6781d1a6ac2cc280c330ecbab6cb7931b721552', b'T0E9PT09PT0=', 825026, 'd2e2adf7177b7a8afddbc12d1634cf23ea1a71020f6a1308070a16400fb68fde', 1455816, b'4F553D3D3D3D3D3D', 1165366, 1242964, b'4F493D3D3D3D3D3D', 652094, 597296, '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce', '4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a', b'54314539505430395054303D', 1242964, 368664, b'TVU9PT09PT0=', b'cw==', 1602214]
'''

1.分析

这题就是考察多种加密方式的解密办法了。对于明文中的某一个字符,从三种加密方式中随机选择一种来对其加密。三种方式分别是sha256(x.encode()).hexdigest()这种计算sha256的,pow(114514,ord(x),1919810)这种将明文作为指数计算密文的,还有就是base系列,而且这里涉及到了连续的base!(害怕
解题就是根据题目最后给出的加密结果对应出加密方式,见招拆招即可,解法很明确,但求解过程的具体实施方式不唯一,比如对于sha256和连续base解码可以用在线工具,也可以借助此题锻炼一下自己写Python脚本的能力~运行自己写的脚本直接出解密结果比去在线工具里点点点舒服太多了有木有?!不能懒于动脑啊喂(
因为这里的加密操作每次都只针对flag中的一个字符,所以还是很容易“爆破”的,遍历的范围只要写成ascii码中的可见字符范围32-127即可。个人觉得这里写连续base系列的解码有点意思(自己第一时间没写出来2333
想解释的都写在脚本里了~

2.解题脚本

from hashlib import sha256
from base64 import *
from Crypto.Util.number import *
import numbers
import re

#遍历一下可见字符,看看哪个字符的sha256结果与密文一致即可
def f_sha256(x):
    for i in range(32,128):
        if sha256(chr(i).encode()).hexdigest()==x:
            return chr(i)
            break

#遍历一下可见字符,看看哪个字符作为指数的计算结果与密文一致
def f_num(x):
    for i in range(32,128):
        if 114514**i%1919810==x:
            return chr(i)
            break
#看看密文的样子对应的是哪一种base
def baseDec(text):
    try:
        if re.match("^[0-9A-F]+$",text.decode()) is not None:
            return b16decode(text)
    except:
        pass
    
    try:
        if re.match("^[A-Z2-7=]+$",text.decode()) is not None:
            return b32decode(text)
    except:
        pass

    try:
        if re.match("^[A-Za-z0-9+/=]+$",text.decode()) is not None:
            return b64decode(text)
    except:
        pass

#可以连续解码base16/32/64编码直到不能再解码
def autoDec(text):
    layer = 0
    l=[0]
    while True:
        try:
            text = baseDec(text) #找不到匹配时,会执行except,text的值是None
            l.append(text.decode())#如果上一步的text是None,这一步就会异常,当程序在try内部出现异常时,会跳过try中剩下的代码,直接跳转到except中的语句处理异常
            layer += 1 #当前是第几次解码成功,layer就是几,如果之前异常了就不会执行到这里
            #print("第{0}层:\n".format(layer),text.decode())
            #print(l)
        except:
            return l[layer]  #layer处存的就是最后一次成功解码的结果

robot=['252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111', 1495846, 1452754, b'M4======', '021fb596db81e6d02bf3d2586ee3981fe519f275c0ac9ca76bbcf2ebb4097d96', '2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6', '4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a', b'Tg==', '1b16b1df538ba12dc3f97edbb85caa7050d46c148134290feba80f8236c83db9', b'52304539505430395054303D', 'e3b98a4da31a127d4bde6e43033f66ba274cab0eb7eb1c70ec41402bf6273dd8', b'58773D3D', '3f39d5c348e5b79d06e842c114e6cc571583bbf44e4b0ebfda1a01ec05745d43', '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce', '2e7d2c03a9507ae265ecf5b5356885a53393a2029d241394997265a1a25aefc6', b'T0k9PT09PT0=', '18f5384d58bcb1bba0bcd9e6a6781d1a6ac2cc280c330ecbab6cb7931b721552', b'T0E9PT09PT0=', 825026, 'd2e2adf7177b7a8afddbc12d1634cf23ea1a71020f6a1308070a16400fb68fde', 1455816, b'4F553D3D3D3D3D3D', 1165366, 1242964, b'4F493D3D3D3D3D3D', 652094, 597296, '4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce', '4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a', b'54314539505430395054303D', 1242964, 368664, b'TVU9PT09PT0=', b'cw==', 1602214]


flag=''
for r in robot:
    if isinstance(r,numbers.Number): #如果密文是数字
        flag+=f_num(r)
    elif len(r)==64:  #如果密文是sha256的结果
        flag+=f_sha256(r)
    else:  #不是前两者那就是base系列了
        flag+=autoDec(r)
  
print(flag)

flag: flag{c4Nn0t_D3crYpt_buT_r3p34t_Yes}

四、ezPRNG

原题如下:

from Crypto.Util.number import *
flag = b'xxxxxxxxxxxxxxxxxxxx'

class my_prng_lcg:
    def __init__(self, seed, multiplier, increment, modulus):
        self.state = seed
        self.multiplier = multiplier
        self.increment = increment
        self.modulus = modulus

    def random(self):
        self.state = (self.state * self.multiplier + self.increment) % self.modulus
        return self.state

PRNG = my_prng_lcg(bytes_to_long(flag), getRandomInteger(256), getRandomInteger(256), getRandomInteger(256))
gift = []
for i in range(6):
    gift.append(PRNG.random())

qwq = bytes_to_long(flag)
print(qwq.bit_length())
print(gift)
# [32579077549265101609729134002322479188058664203229584246639330306875565342934, 30627296760863751873213598737521260410801961411772904859782399797798775242121, 59045755507520598673072877669036271379314362490837080079400207813316110037822, 29714794521560972198312794885289362350476307292503308718904661896314434077717, 3378007627369454232183998646610752441039379051735310926898417029172995488622, 35893579613746468714922176435597562302206699188445795487657524606666534642489]

由于我之前是没接触过lcg方面的题目的,或者说可能接触过但没做出来过也没去学,所以这道题拿过来非常懵,觉得“啥也不知道这咋求”……然后还一度走偏了,以为考的是近似最大公约数(AGCD)来着,迟迟不会做,觉得这道题一定是超级难(同时由于同事很短的时间内就解出来了所以自己陷入emo……
再后来还是不会只能去问同事,同事提示我搜一搜lcg(我就死活没看到题目的脚本中就提到lcg了……),我才知道原来这题特别容易找到可以参考的解题脚本……但自己这么长时间都不会,也搜不到有用的东西,所以也不能说简单……QAQ

1.分析

先贴上我学习有关lcg(线性同余生成器)的理论与解题方法的博客:参考博客地址
感谢不认识的大佬!
线性同余生成器是一种产生伪随机数的方法。涉及三个整数,乘数 a、增量 b和模数 m,其中a,b,m是由生成器设定的常数。

x(n+1)=(x(n)*a+b) mod m

这里n和n+1是下标,表示第n个x乘a加b模m后的结果是第n+1个x。
回到这道题,情况对应于上面链接的博文中的例5,就是已知的只有密文,乘数a、增量b、模数m都是不知道的,但可以用上面链接的博文中的公式4:

tn=Xn+1-Xn,m=gcd((tn+1tn-1 - tntn) , (tntn-2 - tn-1tn-1))

求出m,知道了m就又可以套公式求a和b,进而解密。

贴一个公式的截图方便看:(也是上面博客的
NewStarCTF公开赛week2密码学题目wp_第1张图片

2.解密脚本

然后学完上面博客的,自己写了个脚本,不如人家写的直接,不过可以完全匹配上上面计算m的公式的下标~

from Crypto.Util.number import *
import gmpy2

def gcd(a,b): 
    if(b==0): 
        return a 
    else: 
        return gcd(b,a%b) 

s=[32579077549265101609729134002322479188058664203229584246639330306875565342934, 30627296760863751873213598737521260410801961411772904859782399797798775242121, 59045755507520598673072877669036271379314362490837080079400207813316110037822, 29714794521560972198312794885289362350476307292503308718904661896314434077717, 3378007627369454232183998646610752441039379051735310926898417029172995488622, 35893579613746468714922176435597562302206699188445795487657524606666534642489]

t = []
for i in range(1,6): #因为要保证i-1>=0,所以i要从1开始取,取1-5共5个值,t的下标是0-4
    t.append(s[i]-s[i-1]) 
all_n = []
for i in range(2,4): #因为要保证i-2>=0所以从2开始取,i+1又不能超过4所以i最大取到3
    all_n.append(gcd((t[i+1]*t[i-1]-t[i]*t[i]), (t[i-2]*t[i]-t[i-1]*t[i-1]))) 
#print(all_n) #all_n=[-121345174246418575181911383111384744844396268276674523949961216790284235179004, -4247081098624650131366898408898466069553869389683608338248642587659948231265140]
for n in all_n:
    n=abs(n)  #要保证n是正数
    a=(s[2]-s[1])*gmpy2.invert((s[1]-s[0]),n)%n #套公式求a
    ani=gmpy2.invert(a,n)
    b=(s[1]-a*s[0])%n  #套公式求b
    seed = (ani*(s[0]-b))%n  #套公式求seed
    print(seed)
    print(long_to_bytes(seed))
    #因为all_in中有两个元素,对应两个模数,所以输出两个结果,有一个是flag

flag: flag{lcG_1s_s0_34sy}

五、ezRabin

原题如下:

from Crypto.Util.number import *
from somewhere_you_do_not_know import flag
#flag格式为 flag{XXXX}
def ezprime(n):
    p=getPrime(n)
    while p%4!=3:
        p=getPrime(n)
    return p
p=ezprime(512)
q=ezprime(512)
n=p*q
m=bytes_to_long(flag)
m=(m<<(300))+getRandomNBitInteger(300)
assert m**2>n and m<n
c=pow(m,4,n)
print('c=',c)
print('p=',p)
print('q=',q)
'''
c= 59087040011818617875466940950576089096932769518087477304162753047334728508009365510335057824251636964132317478310267427589970177277870220660958570994888152191522928881774614096675980017700457666192609573774572571582962861504174396725705862549311100229145101667835438230371282904888448863223898642183925834109
p= 10522889477508921233145726452630168129218487981917965097647277937267556441871668611904567713868254050044587941828674788953975031679913879970887998582514571
q= 11287822338267163056031463255265099337492571870189068887689824393221951058498526362126606231275830844407608185240702408947800715624427717739233431252556379
就要花里胡哨(
'''

1.分析

看题目名就可以知道考察的是Rabin加密,这个加密和RSA很像,但又有区别,解密过程更是大有不同。
Rabin与RSA一样有参数p、q、e,且n=p*q,但Rabin中e取固定值e=2,p和q都是模4余3的素数。
关于Rabin的理论知识和加解密过程可以参考博客:参考来源
感谢不认识的大佬~

2.解密脚本

可以从网上找标准的Rabin解密脚本(e=2),过程十分固定,本题e=4只要依据e=2稍作修改,将4次方看做平方外再套一层平方即可,也就是进行两次e=2时的解密,一共得到4*4=16个解,再右移30位后从中找到flag即可。

import gmpy2
from Crypto.Util.number import *

p=10522889477508921233145726452630168129218487981917965097647277937267556441871668611904567713868254050044587941828674788953975031679913879970887998582514571
q=11287822338267163056031463255265099337492571870189068887689824393221951058498526362126606231275830844407608185240702408947800715624427717739233431252556379
n=p*q
e=2
c=59087040011818617875466940950576089096932769518087477304162753047334728508009365510335057824251636964132317478310267427589970177277870220660958570994888152191522928881774614096675980017700457666192609573774572571582962861504174396725705862549311100229145101667835438230371282904888448863223898642183925834109
inv_p = gmpy2.invert(p, q)
inv_q = gmpy2.invert(q, p)

def de_rabin(c):
    mp = pow(c, (p+1) // 4, p)
    mq = pow(c, (q+1) // 4, q)
    a = (inv_p * p * mq + inv_q * q * mp) % n
    b = n-int(a)
    c = (inv_p * p * mq - inv_q * q * mp) % n
    d = n-int(c)
    return a,b,c,d

'''e=4的解密,就是做两次e=2的rabin解密,
第一次得到4个解,将4个解作为新的c分别再做一次e=2的rabin解密,
所以共得到16个解,右移300位后看看哪个解是flag'''

a,b,c,d=de_rabin(c)
a1,a2,a3,a4=de_rabin(a)
b1,b2,b3,b4=de_rabin(b)
c1,c2,c3,c4=de_rabin(c)
d1,d2,d3,d4=de_rabin(d)

l=[a1,a2,a3,a4,b1,b2,b3,b4,c1,c2,c3,c4,d1,d2,d3,d4]
for ll in l:
    print(long_to_bytes(ll>>300))

flag: flag{R4bin_3ncrypti0n_with_3_mod_4_is_two_e4sy}


总结

现在做题效率很低,因为越做越难了,但好像也没有特别难,就是自己水平不够而已。好像也挺久了吧,从网鼎、泰山杯(不是酱油就是准备了一波然后没去比……只是做一个参考的时间节点)之后到现在总是遇到不会的题,学会之后又懒于归纳总结。总觉得还是记不住、理解不到位,也有点厌倦做题了,想开新的领域(比如逆向?pwn?
希望学习状态能快点好起来吧,能获得正反馈的话就好了。

你可能感兴趣的:(BUUCTF刷题记录,密码学,网络安全,python)